Kandra.Domain.Accounting.AccountBalance
A running total per (account, subconto combination). SQL Server and PostgreSQL both treat NULL as distinct-from-itself in unique indexes, so a plain unique index over the five nullable slot columns cannot enforce "one balance row per (account, subconto combination)" - SubcontoHash sidesteps this.
Properties
| Property | Description |
|---|
AccountCode | The account this balance belongs to, as a sort-key string (see ToSortKey). |
ClosingBalance | The current running total (typically DebitTurnover minus CreditTurnover, sign convention depending on the account). |
CreditTurnover | Sum of credit-side deltas applied to this balance row so far. |
DebitTurnover | Sum of debit-side deltas applied to this balance row so far. |
Id | This row's own identity. |
Slot1 | Subconto slot 1 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there. |
Slot2 | Subconto slot 2 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there. |
Slot3 | Subconto slot 3 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there. |
Slot4 | Subconto slot 4 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there. |
Slot5 | Subconto slot 5 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there. |
SubcontoHash | Lowercase hex-encoded, always exactly 64 characters - same "text, not raw bytes" convention as Blob.Sha256Hash (Kandra.Domain.Entities.Other.Blob), for the same reason: lets anyone eyeball/query the hash directly in a DB tool without an encode/decode step. See SubcontoValueSet.ComputeHash(). |
Version | App-managed optimistic-concurrency counter, bumped on every write - a fallback for any read-modify-write path outside AccountBalanceUpdater's own atomic update. |
Kandra.Domain.Accounting.AccountCode
Value type for a chart-of-accounts code: an ordered list of non-negative segment numbers (e.g. 46.5.1), one segment per level of nesting from the chart's roots down to the account itself. Two codes are equal only when every segment matches, and Of/ Parse are the usual ways a configuration builds one; comparisons/hierarchy checks (IsChildOf, Parent) work purely off the segment list, with no dependency on an actual ChartOfAccounts instance.
Constructors
| Constructor | Description |
|---|
AccountCode(Int32[]) | Builds a code directly from its segments (e.g. new AccountCode(46, 5, 1)). |
Properties
| Property | Description |
|---|
Depth | Number of segments - how deep this code sits in the chart (1 = a root-level account). |
Parent | The code one level up (all segments but the last), or null when this code is already a root (Depth 1). |
Segments | The raw segment numbers, most-significant first. |
Methods
| Method | Description |
|---|
CompareTo(AccountCode) | Segment-by-segment comparison (shorter is "less" when it's a prefix of the longer one), matching ToSortKey's ordering. Returns: Negative if this code sorts before other, positive if after, zero if equal. |
Equals(AccountCode) | True when both codes have the exact same segments. Returns: True if the segment lists match. |
Equals(Object) | True when obj is an AccountCode with the exact same segments. Returns: True if obj is an equal AccountCode. |
GetHashCode | undocumented Returns: A hash code consistent with Equals. |
IsChildOf(AccountCode) | True when parent's segments are a strict prefix of this code's segments. Returns: True if this code is nested somewhere under parent. |
Of(Int32[]) | Builds a code from its segments - equivalent to the constructor, for a fluent call site. Returns: The resulting code. |
op_Equality(AccountCode, AccountCode) | Equivalent to Equals. Returns: True if the codes are equal. |
op_Inequality(AccountCode, AccountCode) | Equivalent to the negation of Equals. Returns: True if the codes are not equal. |
Parse(String) | Parses the human-entered dotted form (e.g. "46.5.1", no zero-padding required). Returns: The parsed code. |
ParsePadded(String) | Parses the zero-padded storage/sort form (e.g. "046.005.001"), the inverse of ToSortKey. Returns: The parsed code. |
ToSortKey(Int32) | The zero-padded storage/sort form, e.g. "046.005.001" - sorts lexicographically the same as CompareTo. Returns: The dot-separated, zero-padded segment numbers. |
ToString | The human-readable dotted form, e.g. "46.5.1" - unpadded, the inverse of Parse. Returns: The dot-separated segment numbers. |
Kandra.Domain.Accounting.AccountFolder
A non-postable organizational node with children - groups related accounts/subfolders under one display node in the chart tree. Never carries subconto slots and is never a valid posting target; only its descendant leaves (AccountLeafBase) are postable.
Constructors
| Constructor | Description |
|---|
AccountFolder(Int32, String, IReadOnlyList<AccountNode>, Boolean) | undocumented |
Properties
| Property | Description |
|---|
Children | This folder's direct children (leaves and/or subfolders), in declaration/display order. Every child's Number must be unique among these siblings. |
Kandra.Domain.Accounting.AccountLeaf
A postable leaf account with no subconto slots at all - the plain, no-analytics case.
Constructors
| Constructor | Description |
|---|
AccountLeaf(Int32, String, Boolean) | undocumented |
Kandra.Domain.Accounting.AccountLeafBase
Base of the postable-leaf family (AccountLeaf/AccountLeaf.. AccountLeaf/AccountLeafGeneral) - a leaf, unlike an AccountFolder, is structurally childless and is the only kind of AccountNode a PostingTransaction may target. A configuration never derives from this class directly; it instantiates one of the concrete leaf types in its BuildChart implementation.
Constructors
| Constructor | Description |
|---|
AccountLeafBase(Int32, String, IReadOnlyList<SubcontoSlotDeclaration>, Boolean) | undocumented |
Properties
| Property | Description |
|---|
SubcontoSlots | This leaf's declared subconto slots, in order (slot 0 first) - empty for a leaf with no subconto. |
Fields
| Field | Description |
|---|
MaxSubconto | The maximum number of subconto slots any single account may declare (5). |
Kandra.Domain.Accounting.AccountLeafGeneral
Escape hatch for a leaf whose slot shapes aren't a plain one-type-per-slot pattern - in particular composite ("settlement documents"-style) slots accepting several member types, built with OneOf and overloads, or slot counts beyond the generic-arity AccountLeaf family's fixed shapes.
Constructors
| Constructor | Description |
|---|
AccountLeafGeneral(Int32, String, IReadOnlyList<SubcontoSlotDeclaration>, Boolean) | undocumented |
Kandra.Domain.Accounting.AccountLeaf<T0>
A postable leaf account with exactly one subconto slot, restricted to a single member type.
Constructors
| Constructor | Description |
|---|
AccountLeaf(Int32, String, Boolean) | undocumented |
Kandra.Domain.Accounting.AccountLeaf<T0, T1>
A postable leaf account with two subconto slots, each restricted to a single member type.
Constructors
| Constructor | Description |
|---|
AccountLeaf(Int32, String, Boolean) | undocumented |
Kandra.Domain.Accounting.AccountLeaf<T0, T1, T2>
A postable leaf account with three subconto slots, each restricted to a single member type.
Constructors
| Constructor | Description |
|---|
AccountLeaf(Int32, String, Boolean) | undocumented |
Kandra.Domain.Accounting.AccountLeaf<T0, T1, T2, T3>
A postable leaf account with four subconto slots, each restricted to a single member type.
Constructors
| Constructor | Description |
|---|
AccountLeaf(Int32, String, Boolean) | undocumented |
Kandra.Domain.Accounting.AccountLeaf<T0, T1, T2, T3, T4>
A postable leaf account with five subconto slots (the maximum, see MaxSubconto), each restricted to a single member type.
Constructors
| Constructor | Description |
|---|
AccountLeaf(Int32, String, Boolean) | undocumented |
Kandra.Domain.Accounting.AccountNode
A node in a ChartOfAccounts tree. A node is either a folder (has children, cannot be posted to, has no subconto) or a leaf (no children, can be posted to, may have 0-5 subconto slots - see AccountLeafBase). Leaf-ness is structural (no children), not tied to depth.
Constructors
| Constructor | Description |
|---|
AccountNode(Int32, String, Boolean) | undocumented |
Properties
| Property | Description |
|---|
IsLiteral | Mirrors VisibleNameAttribute's Value/IsLiteral pair: by default (false) Name is a localization resource key, resolved via IsLiteral ? Name : localizer[Name] (see Kandra.Application.Services.Accounting. AccountNodeLocalizationExtensions.GetDisplayName). Set true to use Name as a literal, unlocalized display string instead. |
Name | Display name or, when IsLiteral is false, a localization resource key for it - see IsLiteral. |
Number | This node's own number within its parent (folder or chart root) - siblings must have distinct numbers; combined with its ancestors' numbers this forms the node's AccountCode (see ChartFlattener). |
Kandra.Domain.Accounting.AccountWithSubconto
One side (source or destination) of a PostingTransaction: an account plus the subconto values posted against it. Built via IChartOfAccountsService.AsOf(...)/ IPostingService.AsOf(...) -> AccountWithSubcontoBuilder.With(...) -> Build() - not meant to be constructed directly.
Constructors
| Constructor | Description |
|---|
AccountWithSubconto(AccountCode, ISubcontoSet) | One side (source or destination) of a PostingTransaction: an account plus the subconto values posted against it. Built via IChartOfAccountsService.AsOf(...)/ IPostingService.AsOf(...) -> AccountWithSubcontoBuilder.With(...) -> Build() - not meant to be constructed directly. |
Properties
| Property | Description |
|---|
Account | The account this side of the transaction posts against. |
Subconto | The subconto values posted against Account, or null when it declares no subconto slots. |
Kandra.Domain.Accounting.AccountWithSubcontoBuilder
Fluent builder for one side of a PostingTransaction - obtained via IChartOfAccountsService.AsOf(...)/IPostingService.AsOf(...), populated with With(...), finished with Build. Deliberately does not duplicate PostingService.Validate's slot-count/type/order checks here - Build just assembles the value; PostAsync remains the single authority on whether it's actually valid for this leaf.
Constructors
| Constructor | Description |
|---|
AccountWithSubcontoBuilder(AccountCode, AccountLeafBase) | undocumented |
Methods
| Method | Description |
|---|
Build | Finishes the builder into an immutable posting side. Does not validate slot count/type/order against the leaf - see this class's own doc comment for why. Returns: The account plus whatever subconto set (if any) was attached via a prior With(...) call. |
With(ISubcontoSet) | Attaches an already-built ISubcontoSet directly, bypassing every other With(...) overload. Returns: This builder, for chaining. |
With(SubcontoSlot[]) | Untyped, but with an explicit TypeId per slot (via SubcontoSlot) instead of inferring one from the account's own single-member declaration - the one path that can post to a composite (OneOf) slot without a compile-time CLR type, as long as the caller already knows which member type applies. Each non-null slot's TypeId is checked against the account's own declaration; naming a TypeId the slot doesn't accept throws. Same "must cover every declared slot" rule as With - see its own doc comment. Returns: This builder, for chaining. |
With(Nullable<Guid>[]) | Untyped: infers each slot's TypeId from the account's own declaration, since the caller has no compile-time CLR type to offer (e.g. the universal AccountTransaction document, whose subconto values are picked through the UI as bare Guids). Accepts 1-5 args via the implicit Guid -> Guid? conversion, so With(id1)/With(id1, id2)/... all resolve here without a separate overload per arity. ids must cover at least every declared slot (a caller may still pass more - e.g. AccountTransaction's fixed 5 flat fields against an account declaring fewer - the extras beyond the declared count are simply never read); an individual slot value may still be null (no subconto in that position), but silently under-supplying the array itself would hide a caller bug, so that throws instead. Throws too if a slot with an actual value is composite (OneOf) - there's no way to disambiguate which member type a bare Guid belongs to. Returns: This builder, for chaining. |
With<T0>(T0) | Attaches a one-slot subconto value from an actual entity instance, inferring T1 from it. Returns: This builder, for chaining. |
With<T0>(Guid) | Attaches a one-slot subconto value from a raw entity id. Returns: This builder, for chaining. |
With<T0, T1>(T0, T1) | Attaches two-slot subconto values from actual entity instances, inferring the type arguments from them. Returns: This builder, for chaining. |
With<T0, T1>(Guid, Guid) | Attaches two-slot subconto values from raw entity ids. Returns: This builder, for chaining. |
With<T0, T1, T2>(T0, T1, T2) | Attaches three-slot subconto values from actual entity instances, inferring the type arguments from them. Returns: This builder, for chaining. |
With<T0, T1, T2>(Guid, Guid, Guid) | Attaches three-slot subconto values from raw entity ids. Returns: This builder, for chaining. |
With<T0, T1, T2, T3>(T0, T1, T2, T3) | Attaches four-slot subconto values from actual entity instances, inferring the type arguments from them. Returns: This builder, for chaining. |
With<T0, T1, T2, T3>(Guid, Guid, Guid, Guid) | Attaches four-slot subconto values from raw entity ids. Returns: This builder, for chaining. |
With<T0, T1, T2, T3, T4>(T0, T1, T2, T3, T4) | Attaches five-slot subconto values from actual entity instances, inferring the type arguments from them. Returns: This builder, for chaining. |
With<T0, T1, T2, T3, T4>(Guid, Guid, Guid, Guid, Guid) | Attaches five-slot subconto values (the maximum, see MaxSubconto) from raw entity ids. Returns: This builder, for chaining. |
Kandra.Domain.Accounting.ChartFlattener
Walks an AccountNode tree (typically ChartOfAccounts.Roots) and yields every leaf together with its fully-qualified AccountCode, computed from the chain of Number values from the roots down to that leaf. Folders contribute only to the code prefix; they never appear in the output themselves, since only leaves are postable.
Methods
| Method | Description |
|---|
Flatten(IEnumerable<AccountNode>, IEnumerable<Int32>) | Recursively flattens a chart (sub)tree into (code, leaf) pairs, depth-first, in declaration order. Returns: Every leaf reachable from items, paired with its full AccountCode. |
Kandra.Domain.Accounting.ChartOfAccounts
Name is required, not optional, on every AccountNode - a chart of accounts is mostly used for display/reporting, where an unnamed node is useless. Uniqueness of Name is deliberately not enforced - two accounts legitimately sharing a display name is normal in real charts of accounts; AccountCode/ Number remain the only identity-bearing values.
Constructors
| Constructor | Description |
|---|
ChartOfAccounts(IReadOnlyList<AccountNode>) | undocumented |
Properties
| Property | Description |
|---|
Roots | The chart's top-level nodes (folders and/or leaves), in declaration/display order. Every root's Number must be unique among these siblings. |
Kandra.Domain.Accounting.DynamicSubcontoSet
Runtime-built ISubcontoSet - the counterpart to the compile-time-generic SubcontoSet..SubcontoSet family, for the one call site that genuinely can't know its slot types at compile time: posting a document whose subconto values were picked by a user through the UI as runtime (TypeId, Guid) pairs (e.g. the universal account-transaction document), rather than derived from the document's own already-typed foreign keys the way every other document's posting call builds Set today. ISubcontoSet is already just one positional list of already-resolved slots, so this is a direct, trivial wrapper.
Constructors
| Constructor | Description |
|---|
DynamicSubcontoSet(IReadOnlyList<SubcontoSlot>) | Runtime-built ISubcontoSet - the counterpart to the compile-time-generic SubcontoSet..SubcontoSet family, for the one call site that genuinely can't know its slot types at compile time: posting a document whose subconto values were picked by a user through the UI as runtime (TypeId, Guid) pairs (e.g. the universal account-transaction document), rather than derived from the document's own already-typed foreign keys the way every other document's posting call builds Set today. ISubcontoSet is already just one positional list of already-resolved slots, so this is a direct, trivial wrapper. |
Properties
| Property | Description |
|---|
Values | The resolved slot values passed to the constructor, positional. |
Kandra.Domain.Accounting.EnumSubconto<T0>
Adapts any attribute-decorated enum into an ISubconto, for a subconto kind whose value set is a small, closed C# enum (e.g. a payment method) rather than a catalog entity - the enum owns its own Guids via attributes, right next to the values they identify, so there's nothing to separately register.
Constructors
| Constructor | Description |
|---|
EnumSubconto(T0) | undocumented |
Properties
| Property | Description |
|---|
Id | The Value member's own [SubcontoId] Guid. |
Kandra#Domain#Commons#IRootEntity#Id | Explicit Id implementation - read-only in practice; the setter always throws, since identity is fully determined by Value at construction time. |
TypeId | This closed generic's static TypeId - the same value for every EnumSubconto instance of a given TEnum, read off TEnum's own [TypeId] attribute. |
Value | The wrapped enum member. |
Methods
| Method | Description |
|---|
FromId(Guid) | Reverse of the constructor: builds the EnumSubconto whose [SubcontoId] is id - needed to turn a runtime-picked subconto value (a raw Guid entity id chosen through the UI, whose meaning as "which enum member" is otherwise opaque) back into a concrete ISubconto instance for posting. Deliberately duplicates the small reflection-backed reverse-lookup dictionary here rather than referencing Kandra.Forms.Accounting.SubcontoEnumValues<TEnum>'s own client-side ByGuid (same reasoning ResolveValueIds' own doc history already established: cheaper than adding a Kandra.Domain -> Kandra.Forms reference for one ~1-line lookup). Returns: The wrapping EnumSubconto for the matching member. |
Kandra.Domain.Accounting.IChartOfAccountsBuilder
Supplies the concrete chart to IChartOfAccountsService. An application (e.g. KandraWms) implements and registers exactly one of these - it is the only application-specific piece of the whole chart-of-accounts/posting subsystem.
Methods
| Method | Description |
|---|
BuildChart | Builds the application's whole chart of accounts, once, at startup. Returns: The complete, immutable chart used for the lifetime of the application. |
Kandra.Domain.Accounting.IChartOfAccountsService
Engine-implemented, injected directly by ordinary constructor injection (unlike register writers/the posting service, reading the chart and starting a posting-side builder needs no submit-scope gating). Wraps the single ChartOfAccounts built by the application's IChartOfAccountsBuilder, resolved once at startup, and is the main way application code looks up leaves/subconto metadata or begins building a PostingTransaction side via AsOf.
Properties
| Property | Description |
|---|
Chart | The application's whole chart, as built by its IChartOfAccountsBuilder. |
Methods
| Method | Description |
|---|
AsOf(AccountCode) | Starts building one side of a posting transaction for this account - see AccountWithSubcontoBuilder. Returns: A builder for attaching subconto values and finishing with Build(). |
AsOf(Int32[]) | Same as AsOf, building the AccountCode from raw segments first. Returns: A builder for attaching subconto values and finishing with Build(). |
AsOf(String) | Same as AsOf, parsing the account code from its human-readable dotted form first. Returns: A builder for attaching subconto values and finishing with Build(). |
FindAccountsBySubcontoType(Guid) | Returns, for a given subconto kind, the set of leaf accounts that declare it, grouped by the slot index (0-based) at which it appears. Returns: For each slot index where the type appears, the account codes declaring it there. |
GetLeaf(AccountCode) | Resolves a code to its leaf, throwing if it doesn't name one. Returns: The leaf named by code. |
GetSubcontoInfo(Guid) | Reverse of the Type overload: same SubcontoInfo, looked up by TypeId instead. Returns: The resolved info, or null when typeId isn't declared anywhere in this chart. |
GetSubcontoInfo(Type) | Resolves everything the chart knows about a subconto CLR type - TypeId, structural kind, and (kind-dependent) EnumType/RootType, computed once at chart-construction time with no separate registration step. Returns: The resolved info, or null when subcontoType isn't declared anywhere in this chart. |
TryGetLeaf(AccountCode, AccountLeafBase) | Attempts to resolve a code to its leaf without throwing. Returns: True if code names a leaf in this chart. |
Kandra.Domain.Accounting.ISubcontoSet
Passes concrete subconto values into a posting call: a positional list of already-resolved (TypeId, EntityId) pairs, one per declared slot - null means "no subconto in this position." Kept intentionally thin, since everything downstream (validation, persistence) only ever reads TypeId/EntityId off a slot, never a hydrated entity - see SubcontoSlot's own doc comment.
Properties
| Property | Description |
|---|
Values | The resolved slot values, positional (index 0 = slot 1, and so on) - null at an index means "no subconto in that slot." |
Kandra.Domain.Accounting.ISubcontoTypeSet
The compile-time-typed flavor of ISubcontoSet - additionally exposes the CLR Type behind each slot, purely for nicer PostingService validation diagnostics (e.g. "expected Counterparty, got Item" instead of two bare Guids). Implemented by SubcontoSet..SubcontoSet; the dynamic/guid-only builder path (AccountWithSubcontoBuilder's untyped With(...) overload, DynamicSubcontoSet) implements plain ISubcontoSet only - it never has a CLR type to offer.
Properties
| Property | Description |
|---|
Types | The CLR type behind each slot, positional and parallel to Values - null at an index whenever the corresponding value is also null. |
Kandra.Domain.Accounting.PostingBatch
One batch per document, ever - PostAsync merges the incoming transaction list against whatever's currently active for the document (see PostingTransactionRecord), instead of reversing and reinserting on every re-post.
Properties
| Property | Description |
|---|
CreatedAtUtc | When this batch was first created. |
Date | The batch's posting date, as supplied to the merge. |
Description | Optional free-text description, as supplied to the merge. |
DocumentId | The document this batch belongs to - unique; a document has at most one batch, ever. |
Id | This batch's own identity. |
LastModifiedAtUtc | When this batch was last touched by a merge. |
Revision | Increments only when a merge actually writes something (i.e. a re-post that changed nothing leaves this untouched). |
Transactions | Every transaction row ever written for this batch - both currently-active and deprecated rows live here together (see IsActive). |
Kandra.Domain.Accounting.PostingMergeResult
Result of merging a document's candidate posting lines against its existing batch (see IPostingRepository.PrepareMergedBatchAsync) - tells the caller exactly which rows changed effect, so only those need to be applied/reverted against AccountBalance rather than reversing and reapplying the whole batch on every re-post.
Constructors
| Constructor | Description |
|---|
PostingMergeResult(PostingBatch, Boolean, IReadOnlyList<PostingTransactionRecord>, IReadOnlyList<PostingTransactionRecord>) | Result of merging a document's candidate posting lines against its existing batch (see IPostingRepository.PrepareMergedBatchAsync) - tells the caller exactly which rows changed effect, so only those need to be applied/reverted against AccountBalance rather than reversing and reapplying the whole batch on every re-post. |
Properties
| Property | Description |
|---|
Activated | Rows that just turned active - their effect on AccountBalance must be applied. |
Batch | The document's batch, created if it didn't already exist. |
Deprecated | Rows that just turned inactive - their effect on AccountBalance must be reverted. |
HasChanges | False, with both Deprecated and Activated empty, when every candidate line already matched what was active - no write occurred. |
Kandra.Domain.Accounting.PostingTransaction
A posting is never a bare amount against an isolated account. It is always an explicit, linked pair: source (where value leaves - conventionally the credited account) and destination (where value arrives - conventionally the debited account). A batch is a list of such linked pairs, not two independent lists of debit/credit lines - this makes it structurally impossible to end up with an unbalanced or "dangling" posting, because every transaction already carries both of its ends.
Constructors
| Constructor | Description |
|---|
PostingTransaction(Guid, AccountWithSubconto, AccountWithSubconto, Decimal, Nullable<Guid>, Nullable<Decimal>) | A posting is never a bare amount against an isolated account. It is always an explicit, linked pair: source (where value leaves - conventionally the credited account) and destination (where value arrives - conventionally the debited account). A batch is a list of such linked pairs, not two independent lists of debit/credit lines - this makes it structurally impossible to end up with an unbalanced or "dangling" posting, because every transaction already carries both of its ends. |
Properties
| Property | Description |
|---|
Amount | The posted amount in the functional/base currency - always set, regardless of whether a foreign-currency amount is also carried. |
CurrencyAmount | The same posting expressed in CurrencyId's currency, or null when this transaction carries no foreign-currency amount. |
CurrencyId | Either both CurrencyId and CurrencyAmount are set (the transaction also carries a foreign-currency amount) or both are null (functional-currency-only transaction). Amount itself is always in the functional/base currency. |
Destination | Same shape as Source, the other end of the posting. |
LineId | The caller's own stable identifier for this line - typically the source document's own line id (e.g. a document line's Id). Used by the merge (Kandra.Persistence) to correlate a line across re-posts - not something PostingTransaction generates itself. |
Source | Built via IChartOfAccountsService.AsOf(...)/IPostingService.AsOf(...) -> AccountWithSubcontoBuilder.With(...) -> Build(). |
Kandra.Domain.Accounting.PostingTransactionRecord
Both sides (source and destination) of one elementary posting live on the same row - the direct implementation of "transactions can't be taken from nowhere and targeted to nowhere": there is no separate debit row and credit row to correlate via a foreign key, the pair is the row. A row is written once and, other than the IsActive/DeprecatedAtUtc flip below, never mutated again - a changed or removed line (relative to what's currently active for the document) is deprecated in place, not deleted, so history stays queryable. Correlation across re-posts is by LineId, not row position.
Properties
| Property | Description |
|---|
Amount | The posted amount in the functional/base currency. |
Batch | Navigation to the owning batch - used by movement/turnover reads that filter by Batch.Date. |
BatchId | The PostingBatch this row belongs to. |
CreatedAtUtc | When this row was written. |
CurrencyAmount | The same posting expressed in CurrencyId's currency, or null when this transaction carries no foreign-currency amount. |
CurrencyId | Set together with CurrencyAmount when this transaction also carries a foreign-currency amount; both null for a functional-currency-only transaction. |
DeprecatedAtUtc | Null while IsActive; set once, when a later merge superseded this row. |
DestinationAccountCode | The destination account's zero-padded sort-key code (see ToSortKey). |
DestinationSlot1 | The destination side's subconto slot 1, or null if the destination account declares no slot there. |
DestinationSlot2 | The destination side's subconto slot 2, or null if the destination account declares no slot there. |
DestinationSlot3 | The destination side's subconto slot 3, or null if the destination account declares no slot there. |
DestinationSlot4 | The destination side's subconto slot 4, or null if the destination account declares no slot there. |
DestinationSlot5 | The destination side's subconto slot 5, or null if the destination account declares no slot there. |
Id | This row's own identity - stable across re-posts, unlike LineId which correlates a row with the source document line it came from. |
IsActive | True while this row is the current effect of its LineId; false once superseded by a later merge (see DeprecatedAtUtc). |
LineId | The caller's own stable identifier for this line (see LineId) - used to correlate this row across re-posts. |
SourceAccountCode | The source account's zero-padded sort-key code (see ToSortKey). |
SourceSlot1 | The source side's subconto slot 1, or null if the source account declares no slot there. |
SourceSlot2 | The source side's subconto slot 2, or null if the source account declares no slot there. |
SourceSlot3 | The source side's subconto slot 3, or null if the source account declares no slot there. |
SourceSlot4 | The source side's subconto slot 4, or null if the source account declares no slot there. |
SourceSlot5 | The source side's subconto slot 5, or null if the source account declares no slot there. |
Kandra.Domain.Accounting.Subconto
Static helpers for both declaring chart slots (Of/OneOf and overloads, building SubcontoSlotDeclaration) and building actual posting call-site values (Set and overloads, building SubcontoSet and friends). A static generic method infers its type arguments from the arguments passed to it, a constructor invocation does not - so e.g. Subconto.Set<Item>(item.Id) only needs an explicit type argument because a bare Guid can't imply one; the instance overloads (Set(item)) infer it for free from the argument itself.
Methods
| Method | Description |
|---|
Of<T0> | Declares a chart slot that accepts exactly one subconto type - the common case, used from an BuildChart implementation. Returns: A single-member slot declaration for T. |
OneOf(IReadOnlyList<Type>) | Runtime/params form for composite slots with more members than convenient generic arity covers - real "settlement documents"-style slots in mature configurations commonly list 6-10 document types. TypeId is resolved via TypeIdResolver here (not the static-abstract-member trick the generic overloads above use for free) since only a runtime Type is known at this call site. Returns: A slot declaration with one member per entry in memberTypes. |
OneOf<T0, T1> | Declares a composite chart slot that accepts any one of two subconto types (e.g. a "settlement documents"-style slot). Returns: A two-member slot declaration. |
OneOf<T0, T1, T2> | Declares a composite chart slot that accepts any one of three subconto types. Returns: A three-member slot declaration. |
OneOf<T0, T1, T2, T3> | Declares a composite chart slot that accepts any one of four subconto types. Returns: A four-member slot declaration. |
Set<T0>(T0) | Builds the posting-time value for a one-slot account from an actual entity instance, inferring T1 from it. Returns: A typed set carrying the resolved slot value. |
Set<T0>(Guid) | Builds the posting-time value for a one-slot account from a raw entity id. Returns: A typed set carrying the resolved slot value. |
Set<T0, T1>(T0, T1) | Builds the posting-time value for a two-slot account from actual entity instances, inferring the type arguments from them. Returns: A typed set carrying the resolved slot values. |
Set<T0, T1>(Guid, Guid) | Builds the posting-time value for a two-slot account from raw entity ids. Returns: A typed set carrying the resolved slot values. |
Set<T0, T1, T2>(T0, T1, T2) | Builds the posting-time value for a three-slot account from actual entity instances, inferring the type arguments from them. Returns: A typed set carrying the resolved slot values. |
Set<T0, T1, T2>(Guid, Guid, Guid) | Builds the posting-time value for a three-slot account from raw entity ids. Returns: A typed set carrying the resolved slot values. |
Set<T0, T1, T2, T3>(T0, T1, T2, T3) | Builds the posting-time value for a four-slot account from actual entity instances, inferring the type arguments from them. Returns: A typed set carrying the resolved slot values. |
Set<T0, T1, T2, T3>(Guid, Guid, Guid, Guid) | Builds the posting-time value for a four-slot account from raw entity ids. Returns: A typed set carrying the resolved slot values. |
Set<T0, T1, T2, T3, T4>(T0, T1, T2, T3, T4) | Builds the posting-time value for a five-slot account from actual entity instances, inferring the type arguments from them. Returns: A typed set carrying the resolved slot values. |
Set<T0, T1, T2, T3, T4>(Guid, Guid, Guid, Guid, Guid) | Builds the posting-time value for a five-slot account (the maximum, see MaxSubconto) from raw entity ids. Returns: A typed set carrying the resolved slot values. |
Kandra.Domain.Accounting.SubcontoInfo
Everything the engine knows about one subconto CLR type actually referenced somewhere in a chart, computed once at chart-construction time (IChartOfAccountsService) and handed back whole from either lookup direction (by Type or by TypeId) - the single place both slot-declaration rendering (ChartOfAccountsTreeService) and posted-value resolution (SubcontoResolutionService) get their classification from, instead of each re-deriving it.
Constructors
| Constructor | Description |
|---|
SubcontoInfo(Guid, Type, SubcontoKind, Type, Type) | Everything the engine knows about one subconto CLR type actually referenced somewhere in a chart, computed once at chart-construction time (IChartOfAccountsService) and handed back whole from either lookup direction (by Type or by TypeId) - the single place both slot-declaration rendering (ChartOfAccountsTreeService) and posted-value resolution (SubcontoResolutionService) get their classification from, instead of each re-deriving it. |
Properties
| Property | Description |
|---|
EnumType | Set only when Kind is Enum - the unwrapped TEnum itself, not the closed EnumSubconto<TEnum> wrapper. |
Kind | What structural kind of subconto this type is (dictionary-backed, enum-backed, ...). |
RootType | Set only when Kind is Dictionary - the hierarchy root the lookup repository is actually DI-registered for (equal to Type itself for a flat dictionary). |
Type | The subconto CLR type itself (e.g. a dictionary entity type, or a closed EnumSubconto<TEnum>). |
TypeId | The subconto CLR type's static TypeId, as declared in the chart. |
Kandra.Domain.Accounting.SubcontoSet
The zero-slot case: an ISubcontoSet with no values at all, for completeness alongside the SubcontoSet..SubcontoSet family (an account with no subconto slots simply posts with a null ISubcontoSet instead, so this type has no Subconto.Set factory of its own).
Properties
| Property | Description |
|---|
Values | Always empty - there are no slots to hold values. |
Kandra.Domain.Accounting.SubcontoSet<T0>
Compile-time-typed ISubcontoTypeSet for a one-slot account, returned by Set.
Constructors
| Constructor | Description |
|---|
SubcontoSet(Guid) | undocumented |
Properties
| Property | Description |
|---|
Types | Slot 1's CLR type (T1), as the single element of the positional list. |
Value1 | The resolved (TypeId, EntityId) pair for slot 1. |
Values | Slot 1's value, as the single element of the positional list. |
Kandra.Domain.Accounting.SubcontoSet<T0, T1>
Compile-time-typed ISubcontoTypeSet for a two-slot account, returned by Set.
Constructors
| Constructor | Description |
|---|
SubcontoSet(Guid, Guid) | undocumented |
Properties
| Property | Description |
|---|
Types | Both slots' CLR types (T1, T2), positional. |
Value1 | The resolved (TypeId, EntityId) pair for slot 1. |
Value2 | The resolved (TypeId, EntityId) pair for slot 2. |
Values | Both slots' values, positional (slot 1, then slot 2). |
Kandra.Domain.Accounting.SubcontoSet<T0, T1, T2>
Compile-time-typed ISubcontoTypeSet for a three-slot account, returned by Set.
Constructors
| Constructor | Description |
|---|
SubcontoSet(Guid, Guid, Guid) | undocumented |
Properties
| Property | Description |
|---|
Types | All three slots' CLR types (T1, T2, T3), positional. |
Value1 | The resolved (TypeId, EntityId) pair for slot 1. |
Value2 | The resolved (TypeId, EntityId) pair for slot 2. |
Value3 | The resolved (TypeId, EntityId) pair for slot 3. |
Values | All three slots' values, positional (slot 1, 2, then 3). |
Kandra.Domain.Accounting.SubcontoSet<T0, T1, T2, T3>
Compile-time-typed ISubcontoTypeSet for a four-slot account, returned by Set.
Constructors
| Constructor | Description |
|---|
SubcontoSet(Guid, Guid, Guid, Guid) | undocumented |
Properties
| Property | Description |
|---|
Types | All four slots' CLR types (T1..T4), positional. |
Value1 | The resolved (TypeId, EntityId) pair for slot 1. |
Value2 | The resolved (TypeId, EntityId) pair for slot 2. |
Value3 | The resolved (TypeId, EntityId) pair for slot 3. |
Value4 | The resolved (TypeId, EntityId) pair for slot 4. |
Values | All four slots' values, positional (slot 1 through 4). |
Kandra.Domain.Accounting.SubcontoSet<T0, T1, T2, T3, T4>
Compile-time-typed ISubcontoTypeSet for a five-slot account (the maximum, see MaxSubconto), returned by Set.
Constructors
| Constructor | Description |
|---|
SubcontoSet(Guid, Guid, Guid, Guid, Guid) | undocumented |
Properties
| Property | Description |
|---|
Types | All five slots' CLR types (T1..T5), positional. |
Value1 | The resolved (TypeId, EntityId) pair for slot 1. |
Value2 | The resolved (TypeId, EntityId) pair for slot 2. |
Value3 | The resolved (TypeId, EntityId) pair for slot 3. |
Value4 | The resolved (TypeId, EntityId) pair for slot 4. |
Value5 | The resolved (TypeId, EntityId) pair for slot 5. |
Values | All five slots' values, positional (slot 1 through 5). |
Kandra.Domain.Accounting.SubcontoSlot
Storage-shaped subconto slot value - a single (TypeId, EntityId) pair, mapped as an EF Core optional complex type (see Kandra.Persistence). A slot is never meaningfully "half set" - TypeId and EntityId are always populated together or not at all - so both are required inside the record, and it's the containing property that's nullable (SubcontoSlot?): null means "no subconto in this position."
Constructors
| Constructor | Description |
|---|
SubcontoSlot(Guid, Guid) | Storage-shaped subconto slot value - a single (TypeId, EntityId) pair, mapped as an EF Core optional complex type (see Kandra.Persistence). A slot is never meaningfully "half set" - TypeId and EntityId are always populated together or not at all - so both are required inside the record, and it's the containing property that's nullable (SubcontoSlot?): null means "no subconto in this position." |
Properties
| Property | Description |
|---|
EntityId | The referenced entity's own id. |
TypeId | The subconto CLR type's static TypeId (which ISubconto concrete type this value belongs to). |
Kandra.Domain.Accounting.SubcontoSlotDeclaration
Declares the set of concrete ISubconto types acceptable in one subconto slot of an AccountLeafBase. A slot's declared type is not always a single CLR type - a "composite" slot (e.g. the classic "settlement documents" slot on a receivables/payables account) accepts several different concrete types in the same slot. A simple slot is just the degenerate case of a one-element set - every concrete type keeps its own distinct TypeId, nothing is shared or collapsed.
Constructors
| Constructor | Description |
|---|
SubcontoSlotDeclaration(IReadOnlyList<SubcontoTypeMember>) | Declares the set of concrete ISubconto types acceptable in one subconto slot of an AccountLeafBase. A slot's declared type is not always a single CLR type - a "composite" slot (e.g. the classic "settlement documents" slot on a receivables/payables account) accepts several different concrete types in the same slot. A simple slot is just the degenerate case of a one-element set - every concrete type keeps its own distinct TypeId, nothing is shared or collapsed. |
Properties
| Property | Description |
|---|
Members | The acceptable member type(s) for this slot - one element for a simple slot, several for a composite ("one of") slot. Built via Of/OneOf and overloads. |
MemberTypes | The bare CLR types behind Members, in the same order. |
Methods
| Method | Description |
|---|
Accepts(Guid) | True when a value carrying this TypeId is a valid member of this slot. Returns: True if typeId matches one of this slot's Members. |
Accepts(Type) | True when a value of this CLR type is a valid member of this slot. Returns: True if runtimeType is one of this slot's Members. |
Kandra.Domain.Accounting.SubcontoTypeMember
One concrete CLR type - plus its already-resolved static TypeId - acceptable as a member of a subconto slot. Captured once at chart-declaration time (see Subconto): every generic Of/OneOf overload gets TypeId for free via T's static abstract TypeId member, no reflection; only the params-Type[] escape hatch still needs TypeIdResolver for the one case where only a runtime Type is known.
Constructors
| Constructor | Description |
|---|
SubcontoTypeMember(Guid, Type) | One concrete CLR type - plus its already-resolved static TypeId - acceptable as a member of a subconto slot. Captured once at chart-declaration time (see Subconto): every generic Of/OneOf overload gets TypeId for free via T's static abstract TypeId member, no reflection; only the params-Type[] escape hatch still needs TypeIdResolver for the one case where only a runtime Type is known. |
Properties
| Property | Description |
|---|
Type | The concrete ISubconto CLR type this member represents. |
TypeId | The member type's already-resolved static TypeId. |
Kandra.Domain.Accounting.SubcontoValueSet
The shape used when mapping a domain ISubcontoSet (what a caller passes into PostAsync) into the five persisted slots for one side (source/destination) of a PostingTransactionRecord, or for AccountBalance: five optional SubcontoSlot values as flat scalar columns, so reports can join EntityId directly to reference tables (Counterparty, Item, ...) without parsing anything.
Methods
| Method | Description |
|---|
ComputeHash | undocumented |
FromSlots(SubcontoSlot, SubcontoSlot, SubcontoSlot, SubcontoSlot, SubcontoSlot) | undocumented |
FromTypedSet(ISubcontoSet) | undocumented |
Kandra.Domain.ChangeTracking.ChangeEvent
One row of the change log (gap C07) - written alongside the entity/constant it describes, in the same DbContext/SaveChanges as that write (see IChangeEventRecorder). Deliberately holds the DTO's JSON, not the EF entity's, so the audit trail never leaks internal entity shape.
Properties
| Property | Description |
|---|
Action | The kind of change performed (create/update/delete, etc.). |
Date | UTC timestamp the change was recorded at. |
EntityId | Guid.Empty for constants - they have no per-row Guid identity, only a string Name (captured, if at all, inside PayloadJson). |
Id | Primary key of this log row. |
PayloadJson | DTO JSON. Null for Delete actions (no payload by design) and whenever PayloadStorageEnabled is false (the ChangeEventPayload feature flag was off at write time). |
PayloadSchemaVersion | Reserved for future schema evolution of PayloadJson. Always null today. |
PayloadStorageEnabled | True when the ChangeEventPayload feature flag was on at write time - distinguishes "flag was off" from "no payload for another reason (a Delete)" when PayloadJson is null. |
Scope | Which kind of object this event describes (Document/Dictionary/Constant/...). |
TypeId | IEntityWithTypeId.TypeId of the concrete Document/Dictionary type, or the constant descriptor's TypeId for Scope == Constant. |
UserId | Id of the user who performed the change. |
Kandra.Domain.Commons.IDictionaryEntity
Shape of every dictionary entity: IEntity plus a display Name. Satisfied automatically via DictionaryBase - a configuration developer derives their dictionary entity from that base class rather than implementing this interface directly.
Properties
| Property | Description |
|---|
Name | The dictionary item's display name. |
Kandra.Domain.Commons.IDocumentEntity
Shape of every document entity: IEntity plus a transaction Date and an IsActive (posted/submitted) flag. Satisfied automatically via DocumentBase - a configuration developer derives their document entity from that base class rather than implementing this interface directly.
Properties
| Property | Description |
|---|
Date | The document's transaction date/time, stored in UTC. |
IsActive | true once the document has been submitted/posted; false while still a draft. |
Kandra.Domain.Commons.IDocumentTypeResolver
Configuration-registered Guid(DocumentTypeId) -> concrete entity Type mapping - the runtime-typed counterpart to TypeIdResolver's compile-time-typed-to-Guid direction. One implementation per configuration (e.g. KandraWms.Persistence.Documents.WmsDocumentTypeResolver, a thin wrapper over that project's own reflection-built DocumentTypeRegistry), registered as a singleton - the engine layer (IDocumentReaderService) depends only on this abstraction, never on a configuration-specific registry directly, same engine/config split as IChartOfAccountsBuilder.
Methods
| Method | Description |
|---|
Resolve(Guid) | Resolves a DocumentTypeId Guid back to its concrete Document entity CLR type. Returns: The concrete Document type, or null when documentTypeId isn't a registered Document type in this configuration (e.g. stale data, or a type removed from the codebase) - callers skip/no-op rather than throw. |
Kandra.Domain.Commons.IEntity
Shape shared by every entity that has a business Code (dictionaries, documents) - mirrors Kandra.Forms.IEntityDto on the Forms plane. Deliberately not merged into IRootEntity itself: IRootEntity is the truly universal "has an Id" contract (e.g. subconto slots like EnumSubconto have no Code concept at all), so Code only belongs one level down, on the entity kinds that actually have one.
Properties
| Property | Description |
|---|
Code | The entity's business-facing code (independent of Id). |
Kandra.Domain.Commons.IEntityRow
Shape required of a document's tabular-section row class: just an Id. Implemented directly by every table-part row entity a configuration declares (e.g. a Waybill line); it's what lets the engine's orphan-removal machinery (IRowRemover) operate generically over any row type.
Properties
| Property | Description |
|---|
Id | The row's primary key. |
Kandra.Domain.Commons.IEntityWithRows<T0>
Mandatory on every document entity, even one with no tabular sections at all (an empty RowNavigations is still required). Implemented directly on the concrete document *Base class; it's what lets the generic CRUD save pipeline reconcile a document's table-part collections against what's already in the database without per-entity reflection.
Properties
| Property | Description |
|---|
RowNavigations | The document's own list of navigation-property selectors, one per tabular-section collection (e.g. x => x.Lines). Used by the save pipeline to discover which collections to reconcile. |
Methods
| Method | Description |
|---|
RemoveOrphans(T0, IRowRemover) | Removes rows present in the previously-fetched entity but no longer present in the current one, across every collection named by RowNavigations. |
Kandra.Domain.Commons.IEntityWithTypeId
Marker for anything that carries a static, per-CLR-type identifier (e.g. "this concrete subconto kind is identified by this Guid"). Generic name because it is a reusable shape, not tied to subconto specifically.
Properties
| Property | Description |
|---|
TypeId | The fixed Guid identifying this concrete CLR type, shared by every instance of it. |
Kandra.Domain.Commons.IHierarchicalItem
A dictionary item that participates in a parent/child tree. Every hierarchical dictionary carries a materialized ancestor Path unconditionally (no separate opt-in mixin) - it is what makes cycle-safe reparenting, breadcrumbs, and subtree queries cheap, and its storage cost is small enough not to warrant an opt-out. ParentId remains the single source of truth; Path is a server-maintained index over it, never client-writable. Satisfied automatically via HierarchicalDictionaryBase - a configuration developer doesn't implement this directly.
Properties
| Property | Description |
|---|
Depth | Nesting level, 0 for a root-level item, engine-maintained from ParentId. |
ParentId | The parent item's Id, or null for a root-level item. |
Path | Materialized ancestor path (root-to-parent), engine-maintained from ParentId and never client-writable. See PathEncoding for how each ancestor's Id is encoded into a segment. |
Kandra.Domain.Commons.IRootEntity
Universal "has an Id" contract - the root of the entity-shape hierarchy. Satisfied transitively via DictionaryBase/DocumentBase/register bases; a configuration developer does not implement this directly, it's picked up automatically by deriving from an engine base class.
Properties
| Property | Description |
|---|
Id | The entity's primary key. |
Kandra.Domain.Commons.IRowRemover
Engine-supplied helper passed into RemoveOrphans. The engine provides the only implementation; a configuration developer's document entity calls it, it never implements this interface itself.
Methods
| Method | Description |
|---|
RemoveOrphans<T0>(IList<T0>, IList<T0>) | Removes any row present in fetched but absent from keep. |
Kandra.Domain.Commons.ISubconto
A concrete subconto (analytical dimension) value is both a domain entity (has its own Id via IRootEntity) and tagged with its subconto kind (TypeId) via IEntityWithTypeId.
Kandra.Domain.Commons.PathEncoding
Encodes a hierarchical dictionary item's Guid as one materialized-Path segment. Base64 URL-safe, unpadded: 22 chars per segment vs. 32 for a raw hex Guid (~30% smaller), and the URL-safe alphabet avoids '/' colliding with the path delimiter. See docs/hierarchical-dictionaries-architecture.md §2.4.1 for the full rationale - this is that scheme, unchanged.
Methods
| Method | Description |
|---|
DecodeSegment(String) | Reverses EncodeSegment, recovering the original Id from one Path segment. Returns: The decoded Id. |
EncodeSegment(Guid) | Encodes one item's Id as a single URL-safe, unpadded Base64 Path segment. Returns: The 22-character encoded segment. |
Kandra.Domain.Commons.TypeIdResolver
Reflection-based, cached lookup of a concrete IEntityWithTypeId-implementing type's static TypeId, keyed by Type. Used where only the runtime type is known, not a compile-time generic parameter - e.g. a hierarchical dictionary's polymorphic ITEM_ENTITY (the abstract base doesn't implement IEntityWithTypeId, only its concrete FOLDER_ENTITY/LEAF_ENTITY subtypes do, each with its own TypeId), or a soft-deleted row where only its Id was known before the fetch. Same static-abstract-member convention as IEntityWithTypeId, resolved dynamically instead of via a generic constraint - mirrors KandraWms.Persistence.Documents.DocumentTypeRegistry's reflection, generalized to the engine layer.
Methods
| Method | Description |
|---|
Resolve(Type) | Looks up (and caches) the static TypeId declared by an IEntityWithTypeId-implementing type. Returns: The Guid returned by that type's static TypeId property. |
Kandra.Domain.Constants.ConstantRecord
A single stored constant value, keyed by name + the date it applies from. Deliberately a plain flat entity — no dimensions/details owned-type composition (the register engine's shape is overkill for a name+date+json row; see docs/kandra-architecture.md §2.7). Inherits audit stamps, soft-delete and the optimistic-concurrency Version from ChangesInfo.
Properties
| Property | Description |
|---|
Date | The date this value becomes effective from. |
Id | Primary key of this row. |
Name | The constant's name - identifies which constant this value belongs to. |
ValueJson | The constant's value, serialized as JSON. |
Kandra.Domain.Entities.ChangesInfo
Common audit/concurrency columns shared by every business entity - base of DictionaryBase, DocumentBase, ConstantRecord and ScheduledJobDefinition. A configuration developer never derives from this directly; it comes along automatically via those bases.
Properties
| Property | Description |
|---|
Created | UTC creation timestamp, defaulted at construction time. Not serialized to the wire. |
CreatorUserId | Id of the user who created the row. Not serialized to the wire. |
IsDeleted | true once the row has been soft-deleted. Not serialized to the wire. |
Modified | UTC last-modification timestamp, or null if never modified since creation. Not serialized to the wire. |
ModifierUserId | Id of the user who last modified the row, or null if never modified since creation. Not serialized to the wire. |
Version | Manually-incremented optimistic-concurrency token, checked/bumped on every update. |
Kandra.Domain.Entities.Dictionaries.DictionaryBase
Base class for every flat (non-hierarchical) dictionary entity. A configuration developer derives their concrete dictionary *Base class from this directly; deriving satisfies IDictionaryEntity automatically and picks up the audit/concurrency columns from ChangesInfo. Code and Name are virtual so a concrete entity can re-declare them with its own attributes (e.g. a different [MaxLength]) per the platform's contract-property convention.
Properties
| Property | Description |
|---|
Code | The dictionary item's business code. |
Id | The entity's primary key (sequential v7 Guid). |
Name | The dictionary item's display name. |
Kandra.Domain.Entities.Dictionaries.HierarchicalDictionaryBase
Base class for any hierarchical dictionary. Inherits the flat base — Id / Code / Name are unchanged and still mandatory for every hierarchical dictionary. Path/Depth are maintained by the engine (HierarchicalDictionaryCrudService), never set from a DTO.
Properties
| Property | Description |
|---|
Depth | Nesting level, 0 for a root-level item. Engine-maintained from ParentId, never set from a DTO. |
ParentId | The parent item's Id, or null for a root-level item. The single source of truth for the tree shape. |
Path | Materialized ancestor path. Engine-maintained from ParentId, never set from a DTO. |
Kandra.Domain.Entities.Documents.DocumentBase
Base class for every document entity. A configuration developer derives their concrete document *Base class from this directly; deriving satisfies IDocumentEntity automatically and picks up the audit/concurrency columns from ChangesInfo. Code is virtual so a concrete entity can re-declare it with its own attributes, per the platform's contract-property convention.
Constructors
| Constructor | Description |
|---|
DocumentBase(Guid) | Initializes a new document with its fixed DocumentTypeId. |
Properties
| Property | Description |
|---|
Code | The document's business code (e.g. a formatted document number). |
Date | The document's transaction date/time, stored in UTC, defaulted at construction time. |
DocumentTypeId | Identifies the concrete document type this row is (e.g. resolved back to a CLR type via IDocumentTypeResolver). Fixed for the lifetime of the row by the constructor - never reassigned after construction. |
Id | The entity's primary key (sequential v7 Guid). |
IsActive | true once the document has been submitted/posted; false while still a draft. |
Kandra.Domain.Entities.ISoftDeleted
Marks an entity as soft-deletable. Satisfied automatically via ChangesInfo - a configuration developer never implements this directly, it comes along for free with every Dictionary/Document entity.
Properties
| Property | Description |
|---|
IsDeleted | true once the row has been soft-deleted; it stays in the database, filtered out of normal queries. |
Kandra.Domain.Entities.Other.Blob
One unique piece of content, identified by its SHA-256 hash - stored exactly once regardless of how many uploads produced it (docs/blob-storage-architecture.md AD-1). Never referenced directly outside the blob-storage subsystem; consumers only ever see a BlobReference id (AD-2).
Properties
| Property | Description |
|---|
ContentType | Content type sniffed from the uploaded bytes' magic number, or null if it couldn't be determined. |
CreatedAt | UTC timestamp this content was first stored. |
Id | The blob's primary key. |
InlineContent | Populated instead of StorageKey when the content is at or below BlobStorageOptions.InlineStorageThresholdBytes (AD-11) - avoids IBlobContentStore entirely for small attachments. |
References | Every BlobReference pointing at this content (one blob can back many uploads/attachments). |
Sha256Hash | Lowercase hex-encoded, always exactly 64 characters. Stored as text (not raw bytes) - a bit less storage-efficient, but lets anyone eyeball/query the hash directly in a DB tool without an encode/decode step, which matters more for a low-volume table like this one. |
SizeBytes | Size of the content in bytes. |
StorageKey | Sharded on-disk/object-store key (AD-12). Null when InlineContent is set - exactly one of the two is ever non-null (AD-11, enforced by a DB check constraint). |
Kandra.Domain.Entities.Other.BlobReference
The only externally-visible id of an uploaded file (AD-2) - opaque, unrelated to the content hash. One row per upload event/attachment, covering its whole lifecycle: created Active+ephemeral on upload, promoted (EntityId/EntityTypeId set, ExpiresAt cleared) once attached to an owning entity, Tombstoned on detach/expiry, purged by GC once PurgeAfter elapses (docs/blob-storage-architecture.md AD-3/AD-4).
Properties
| Property | Description |
|---|
Blob | Navigation to the underlying content-addressed blob. |
BlobId | Id of the Blob this reference points at. |
CreatedAt | UTC timestamp this reference row was created (i.e. upload time). |
CreatedBySubjectId | Uploader's user id - used for pre-promotion authorization (only the creator may read/delete an ephemeral reference before it's promoted). |
DeclaredContentType | As declared by the client, distinct from Blob.ContentType (which is sniffed from magic bytes). |
EntityId | Id of the owning entity, set together with EntityTypeId on promotion; null while ephemeral. |
EntityTag | Optional human-readable note the owning entity's behavior may set (e.g. "Waybill #WB.2600019") so an admin browsing Sys_BlobReferences directly can identify what a reference belongs to without resolving EntityId/EntityTypeId back to a record. Purely informational - never read by the blob storage subsystem itself, and null while ephemeral (set only on/after promotion). |
EntityTypeId | TypeId of the owning entity's CLR type, set together with EntityId on promotion. |
ExpiresAt | TTL for an Active ephemeral reference. Null once promoted, or if the caller explicitly requested a non-expiring reference. |
Id | The reference's primary key - this, not BlobId, is the id a consumer stores/passes around. |
OriginalFileName | As declared by the uploading client - per-reference, not per-blob (same bytes can arrive under different filenames across uploads). |
PurgeAfter | GC purges a Tombstoned row once this elapses. |
State | Where this reference is in its Active/Tombstoned lifecycle. |
TombstonedAt | UTC timestamp the reference was tombstoned (detached or expired), or null while still Active. |
TombstoneReason | Freeform note on why the reference was tombstoned (e.g. "detached", "expired"), or null while still Active. |
Kandra.Domain.Entities.Other.DocumentLink
A directed reference between two Documents - either the "created from source" relationship (e.g. a Waybill created from an Invoice) or an ad-hoc link a user/data processor establishes independently (e.g. a Payment linked to the Invoice it pays). Written by a document's own OnSaveAsync (via IDocumentLinkService.SyncLinksAsync) - deliberately not tied to Submit/Unsubmit/Delete, and never cascade-deleted when either endpoint document is deleted (a link is a permanent historical trail; a row whose target no longer exists just fails to resolve for display, handled gracefully by IDocumentLinkService.GetLinksAsync rather than thrown). v1 is Document-to-Document only, but *EntityTypeId (not a fixed CLR type) is stored on both ends so the schema doesn't need to change if this widens to other entity kinds later.
Properties
| Property | Description |
|---|
EstablishedAt | UTC timestamp the link was established. |
FromEntityId | Id of the entity/document on the "from" side of the link. |
FromEntityTypeId | TypeId of the "from" side's CLR type. |
FromLineId | Set only when the link is scoped to one specific tabular-section row on the source side, e.g. a per-line "sourced from this Invoice line" relationship - null for a header-level link. |
Id | The link row's primary key. |
Tag | Freeform label identifying what kind of relationship this is (e.g. "SourceInvoice", "Payment") - part of a link's identity for sync purposes (see IDocumentLinkService.SyncLinksAsync), not just display text. |
ToEntityId | Id of the entity/document on the "to" side of the link. |
ToEntityTypeId | TypeId of the "to" side's CLR type. |
ToLineId | Set only when the link is scoped to one specific tabular-section row on the target side - null for a header-level link. |
Kandra.Domain.Entities.Other.IdNameLookupModel
Minimal shared shape for a dropdown/lookup projection over any dictionary/document entity - just enough to render a selectable option and flag soft-deleted rows, without pulling the whole entity across the wire.
Constructors
| Constructor | Description |
|---|
IdNameLookupModel(Guid, String, Boolean) | Minimal shared shape for a dropdown/lookup projection over any dictionary/document entity - just enough to render a selectable option and flag soft-deleted rows, without pulling the whole entity across the wire. |
Properties
| Property | Description |
|---|
Id | The entity's Id. |
IsDeleted | Whether the entity is soft-deleted, so a consumer can render it disabled/struck-through. |
Name | The entity's display text (e.g. its Name, or a combined Code/Name string). |
Kandra.Domain.Entities.Other.Settings
Single-row engine-level settings record (backed by ISettingsRepository/SettingsController). A configuration developer doesn't derive from or implement this - it's engine-owned infrastructure, currently just an identity placeholder with no settings fields defined yet.
Properties
| Property | Description |
|---|
Id | Fixed at 1 in practice - there is only ever one settings row. |
Kandra.Domain.Enums.ApplicationArea
Which part of the engine a piece of functionality (e.g. a numbering bucket, a permission, a log entry) belongs to. Used to scope/classify cross-cutting infrastructure by the kind of entity it applies to, without a hard dependency on the concrete entity type.
Fields
| Field | Description |
|---|
Constant | Belongs to a Constant. |
DataProcessor | Belongs to a DataProcessor. |
Dictionary | Belongs to a Dictionary entity. |
Document | Belongs to a Document entity. |
License | Belongs to the licensing subsystem. |
Other | Anything not covered by the other areas. |
Report | Belongs to a Report. |
Scheduler | Belongs to the job scheduler subsystem. |
Kandra.Domain.Exceptions.CycleDetectedException
A hierarchical dictionary move/reparent was rejected because it would create a cycle (moving an item under its own descendant). Mapped by HttpGlobalExceptionFilter to HTTP 400 Bad Request.
Constructors
| Constructor | Description |
|---|
CycleDetectedException(String) | Creates the exception with the given message. |
Kandra.Domain.Exceptions.DomainException
Base of every engine domain exception. A configuration can either catch (DomainException) broadly to handle any engine-raised business error the same way, or derive a configuration-specific subclass to get uniform HttpGlobalExceptionFilter HTTP-status mapping for free: any DomainException not explicitly mapped to another status (like NotFoundException to 404 or InvalidStateException to 409) is mapped to HTTP 400 Bad Request.
Constructors
| Constructor | Description |
|---|
DomainException | Creates the exception with no message. |
DomainException(String, Exception) | Creates the exception with the given message, wrapping an underlying cause. |
DomainException(String) | Creates the exception with the given message. |
Kandra.Domain.Exceptions.IdentityOperationException
An ASP.NET Core Identity operation (role/user create, update, delete, password reset) failed; carries the Identity-supplied error description as the message. Mapped by HttpGlobalExceptionFilter to HTTP 400 Bad Request.
Constructors
| Constructor | Description |
|---|
IdentityOperationException(String) | Creates the exception with the given message. |
Kandra.Domain.Exceptions.InsufficientBalanceException
A register write was rejected because it would drive a resource value negative (e.g. not enough balance for the touched dimensions). Mapped by HttpGlobalExceptionFilter to HTTP 400 Bad Request.
Constructors
| Constructor | Description |
|---|
InsufficientBalanceException(String) | Creates the exception with the given message. |
Kandra.Domain.Exceptions.InvalidStateException
The requested operation is rejected because of the entity's current state (e.g. deleting a still-submitted document, or a folder that still contains items). Mapped by HttpGlobalExceptionFilter to HTTP 409 Conflict.
Constructors
| Constructor | Description |
|---|
InvalidStateException(String) | Creates the exception with the given message. |
Kandra.Domain.Exceptions.NotFoundException
Thrown when a requested entity or resource doesn't exist. Mapped by HttpGlobalExceptionFilter to HTTP 404 Not Found.
Constructors
| Constructor | Description |
|---|
NotFoundException | Creates the exception with the default "Not found" message. |
NotFoundException(String) | Creates the exception with the given message. |
Kandra.Domain.Exceptions.NumberingRetryLimitExceededException
A Gapless-family GetNextAsync exhausted its bounded collision-retry loop (e.g. a pathological formatter that ignores its input, or a bulk manual import that pre-claimed a large contiguous range) without finding a free formatted number. Mapped by HttpGlobalExceptionFilter to HTTP 400 Bad Request.
Constructors
| Constructor | Description |
|---|
NumberingRetryLimitExceededException(String) | Creates the exception with the given message. |
Kandra.Domain.Exceptions.PrintRendererNotFoundException
No print renderer is registered for the requested (print form, render target, tag) combination - e.g. an unimplemented export format (Excel/Xml/Word/Html) or an unknown layoutTag. Mapped by HttpGlobalExceptionFilter to HTTP 404 Not Found.
Constructors
| Constructor | Description |
|---|
PrintRendererNotFoundException(String) | Creates the exception with the given message. |
Kandra.Domain.Exceptions.ValidationFailedException
Thrown by the FluentValidation integration when a Dto fails validation. Carries one Error per failed rule. Mapped by HttpGlobalExceptionFilter to HTTP 400 Bad Request, with Errors attached to the problem-details response as validationErrors.
Constructors
| Constructor | Description |
|---|
ValidationFailedException(IEnumerable<Error>) | Thrown by the FluentValidation integration when a Dto fails validation. Carries one Error per failed rule. Mapped by HttpGlobalExceptionFilter to HTTP 400 Bad Request, with Errors attached to the problem-details response as validationErrors. |
Properties
| Property | Description |
|---|
Errors | The individual property validation failures that caused this exception. |
Kandra.Domain.Exceptions.ValidationFailedException.Error
One failed validation rule for a single Dto property.
Properties
| Property | Description |
|---|
ErrorMessage | The human-readable message describing why validation failed. |
PropertyName | The name of the Dto property that failed validation. |
Kandra.Domain.Identity.ApplicationRole
Kandra's concrete ASP.NET Identity role, closed over a Guid key. One role entity for the whole app - not meant to be subclassed by a configuration.
Properties
| Property | Description |
|---|
Claims | The claims granted to every member of this role. |
UserRoles | The users that are members of this role. |
Kandra.Domain.Identity.ApplicationRoleClaim
One claim assigned to an ApplicationRole, granted to every member of that role (Identity's standard role-claim table).
Properties
| Property | Description |
|---|
Role | The role this claim belongs to. |
Kandra.Domain.Identity.ApplicationUser
Kandra's concrete ASP.NET Identity user, closed over a Guid key. A configuration doesn't subclass this further - it's the one user entity for the whole app, extended in place with the engine's own JWT/ban-state fields alongside the standard Identity navigation collections.
Properties
| Property | Description |
|---|
Claims | This user's claims (Identity's own claim table, not the JWT's feature claims). |
IsBanned | True when the user is banned from logging in. |
LastIssuedJwt | UTC time the user's most recent JWT was issued at. |
LastLogin | UTC time of this user's most recent successful login. |
LastLogout | UTC time of this user's most recent logout. |
LastPasswordChange | UTC time this user's password was last changed. |
Logins | This user's external login providers. |
RefreshToken | The current refresh token issued to this user, if any. |
RefreshTokenExpiryTime | UTC expiry of RefreshToken. |
Tokens | This user's stored Identity tokens (e.g. password-reset tokens). |
UserRoles | The roles this user is a member of. |
Kandra.Domain.Identity.ApplicationUserClaim
One claim assigned directly to an ApplicationUser (Identity's standard user-claim table).
Properties
| Property | Description |
|---|
User | The user this claim belongs to. |
Kandra.Domain.Identity.ApplicationUserLogin
One external login (e.g. an OAuth provider) linked to an ApplicationUser (Identity's standard user-login table).
Properties
| Property | Description |
|---|
User | The user this external login belongs to. |
Kandra.Domain.Identity.ApplicationUserRole
Join entity linking an ApplicationUser to an ApplicationRole (Identity's standard user-role membership table).
Properties
| Property | Description |
|---|
Role | The role side of the membership. |
User | The user side of the membership. |
Kandra.Domain.Identity.ApplicationUserToken
One stored Identity token (e.g. a password-reset or two-factor token) for an ApplicationUser (Identity's standard user-token table).
Properties
| Property | Description |
|---|
User | The user this token belongs to. |
Kandra.Domain.Numbering.GaplessCounter
Raw, atomically-incremented counter for one Gapless-family numbering bucket. See SimpleCounter for the long-vs-ulong storage note.
Properties
| Property | Description |
|---|
Bucket | The numbering bucket's name - primary key of this row. |
CurrentValue | The last value issued for this bucket. |
Version | Optimistic-concurrency token, incremented on every atomic update. |
Kandra.Domain.Numbering.GaplessIssued
Durable record of one formatted Gapless-family number, generated or manually entered. The unique index on (Bucket, FormattedNumber) is the collision-detection mechanism itself (spec 3.2) - callers rely on the constraint violation, never a separate check-then-insert.
Properties
| Property | Description |
|---|
Bucket | The owning numbering bucket's name. |
FormattedNumber | The final, formatted number as issued - the value callers actually see/store. |
Id | Primary key of this row. |
IssuedAt | UTC timestamp this number was issued at. |
Kind | Whether this number was generated by the numbering system or entered manually. |
RawValue | The raw counter value this number was derived from, if any (null for a purely manual entry with no underlying raw value). |
RequestorId | Id of the user/caller that requested this number, if known. |
RequestorTag | Free-form tag identifying the requestor when there's no RequestorId (e.g. a background job or import). |
Kandra.Domain.Numbering.IssuanceKind
How a GaplessIssued number came to be issued.
Fields
| Field | Description |
|---|
Generated | Produced by the numbering system itself, from the bucket's counter. |
Manual | Entered by a caller (e.g. a user typing in a document number) rather than generated. |
Kandra.Domain.Numbering.SimpleCounter
Raw, atomically-incremented counter for one Simple-family numbering bucket. Stored as Int64 (not UInt64) because none of the 3 supported providers (SqlServer/PostgreSql/SQLite) has a portable native unsigned 64-bit column type; the public numbering-service API stays UInt64, converting at the read/write boundary.
Properties
| Property | Description |
|---|
Bucket | The numbering bucket's name - primary key of this row. |
CurrentValue | The last value issued for this bucket. |
Version | Optimistic-concurrency token, incremented on every atomic update. |
Kandra.Domain.Numbering.SimplePoolEntry
One raw, leased-but-not-yet-drawn value for a Simple-family named pool. Bucket is a foreign key to Bucket - a pool entry can only exist for a bucket whose counter already exists, true by construction since leasing always creates-or-updates the counter row first. Drawing a value deletes the row (consumed exactly once); the formatter is applied at draw time, not lease time (spec 3.1).
Properties
| Property | Description |
|---|
Bucket | The owning Bucket. |
Id | Primary key of this pool entry. |
PoolName | The named pool this value was leased for. |
RawValue | The leased, not-yet-drawn raw counter value. |
Kandra.Domain.Registers.BalanceRegister<T0, T1>
Central register class base for the balance-register family: an event log (TMovement) plus a maintained current-balance projection (TBalance). An empty marker "handle" type — a configuration declares one as sealed partial class XRegister : BalanceRegister<XMovement, XBalance> with no members of its own; the engine uses the closed generic type itself as the register's identity for DI registration and writer/reader resolution.
Kandra.Domain.Registers.Balance<T0, T1>
Balance entity base: one row per dimension combination, resources = current balance. No details — they are movement-scoped by definition.
Properties
| Property | Description |
|---|
Dimensions | The analytical key identifying which balance this row is for. |
Id | Surrogate PK — EF Core cannot key an entity on owned-type properties (the dimensions). |
Resources | The current accumulated value for Dimensions. |
Version | Optimistic concurrency token guarding the delta upsert — a plain counter incremented by the engine on every delta apply, portable across providers (unlike a DB-generated rowversion, which SQLite/PostgreSql don't support the same way SQL Server does). |
Kandra.Domain.Registers.DocumentInfoRecord<T0, T1>
Periodic information-register record subordinate to a recorder (design §11.1): produced only by posting a document, reverted automatically on unpost/deletion — the right tool for state that is a consequence of a business operation (exchange rates set by a document, process/status milestones), as opposed to PeriodicInfoRecord's independent write mode.
Properties
| Property | Description |
|---|
Active | Whether this record is currently in effect — set false instead of deleting on unpost/revert. |
Document | Navigation to the document that posted this record. |
DocumentId | Id of the document that posted this record (the recorder). |
LineId | Id of the document line this record was posted from — assigned by document-level posting code, not the engine. |
Kandra.Domain.Registers.DocumentInfoRegister<T0>
Central register class base for the document-bound info-register family (design §11.1): records are produced only by posting a document, carry the recorder identity, and are reverted automatically on unpost/deletion — the same record-set pipeline movements use, minus the balance delta.
Kandra.Domain.Registers.IBalance
Engine-managed header of a balance row (one per dimension combination). Implemented by Balance.
Properties
| Property | Description |
|---|
Id | Surrogate primary key of the balance row. |
Kandra.Domain.Registers.IDocumentInfoRecord
A periodic information-register record subordinate to a recorder (design §11.1): produced only by posting a document, reverted automatically on unpost/deletion — the document-bound tier of the family. Implemented by DocumentInfoRecord.
Properties
| Property | Description |
|---|
Active | Whether this record is currently in effect — set false instead of deleting on unpost/revert. |
DocumentId | Id of the document that posted this record (the recorder). |
LineId | Id of the document line this record was posted from — assigned by document-level posting code, not the engine. |
Kandra.Domain.Registers.IInfoRecord
Engine-managed header of an information-register record: a stored value keyed by dimensions (the info-register analogue of IMovement). The base of the three-tier family (design §11): non-periodic (this interface alone), IPeriodicInfoRecord (adds a point in time), IDocumentInfoRecord (adds a recorder). Implemented by InfoRecord and its subclasses — domain code never implements this directly.
Properties
| Property | Description |
|---|
Id | Surrogate primary key of the info-record row. |
Kandra.Domain.Registers.IMovement
Engine-managed header of a movement (register event-log row). Implemented by Movement — domain code never implements this directly.
Properties
| Property | Description |
|---|
Active | Whether this movement is currently in effect — set false instead of deleting on unpost/revert. |
DocumentId | Id of the document that posted this movement (the recorder). |
Id | Surrogate primary key of the movement row. |
IsExpense | Which of the two legs of a transfer-shaped operation this movement represents — resource values are never negative, direction is expressed only through this flag. |
LineId | Id of the document line this movement was posted from — assigned by document-level posting code, not the engine. Two movements may share a LineId when they differ by IsExpense. |
Period | The point in time this movement is recorded at (the register's event-log ordering key). |
Kandra.Domain.Registers.IPeriodicInfoRecord
An information-register record with a point in time (design §11): the value is a snapshot as of Period, not a permanent fact. Implemented by PeriodicInfoRecord.
Properties
| Property | Description |
|---|
Period | The point in time this record's value is a snapshot as of. |
Kandra.Domain.Registers.IRegisterDetails
Marker for a register's details class — the non-aggregated payload (movement-scoped attributes on accumulation registers).
Kandra.Domain.Registers.IRegisterDimensions<T0>
A register's dimensions class — the analytical key (dictionary references and simple scalars) owned by every entity of the register. Implements IEquatable itself (hand-written Equals/GetHashCode over the dimension members) so the engine can use dimension values as dictionary/grouping keys via the ordinary, fast Default path — no reflection-based comparer needed.
Methods
| Method | Description |
|---|
BuildEqualityPredicate | Hand-written, EF-translatable equality against this instance's own values — Equals can't fill this role, EF Core cannot translate a call into a custom method body into SQL. Used by DimensionPredicateBuilder to build a targeted WHERE clause instead of loading a whole table client-side. Returns: An expression testing whether a TSelf instance equals this one's own dimension values. |
Kandra.Domain.Registers.IRegisterResources<T0>
A register's resources class — the accumulated values, carrying its own algebra. The arithmetic operators are total functions: they never throw, never clamp. EnsureNonNegative and IsZero are the two checks the engine needs and can't derive generically without knowing the concrete members — implemented directly by each resources class, no reflection.
Properties
| Property | Description |
|---|
IsZero | True when every member equals the additive identity. |
Methods
| Method | Description |
|---|
EnsureNonNegative | Throws if any member is negative. Movement-level invariant (design §1.2: resource values are never negative, direction is expressed only via IsExpense) and also used as the balance guard — after a delta is applied, the touched balance's resources must still satisfy this. |
Kandra.Domain.Registers.InfoRecord<T0, T1>
Information-register record entity base: a value stored per dimension combination. Unlike Movement it carries no resources/direction: an info record stores a value, it doesn't accumulate one. Base of the three-tier family (design §11) — see PeriodicInfoRecord and DocumentInfoRecord for the tiers actually used by domain code today.
Properties
| Property | Description |
|---|
Details | The stored value/payload for Dimensions. |
Dimensions | The analytical key this record is stored under. |
Id | Surrogate primary key of the info-record row. |
Kandra.Domain.Registers.InfoRegister<T0>
Central register class base for the information-register family: a log of stored values (TRecord), no accumulation/balance projection. Base of the three-tier family (design §11) — see PeriodicInfoRegister and DocumentInfoRegister for the tiers actually used by domain code today.
Kandra.Domain.Registers.Movement<T0, T1, T2>
Movement entity base: the event-log row. Components are mapped as owned types into the same table by the persistence layer — their members become ordinary columns.
Properties
| Property | Description |
|---|
Active | Whether this movement is currently in effect — set false instead of deleting on unpost/revert. |
Details | The non-aggregated payload carried alongside this movement. |
Dimensions | The analytical key this movement is posted against. |
Document | Navigation to the document that posted this movement. |
DocumentId | Id of the document that posted this movement (the recorder). |
Id | Surrogate primary key of the movement row. |
IsExpense | Which of the two legs of a transfer-shaped operation this movement represents — resource values are never negative, direction is expressed only through this flag. |
LineId | Id of the document line this movement was posted from. Assigned by the document-level posting code (it owns the line structure), not by the engine — the engine only replaces the record set, it doesn't know how movements correspond to document lines. Two movements may share a LineId when they differ by IsExpense (the two legs of one transfer line): the record-set identity is (DocumentId, LineId, IsExpense), not (DocumentId, LineId) alone. |
Period | The point in time this movement is recorded at (the register's event-log ordering key). |
Resources | The delta value this movement applies to the balance identified by Dimensions. |
Kandra.Domain.Registers.NoDetails
Shipped placeholder for registers with no details.
Kandra.Domain.Registers.NonNegativeQuantityResources
QuantityResources with the non-negative invariant enforced — use this for any register whose quantity must never go negative (stock, most accumulation registers). A separate closed generic-math type, not a runtime flag on QuantityResources: static abstract operators (+, -, AdditiveIdentity) are per-closed-type, so guarded and unguarded registers genuinely need distinct TSelfs to share the arithmetic while differing on the guard.
Properties
| Property | Description |
|---|
AdditiveIdentity | The additive identity: zero quantity. |
Methods
| Method | Description |
|---|
EnsureNonNegative | Throws InsufficientBalanceException if Quantity is negative. |
op_Addition(NonNegativeQuantityResources, NonNegativeQuantityResources) | Adds two quantities. |
op_Subtraction(NonNegativeQuantityResources, NonNegativeQuantityResources) | Subtracts one quantity from another. |
op_UnaryNegation(NonNegativeQuantityResources) | Negates a quantity. |
Kandra.Domain.Registers.PeriodicInfoRecord<T0, T1>
Periodic information-register record: a value as of a point in time, written directly — the independent write mode (design §11): imports, admin UI, dictionary behaviors edit rows without going through document posting. Uniqueness is enforced on dimensions + Period.
Properties
| Property | Description |
|---|
Period | The point in time this record's value is a snapshot as of. |
Kandra.Domain.Registers.PeriodicInfoRegister<T0>
Central register class base for the periodic-info-register family (design §11): rows are written directly — the independent write mode — rather than only through document posting. Registered in the container (see AddPeriodicInfoRegister<TRecord,TDimensions,TDetails>), unlike accumulation writers which are posting-only.
Kandra.Domain.Registers.QuantityResources
Shipped resources class for registers that accumulate a single quantity. Places no constraint on the sign of that quantity — EnsureNonNegative is a no-op. Registers that need the non-negative invariant (design §1.2 — most accumulation registers do, e.g. stock can't go negative) should use NonNegativeQuantityResources instead (see TD-002 in techdebt/TECH_DEBT.md for why this is a subclass rather than a flag).
Properties
| Property | Description |
|---|
AdditiveIdentity | The additive identity: zero quantity. |
IsZero | True when Quantity is zero. |
Quantity | The accumulated quantity. May be negative — this class enforces no sign constraint. |
Methods
| Method | Description |
|---|
EnsureNonNegative | No-op — this class places no constraint on the sign of Quantity. |
op_Addition(QuantityResources, QuantityResources) | Adds two quantities. |
op_Subtraction(QuantityResources, QuantityResources) | Subtracts one quantity from another. |
op_UnaryNegation(QuantityResources) | Negates a quantity. |
Kandra.Domain.Registers.TurnoverRegister<T0>
Central register class base for the turnover-register family: an event log (TMovement) with no maintained balance projection — the movements log is the whole register (design §4, §9.2). An empty marker "handle" type — a configuration declares one as sealed partial class XRegister : TurnoverRegister<XMovement> with no members of its own. Infrastructure only: no live KandraWms consumer yet, treat as validated-but-unproven.
Kandra.Domain.Scheduling.JobKind
The kind of work a scheduled job performs. Only DataProcessor exists today; a future kind (e.g. a DB backup job) adds a new member here plus its own IJob implementation - see docs/kandra-architecture.md.
Fields
| Field | Description |
|---|
DataProcessor | The job runs a registered Data Processor (see ProcessorName). |
Kandra.Domain.Scheduling.JobRunStatus
Outcome of one ScheduledJobRun.
Fields
| Field | Description |
|---|
Failed | The job threw during execution. |
Running | The job is currently executing. |
Succeeded | The job finished without throwing. |
Vetoed | Quartz vetoed the job before it started (e.g. via a trigger listener). |
Kandra.Domain.Scheduling.MisfirePolicy
Mirrors Quartz's own per-trigger-type misfire instruction families at a level generic enough to cover both Cron and Simple triggers - mapped down to the concrete Quartz constant for the trigger's actual type when the live ITrigger is built (see SchedulerBootstrapService).
Fields
| Field | Description |
|---|
DoNothing | Skip the missed fire(s) entirely and wait for the next scheduled time. |
FireOnceNow | Fire immediately, once, to make up for the missed fire(s). |
SmartPolicy | Quartz's "smart policy" - let Quartz pick the instruction appropriate for the trigger type. |
Kandra.Domain.Scheduling.ScheduledJobDefinition
A scheduled job's persisted definition - the single source of truth Quartz's in-memory RAMJobStore is rehydrated from on every app start (see SchedulerBootstrapService). EF-free POCO, hand-mapped (not generator-driven) into PlatformDbContext, same as ConstantRecord/PostingBatch.
Properties
| Property | Description |
|---|
Description | Optional free-form description of what this job does. |
Enabled | Whether this job is currently scheduled to run at all. |
Id | Primary key of this job definition. |
Kind | What kind of work this job performs. |
Name | Display name for this scheduled job. |
ProcessorName | The Data Processor's registered Name (see IDataProcessorRegistry). Required when Kind == DataProcessor. |
RunAsUserId | The user identity this job authorizes and audit-stamps as when it runs (see ICallerContext). |
Trigger | The job's single trigger configuration (v1 supports one trigger per job). |
Kandra.Domain.Scheduling.ScheduledJobRun
One execution attempt of a scheduled job - written by SchedulerHistoryListener on JobToBeExecuted/JobWasExecuted, keyed by Quartz's own FireInstanceId. A plain append-mostly log row, not a ChangesInfo-audited entity - nobody edits history.
Properties
| Property | Description |
|---|
ErrorMessage | The exception message, if the run failed. |
FinishedAtUtc | UTC time this run finished, if it has. |
Id | Primary key of this run row. |
JobDefinitionId | The ScheduledJobDefinition this run belongs to. |
JobName | Snapshot of ScheduledJobDefinition.Name at the moment this run started - denormalized rather than joined live, so a run's history stays readable after the job is renamed or deleted (JobDefinitionId is kept too, for anything that still needs the live link while the job exists). |
QuartzFireInstanceId | Quartz's own unique identifier for this specific fire/execution attempt. |
ResultSummary | Optional free-form summary of what the run did, set by the job itself. |
StartedAtUtc | UTC time this run started executing. |
Status | This run's current or final outcome. |
Kandra.Domain.Scheduling.TriggerConfig
One trigger's worth of scheduling configuration, embedded directly on ScheduledJobDefinition (v1 is one trigger per job - see docs/kandra-architecture.md). Mapped as an EF complex property, not an owned entity with its own key/table.
Properties
| Property | Description |
|---|
CronExpression | Required when Type == Cron. |
EndAtUtc | UTC instant the trigger stops firing. Null means it never expires. |
IntervalSeconds | Required when Type == Simple. |
Misfire | What Quartz should do when a fire time is missed. Defaults to letting Quartz decide. |
RepeatCount | Null means repeat forever. Only meaningful when Type == Simple. |
StartAtUtc | UTC instant the trigger becomes active. Null means active immediately. |
TimeZoneId | IANA or Windows time zone id. Used for Quartz's own cron evaluation (Cron triggers) and stamped as the job's caller time zone during execution (see ICallerContext). Defaults to UTC. |
Type | Which trigger family this configuration describes - which of the other properties apply. |
Kandra.Domain.Scheduling.TriggerType
Which family of Quartz trigger a TriggerConfig describes.
Fields
| Field | Description |
|---|
Cron | A cron-expression-driven trigger (see CronExpression). |
Simple | A fixed-interval trigger (see IntervalSeconds/RepeatCount). |