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
14 KiB
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)
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
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
ReceiptSearchConfigurationclasses 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:
- Filter Preparation: Adjusts filter based on user context (web accounts, permissions)
- Configuration Iteration: Loops through all receipt type configurations
- SQL Generation: Creates receipt-type-specific SQL queries with parameters
- Query Execution: Executes raw SQL with 5-minute timeout
- Result Aggregation: Combines results from all receipt types
- Result Sorting: Orders by ObjectKind, then by Number descending
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 typeOnlyActiveWhereStatement: SQL for filtering active receipts
-
Filter WHERE Statements: Each filter property has a corresponding WHERE clause property:
AccountI3DWhereStatement: SQL for filtering by accountReceiptNumberWhereStatement: SQL for filtering by receipt numberDateFromWhereStatement,DateToWhereStatement: Date range filteringSearchTextWhereStatement: Full-text search implementation- And many more...
-
Permission Integration:
ShowRight: Required right to view receipts of this typeOnlyOwnRight: Right that restricts to user's own receiptsOnlyOwnBranchRight: 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
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:
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
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
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:
[DataMember]
public bool? IsHiddenInHelpdesk { get; set; }
Step 2: Add to Base Configuration
Add the corresponding WHERE statement property to ReceiptSearchConfiguration.cs:
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:
public override string IsHiddenInHelpdeskWhereStatement => "AND AK.IsHiddenInHelpdesk = :IsHiddenInHelpdesk";
Step 4: Update SQL Generation
Add the filter logic to ReceiptSearcher.CreateSqlStatementAndParameters:
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:
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.