#887: AbstractRechnungDO.kt

projectforge-business/src/main/kotlin/org/projectforge/business/fibu/AbstractRechnungDO.kt

Type: Kotlin @MappedSuperclass abstract JPA entity — Base class for all invoice domain objects

Purpose: Defines the shared database schema and business logic for both outgoing customer invoices (RechnungDO) and incoming creditor invoices (EingangsrechnungDO), providing common fields, Hibernate Search indexing, calculated properties, and position management.

Source path: projectforge-business/src/main/kotlin/org/projectforge/business/fibu/AbstractRechnungDO.kt

227 lines · 138 code · 59 comments · 30 blank

The shared data model foundation for all invoice types in ProjectForge. It extends DefaultBaseDO (the framework base entity with auditing fields like created, lastUpdate, deleted) and implements IRechnung (shared invoice interface) and DisplayNameCapable (for UI rendering). It uses JPA @MappedSuperclass so its fields are inherited by concrete entity tables without creating a separate table. All properties are declared open to support Hibernate's lazy-loading proxy mechanism.

Class Hierarchy

DefaultBaseDO <- AbstractRechnungDO <- RechnungDO
                                    <- EingangsrechnungDO
IRechnung, DisplayNameCapable (interfaces implemented by AbstractRechnungDO)

Database Fields

Core Invoice Fields

PropertyDB ColumnTypeConstraints / Annotations
datumLocalDate?@Column(nullable = false), @GenericField (Hibernate Search)
betreffString?@Column(length = 4000), @FullTextField
bemerkungString?@Column(length = 4000), @FullTextField
besonderheitenString?@Column(length = 4000), @FullTextField
faelligkeitLocalDate?@GenericField
bezahlDatumbezahl_datumLocalDate?@GenericField
zahlBetragzahl_betragBigDecimal?scale = 2, precision = 12; actual gross amount paid
currencyString?@Column(length = 10), @FullTextField

Konto (Account) Relationship

PropertyTypeAnnotations
kontoKontoDO?@ManyToOne(fetch = LAZY), @JoinColumn(name = "konto_id"), @IndexedEmbedded(includeDepth = 1), @JsonSerialize(using = IdOnlySerializer::class), @IndexingDependency(reindexOnUpdate = SHALLOW)

The DATEV account associated with this invoice. For outgoing invoices, this defaults to the account assigned to the customer or project. For incoming invoices, it represents the creditor's chart of accounts entry.

Discount / Skonto Fields

PropertyTypeDescription
discountPercentBigDecimal?Early payment discount percentage (e.g., 2 for 2%)
discountMaturityLocalDate?Date until which the discount applies

Transient / Computed Fields

Year

open val year: Int
    get() = datum?.year ?: 0

Derived from datum. Indexed via @GenericField + @IndexingDependency(derivedFrom = ...) to enable Hibernate Search queries by year without storing it in the database.

Payment Terms (Computed)

PropertyTypeDescription
zahlungsZielInTagenInt?Days between invoice date and due date
discountZahlungsZielInTagenInt?Days between invoice date and discount maturity

Both are computed by recalculate() using PFDateTime.daysBetween(). They are @Transient (not persisted).

uiStatus / uiStatusAsXml

User interface state is persisted as XML in uiStatusAsXml (@Column(length = 10000)) and lazily deserialized to a RechnungUIStatus instance via XmlObjectReader. The getter caches the deserialized object. Annotated with @NoHistory to exclude from history tracking.

info (RechnungInfo)

@Transient @CandHIgnore lateinit var info: RechnungInfo

The calculated invoice summary (net/gross totals, VAT breakdown, cost assignments). Initialized by RechnungCalculator and cached by AbstractRechnungCache. Marked @CandHIgnore to prevent history/audit processing. The isInfoInitialized check prevents re-initialization.

ensuredInfo

val ensuredInfo: RechnungInfo
    get() {
        if (!isInfoInitialized) { RechnungCalculator.calculate(this) }
        return info
    }

Guaranteed accessor — if info hasn't been calculated yet (e.g., after deserialization or when the cache didn't pre-populate it), it forces calculation on demand.

Hibernate Search / Indexing Strategy

AnnotationUsage
@GenericFieldDate and numeric fields — exact filtering, not analyzed full-text
@FullTextFieldText fields (betreff, bemerkung, besonderheiten, currency) — analyzed full-text search
@IndexedEmbedded(includeDepth = 1)konto — nested entity indexing with limited depth
@IndexingDependency(reindexOnUpdate = SHALLOW)konto — only reindex when the konto itself changes (not its nested children)
@IndexingDependency(derivedFrom = ...)year — transparently derived from datum field

Abstract Methods (Implemented by Subclasses)

MethodDescription
abstractPositionen: List<AbstractRechnungsPositionDO>?Returns the collection of position/line items for this invoice
isValid: BooleanTrue if the invoice is issued, not canceled, not deleted, and not only planned
addPositionWithoutCheck(position)Adds a position to the internal collection without validation
setAbstractRechnung(position)Sets the back-reference from position to this invoice
ensureAndGetPositionen()Lazily initializes and returns the positions collection

Key Business Methods

recalculate()

Computes zahlungsZielInTagen and discountZahlungsZielInTagen from datum, faelligkeit, and discountMaturity. If datum is null, sets both to null.

addPosition(position: AbstractRechnungsPositionDO)

Adds a new line item to the invoice with automatic number assignment:

  1. Ensures the positions list exists
  2. Finds the maximum existing position number and increments by 1 (or uses 1 for the first position)
  3. Sets the back-reference via setAbstractRechnung()
  4. Adds to the internal collection via addPositionWithoutCheck()

hasKostZuweisungen(): Boolean

Returns true if any position on this invoice has cost assignments (kostZuweisungen).

getAbstractPosition(idx: Int): AbstractRechnungsPositionDO?

Returns the position at the given 0-based index, or null if out of bounds.

Architecture Notes

Git History

868d6abb7 2025 -> 2026
5d8d7db72 WIP: currency in incoming invoices.
8c2bf04f0 WIP: currency conversion with Claude Code.
16b87b4ce WIP: Import of creditor invoices
175d72c14 WIP: Import of creditor invoices
4d4c6b449 Currency field added to invoices (incoming and outgoing)
aa20b7708 NewGroupSelectPanel fixed (used in wizard). (Eingangs)RechnungsPositionDO: @get:JoinColumn -> mappedBy.
1e0e7225b KostzuweisungsExport of invoices ignores now invalid invoices and 0.00 positions.
63081666f Source file headers: 2024-> 2025.
b3782c8a8 Migration stuff in progress...
b810d1c78 Migration stuff in progress... (all tests of all packages: OK).
ba2479571 Migration stuff in progress...
b47c21af6 Refactored caching and calculations with invoices (not yet finished)
ccb7ca64d Migration stuff in progress...
67a66479e Migration stuff in progress...
4c04cfd65 MAJOR-CHANGE! Migration of integer id's to Long id's (including fk's etc.)
ad9ac6b63 Migration stuff in progress...
f5c09f87f Migration stuff in progress...
c04fb0d51 Migration stuff in progress...
06828f490 Migration stuff in progress...
b6092df09 Copyright 2023 -> 2024
ab45d51fa Copyright 2001-2022 -> 2001-2023.
6307a3dbe *RechnungPagesRest: show kost including tooltip.
2dc192b7a AG grid: auto-format of currency, Formatter works now also with AG grid (rating, dates, timestamps). Currency rendering by client (Formatter.jsx) added.
68402a26b Eingangsrechnungsliste: User discount date and due date.
a286007ee Mass update of creditor invoices: handling of discount implemented.
5f7ef41b8 Copyright 2021 -> 2022
a7fceb731 Some compiler warnings fixed.
cd27dd997 package xstream -> xmlstream. (should be replaced by xstream later).
ceb63e8a1 Source code header: (C) 2001-2021.
07cb34acb IRechnung, IRechnungsPosition introduced. DTOs fro Eingangsrechnung{sPosition}DO added.
552603f75 Rename setter methods in RechnungDO to avoid conflicts with Jackson
92112cdb1 Add InvoicePositionComponent
5064afcec LocalDate Bridge for hibernate search fixed.
b209e00ba PFDay.from -> from, fromOrNow, fromOrNull, PFDateTime.from -> from, fromOrNow, fromOrNull
7c1c48782 Replacing more instance of java.sql.Date with java.time.LocalDate Replacing instances of java sql.Timestamp with java.util.Date
78b436d9e Replace instances of java.util.date and java.sql.Date with java.time.LocalDate
7c79f1922 Copyright of source header -> 2020.
13cfb0330 WIP: Migration to PFDate and PFDateTime.
24019a0f5 Eliminate DateHolder occurences
1dc951c03 Remove instances of DayHolder
e4cdd8d4b Revert "More Holder replacements"
f604a1afe More Holder replacements
19b97fcc3 DateBridge: encoding = EncodingType.STRING for all DateBridges in all DO's.
fbe4381e7 DateBridge: string encoding removed again.
76c5c6955 WIP: MultiFieldQueryParser
8675a1dbe Found big performance issue: Declared all Kotlin JPA entities and their properties as open. Lazy loading wasn't supported by Hibernate and results in thousands of JPA queries...
d36fe6ad9 Heavy WIP: refactoring
a55ef0c14 Heavy WIP: refactoring of (Eingangs)Rechnung*. Found a big performance killer!!!!!
812b5b751 Heavy WIP: refactoring of (Eingangs)Rechnung*. Found a big performance killer!!!!!
39cdd224c Hibernate search: DateBridge...
339e017b4 ElementRegistry: @PropertyInfo is now also supported for getter methods of read-only vals.
f2b55f6aa RechnungsPositionDO.java - RechnungsPositionDO.kt EingangsrechnungsPositionDO.java -> EingangsrechnungsPositionDO.kt AbstractRechnungsPositionDO.java -> AbstractRechnungsPositionDO.kt
050a5ccd7 @NoHistory needed as field annotation: @field:NoHistory.
05244ff19 CopyRight of all Kotlin file-header updated or created.
f55615812 Kotlin migration of {Abstract|Eingangs}RechnungDO...
39a2bd24f AbstractRechnungDO: More open attributes
bd9803e81 AbstractRechnungDO: Add open to previously protected attributes.
103e4f592 AbstractRechnungDO.java -> AbstractRechnungDO.kt