# 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 request)` ```csharp public Response SearchReceiptsThroughPaging(Request request) { var result = this.Session.GetBL().SearchReceipts( this.GetLoggedInUserByTicket(request.Ticket), request.Data.Filter, request.Data.Page, request.Data.EntriesPerPage); return Response.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 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.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 SearchReceipts(ReceiptSearchFilter filter, LoggedInUser user) { var result = new List(); 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(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.