Files
Christoph Schwörer f045b99a25 Codebasis als Dateien ins Arbeitsrepo statt als Gitlink
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
2026-08-26 07:43:51 +02:00

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.