Перейти до основного вмісту

Kandra.Domain

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

PropertyDescription
AccountCodeThe account this balance belongs to, as a sort-key string (see ToSortKey).
ClosingBalanceThe current running total (typically DebitTurnover minus CreditTurnover, sign convention depending on the account).
CreditTurnoverSum of credit-side deltas applied to this balance row so far.
DebitTurnoverSum of debit-side deltas applied to this balance row so far.
IdThis row's own identity.
Slot1Subconto slot 1 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there.
Slot2Subconto slot 2 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there.
Slot3Subconto slot 3 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there.
Slot4Subconto slot 4 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there.
Slot5Subconto slot 5 of the (account, subconto combination) this balance tracks, or null if the account declares no slot there.
SubcontoHashLowercase 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().
VersionApp-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

ConstructorDescription
AccountCode(Int32[])Builds a code directly from its segments (e.g. new AccountCode(46, 5, 1)).

Properties

PropertyDescription
DepthNumber of segments - how deep this code sits in the chart (1 = a root-level account).
ParentThe code one level up (all segments but the last), or null when this code is already a root (Depth 1).
SegmentsThe raw segment numbers, most-significant first.

Methods

MethodDescription
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.
GetHashCodeundocumented 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.
ToStringThe 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

ConstructorDescription
AccountFolder(Int32, String, IReadOnlyList<AccountNode>, Boolean)undocumented

Properties

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

ConstructorDescription
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

ConstructorDescription
AccountLeafBase(Int32, String, IReadOnlyList<SubcontoSlotDeclaration>, Boolean)undocumented

Properties

PropertyDescription
SubcontoSlotsThis leaf's declared subconto slots, in order (slot 0 first) - empty for a leaf with no subconto.

Fields

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

ConstructorDescription
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

ConstructorDescription
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

ConstructorDescription
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

ConstructorDescription
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

ConstructorDescription
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

ConstructorDescription
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

ConstructorDescription
AccountNode(Int32, String, Boolean)undocumented

Properties

PropertyDescription
IsLiteralMirrors 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.
NameDisplay name or, when IsLiteral is false, a localization resource key for it - see IsLiteral.
NumberThis 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

ConstructorDescription
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

PropertyDescription
AccountThe account this side of the transaction posts against.
SubcontoThe 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

ConstructorDescription
AccountWithSubcontoBuilder(AccountCode, AccountLeafBase)undocumented

Methods

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

MethodDescription
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

ConstructorDescription
ChartOfAccounts(IReadOnlyList<AccountNode>)undocumented

Properties

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

ConstructorDescription
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

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

ConstructorDescription
EnumSubconto(T0)undocumented

Properties

PropertyDescription
IdThe Value member's own [SubcontoId] Guid.
Kandra#Domain#Commons#IRootEntity#IdExplicit Id implementation - read-only in practice; the setter always throws, since identity is fully determined by Value at construction time.
TypeIdThis closed generic's static TypeId - the same value for every EnumSubconto instance of a given TEnum, read off TEnum's own [TypeId] attribute.
ValueThe wrapped enum member.

Methods

MethodDescription
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

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

PropertyDescription
ChartThe application's whole chart, as built by its IChartOfAccountsBuilder.

Methods

MethodDescription
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

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

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

PropertyDescription
CreatedAtUtcWhen this batch was first created.
DateThe batch's posting date, as supplied to the merge.
DescriptionOptional free-text description, as supplied to the merge.
DocumentIdThe document this batch belongs to - unique; a document has at most one batch, ever.
IdThis batch's own identity.
LastModifiedAtUtcWhen this batch was last touched by a merge.
RevisionIncrements only when a merge actually writes something (i.e. a re-post that changed nothing leaves this untouched).
TransactionsEvery 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

ConstructorDescription
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

PropertyDescription
ActivatedRows that just turned active - their effect on AccountBalance must be applied.
BatchThe document's batch, created if it didn't already exist.
DeprecatedRows that just turned inactive - their effect on AccountBalance must be reverted.
HasChangesFalse, 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

ConstructorDescription
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

PropertyDescription
AmountThe posted amount in the functional/base currency - always set, regardless of whether a foreign-currency amount is also carried.
CurrencyAmountThe same posting expressed in CurrencyId's currency, or null when this transaction carries no foreign-currency amount.
CurrencyIdEither 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.
DestinationSame shape as Source, the other end of the posting.
LineIdThe 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.
SourceBuilt 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

PropertyDescription
AmountThe posted amount in the functional/base currency.
BatchNavigation to the owning batch - used by movement/turnover reads that filter by Batch.Date.
BatchIdThe PostingBatch this row belongs to.
CreatedAtUtcWhen this row was written.
CurrencyAmountThe same posting expressed in CurrencyId's currency, or null when this transaction carries no foreign-currency amount.
CurrencyIdSet together with CurrencyAmount when this transaction also carries a foreign-currency amount; both null for a functional-currency-only transaction.
DeprecatedAtUtcNull while IsActive; set once, when a later merge superseded this row.
DestinationAccountCodeThe destination account's zero-padded sort-key code (see ToSortKey).
DestinationSlot1The destination side's subconto slot 1, or null if the destination account declares no slot there.
DestinationSlot2The destination side's subconto slot 2, or null if the destination account declares no slot there.
DestinationSlot3The destination side's subconto slot 3, or null if the destination account declares no slot there.
DestinationSlot4The destination side's subconto slot 4, or null if the destination account declares no slot there.
DestinationSlot5The destination side's subconto slot 5, or null if the destination account declares no slot there.
IdThis row's own identity - stable across re-posts, unlike LineId which correlates a row with the source document line it came from.
IsActiveTrue while this row is the current effect of its LineId; false once superseded by a later merge (see DeprecatedAtUtc).
LineIdThe caller's own stable identifier for this line (see LineId) - used to correlate this row across re-posts.
SourceAccountCodeThe source account's zero-padded sort-key code (see ToSortKey).
SourceSlot1The source side's subconto slot 1, or null if the source account declares no slot there.
SourceSlot2The source side's subconto slot 2, or null if the source account declares no slot there.
SourceSlot3The source side's subconto slot 3, or null if the source account declares no slot there.
SourceSlot4The source side's subconto slot 4, or null if the source account declares no slot there.
SourceSlot5The 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

MethodDescription
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

ConstructorDescription
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

PropertyDescription
EnumTypeSet only when Kind is Enum - the unwrapped TEnum itself, not the closed EnumSubconto<TEnum> wrapper.
KindWhat structural kind of subconto this type is (dictionary-backed, enum-backed, ...).
RootTypeSet only when Kind is Dictionary - the hierarchy root the lookup repository is actually DI-registered for (equal to Type itself for a flat dictionary).
TypeThe subconto CLR type itself (e.g. a dictionary entity type, or a closed EnumSubconto<TEnum>).
TypeIdThe 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

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

ConstructorDescription
SubcontoSet(Guid)undocumented

Properties

PropertyDescription
TypesSlot 1's CLR type (T1), as the single element of the positional list.
Value1The resolved (TypeId, EntityId) pair for slot 1.
ValuesSlot 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

ConstructorDescription
SubcontoSet(Guid, Guid)undocumented

Properties

PropertyDescription
TypesBoth slots' CLR types (T1, T2), positional.
Value1The resolved (TypeId, EntityId) pair for slot 1.
Value2The resolved (TypeId, EntityId) pair for slot 2.
ValuesBoth 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

ConstructorDescription
SubcontoSet(Guid, Guid, Guid)undocumented

Properties

PropertyDescription
TypesAll three slots' CLR types (T1, T2, T3), positional.
Value1The resolved (TypeId, EntityId) pair for slot 1.
Value2The resolved (TypeId, EntityId) pair for slot 2.
Value3The resolved (TypeId, EntityId) pair for slot 3.
ValuesAll 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

ConstructorDescription
SubcontoSet(Guid, Guid, Guid, Guid)undocumented

Properties

PropertyDescription
TypesAll four slots' CLR types (T1..T4), positional.
Value1The resolved (TypeId, EntityId) pair for slot 1.
Value2The resolved (TypeId, EntityId) pair for slot 2.
Value3The resolved (TypeId, EntityId) pair for slot 3.
Value4The resolved (TypeId, EntityId) pair for slot 4.
ValuesAll 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

ConstructorDescription
SubcontoSet(Guid, Guid, Guid, Guid, Guid)undocumented

Properties

PropertyDescription
TypesAll five slots' CLR types (T1..T5), positional.
Value1The resolved (TypeId, EntityId) pair for slot 1.
Value2The resolved (TypeId, EntityId) pair for slot 2.
Value3The resolved (TypeId, EntityId) pair for slot 3.
Value4The resolved (TypeId, EntityId) pair for slot 4.
Value5The resolved (TypeId, EntityId) pair for slot 5.
ValuesAll 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

ConstructorDescription
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

PropertyDescription
EntityIdThe referenced entity's own id.
TypeIdThe 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

ConstructorDescription
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

PropertyDescription
MembersThe 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.
MemberTypesThe bare CLR types behind Members, in the same order.

Methods

MethodDescription
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

ConstructorDescription
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

PropertyDescription
TypeThe concrete ISubconto CLR type this member represents.
TypeIdThe 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

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

PropertyDescription
ActionThe kind of change performed (create/update/delete, etc.).
DateUTC timestamp the change was recorded at.
EntityIdGuid.Empty for constants - they have no per-row Guid identity, only a string Name (captured, if at all, inside PayloadJson).
IdPrimary key of this log row.
PayloadJsonDTO JSON. Null for Delete actions (no payload by design) and whenever PayloadStorageEnabled is false (the ChangeEventPayload feature flag was off at write time).
PayloadSchemaVersionReserved for future schema evolution of PayloadJson. Always null today.
PayloadStorageEnabledTrue 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.
ScopeWhich kind of object this event describes (Document/Dictionary/Constant/...).
TypeIdIEntityWithTypeId.TypeId of the concrete Document/Dictionary type, or the constant descriptor's TypeId for Scope == Constant.
UserIdId 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

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

PropertyDescription
DateThe document's transaction date/time, stored in UTC.
IsActivetrue 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

MethodDescription
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

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

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

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

MethodDescription
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

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

PropertyDescription
DepthNesting level, 0 for a root-level item, engine-maintained from ParentId.
ParentIdThe parent item's Id, or null for a root-level item.
PathMaterialized 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

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

MethodDescription
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

MethodDescription
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

MethodDescription
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

PropertyDescription
DateThe date this value becomes effective from.
IdPrimary key of this row.
NameThe constant's name - identifies which constant this value belongs to.
ValueJsonThe 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

PropertyDescription
CreatedUTC creation timestamp, defaulted at construction time. Not serialized to the wire.
CreatorUserIdId of the user who created the row. Not serialized to the wire.
IsDeletedtrue once the row has been soft-deleted. Not serialized to the wire.
ModifiedUTC last-modification timestamp, or null if never modified since creation. Not serialized to the wire.
ModifierUserIdId of the user who last modified the row, or null if never modified since creation. Not serialized to the wire.
VersionManually-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

PropertyDescription
CodeThe dictionary item's business code.
IdThe entity's primary key (sequential v7 Guid).
NameThe 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

PropertyDescription
DepthNesting level, 0 for a root-level item. Engine-maintained from ParentId, never set from a DTO.
ParentIdThe parent item's Id, or null for a root-level item. The single source of truth for the tree shape.
PathMaterialized 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

ConstructorDescription
DocumentBase(Guid)Initializes a new document with its fixed DocumentTypeId.

Properties

PropertyDescription
CodeThe document's business code (e.g. a formatted document number).
DateThe document's transaction date/time, stored in UTC, defaulted at construction time.
DocumentTypeIdIdentifies 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.
IdThe entity's primary key (sequential v7 Guid).
IsActivetrue 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

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

PropertyDescription
ContentTypeContent type sniffed from the uploaded bytes' magic number, or null if it couldn't be determined.
CreatedAtUTC timestamp this content was first stored.
IdThe blob's primary key.
InlineContentPopulated instead of StorageKey when the content is at or below BlobStorageOptions.InlineStorageThresholdBytes (AD-11) - avoids IBlobContentStore entirely for small attachments.
ReferencesEvery BlobReference pointing at this content (one blob can back many uploads/attachments).
Sha256HashLowercase 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.
SizeBytesSize of the content in bytes.
StorageKeySharded 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

PropertyDescription
BlobNavigation to the underlying content-addressed blob.
BlobIdId of the Blob this reference points at.
CreatedAtUTC timestamp this reference row was created (i.e. upload time).
CreatedBySubjectIdUploader's user id - used for pre-promotion authorization (only the creator may read/delete an ephemeral reference before it's promoted).
DeclaredContentTypeAs declared by the client, distinct from Blob.ContentType (which is sniffed from magic bytes).
EntityIdId of the owning entity, set together with EntityTypeId on promotion; null while ephemeral.
EntityTagOptional 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).
EntityTypeIdTypeId of the owning entity's CLR type, set together with EntityId on promotion.
ExpiresAtTTL for an Active ephemeral reference. Null once promoted, or if the caller explicitly requested a non-expiring reference.
IdThe reference's primary key - this, not BlobId, is the id a consumer stores/passes around.
OriginalFileNameAs declared by the uploading client - per-reference, not per-blob (same bytes can arrive under different filenames across uploads).
PurgeAfterGC purges a Tombstoned row once this elapses.
StateWhere this reference is in its Active/Tombstoned lifecycle.
TombstonedAtUTC timestamp the reference was tombstoned (detached or expired), or null while still Active.
TombstoneReasonFreeform note on why the reference was tombstoned (e.g. "detached", "expired"), or null while still Active.

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

PropertyDescription
EstablishedAtUTC timestamp the link was established.
FromEntityIdId of the entity/document on the "from" side of the link.
FromEntityTypeIdTypeId of the "from" side's CLR type.
FromLineIdSet 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.
IdThe link row's primary key.
TagFreeform 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.
ToEntityIdId of the entity/document on the "to" side of the link.
ToEntityTypeIdTypeId of the "to" side's CLR type.
ToLineIdSet 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

ConstructorDescription
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

PropertyDescription
IdThe entity's Id.
IsDeletedWhether the entity is soft-deleted, so a consumer can render it disabled/struck-through.
NameThe 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

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

FieldDescription
ConstantBelongs to a Constant.
DataProcessorBelongs to a DataProcessor.
DictionaryBelongs to a Dictionary entity.
DocumentBelongs to a Document entity.
LicenseBelongs to the licensing subsystem.
OtherAnything not covered by the other areas.
ReportBelongs to a Report.
SchedulerBelongs 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

ConstructorDescription
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

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

ConstructorDescription
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

ConstructorDescription
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

ConstructorDescription
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

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

ConstructorDescription
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

ConstructorDescription
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

ConstructorDescription
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

PropertyDescription
ErrorsThe individual property validation failures that caused this exception.

Kandra.Domain.Exceptions.ValidationFailedException.Error

One failed validation rule for a single Dto property.

Properties

PropertyDescription
ErrorMessageThe human-readable message describing why validation failed.
PropertyNameThe 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

PropertyDescription
ClaimsThe claims granted to every member of this role.
UserRolesThe 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

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

PropertyDescription
ClaimsThis user's claims (Identity's own claim table, not the JWT's feature claims).
IsBannedTrue when the user is banned from logging in.
LastIssuedJwtUTC time the user's most recent JWT was issued at.
LastLoginUTC time of this user's most recent successful login.
LastLogoutUTC time of this user's most recent logout.
LastPasswordChangeUTC time this user's password was last changed.
LoginsThis user's external login providers.
RefreshTokenThe current refresh token issued to this user, if any.
RefreshTokenExpiryTimeUTC expiry of RefreshToken.
TokensThis user's stored Identity tokens (e.g. password-reset tokens).
UserRolesThe 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

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

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

PropertyDescription
RoleThe role side of the membership.
UserThe 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

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

PropertyDescription
BucketThe numbering bucket's name - primary key of this row.
CurrentValueThe last value issued for this bucket.
VersionOptimistic-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

PropertyDescription
BucketThe owning numbering bucket's name.
FormattedNumberThe final, formatted number as issued - the value callers actually see/store.
IdPrimary key of this row.
IssuedAtUTC timestamp this number was issued at.
KindWhether this number was generated by the numbering system or entered manually.
RawValueThe raw counter value this number was derived from, if any (null for a purely manual entry with no underlying raw value).
RequestorIdId of the user/caller that requested this number, if known.
RequestorTagFree-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

FieldDescription
GeneratedProduced by the numbering system itself, from the bucket's counter.
ManualEntered 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

PropertyDescription
BucketThe numbering bucket's name - primary key of this row.
CurrentValueThe last value issued for this bucket.
VersionOptimistic-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

PropertyDescription
BucketThe owning Bucket.
IdPrimary key of this pool entry.
PoolNameThe named pool this value was leased for.
RawValueThe 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

PropertyDescription
DimensionsThe analytical key identifying which balance this row is for.
IdSurrogate PK — EF Core cannot key an entity on owned-type properties (the dimensions).
ResourcesThe current accumulated value for Dimensions.
VersionOptimistic 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

PropertyDescription
ActiveWhether this record is currently in effect — set false instead of deleting on unpost/revert.
DocumentNavigation to the document that posted this record.
DocumentIdId of the document that posted this record (the recorder).
LineIdId 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

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

PropertyDescription
ActiveWhether this record is currently in effect — set false instead of deleting on unpost/revert.
DocumentIdId of the document that posted this record (the recorder).
LineIdId 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

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

PropertyDescription
ActiveWhether this movement is currently in effect — set false instead of deleting on unpost/revert.
DocumentIdId of the document that posted this movement (the recorder).
IdSurrogate primary key of the movement row.
IsExpenseWhich of the two legs of a transfer-shaped operation this movement represents — resource values are never negative, direction is expressed only through this flag.
LineIdId 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.
PeriodThe 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

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

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

PropertyDescription
IsZeroTrue when every member equals the additive identity.

Methods

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

PropertyDescription
DetailsThe stored value/payload for Dimensions.
DimensionsThe analytical key this record is stored under.
IdSurrogate 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

PropertyDescription
ActiveWhether this movement is currently in effect — set false instead of deleting on unpost/revert.
DetailsThe non-aggregated payload carried alongside this movement.
DimensionsThe analytical key this movement is posted against.
DocumentNavigation to the document that posted this movement.
DocumentIdId of the document that posted this movement (the recorder).
IdSurrogate primary key of the movement row.
IsExpenseWhich of the two legs of a transfer-shaped operation this movement represents — resource values are never negative, direction is expressed only through this flag.
LineIdId 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.
PeriodThe point in time this movement is recorded at (the register's event-log ordering key).
ResourcesThe 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

PropertyDescription
AdditiveIdentityThe additive identity: zero quantity.

Methods

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

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

PropertyDescription
AdditiveIdentityThe additive identity: zero quantity.
IsZeroTrue when Quantity is zero.
QuantityThe accumulated quantity. May be negative — this class enforces no sign constraint.

Methods

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

FieldDescription
DataProcessorThe job runs a registered Data Processor (see ProcessorName).

Kandra.Domain.Scheduling.JobRunStatus

Outcome of one ScheduledJobRun.

Fields

FieldDescription
FailedThe job threw during execution.
RunningThe job is currently executing.
SucceededThe job finished without throwing.
VetoedQuartz 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

FieldDescription
DoNothingSkip the missed fire(s) entirely and wait for the next scheduled time.
FireOnceNowFire immediately, once, to make up for the missed fire(s).
SmartPolicyQuartz'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

PropertyDescription
DescriptionOptional free-form description of what this job does.
EnabledWhether this job is currently scheduled to run at all.
IdPrimary key of this job definition.
KindWhat kind of work this job performs.
NameDisplay name for this scheduled job.
ProcessorNameThe Data Processor's registered Name (see IDataProcessorRegistry). Required when Kind == DataProcessor.
RunAsUserIdThe user identity this job authorizes and audit-stamps as when it runs (see ICallerContext).
TriggerThe 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

PropertyDescription
ErrorMessageThe exception message, if the run failed.
FinishedAtUtcUTC time this run finished, if it has.
IdPrimary key of this run row.
JobDefinitionIdThe ScheduledJobDefinition this run belongs to.
JobNameSnapshot 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).
QuartzFireInstanceIdQuartz's own unique identifier for this specific fire/execution attempt.
ResultSummaryOptional free-form summary of what the run did, set by the job itself.
StartedAtUtcUTC time this run started executing.
StatusThis 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

PropertyDescription
CronExpressionRequired when Type == Cron.
EndAtUtcUTC instant the trigger stops firing. Null means it never expires.
IntervalSecondsRequired when Type == Simple.
MisfireWhat Quartz should do when a fire time is missed. Defaults to letting Quartz decide.
RepeatCountNull means repeat forever. Only meaningful when Type == Simple.
StartAtUtcUTC instant the trigger becomes active. Null means active immediately.
TimeZoneIdIANA 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.
TypeWhich trigger family this configuration describes - which of the other properties apply.

Kandra.Domain.Scheduling.TriggerType

Which family of Quartz trigger a TriggerConfig describes.

Fields

FieldDescription
CronA cron-expression-driven trigger (see CronExpression).
SimpleA fixed-interval trigger (see IntervalSeconds/RepeatCount).