syntax = "proto3"; package komp_ac.accounting; import "ecb.proto"; // Mutable informational journals. A journal may contain only debits, only // credits, or any non-zero balance. Balance is reported but never enforced. // Accounting periods close date ranges inside a profile and freeze account sums // without replacing the live accounts projection. Closed periods can be reopened; // approved periods are final. service Accounting { // Resolve or atomically create every node in a parsed account path. rpc EnsureAccount(EnsureAccountRequest) returns (Account); // Create a journal with its first lines when journal_id is absent, or append // all supplied lines atomically when journal_id identifies an existing one. rpc PostJournal(PostJournalRequest) returns (Journal); // Close a balanced journal. Closed journals can be reopened while their // accounting period is open. rpc CloseJournal(CloseJournalRequest) returns (Journal); rpc ReopenJournal(ReopenJournalRequest) returns (Journal); // Return journal lines and credits-minus-debits informational balance. rpc GetJournal(GetJournalRequest) returns (Journal); // Search profile journals by user-facing name while retaining journal ids // as the stable relationship keys. rpc SearchJournals(SearchJournalsRequest) returns (SearchJournalsResponse); // Return the profile-wide count of balanced and unbalanced journals. rpc GetAccountingStatus(GetAccountingStatusRequest) returns (AccountingStatus); // Return an oldest-first, paginated queue of unbalanced journals. rpc ListUnbalancedJournals(ListUnbalancedJournalsRequest) returns (ListUnbalancedJournalsResponse); // Soft-delete one line while retaining it for audit display. rpc SoftDeleteJournalLine(SoftDeleteJournalLineRequest) returns (Journal); // Atomically supersede active lines and append corrected replacements. The // originals and their conversion evidence remain immutable audit history. rpc CorrectJournal(CorrectJournalRequest) returns (Journal); // Create an open accounting period for a profile. Periods may be any length // (month, half-year, year). Overlapping ranges for the same profile are rejected. rpc ConfigureAccountingPeriod(ConfigureAccountingPeriodRequest) returns (AccountingPeriod); // Closing snapshots balances and blocks mutations through the period end. // A closed period may be reopened until the profile is approved. rpc CloseAccountingPeriod(CloseAccountingPeriodRequest) returns (AccountingPeriod); rpc ReopenAccountingPeriod(ReopenAccountingPeriodRequest) returns (AccountingPeriod); // Approves the profile's financial statement. One profile is one accounting // period, and approval happens once for it, not once per period inside it. // Every period must be closed first, and none may be reopened afterwards. rpc ApproveProfile(ApproveProfileRequest) returns (ProfileApproval); rpc GetProfileApproval(GetProfileApprovalRequest) returns (ProfileApproval); rpc GetAccountingPeriod(GetAccountingPeriodRequest) returns (AccountingPeriod); rpc ListAccountingPeriods(ListAccountingPeriodsRequest) returns (ListAccountingPeriodsResponse); rpc ListPeriodBalances(ListPeriodBalancesRequest) returns (ListPeriodBalancesResponse); // Materialize one row per directly transferable source account in a table // containing an ACCOUNTING_TRANSFER definition. Source fields are frozen // accounting facts; target fields start as an unchanged same-path copy and // may then be changed by the table's Steel logic or by the user. rpc GenerateAccountingTransfer(GenerateAccountingTransferRequest) returns (GenerateAccountingTransferResponse); // Administratively map one account in a predecessor profile to one account // in the current profile. Existing activity on the target account is allowed. rpc MapOpeningBalanceAccount(MapOpeningBalanceAccountRequest) returns (OpeningBalanceAccountMapping); rpc UnmapOpeningBalanceAccount(UnmapOpeningBalanceAccountRequest) returns (UnmapOpeningBalanceAccountResponse); rpc ListOpeningBalanceAccounts(ListOpeningBalanceAccountsRequest) returns (ListOpeningBalanceAccountsResponse); } message EnsureAccountRequest { string profile_name = 1; // Slash-delimited root-to-leaf account path, for example "123/12/1". string account = 2; // Currency accepted by source postings to the leaf account. Empty uses the // profile accounting currency. Missing ancestors are created in the profile // accounting currency. string denomination_currency = 3; } message Account { int64 id = 1; optional int64 parent_account_id = 2; string segment = 3; // Slash-delimited root-to-leaf account path. string account = 4; string denomination_currency = 5; } enum JournalSide { JOURNAL_SIDE_UNSPECIFIED = 0; JOURNAL_SIDE_DEBIT = 1; JOURNAL_SIDE_CREDIT = 2; } // A period is only ever open or closed. Approval is a fact about the whole // profile, because the profile is the accounting period being reported on. enum AccountingPeriodStatus { ACCOUNTING_PERIOD_STATUS_UNSPECIFIED = 0; ACCOUNTING_PERIOD_STATUS_OPEN = 1; ACCOUNTING_PERIOD_STATUS_CLOSED = 2; } // How long one accounting period lasts. MONTH and YEAR span whole months, so // the caller states only the start and the end is derived. PARTIAL is the // exception for periods real life cut short. enum AccountingPeriodType { ACCOUNTING_PERIOD_TYPE_UNSPECIFIED = 0; // One calendar month. ACCOUNTING_PERIOD_TYPE_MONTH = 1; // Twelve consecutive months. Starting in January gives the calendar year; // any other start month gives a business year (hospodársky rok). ACCOUNTING_PERIOD_TYPE_YEAR = 2; // Neither a whole month nor a whole year, because the entity was formed or // terminated part-way through one. The only type carrying an explicit end. ACCOUNTING_PERIOD_TYPE_PARTIAL = 3; } message PostJournalRequest { string profile_name = 1; // Absent creates a new journal. Present must identify an existing journal; // an unknown id is never treated as a request to create one. optional int64 journal_id = 2; // Currency of the amounts supplied by this request. Required when creating; // empty uses the profile accounting currency when appending. Journal amounts // are stored and returned in the profile accounting currency. string currency = 3; // Applied when creating and required to be empty when appending. string description = 4; // At least one line is required. The lines are inserted atomically and the // journal is allowed to remain unbalanced. repeated JournalLineInput lines = 5; // Required, unique within the profile, and at most 10 characters when // creating. Must be empty when appending to an existing journal selected by // journal_id. string journal_name = 6; // Accounting date in YYYY-MM-DD. Empty defaults to the current UTC date when // creating. Must be empty when appending. Rejected when it falls at or before // the latest closed or approved accounting boundary for the profile. string accounting_date = 7; } message JournalLineInput { JournalSide side = 1; // Slash-delimited root-to-leaf account path. string account = 2; string amount = 3; string description = 4; // Empty uses previous publication and ECB. Set this per line when the // accountant needs a different rule, a saved custom rate, or a one-off rate. optional komp_ac.ecb.ExchangeRateSelection exchange_rate_selection = 5; } message CloseJournalRequest { string profile_name = 1; int64 journal_id = 2; } message ReopenJournalRequest { string profile_name = 1; int64 journal_id = 2; } message GetJournalRequest { string profile_name = 1; int64 journal_id = 2; // False returns the live journal. True also returns superseded/deleted lines // so an authorized journal reader can inspect the correction trail. bool include_deleted = 3; } message GetAccountingStatusRequest { string profile_name = 1; } message SearchJournalsRequest { string profile_name = 1; // Case-insensitive literal substring. Empty returns all profile journals. string name_query = 2; // Zero uses 50. The maximum is 500. int32 page_size = 3; // Opaque cursor returned by the previous response. string page_token = 4; } message JournalSummary { int64 journal_id = 1; string journal_name = 2; string currency = 3; string description = 4; string total_debit = 5; string total_credit = 6; string balance = 7; int64 active_line_count = 8; bool closed = 9; string created_at = 10; string accounting_date = 11; } message SearchJournalsResponse { repeated JournalSummary journals = 1; int64 total_count = 2; string next_page_token = 3; } message AccountingStatus { bool all_journals_balanced = 1; int64 journal_count = 2; int64 balanced_journal_count = 3; int64 unbalanced_journal_count = 4; string calculated_at = 5; } message ListUnbalancedJournalsRequest { string profile_name = 1; // Zero uses 50. The maximum is 500. int32 page_size = 2; // Opaque cursor returned by the previous response. Empty starts at the // oldest unbalanced journal. string page_token = 3; // Optional ISO 4217 currency filter. Empty includes every currency. string currency = 4; } message UnbalancedJournalSummary { int64 journal_id = 1; string description = 2; string currency = 3; string total_debit = 4; string total_credit = 5; // Signed credits-minus-debits difference. string balance = 6; // Side and positive amount needed to make the journal balance zero. JournalSide missing_side = 7; string missing_amount = 8; int64 active_line_count = 9; string created_at = 10; string journal_name = 11; string accounting_date = 12; } message ListUnbalancedJournalsResponse { repeated UnbalancedJournalSummary journals = 1; int64 total_unbalanced_count = 2; string next_page_token = 3; } message SoftDeleteJournalLineRequest { string profile_name = 1; int64 journal_id = 2; int64 journal_line_id = 3; } message CorrectJournalRequest { string profile_name = 1; int64 journal_id = 2; // One or more line replacements committed as a single correction. Batching // lets both sides of a closed balanced journal be corrected together. repeated JournalLineCorrection corrections = 3; } message JournalLineCorrection { int64 journal_line_id = 1; // Full replacement line. Its amount is expressed in currency. JournalLineInput replacement = 2; string currency = 3; // Required independently of any exceptional exchange-rate reason and stored // permanently beside this original/replacement pair. string correction_reason = 4; } message JournalLine { int64 id = 1; int32 line_number = 2; JournalSide side = 3; // Slash-delimited root-to-leaf path of the posted account. string account = 4; string amount = 5; string description = 6; bool deleted = 7; string created_at = 8; string deleted_at = 9; string source_table_name = 10; int64 source_record_id = 11; int64 source_row_revision = 12; // Populated on a replacement created by CorrectJournal. int64 supersedes_line_id = 13; string correction_reason = 14; string corrected_by_user_id = 15; // Original foreign-currency input and immutable conversion evidence. Empty/ // zero when the line did not require conversion. string original_amount = 16; string original_currency = 17; int64 conversion_evidence_id = 18; } message Journal { int64 id = 1; string profile_name = 2; string currency = 3; string description = 4; string created_at = 5; repeated JournalLine lines = 6; string total_debit = 7; string total_credit = 8; // Informational difference calculated as credits minus debits. string balance = 9; bool closed = 10; string closed_at = 11; string closed_by_user_id = 12; string journal_name = 13; string accounting_date = 14; } message ConfigureAccountingPeriodRequest { string profile_name = 1; // First day of the period, YYYY-MM-DD. Must be the first day of a month. string period_start = 2; // Optional link to the preceding period in a carry chain. optional int64 previous_period_id = 4; // Required. Decides the length of the period. AccountingPeriodType period_type = 5; // Last day of the period, YYYY-MM-DD. Required for PARTIAL and rejected for // every other type, whose end follows from the start. string period_end = 6; } message CloseAccountingPeriodRequest { int64 period_id = 1; } message ReopenAccountingPeriodRequest { int64 period_id = 1; } message ApproveProfileRequest { string profile_name = 1; } message GetProfileApprovalRequest { string profile_name = 1; } // Approval state of one profile's financial statement. message ProfileApproval { string profile_name = 1; bool approved = 2; // Empty while the profile is unapproved. string approved_at = 3; string approved_by_user_id = 4; } message GetAccountingPeriodRequest { int64 period_id = 1; } message ListAccountingPeriodsRequest { string profile_name = 1; } message ListAccountingPeriodsResponse { repeated AccountingPeriod periods = 1; } message AccountingPeriod { int64 id = 1; string profile_name = 2; string period_start = 3; string period_end = 4; AccountingPeriodStatus status = 5; int64 previous_period_id = 6; string closed_at = 7; string closed_by_user_id = 8; AccountingPeriodType period_type = 11; } message ListPeriodBalancesRequest { int64 period_id = 1; } message PeriodBalance { int64 period_id = 1; // Slash-delimited root-to-leaf path of this frozen balance's account. string account = 2; string currency = 3; // Signed nets use debit-positive convention. string opening_balance = 4; string period_debit = 5; string period_credit = 6; string closing_balance = 7; } message PeriodDenominationBalance { int64 period_id = 1; // Exact denominated account. Unlike book balances, this quantity is not // rolled up into parent accounts with a different denomination. string account = 2; string denomination_currency = 3; // Signed nets use debit-positive convention and remain in the denomination // currency without conversion during carry-forward. string opening_quantity = 4; string period_debit_quantity = 5; string period_credit_quantity = 6; string closing_quantity = 7; } message ListPeriodBalancesResponse { repeated PeriodBalance balances = 1; repeated PeriodDenominationBalance denomination_balances = 2; } message GenerateAccountingTransferRequest { string profile_name = 1; string table_name = 2; int64 source_period_id = 3; int64 target_period_id = 4; } message GenerateAccountingTransferResponse { int64 table_definition_id = 1; int64 generated_row_count = 2; } message MapOpeningBalanceAccountRequest { // Open period whose profile receives the opening balance. Its configured // previous_period_id identifies the source profile and period. int64 target_period_id = 1; string source_account = 2; string target_account = 3; } message UnmapOpeningBalanceAccountRequest { int64 target_period_id = 1; string target_account = 2; } message UnmapOpeningBalanceAccountResponse { bool removed = 1; } message ListOpeningBalanceAccountsRequest { int64 target_period_id = 1; } enum OpeningBalanceStatus { OPENING_BALANCE_STATUS_UNSPECIFIED = 0; // The predecessor is still open, so its closed journal activity can change. OPENING_BALANCE_STATUS_PROVISIONAL = 1; // The value comes from the predecessor's frozen period snapshot. OPENING_BALANCE_STATUS_FINAL = 2; } message OpeningBalanceAccountMapping { int64 target_period_id = 1; int64 source_period_id = 2; string source_profile_name = 3; string source_account = 4; string target_profile_name = 5; string target_account = 6; string currency = 7; // Signed balances use the debit-positive convention. string opening_balance = 8; string period_debit = 9; string period_credit = 10; string current_balance = 11; OpeningBalanceStatus status = 12; string created_at = 13; string created_by_user_id = 14; string denomination_currency = 15; string denomination_opening_quantity = 16; string denomination_period_debit_quantity = 17; string denomination_period_credit_quantity = 18; string denomination_current_quantity = 19; } // A portion of a predecessor account's value that no mapping carries into the // target period. Closing the target period drops this value, so each entry must // either be covered by a broader mapping or deliberately closed out. message UnmappedSourceBalance { int64 source_period_id = 1; string source_profile_name = 2; string source_account = 3; string currency = 4; // Signed portion not covered by a mapping, using the debit-positive // convention. This is never zero and may be less than the account's // aggregate closing balance when descendants are mapped separately. string unmapped_balance = 5; OpeningBalanceStatus status = 6; } message UnmappedSourceDenominationBalance { int64 source_period_id = 1; string source_profile_name = 2; string source_account = 3; string denomination_currency = 4; // Exact foreign-currency quantity that no mapping carries forward. string unmapped_quantity = 5; OpeningBalanceStatus status = 6; } message ListOpeningBalanceAccountsResponse { repeated OpeningBalanceAccountMapping mappings = 1; // Empty while the target period still inherits every predecessor balance by // account path. Populated once the period carries balances by explicit // mapping only, which happens when the predecessor belongs to another // profile or when the period has at least one mapping of its own. repeated UnmappedSourceBalance unmapped_source_balances = 2; repeated UnmappedSourceDenominationBalance unmapped_source_denomination_balances = 3; }