QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142) getrackt, ohne .gitmodules und ohne erreichbares Remote. Der Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen. Umstellung: - Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6) - Gitlink aus dem Index entfernt - Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
357 lines
14 KiB
Markdown
357 lines
14 KiB
Markdown
# Receipt Search Architecture
|
|
|
|
This document explains how the receipt search system works in the c-entron.NET backend, including filter application, shared logic across receipt types, and how to add new searchable properties.
|
|
|
|
## Overview
|
|
|
|
The receipt search system provides a unified search interface across all receipt types (offers, orders, delivery lists, invoices, contracts, credit vouchers, pickup lists, and supplier receipts). It uses a configurable, extensible architecture that allows different receipt types to implement their own search logic while sharing common filtering capabilities.
|
|
|
|
## Architecture Components
|
|
|
|
### 1. REST API Layer
|
|
|
|
**Entry Point:** `CentronRestService.SearchReceiptsThroughPaging`
|
|
- **Location:** `src/webservice/Centron.Host/Services/CentronRestServiceParts/CentronRestService.Receipts.cs`
|
|
- **Method:** `SearchReceiptsThroughPaging(Request<SearchReceiptsThroughPagingRequest> request)`
|
|
|
|
```csharp
|
|
public Response<ReceiptSearchItemPagingDTO> SearchReceiptsThroughPaging(Request<SearchReceiptsThroughPagingRequest> request)
|
|
{
|
|
var result = this.Session.GetBL<ReceiptSearchWebServiceBL>().SearchReceipts(
|
|
this.GetLoggedInUserByTicket(request.Ticket),
|
|
request.Data.Filter,
|
|
request.Data.Page,
|
|
request.Data.EntriesPerPage);
|
|
|
|
return Response<ReceiptSearchItemPagingDTO>.FromBLResult(result);
|
|
}
|
|
```
|
|
|
|
### 2. Business Logic Layer
|
|
|
|
**Primary Class:** `ReceiptSearchWebServiceBL`
|
|
- **Location:** `src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearchWebServiceBL.cs`
|
|
- **Responsibility:** Coordinates search operations, handles pagination, and manages user context
|
|
|
|
```csharp
|
|
public Result<ReceiptSearchItemPagingDTO> SearchReceipts(LoggedInUser user,
|
|
ReceiptSearchFilter filter, int page, int entriesPerPage)
|
|
{
|
|
var receipts = new ReceiptSearcher(this.Session).SearchReceipts(filter, user);
|
|
|
|
// Apply pagination and return results
|
|
ReceiptSearchItemPagingDTO pagingDTO = new ReceiptSearchItemPagingDTO()
|
|
{
|
|
Count = receipts.Count,
|
|
CurrentPage = page,
|
|
PageCount = (int)Math.Ceiling(receipts.Count/(decimal) entriesPerPage),
|
|
Result = receipts.OrderByDescending(o => o.Date).Skip((page - 1) * entriesPerPage).Take(entriesPerPage).ToList()
|
|
};
|
|
return Result<ReceiptSearchItemPagingDTO>.AsSuccess(pagingDTO);
|
|
}
|
|
```
|
|
|
|
### 3. Core Search Engine
|
|
|
|
**Primary Class:** `ReceiptSearcher`
|
|
- **Location:** `src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs`
|
|
- **Responsibility:** Executes searches across all receipt types using configuration-driven SQL generation
|
|
|
|
#### Key Features:
|
|
- **Multi-Receipt Type Support:** Searches across multiple receipt types simultaneously
|
|
- **Configuration-Driven:** Uses `ReceiptSearchConfiguration` classes for each receipt type
|
|
- **Raw SQL Execution:** Generates and executes optimized SQL queries for performance
|
|
- **User Context Handling:** Applies user-specific filters (web accounts, branches, permissions)
|
|
|
|
#### Search Process:
|
|
1. **Filter Preparation:** Adjusts filter based on user context (web accounts, permissions)
|
|
2. **Configuration Iteration:** Loops through all receipt type configurations
|
|
3. **SQL Generation:** Creates receipt-type-specific SQL queries with parameters
|
|
4. **Query Execution:** Executes raw SQL with 5-minute timeout
|
|
5. **Result Aggregation:** Combines results from all receipt types
|
|
6. **Result Sorting:** Orders by ObjectKind, then by Number descending
|
|
|
|
```csharp
|
|
public IList<ReceiptSearchItemDTO> SearchReceipts(ReceiptSearchFilter filter, LoggedInUser user)
|
|
{
|
|
var result = new List<ReceiptSearchItemDTO>();
|
|
|
|
this.PrepareFilterForWebAccounts(filter, user);
|
|
|
|
foreach (var configuration in this._receiptSearchConfigurations)
|
|
{
|
|
if (filter.ReceiptKinds == null || filter.ReceiptKinds.Count == 0 || filter.ReceiptKinds.Contains(configuration.ReceiptKind))
|
|
{
|
|
var query = this.CreateSqlStatementAndParameters(configuration, filter, user);
|
|
|
|
if (query == null) continue; // Receipt type doesn't support this filter
|
|
|
|
var sqlStatement = query.Item1;
|
|
var parameters = query.Item2;
|
|
|
|
var receipts = this._rawSqlAccessDAO.ExecuteQuery<ReceiptSearchItemDTO>(sqlStatement, parameters, timeout: TimeSpan.FromMinutes(5));
|
|
result.AddRange(receipts);
|
|
}
|
|
}
|
|
|
|
return result.OrderBy(f => f.ObjectKind).ThenByDescending(f => f.Number).ToList();
|
|
}
|
|
```
|
|
|
|
### 4. Filter Definition
|
|
|
|
**Class:** `ReceiptSearchFilter`
|
|
- **Location:** `src/backend/Centron.Interfaces/Sales/Receipts/ReceiptSearch/ReceiptSearchFilter.cs`
|
|
- **Responsibility:** Defines all available search criteria
|
|
|
|
#### Available Filter Properties:
|
|
- **Basic Search:** `SearchText`, `ReceiptNumber`, `ReceiptNumbers`
|
|
- **Date Range:** `DateFrom`, `DateTo`, `ChangedAfterDate`
|
|
- **Account/Customer:** `AccountI3D`, `AccountNumbers`, `AccountName`
|
|
- **Receipt Types:** `ReceiptKinds` (controls which receipt types to search)
|
|
- **Status:** `IncludeClosedReceipts`, `OnlyOwn`, `OnlyOwnBranch`
|
|
- **Financial:** `GrossPriceFrom`, `GrossPriceTo`, `PaymentConditionI3D`, `DeliveryConditionI3D`
|
|
- **Specialized:** `HourlySurchargeRateI3Ds`, `ContractKindI3Ds`, `ArticleI3Ds`, `CampaignI3D`
|
|
- **Items:** `SearchInReceiptItemText`, `ReceiptItemI3D`
|
|
- **Advanced:** `IsCart`, `OnlyNonCarts`, `IsDownPaymentInvoice`, `IsReceiptTemplate`
|
|
|
|
### 5. Configuration System
|
|
|
|
**Base Class:** `ReceiptSearchConfiguration`
|
|
- **Location:** `src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearchConfiguration.cs`
|
|
- **Responsibility:** Defines the contract for receipt-type-specific search configurations
|
|
|
|
#### Configuration Properties:
|
|
- **Basic Properties:**
|
|
- `ReceiptKind`: Identifies the receipt type (CentronObjectKindNumeric)
|
|
- `GetBaseSelectStatement()`: Returns the base SELECT query for this receipt type
|
|
- `OnlyActiveWhereStatement`: SQL for filtering active receipts
|
|
|
|
- **Filter WHERE Statements:** Each filter property has a corresponding WHERE clause property:
|
|
- `AccountI3DWhereStatement`: SQL for filtering by account
|
|
- `ReceiptNumberWhereStatement`: SQL for filtering by receipt number
|
|
- `DateFromWhereStatement`, `DateToWhereStatement`: Date range filtering
|
|
- `SearchTextWhereStatement`: Full-text search implementation
|
|
- And many more...
|
|
|
|
- **Permission Integration:**
|
|
- `ShowRight`: Required right to view receipts of this type
|
|
- `OnlyOwnRight`: Right that restricts to user's own receipts
|
|
- `OnlyOwnBranchRight`: Right that restricts to user's branch
|
|
|
|
#### Receipt Type Configurations:
|
|
- `OfferReceiptSearchConfiguration` - Offers (AngKopf)
|
|
- `OrderReceiptSearchConfiguration` - Orders (AufKopf)
|
|
- `DeliveryListReceiptSearchConfiguration` - Delivery Lists (LiefKopf)
|
|
- `InvoiceReceiptSearchConfiguration` - Invoices (RechKopf)
|
|
- `ContractReceiptSearchConfiguration` - Contracts (VertragKopf)
|
|
- `CreditVoucherReceiptSearchConfiguration` - Credit Vouchers (GutKopf)
|
|
- `PickupListReceiptSearchConfiguration` - Pickup Lists (AbholKopf)
|
|
- Plus supplier variants for each type
|
|
|
|
## SQL Generation Process
|
|
|
|
The `ReceiptSearcher.CreateSqlStatementAndParameters` method builds SQL queries dynamically based on the provided filter and receipt type configuration:
|
|
|
|
### 1. Base Query Construction
|
|
```csharp
|
|
var baseSelect = configuration.GetBaseSelectStatement(filter);
|
|
var builder = new StringBuilder(baseSelect);
|
|
```
|
|
|
|
### 2. Filter Application
|
|
For each filter property that has a non-null/non-empty value:
|
|
```csharp
|
|
if (filter.AccountI3D != null && filter.AccountI3D > 0)
|
|
{
|
|
var accountWhereStatement = configuration.AccountI3DWhereStatement;
|
|
if (string.IsNullOrWhiteSpace(accountWhereStatement))
|
|
return null; // This receipt type doesn't support this filter
|
|
|
|
builder.AppendLine(accountWhereStatement);
|
|
parameters.Add(new NamedQueryParameter("AccountI3D", filter.AccountI3D, NHibernateUtil.Int32));
|
|
}
|
|
```
|
|
|
|
### 3. Permission Checks
|
|
```csharp
|
|
if (configuration.ShowRight.HasValue && !this._appRightsBL.HasRight(user.AppUser, configuration.ShowRight.Value))
|
|
{
|
|
return null; // User doesn't have permission to search this receipt type
|
|
}
|
|
```
|
|
|
|
### 4. Active Receipts Filter
|
|
```csharp
|
|
if (!filter.IncludeClosedReceipts)
|
|
{
|
|
builder.AppendLine(configuration.OnlyActiveWhereStatement);
|
|
}
|
|
```
|
|
|
|
## Adding New Filter Properties
|
|
|
|
To add a new searchable property (e.g., `IsHiddenInHelpdesk`), follow these steps:
|
|
|
|
### Step 1: Add to ReceiptSearchFilter
|
|
Add the new property to `ReceiptSearchFilter.cs`:
|
|
|
|
```csharp
|
|
[DataMember]
|
|
public bool? IsHiddenInHelpdesk { get; set; }
|
|
```
|
|
|
|
### Step 2: Add to Base Configuration
|
|
Add the corresponding WHERE statement property to `ReceiptSearchConfiguration.cs`:
|
|
|
|
```csharp
|
|
public virtual string IsHiddenInHelpdeskWhereStatement { get; } = null;
|
|
```
|
|
|
|
### Step 3: Update Receipt Type Configurations
|
|
For each receipt type that supports the new filter, implement the WHERE statement:
|
|
|
|
**Example for ContractReceiptSearchConfiguration:**
|
|
```csharp
|
|
public override string IsHiddenInHelpdeskWhereStatement => "AND AK.IsHiddenInHelpdesk = :IsHiddenInHelpdesk";
|
|
```
|
|
|
|
### Step 4: Update SQL Generation
|
|
Add the filter logic to `ReceiptSearcher.CreateSqlStatementAndParameters`:
|
|
|
|
```csharp
|
|
if (filter.IsHiddenInHelpdesk != null)
|
|
{
|
|
var isHiddenInHelpdeskWhere = configuration.IsHiddenInHelpdeskWhereStatement;
|
|
|
|
if (string.IsNullOrWhiteSpace(isHiddenInHelpdeskWhere))
|
|
return null; // This receipt type doesn't support this filter
|
|
|
|
builder.AppendLine(isHiddenInHelpdeskWhere);
|
|
parameters.Add(new NamedQueryParameter("IsHiddenInHelpdesk", filter.IsHiddenInHelpdesk.Value, NHibernateUtil.Boolean));
|
|
}
|
|
```
|
|
|
|
### Step 5: Database Schema Requirements
|
|
Ensure the underlying database tables and views include the new column:
|
|
- **Tables:** Add to base tables (e.g., `VertragKopf`) and version tables (e.g., `VertragKopfVersions`)
|
|
- **Views:** Update views (e.g., `Contracts`, `ContractVersions`) to include the new column
|
|
|
|
## Shared Logic Across Receipt Types
|
|
|
|
The system achieves code reuse through several mechanisms:
|
|
|
|
### 1. Configuration-Driven Architecture
|
|
- Common filter logic is implemented once in `ReceiptSearcher`
|
|
- Receipt-type-specific behavior is encapsulated in configuration classes
|
|
- New receipt types can be added by implementing a new configuration class
|
|
|
|
### 2. Base SELECT Queries
|
|
Each configuration provides a standardized SELECT query that returns `ReceiptSearchItemDTO` properties:
|
|
```csharp
|
|
public override string GetBaseSelectStatement(ReceiptSearchFilter filter)
|
|
{
|
|
return @"
|
|
SELECT
|
|
I3D = AK.I3D,
|
|
ObjectKind = 22,
|
|
Number = AK.Nummer,
|
|
Version = AK.Version,
|
|
Caption = AK.Zusatztext,
|
|
Date = AK.Datum,
|
|
Receiver = AK.Empfaenger,
|
|
AccountI3D = AK.KundenID,
|
|
-- ... more fields
|
|
FROM VertragKopf AK
|
|
-- ... joins
|
|
WHERE 1=1"; // Base WHERE clause for dynamic filter appending
|
|
}
|
|
```
|
|
|
|
### 3. Parameter Handling
|
|
- All configurations use named parameters (`:ParameterName`)
|
|
- Parameter types are consistently defined using NHibernate types
|
|
- Array parameters support IN clauses for multiple values
|
|
|
|
### 4. Permission Integration
|
|
- Rights checking is standardized across all receipt types
|
|
- Each configuration can define specific rights for viewing, own-only, and branch-only access
|
|
- Permission failures result in null queries (no results for that receipt type)
|
|
|
|
## Performance Considerations
|
|
|
|
### 1. Raw SQL Execution
|
|
- Uses raw SQL instead of LINQ/HQL for optimal performance
|
|
- Timeout set to 5 minutes for complex searches
|
|
- Transaction isolation levels configured for consistency
|
|
|
|
### 2. Pagination
|
|
- Results are paginated at the business logic level
|
|
- Sorting is applied after aggregation (may impact performance for large result sets)
|
|
- Consider implementing database-level pagination for very large datasets
|
|
|
|
### 3. Index Requirements
|
|
- Ensure all filterable columns are properly indexed
|
|
- Foreign key columns should have indexes
|
|
- Date range queries benefit from composite indexes
|
|
|
|
### 4. Query Optimization
|
|
- Each receipt type can optimize its base query independently
|
|
- Complex joins are handled in the base SELECT statement
|
|
- WHERE clauses are appended dynamically to avoid query plan issues
|
|
|
|
## Security and Permissions
|
|
|
|
### 1. User Context Handling
|
|
- Web account users are automatically filtered to their associated customer
|
|
- Employee users can be restricted by branch or ownership
|
|
- Permission checking prevents unauthorized access to receipt types
|
|
|
|
### 2. SQL Injection Prevention
|
|
- All user input is parameterized
|
|
- No dynamic SQL concatenation with user values
|
|
- Named parameters ensure type safety
|
|
|
|
### 3. Branch Isolation
|
|
- Branch-specific filtering can be enforced per receipt type
|
|
- User's branch context is automatically applied where configured
|
|
|
|
## Testing Strategies
|
|
|
|
### 1. Unit Testing
|
|
- Test individual configuration classes in isolation
|
|
- Mock filter scenarios for comprehensive coverage
|
|
- Verify SQL generation for all filter combinations
|
|
|
|
### 2. Integration Testing
|
|
- Test complete search workflows with real data
|
|
- Verify permission enforcement
|
|
- Test pagination and sorting behavior
|
|
|
|
### 3. Performance Testing
|
|
- Measure query execution times for large datasets
|
|
- Test timeout behavior under load
|
|
- Validate index effectiveness
|
|
|
|
## Future Enhancements
|
|
|
|
### 1. Elasticsearch Integration
|
|
- Consider moving to Elasticsearch for full-text search capabilities
|
|
- Maintain SQL for structured filtering
|
|
- Hybrid approach for optimal performance
|
|
|
|
### 2. Real-time Filtering
|
|
- Implement WebSocket-based real-time updates
|
|
- Consider caching frequently accessed search results
|
|
|
|
### 3. Advanced Search Features
|
|
- Saved search queries
|
|
- Search history
|
|
- Search result highlighting
|
|
|
|
## Conclusion
|
|
|
|
The receipt search system provides a robust, extensible architecture for searching across all receipt types in the c-entron.NET system. By following the established patterns for adding new filter properties, developers can easily extend search capabilities while maintaining consistency and performance across the entire system.
|
|
|
|
The configuration-driven approach ensures that new receipt types can be added with minimal impact on existing code, while the shared search logic provides consistency and maintainability across all receipt types.
|