Files
Masterarbeit/QuellCode/CentronERP/docs/reference/receipts/receipt-search-architecture.md
T
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

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 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
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

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.