#906: AuftragsRechnungCache.kt

projectforge-business/src/main/kotlin/org/projectforge/business/fibu/AuftragsRechnungCache.kt Type: Spring Component (AbstractCache) · Purpose: Caches the assignment of invoice positions to order positions · Package: org.projectforge.business.fibu
Source path: projectforge-business/src/main/kotlin/org/projectforge/business/fibu/AuftragsRechnungCache.kt 171 lines · 102 code · 52 comments · 17 blank
An in-memory cache that maps invoice positions (RechnungPosInfo) to their associated order positions and orders. This enables fast lookups for the order book to display which invoices are linked to each order, and for the invoice view to show which orders are covered. Handles a cyclic dependency with AuftragsCache by running both caches twice on initialization (a "ready" flag mechanism).

Architecture & Design

Class Hierarchy

Extends AbstractCache, which provides the refresh()/setExpired()/forceReload() lifecycle. Registered as a Spring @Component.

Dependencies

DependencyTypeRole
AuftragsCacheComponentProvides order-to-position mapping; mutual refresh during init
RechnungCacheComponentProvides RechnungInfo and RechnungPosInfo objects
RechnungDaoDAORegistered for BaseDOModifiedListener to invalidate cache on invoice changes
RechnungJdbcServiceServiceFast JDBC query for invoice-position-to-order-position assignments

Internal Data Structures

MapKeyValuePurpose
invoicePositionMapByAuftragIdLong (order ID)MutableSet<Long> (invoice position IDs)Lookup invoice position IDs by order
invoicePositionMapByAuftragsPositionIdLong (order position ID)MutableSet<RechnungPosInfo>Lookup invoice positions by order position
invoicePositionMapByRechnungIdLong (invoice ID)MutableSet<RechnungPosInfo>Lookup order-related invoice positions by invoice

Lifecycle & Initialization

@PostConstruct init() registers two listeners:

Mutual Refresh Pattern (Cyclic Dependency Resolution)

AuftragsCache and AuftragsRechnungCache depend on each other's data. The solution uses a ready flag:

  1. First AuftragsCache.refresh() runs — this cache's data is still empty
  2. onAfterCacheRefresh fires, sets ready = true, unregisters the listener
  3. AuftragsRechnungCache.forceReload() runs — now has AuftragsCache data available
  4. AuftragsCache.forceReload() runs again — now has this cache's data available
  5. Both caches are fully populated

Public Methods

getRechnungsPosInfoByAuftragId(auftragId: Long?): List<RechnungPosInfo>?

Returns invoice positions assigned to a given order. Results are sorted by invoice number, then position number. Returns null if auftragId is null. Calls checkRefresh() to trigger lazy cache population if needed.

getRechnungsPosInfosByAuftragsPositionId(auftragsPositionId: Long?): Set<RechnungPosInfo>?

Returns invoice positions assigned to a specific order position. Returns null for null input.

Refresh Logic

refresh() queries rechnungJdbcService.selectRechnungsPositionenWithAuftragPosition() for the raw JDBC result set (faster than JPA for this bulk operation), then:

Invalidation

When any RechnungDO is inserted or modified, rechnungListener calls setExpired(). The next call to a public getter method will trigger checkRefresh()refresh(), lazily rebuilding the cache mapping.

Related Classes

Git History

868d6abb7 2025 -> 2026
51d352133 AuftragsCache / AuftragsRechnungsCache handling refactored (cyclic usage resolved by running caches twice on initialization)
63081666f Source file headers: 2024-> 2025.
22ccc4877 WIP: Scripting
884ff2c70 Debug message added (missing debitor invoices in orders in production only).
c9508769a Clone of invoices.
d02c8a770 Migration stuff in progress...
ff2cc4cfa Migration stuff in progress... (all tests of all packages: OK).
f31e8064e Migration stuff in progress...
2e9839891 Migration stuff in progress...
973fbf518 Migration stuff in progress...
ba2479571 Migration stuff in progress...
b3293f0cc PersistenceService/Context: stats handling improved.
b47c21af6 Refactored caching and calculations with invoices (not yet finished)