offinance-shared-core 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # offinance-shared-core
2
+
3
+ Shared-core finansial lintas produk.
4
+
5
+ ## Prinsip
6
+ - Offline-first
7
+ - Platform-agnostic
8
+ - DB-agnostic
9
+ - Tanpa UI
10
+
11
+ ## Quick Start
12
+ ```bash
13
+ npm install
14
+ npm test
15
+ npm run verify:boundary
16
+ npm run verify:perf
17
+ ```
18
+
19
+ ## Use Case
20
+ - Host app keuangan murni
21
+ - Host app POS/Koperasi yang mendelegasikan logic finansial ke module ini
22
+
23
+ ## Referensi
24
+ - `docs/00-DOCUMENTATION-HUB.md`
@@ -0,0 +1,44 @@
1
+ import type { FinanceAccount } from './contracts/AccountContract.js';
2
+ import type { FinanceJournalEntry, JournalDraftInput } from './contracts/JournalContract.js';
3
+ import type { FiscalPeriod, TrialBalanceRow, FinanceLedgerScope } from './contracts/FiscalContract.js';
4
+ import type { RapbPlan, CreateRapbInput, RapbVariance } from './contracts/BudgetContract.js';
5
+ interface LedgerAccumulator {
6
+ debit: number;
7
+ credit: number;
8
+ }
9
+ export interface OffinanceCoreState {
10
+ accounts: Map<string, FinanceAccount>;
11
+ periods: Map<string, FiscalPeriod>;
12
+ journals: Map<string, FinanceJournalEntry>;
13
+ balances: Map<string, LedgerAccumulator>;
14
+ rapbPlans: Map<string, RapbPlan>;
15
+ }
16
+ export declare class OffinanceCore {
17
+ private readonly state;
18
+ constructor(initialState?: Partial<OffinanceCoreState>);
19
+ createAccount(account: FinanceAccount): FinanceAccount;
20
+ listAccounts(): FinanceAccount[];
21
+ openFiscalPeriod(period: FiscalPeriod): FiscalPeriod;
22
+ closeFiscalPeriod(periodId: string): FiscalPeriod;
23
+ listFiscalPeriods(): FiscalPeriod[];
24
+ createJournalDraft(input: JournalDraftInput): FinanceJournalEntry;
25
+ postJournal(journalId: string, postedAt?: string): FinanceJournalEntry;
26
+ reverseJournal(journalId: string, reversalId: string): FinanceJournalEntry;
27
+ getTrialBalance(): TrialBalanceRow[];
28
+ getTrialBalanceByScope(scope?: FinanceLedgerScope): TrialBalanceRow[];
29
+ listJournals(scope?: FinanceLedgerScope): FinanceJournalEntry[];
30
+ private assertJournalLinesKnownAccounts;
31
+ private assertJournalPeriodOpen;
32
+ private findPeriodByDate;
33
+ private applyLinesToBalance;
34
+ private computeScopedBalances;
35
+ private matchesScope;
36
+ createRapb(input: CreateRapbInput): RapbPlan;
37
+ getRapb(id: string): RapbPlan | null;
38
+ listRapb(scope?: Partial<RapbPlan>): RapbPlan[];
39
+ approveRapb(id: string, options: string | {
40
+ approvedBy: string;
41
+ }): RapbPlan;
42
+ evaluateVariance(rapbId: string): RapbVariance;
43
+ }
44
+ export {};
@@ -0,0 +1,40 @@
1
+ import type { ActivitySink, CoreAdapterRegistry, CoreRuntime, CoreRuntimeHooks, CoreRuntimeStartOptions, DbAdapter, HttpAdapter, LoggerAdapter, PlatformAdapter, ReadonlyStore, SocketAdapter, Store } from 'ofcore';
2
+ import { type OfinanceDomainServices } from './services/createDbAdapterOfinanceServices.js';
3
+ export type OfinanceDomainServicesFactory = () => Promise<OfinanceDomainServices> | OfinanceDomainServices;
4
+ export type OfinanceRuntimeHooks = Pick<CoreRuntimeHooks<OfinanceDomainServices, never>, 'runMigrations' | 'runSeed' | 'onInit' | 'onStop'>;
5
+ export interface OfinanceRuntimeState {
6
+ phase: 'idle' | 'starting' | 'started' | 'stopping' | 'stopped' | 'error';
7
+ started: boolean;
8
+ startCount: number;
9
+ stopCount: number;
10
+ lastError: string | null;
11
+ lastTransitionAt: string;
12
+ }
13
+ export declare class OfinanceRuntimeCore {
14
+ private readonly runtime;
15
+ private readonly runtimeStateStore;
16
+ domainServices?: OfinanceDomainServices;
17
+ readonly runtimeStore: ReadonlyStore<OfinanceRuntimeState>;
18
+ constructor(runtime: CoreRuntime<OfinanceDomainServices, never>, runtimeStateStore: Store<OfinanceRuntimeState>);
19
+ static builder(): OfinanceRuntimeCoreBuilder;
20
+ get registry(): CoreAdapterRegistry | undefined;
21
+ start(options?: CoreRuntimeStartOptions): Promise<void>;
22
+ stop(): Promise<void>;
23
+ isStarted(): boolean;
24
+ }
25
+ export declare class OfinanceRuntimeCoreBuilder {
26
+ private readonly runtimeBuilder;
27
+ private domainServicesFactory?;
28
+ private runtimeHooks;
29
+ constructor();
30
+ withPlatformAdapter(factory: (core: CoreRuntime<any, any>) => PlatformAdapter): this;
31
+ withDbAdapter(factory: (core: CoreRuntime<any, any>) => DbAdapter): this;
32
+ withHttpAdapter(factory: (core: CoreRuntime<any, any>) => HttpAdapter): this;
33
+ withSocketAdapter(factory: (core: CoreRuntime<any, any>) => SocketAdapter): this;
34
+ withLoggerAdapter(factory: (core: CoreRuntime<any, any>) => LoggerAdapter): this;
35
+ withActivitySink(factory: (core: CoreRuntime<any, any>) => ActivitySink): this;
36
+ withExtension(name: string, factory: (core: CoreRuntime<any, any>) => unknown): this;
37
+ withDomainServicesFactory(factory: OfinanceDomainServicesFactory): this;
38
+ withRuntimeHooks(hooks: Partial<OfinanceRuntimeHooks>): this;
39
+ build(): OfinanceRuntimeCore;
40
+ }
@@ -0,0 +1,33 @@
1
+ import type { CoopLoanDisbursementAccountMapping, CoopLoanRepaymentAccountMapping, CoopSavingDepositAccountMapping, CoopSavingWithdrawalAccountMapping, PosSaleAccountMapping, ScopedAccountMapping } from '../contracts/AdapterContract.js';
2
+ interface ResolveScopedAccountMappingInput<TMapping extends object> {
3
+ tenantId?: string;
4
+ branchId?: string;
5
+ scopedMapping: ScopedAccountMapping<TMapping>;
6
+ }
7
+ export declare function resolveScopedAccountMapping<TMapping extends object>(input: ResolveScopedAccountMappingInput<TMapping>): TMapping;
8
+ export declare function resolvePosSaleAccountMapping(input: {
9
+ tenantId?: string;
10
+ branchId?: string;
11
+ scopedMapping: ScopedAccountMapping<PosSaleAccountMapping>;
12
+ }): PosSaleAccountMapping;
13
+ export declare function resolveCoopLoanDisbursementAccountMapping(input: {
14
+ tenantId?: string;
15
+ branchId?: string;
16
+ scopedMapping: ScopedAccountMapping<CoopLoanDisbursementAccountMapping>;
17
+ }): CoopLoanDisbursementAccountMapping;
18
+ export declare function resolveCoopLoanRepaymentAccountMapping(input: {
19
+ tenantId?: string;
20
+ branchId?: string;
21
+ scopedMapping: ScopedAccountMapping<CoopLoanRepaymentAccountMapping>;
22
+ }): CoopLoanRepaymentAccountMapping;
23
+ export declare function resolveCoopSavingDepositAccountMapping(input: {
24
+ tenantId?: string;
25
+ branchId?: string;
26
+ scopedMapping: ScopedAccountMapping<CoopSavingDepositAccountMapping>;
27
+ }): CoopSavingDepositAccountMapping;
28
+ export declare function resolveCoopSavingWithdrawalAccountMapping(input: {
29
+ tenantId?: string;
30
+ branchId?: string;
31
+ scopedMapping: ScopedAccountMapping<CoopSavingWithdrawalAccountMapping>;
32
+ }): CoopSavingWithdrawalAccountMapping;
33
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { JournalDraftInput } from '../contracts/JournalContract.js';
2
+ import type { CoopLoanDisbursementInput, CoopLoanRepaymentInput, CoopSavingDepositInput, CoopSavingWithdrawalInput, FinanceAdapterContext, PosSaleFinanceInput } from '../contracts/AdapterContract.js';
3
+ export declare function buildJournalFromPosSale(context: FinanceAdapterContext, input: PosSaleFinanceInput): JournalDraftInput;
4
+ export declare function buildJournalFromCoopLoanDisbursement(context: FinanceAdapterContext, input: CoopLoanDisbursementInput): JournalDraftInput;
5
+ export declare function buildJournalFromCoopSavingDeposit(context: FinanceAdapterContext, input: CoopSavingDepositInput): JournalDraftInput;
6
+ export declare function buildJournalFromCoopSavingWithdrawal(context: FinanceAdapterContext, input: CoopSavingWithdrawalInput): JournalDraftInput;
7
+ export declare function buildJournalFromCoopLoanRepayment(context: FinanceAdapterContext, input: CoopLoanRepaymentInput): JournalDraftInput;
@@ -0,0 +1,22 @@
1
+ import type { CoopLoanDisbursementAccountMapping, CoopLoanDisbursementInput, CoopLoanRepaymentAccountMapping, CoopLoanRepaymentInput, CoopSavingDepositAccountMapping, CoopSavingDepositInput, CoopSavingWithdrawalAccountMapping, CoopSavingWithdrawalInput, FinanceAdapterContext, PosSaleAccountMapping, PosSaleFinanceInput, ScopedAccountMapping } from '../contracts/AdapterContract.js';
2
+ type PosSaleScopedInput = Omit<PosSaleFinanceInput, 'paymentAccountId' | 'revenueAccountId' | 'discountAccountId' | 'taxPayableAccountId'> & {
3
+ scopedAccountMapping: ScopedAccountMapping<PosSaleAccountMapping>;
4
+ };
5
+ type CoopLoanScopedInput = Omit<CoopLoanDisbursementInput, 'cashAccountId' | 'receivableAccountId'> & {
6
+ scopedAccountMapping: ScopedAccountMapping<CoopLoanDisbursementAccountMapping>;
7
+ };
8
+ type CoopSavingScopedInput = Omit<CoopSavingDepositInput, 'cashAccountId' | 'savingLiabilityAccountId'> & {
9
+ scopedAccountMapping: ScopedAccountMapping<CoopSavingDepositAccountMapping>;
10
+ };
11
+ type CoopSavingWithdrawalScopedInput = Omit<CoopSavingWithdrawalInput, 'cashAccountId' | 'savingLiabilityAccountId'> & {
12
+ scopedAccountMapping: ScopedAccountMapping<CoopSavingWithdrawalAccountMapping>;
13
+ };
14
+ type CoopLoanRepaymentScopedInput = Omit<CoopLoanRepaymentInput, 'cashAccountId' | 'receivableAccountId' | 'serviceRevenueAccountId' | 'penaltyRevenueAccountId'> & {
15
+ scopedAccountMapping: ScopedAccountMapping<CoopLoanRepaymentAccountMapping>;
16
+ };
17
+ export declare function buildJournalFromScopedPosSale(context: FinanceAdapterContext, input: PosSaleScopedInput): import("../index.js").JournalDraftInput;
18
+ export declare function buildJournalFromScopedCoopLoanDisbursement(context: FinanceAdapterContext, input: CoopLoanScopedInput): import("../index.js").JournalDraftInput;
19
+ export declare function buildJournalFromScopedCoopSavingDeposit(context: FinanceAdapterContext, input: CoopSavingScopedInput): import("../index.js").JournalDraftInput;
20
+ export declare function buildJournalFromScopedCoopSavingWithdrawal(context: FinanceAdapterContext, input: CoopSavingWithdrawalScopedInput): import("../index.js").JournalDraftInput;
21
+ export declare function buildJournalFromScopedCoopLoanRepayment(context: FinanceAdapterContext, input: CoopLoanRepaymentScopedInput): import("../index.js").JournalDraftInput;
22
+ export {};
@@ -0,0 +1,15 @@
1
+ import type { ResponseEnvelope, ScopeRef } from 'ofcore';
2
+ export type CurrencyCode = 'IDR' | 'USD' | string;
3
+ export type FinanceAccountType = 'asset' | 'liability' | 'equity' | 'revenue' | 'expense';
4
+ export interface FinanceAccount extends ScopeRef {
5
+ id: string;
6
+ code: string;
7
+ name: string;
8
+ type: FinanceAccountType;
9
+ currency: CurrencyCode;
10
+ isActive: boolean;
11
+ }
12
+ export interface FinanceAccountServiceContractV2 {
13
+ createAccount(account: FinanceAccount): Promise<ResponseEnvelope<FinanceAccount>>;
14
+ listAccounts(): Promise<ResponseEnvelope<FinanceAccount[]>>;
15
+ }
@@ -0,0 +1,74 @@
1
+ import type { ScopeRef } from 'ofcore';
2
+ import type { JournalDraftInput } from './JournalContract.js';
3
+ export interface FinanceAdapterContext extends ScopeRef {
4
+ journalId: string;
5
+ financeDomainId?: string;
6
+ ledgerProfileId: string;
7
+ occurredAt: string;
8
+ reference?: string;
9
+ }
10
+ export interface PosSaleFinanceInput {
11
+ grossAmount: number;
12
+ discountAmount?: number;
13
+ taxAmount?: number;
14
+ paymentAccountId: string;
15
+ revenueAccountId: string;
16
+ discountAccountId?: string;
17
+ taxPayableAccountId?: string;
18
+ }
19
+ export interface PosSaleAccountMapping {
20
+ paymentAccountId: string;
21
+ revenueAccountId: string;
22
+ discountAccountId?: string;
23
+ taxPayableAccountId?: string;
24
+ }
25
+ export interface CoopLoanDisbursementInput {
26
+ principalAmount: number;
27
+ cashAccountId: string;
28
+ receivableAccountId: string;
29
+ }
30
+ export interface CoopLoanDisbursementAccountMapping {
31
+ cashAccountId: string;
32
+ receivableAccountId: string;
33
+ }
34
+ export interface CoopLoanRepaymentInput {
35
+ principalAmount: number;
36
+ serviceAmount?: number;
37
+ penaltyAmount?: number;
38
+ cashAccountId: string;
39
+ receivableAccountId: string;
40
+ serviceRevenueAccountId?: string;
41
+ penaltyRevenueAccountId?: string;
42
+ }
43
+ export interface CoopLoanRepaymentAccountMapping {
44
+ cashAccountId: string;
45
+ receivableAccountId: string;
46
+ serviceRevenueAccountId?: string;
47
+ penaltyRevenueAccountId?: string;
48
+ }
49
+ export interface CoopSavingDepositInput {
50
+ amount: number;
51
+ cashAccountId: string;
52
+ savingLiabilityAccountId: string;
53
+ }
54
+ export interface CoopSavingDepositAccountMapping {
55
+ cashAccountId: string;
56
+ savingLiabilityAccountId: string;
57
+ }
58
+ export interface CoopSavingWithdrawalInput {
59
+ amount: number;
60
+ cashAccountId: string;
61
+ savingLiabilityAccountId: string;
62
+ }
63
+ export interface CoopSavingWithdrawalAccountMapping {
64
+ cashAccountId: string;
65
+ savingLiabilityAccountId: string;
66
+ }
67
+ export interface ScopedAccountMapping<TMapping extends object> {
68
+ default: TMapping;
69
+ byTenant?: Record<string, Partial<TMapping>>;
70
+ byBranch?: Record<string, Partial<TMapping>>;
71
+ }
72
+ export interface DomainJournalBuilder<TInput> {
73
+ (context: FinanceAdapterContext, input: TInput): JournalDraftInput;
74
+ }
@@ -0,0 +1,76 @@
1
+ import type { ResponseEnvelope, ScopeRef } from 'ofcore';
2
+ import type { FinanceAccountType } from './AccountContract.js';
3
+ export interface RapbLineItem {
4
+ accountId: string;
5
+ accountCode: string;
6
+ accountName: string;
7
+ accountType: FinanceAccountType;
8
+ budgetedDebit: number;
9
+ budgetedCredit: number;
10
+ budgetedNet: number;
11
+ }
12
+ export type RapbStatus = 'draft' | 'approved';
13
+ export interface RapbPlan extends ScopeRef {
14
+ id: string;
15
+ financeDomainId?: string;
16
+ ledgerProfileId?: string;
17
+ periodCode: string;
18
+ title: string;
19
+ status: RapbStatus;
20
+ lines: RapbLineItem[];
21
+ notes?: string;
22
+ createdAt: string;
23
+ approvedAt?: string;
24
+ approvedBy?: string;
25
+ }
26
+ export interface RapbLineItemInput {
27
+ accountId: string;
28
+ budgetedDebit: number;
29
+ budgetedCredit: number;
30
+ }
31
+ export interface CreateRapbInput extends ScopeRef {
32
+ id: string;
33
+ financeDomainId?: string;
34
+ ledgerProfileId?: string;
35
+ periodCode: string;
36
+ title: string;
37
+ lines: RapbLineItemInput[];
38
+ notes?: string;
39
+ }
40
+ export interface RapbVarianceLine {
41
+ accountId: string;
42
+ accountCode: string;
43
+ accountName: string;
44
+ accountType: FinanceAccountType;
45
+ budgetedDebit: number;
46
+ budgetedCredit: number;
47
+ budgetedNet: number;
48
+ actualDebit: number;
49
+ actualCredit: number;
50
+ actualNet: number;
51
+ varianceDebit: number;
52
+ varianceCredit: number;
53
+ varianceNet: number;
54
+ }
55
+ export interface RapbVariance {
56
+ rapbId: string;
57
+ periodCode: string;
58
+ evaluatedAt: string;
59
+ lines: RapbVarianceLine[];
60
+ totalBudgetedRevenue: number;
61
+ totalActualRevenue: number;
62
+ totalBudgetedExpense: number;
63
+ totalActualExpense: number;
64
+ budgetedNetIncome: number;
65
+ actualNetIncome: number;
66
+ netVariance: number;
67
+ }
68
+ export interface RapbServiceContractV2 {
69
+ createRapb(input: CreateRapbInput): Promise<ResponseEnvelope<RapbPlan>>;
70
+ getRapb(id: string): Promise<ResponseEnvelope<RapbPlan | null>>;
71
+ listRapb(scope?: Partial<RapbPlan>): Promise<ResponseEnvelope<RapbPlan[]>>;
72
+ approveRapb(id: string, options: string | {
73
+ approvedBy: string;
74
+ }): Promise<ResponseEnvelope<RapbPlan>>;
75
+ evaluateVariance(rapbId: string): Promise<ResponseEnvelope<RapbVariance>>;
76
+ }
@@ -0,0 +1,34 @@
1
+ import type { ResponseEnvelope, ScopeRef } from 'ofcore';
2
+ import type { FinanceAccountType } from './AccountContract.js';
3
+ export interface FiscalPeriod extends ScopeRef {
4
+ id: string;
5
+ financeDomainId?: string;
6
+ code: string;
7
+ startDate: string;
8
+ endDate: string;
9
+ status: 'open' | 'closed';
10
+ }
11
+ export interface FinanceLedgerBalance {
12
+ accountId: string;
13
+ debit: number;
14
+ credit: number;
15
+ net: number;
16
+ }
17
+ export interface TrialBalanceRow extends FinanceLedgerBalance {
18
+ accountCode: string;
19
+ accountName: string;
20
+ accountType: FinanceAccountType;
21
+ }
22
+ export interface FinanceLedgerScope extends ScopeRef {
23
+ financeDomainId?: string;
24
+ ledgerProfileId?: string;
25
+ occurredFrom?: string;
26
+ occurredTo?: string;
27
+ }
28
+ export interface FiscalPeriodServiceContractV2 {
29
+ openFiscalPeriod(period: FiscalPeriod): Promise<ResponseEnvelope<FiscalPeriod>>;
30
+ closeFiscalPeriod(periodId: string): Promise<ResponseEnvelope<FiscalPeriod>>;
31
+ listFiscalPeriods(): Promise<ResponseEnvelope<FiscalPeriod[]>>;
32
+ getTrialBalance(): Promise<ResponseEnvelope<TrialBalanceRow[]>>;
33
+ getTrialBalanceByScope(scope?: FinanceLedgerScope): Promise<ResponseEnvelope<TrialBalanceRow[]>>;
34
+ }
@@ -0,0 +1,33 @@
1
+ import type { ResponseEnvelope, ScopeRef } from 'ofcore';
2
+ import type { FinanceLedgerScope } from './FiscalContract.js';
3
+ export interface JournalLineInput {
4
+ accountId: string;
5
+ debit: number;
6
+ credit: number;
7
+ memo?: string;
8
+ }
9
+ export interface JournalDraftInput extends ScopeRef {
10
+ id: string;
11
+ financeDomainId?: string;
12
+ ledgerProfileId: string;
13
+ occurredAt: string;
14
+ reference?: string;
15
+ lines: JournalLineInput[];
16
+ }
17
+ export type JournalStatus = 'draft' | 'posted' | 'reversed';
18
+ export interface FinanceJournalEntry extends ScopeRef {
19
+ id: string;
20
+ financeDomainId?: string;
21
+ ledgerProfileId: string;
22
+ occurredAt: string;
23
+ reference?: string;
24
+ status: JournalStatus;
25
+ lines: JournalLineInput[];
26
+ postedAt?: string;
27
+ }
28
+ export interface FinanceJournalServiceContractV2 {
29
+ createJournalDraft(input: JournalDraftInput): Promise<ResponseEnvelope<FinanceJournalEntry>>;
30
+ postJournal(journalId: string, postedAt?: string): Promise<ResponseEnvelope<FinanceJournalEntry>>;
31
+ reverseJournal(journalId: string, reversalId: string): Promise<ResponseEnvelope<FinanceJournalEntry>>;
32
+ listJournals(scope?: FinanceLedgerScope): Promise<ResponseEnvelope<FinanceJournalEntry[]>>;
33
+ }
@@ -0,0 +1,2 @@
1
+ import type { DbAdapter, LoggerAdapter } from 'ofcore';
2
+ export declare function applyPendingMigrations(adapter: DbAdapter, logger?: LoggerAdapter): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import type { Migration } from 'ofcore';
2
+ export declare const migrations: Migration[];
@@ -0,0 +1,10 @@
1
+ import type { DbSchema, TableSchema } from 'ofcore';
2
+ export declare const OFINANCE_TABLES: {
3
+ readonly accounts: "ofinance_accounts";
4
+ readonly periods: "ofinance_periods";
5
+ readonly journals: "ofinance_journals";
6
+ readonly journalLines: "ofinance_journal_lines";
7
+ readonly histories: "ofinance_histories";
8
+ };
9
+ export declare function getOfinanceTableSchemas(): TableSchema[];
10
+ export declare function getOfinanceDbSchema(): DbSchema;
@@ -0,0 +1,15 @@
1
+ import type { FinanceAccount, FinanceAccountType } from '../contracts/AccountContract.js';
2
+ export interface CanonicalAccountTemplate {
3
+ id: string;
4
+ code: string;
5
+ name: string;
6
+ type: FinanceAccountType;
7
+ }
8
+ export declare const CANONICAL_COA_TEMPLATES: CanonicalAccountTemplate[];
9
+ export declare function createCanonicalCoA(currency?: string): FinanceAccount[];
10
+ export declare const COOP_COA_TEMPLATES: CanonicalAccountTemplate[];
11
+ export declare function createCoopCoA(currency?: string): FinanceAccount[];
12
+ export declare function validateCoaIntegrity(accounts: FinanceAccount[]): {
13
+ ok: boolean;
14
+ reason?: string;
15
+ };
@@ -0,0 +1,8 @@
1
+ import type { JournalLineInput } from '../contracts/JournalContract.js';
2
+ export interface JournalValidationResult {
3
+ ok: boolean;
4
+ totalDebit: number;
5
+ totalCredit: number;
6
+ reason?: string;
7
+ }
8
+ export declare function validateJournalBalance(lines: JournalLineInput[]): JournalValidationResult;
@@ -0,0 +1,30 @@
1
+ import type { FinanceAccount } from '../contracts/AccountContract.js';
2
+ import type { FiscalPeriod } from '../contracts/FiscalContract.js';
3
+ import type { JournalDraftInput } from '../contracts/JournalContract.js';
4
+ export type FinanceDomainEvent = {
5
+ type: 'finance.account.created';
6
+ payload: FinanceAccount;
7
+ } | {
8
+ type: 'finance.period.opened';
9
+ payload: FiscalPeriod;
10
+ } | {
11
+ type: 'finance.period.closed';
12
+ payload: {
13
+ periodId: string;
14
+ };
15
+ } | {
16
+ type: 'finance.journal.drafted';
17
+ payload: JournalDraftInput;
18
+ } | {
19
+ type: 'finance.journal.posted';
20
+ payload: {
21
+ journalId: string;
22
+ postedAt?: string;
23
+ };
24
+ } | {
25
+ type: 'finance.journal.reversed';
26
+ payload: {
27
+ journalId: string;
28
+ reversalId: string;
29
+ };
30
+ };
@@ -0,0 +1,4 @@
1
+ import { OffinanceCore } from '../OffinanceCore.js';
2
+ import type { FinanceDomainEvent } from './catalog.js';
3
+ export declare function applyFinanceDomainEvent(core: OffinanceCore, event: FinanceDomainEvent): void;
4
+ export declare function replayFinanceDomainEvents(events: FinanceDomainEvent[]): OffinanceCore;
@@ -0,0 +1,23 @@
1
+ export * from './OffinanceCore.js';
2
+ export * from './OfinanceRuntimeCore.js';
3
+ export * from './services/createDbAdapterOfinanceServices.js';
4
+ export * from './services/OffinanceEnvelopeService.js';
5
+ export * from './services/responseEnvelope.js';
6
+ export * from './contracts/AccountContract.js';
7
+ export * from './contracts/JournalContract.js';
8
+ export * from './contracts/FiscalContract.js';
9
+ export * from './contracts/BudgetContract.js';
10
+ export * from './contracts/AdapterContract.js';
11
+ export * from './adapters/builders.js';
12
+ export * from './adapters/accountMapping.js';
13
+ export * from './adapters/scopedBuilders.js';
14
+ export * from './reporting/financialStatements.js';
15
+ export * from './reporting/closing.js';
16
+ export * from './reporting/exporters.js';
17
+ export * from './reporting/budget.js';
18
+ export * from './events/catalog.js';
19
+ export * from './events/replay.js';
20
+ export * from './domain/chartOfAccounts.js';
21
+ export * from './domain/invariants.js';
22
+ export * from './data/applyPendingMigrations.js';
23
+ export { OFINANCE_TABLES } from './data/schemas.js';
@@ -0,0 +1,2 @@
1
+ function P(n){if(!Array.isArray(n)||n.length===0)return{ok:!1,totalDebit:0,totalCredit:0,reason:"JOURNAL_LINES_EMPTY"};let e=0,t=0;for(let a of n){if(!a||!a.accountId)return{ok:!1,totalDebit:e,totalCredit:t,reason:"JOURNAL_ACCOUNT_REQUIRED"};if(a.debit<0||a.credit<0)return{ok:!1,totalDebit:e,totalCredit:t,reason:"JOURNAL_NEGATIVE_AMOUNT"};e+=a.debit,t+=a.credit}return Math.abs(e-t)>1e-6?{ok:!1,totalDebit:e,totalCredit:t,reason:"JOURNAL_NOT_BALANCED"}:{ok:!0,totalDebit:e,totalCredit:t}}var l=class{constructor(e){this.state={accounts:e?.accounts??new Map,periods:e?.periods??new Map,journals:e?.journals??new Map,balances:e?.balances??new Map,rapbPlans:e?.rapbPlans??new Map}}createAccount(e){if(!e.id||!e.code||!e.name)throw new Error("FINANCE_ACCOUNT_REQUIRED_FIELDS");if(this.state.accounts.has(e.id))throw new Error("FINANCE_ACCOUNT_ALREADY_EXISTS");if(Array.from(this.state.accounts.values()).some(a=>a.code===e.code))throw new Error("FINANCE_ACCOUNT_CODE_DUPLICATE");return this.state.accounts.set(e.id,{...e}),{...e}}listAccounts(){return Array.from(this.state.accounts.values()).map(e=>({...e})).sort((e,t)=>e.code.localeCompare(t.code))}openFiscalPeriod(e){if(!e.id||!e.tenantId||!e.code||!e.startDate||!e.endDate)throw new Error("FISCAL_PERIOD_REQUIRED_FIELDS");if(this.state.periods.has(e.id))throw new Error("FISCAL_PERIOD_ALREADY_EXISTS");if(Date.parse(e.startDate)>Date.parse(e.endDate))throw new Error("FISCAL_PERIOD_INVALID_RANGE");let t={...e,status:"open"};return this.state.periods.set(t.id,t),{...t}}closeFiscalPeriod(e){let t=this.state.periods.get(e);if(!t)throw new Error("FISCAL_PERIOD_NOT_FOUND");let a={...t,status:"closed"};return this.state.periods.set(e,a),{...a}}listFiscalPeriods(){return Array.from(this.state.periods.values()).map(e=>({...e})).sort((e,t)=>e.startDate.localeCompare(t.startDate))}createJournalDraft(e){if(!e.id||!e.tenantId||!e.occurredAt)throw new Error("JOURNAL_REQUIRED_FIELDS");if(this.state.journals.has(e.id))throw new Error("JOURNAL_ALREADY_EXISTS");this.assertJournalLinesKnownAccounts(e.lines),this.assertJournalPeriodOpen(e.occurredAt);let t=P(e.lines);if(!t.ok)throw new Error(t.reason??"JOURNAL_INVALID");let a={id:e.id,tenantId:e.tenantId,branchId:e.branchId,financeDomainId:e.financeDomainId,ledgerProfileId:e.ledgerProfileId,occurredAt:e.occurredAt,reference:e.reference,lines:e.lines.map(r=>({...r})),status:"draft"};return this.state.journals.set(a.id,a),{...a,lines:a.lines.map(r=>({...r}))}}postJournal(e,t=new Date().toISOString()){let a=this.state.journals.get(e);if(!a)throw new Error("JOURNAL_NOT_FOUND");if(a.status==="posted"||a.status==="reversed")return{...a,lines:a.lines.map(o=>({...o}))};this.applyLinesToBalance(a.lines);let r={...a,status:"posted",postedAt:t};return this.state.journals.set(e,r),{...r,lines:r.lines.map(o=>({...o}))}}reverseJournal(e,t){let a=this.state.journals.get(e);if(!a)throw new Error("JOURNAL_NOT_FOUND");if(a.status!=="posted")throw new Error("JOURNAL_NOT_POSTED");if(this.state.journals.has(t))throw new Error("JOURNAL_ALREADY_EXISTS");let r=a.lines.map(c=>({accountId:c.accountId,debit:c.credit,credit:c.debit,memo:c.memo})),o=this.createJournalDraft({id:t,tenantId:a.tenantId,branchId:a.branchId,financeDomainId:a.financeDomainId,ledgerProfileId:a.ledgerProfileId,occurredAt:a.occurredAt,reference:`reversal:${a.id}`,lines:r});return this.postJournal(o.id)}getTrialBalance(){return this.getTrialBalanceByScope()}getTrialBalanceByScope(e){let t=this.computeScopedBalances(e);return this.listAccounts().map(r=>{let o=t.get(r.id)??{debit:0,credit:0};return{accountId:r.id,accountCode:r.code,accountName:r.name,accountType:r.type,debit:o.debit,credit:o.credit,net:o.debit-o.credit}})}listJournals(e){return Array.from(this.state.journals.values()).filter(t=>this.matchesScope(t,e)).map(t=>({...t,lines:t.lines.map(a=>({...a}))})).sort((t,a)=>t.occurredAt.localeCompare(a.occurredAt))}assertJournalLinesKnownAccounts(e){for(let t of e)if(!this.state.accounts.has(t.accountId))throw new Error("JOURNAL_ACCOUNT_NOT_FOUND")}assertJournalPeriodOpen(e){if(this.state.periods.size===0)return;let t=this.findPeriodByDate(e);if(!t)throw new Error("FISCAL_PERIOD_NOT_FOUND_FOR_DATE");if(t.status!=="open")throw new Error("FISCAL_PERIOD_CLOSED")}findPeriodByDate(e){let t=Date.parse(e);for(let a of this.state.periods.values()){let r=Date.parse(a.startDate),o=Date.parse(a.endDate);if(t>=r&&t<=o)return a}return null}applyLinesToBalance(e){for(let t of e){let a=this.state.balances.get(t.accountId)??{debit:0,credit:0};a.debit+=t.debit,a.credit+=t.credit,this.state.balances.set(t.accountId,a)}}computeScopedBalances(e){if(!e||!e.tenantId&&!e.branchId&&!e.financeDomainId&&!e.ledgerProfileId&&!e.occurredFrom&&!e.occurredTo)return this.state.balances;let t=new Map;for(let a of this.state.journals.values())if(a.status==="posted"&&this.matchesScope(a,e))for(let r of a.lines){let o=t.get(r.accountId)??{debit:0,credit:0};o.debit+=r.debit,o.credit+=r.credit,t.set(r.accountId,o)}return t}matchesScope(e,t){return t?!(t.tenantId&&e.tenantId!==t.tenantId||t.branchId&&e.branchId!==t.branchId||t.financeDomainId&&e.financeDomainId!==t.financeDomainId||t.ledgerProfileId&&e.ledgerProfileId!==t.ledgerProfileId||t.occurredFrom&&e.occurredAt<t.occurredFrom||t.occurredTo&&e.occurredAt>t.occurredTo):!0}createRapb(e){if(!e.id||!e.tenantId||!e.periodCode||!e.title)throw new Error("RAPB_REQUIRED_FIELDS");if(!e.lines||e.lines.length===0)throw new Error("RAPB_LINES_REQUIRED");if(this.state.rapbPlans.has(e.id))throw new Error("RAPB_ALREADY_EXISTS");if(e.lines.some(r=>r.budgetedDebit<0||r.budgetedCredit<0))throw new Error("RAPB_NEGATIVE_AMOUNT");let t=e.lines.map(r=>{let o=this.state.accounts.get(r.accountId);if(!o)throw new Error(`RAPB_ACCOUNT_NOT_FOUND:${r.accountId}`);let c=r.budgetedDebit-r.budgetedCredit;return{accountId:r.accountId,accountCode:o.code,accountName:o.name,accountType:o.type,budgetedDebit:r.budgetedDebit,budgetedCredit:r.budgetedCredit,budgetedNet:c}}),a={id:e.id,tenantId:e.tenantId,branchId:e.branchId,financeDomainId:e.financeDomainId,ledgerProfileId:e.ledgerProfileId,periodCode:e.periodCode,title:e.title,status:"draft",lines:t,notes:e.notes,createdAt:new Date().toISOString()};return this.state.rapbPlans.set(a.id,a),a}getRapb(e){return this.state.rapbPlans.get(e)??null}listRapb(e){let t=Array.from(this.state.rapbPlans.values());return e&&(e.tenantId&&(t=t.filter(a=>a.tenantId===e.tenantId)),e.periodCode&&(t=t.filter(a=>a.periodCode===e.periodCode)),e.branchId&&(t=t.filter(a=>a.branchId===e.branchId)),e.financeDomainId&&(t=t.filter(a=>a.financeDomainId===e.financeDomainId)),e.ledgerProfileId&&(t=t.filter(a=>a.ledgerProfileId===e.ledgerProfileId))),t}approveRapb(e,t){let a=this.state.rapbPlans.get(e);if(!a)throw new Error("RAPB_NOT_FOUND");let r=typeof t=="string"?t:t.approvedBy;if(a.status==="approved")throw new Error("RAPB_ALREADY_APPROVED");let o={...a,status:"approved",approvedAt:new Date().toISOString(),approvedBy:r};return this.state.rapbPlans.set(e,o),o}evaluateVariance(e){let t=this.state.rapbPlans.get(e);if(!t)throw new Error("RAPB_NOT_FOUND");let a=this.getTrialBalance(),r=new Map(a.map(i=>[i.accountId,i])),o=0,c=0,d=0,g=0,u=t.lines.map(i=>{let R=r.get(i.accountId),f=R?.debit??0,I=R?.credit??0,O=f-I;return i.accountType==="revenue"?(o+=i.budgetedCredit-i.budgetedDebit,c+=I-f):i.accountType==="expense"&&(d+=i.budgetedDebit-i.budgetedCredit,g+=f-I),{accountId:i.accountId,accountCode:i.accountCode,accountName:i.accountName,accountType:i.accountType,budgetedDebit:i.budgetedDebit,budgetedCredit:i.budgetedCredit,budgetedNet:i.budgetedNet,actualDebit:f,actualCredit:I,actualNet:O,varianceDebit:f-i.budgetedDebit,varianceCredit:I-i.budgetedCredit,varianceNet:O-i.budgetedNet}}),h=o-d,E=c-g;return{rapbId:e,periodCode:t.periodCode,evaluatedAt:new Date().toISOString(),lines:u,totalBudgetedRevenue:o,totalActualRevenue:c,totalBudgetedExpense:d,totalActualExpense:g,budgetedNetIncome:h,actualNetIncome:E,netVariance:E-h}}};import{CoreRuntime as z}from"ofcore";import{InMemoryDbAdapter as K}from"ofcore";import{asReadonlyStore as X,createStore as Z}from"ofcore";function s(n,e){return{data:n,meta:{request_id:e??null,timestamp:new Date().toISOString()}}}function F(n,e,t={}){return{error:{code:n,message:e,details:t.details,retryable:t.retryable,correlationId:t.correlationId,timestamp:new Date().toISOString()},meta:{request_id:t.requestId??null,timestamp:new Date().toISOString()}}}function p(n,e="FINANCE_NOT_IMPLEMENTED",t="Unhandled offinance error."){return n instanceof Error?F(e,n.message||t):F(e,t)}var v=class{constructor(e){this.legacy=e;this.accountService={createAccount:async t=>{try{return s(this.legacy.accountService.createAccount(t))}catch(a){return p(a)}},listAccounts:async()=>{try{return s(this.legacy.accountService.listAccounts())}catch(t){return p(t)}}},this.journalService={createJournalDraft:async t=>{try{return s(this.legacy.journalService.createJournalDraft(t))}catch(a){return p(a)}},postJournal:async(t,a)=>{try{return s(this.legacy.journalService.postJournal(t,a))}catch(r){return p(r)}},reverseJournal:async(t,a)=>{try{return s(this.legacy.journalService.reverseJournal(t,a))}catch(r){return p(r)}},listJournals:async t=>{try{return s(this.legacy.journalService.listJournals(t))}catch(a){return p(a)}}},this.fiscalService={openFiscalPeriod:async t=>{try{return s(this.legacy.fiscalService.openFiscalPeriod(t))}catch(a){return p(a)}},closeFiscalPeriod:async t=>{try{return s(this.legacy.fiscalService.closeFiscalPeriod(t))}catch(a){return p(a)}},listFiscalPeriods:async()=>{try{return s(this.legacy.fiscalService.listFiscalPeriods())}catch(t){return p(t)}},getTrialBalance:async()=>{try{return s(this.legacy.fiscalService.getTrialBalance())}catch(t){return p(t)}},getTrialBalanceByScope:async t=>{try{return s(this.legacy.fiscalService.getTrialBalanceByScope(t))}catch(a){return p(a)}}},this.rapbService={createRapb:async t=>{try{return s(this.legacy.rapbService.createRapb(t))}catch(a){return p(a)}},getRapb:async t=>{try{return s(this.legacy.rapbService.getRapb(t))}catch(a){return p(a)}},listRapb:async t=>{try{return s(this.legacy.rapbService.listRapb(t))}catch(a){return p(a)}},approveRapb:async(t,a)=>{try{return s(this.legacy.rapbService.approveRapb(t,a))}catch(r){return p(r)}},evaluateVariance:async t=>{try{return s(this.legacy.rapbService.evaluateVariance(t))}catch(a){return p(a)}}}}};function T(n){return new v({accountService:n,journalService:n,fiscalService:n,rapbService:n})}async function w(n){let e=new l;return{financeService:T(e)}}var m={accounts:"ofinance_accounts",periods:"ofinance_periods",journals:"ofinance_journals",journalLines:"ofinance_journal_lines",histories:"ofinance_histories"},G=[{name:m.accounts,columns:[{name:"id",type:"string"},{name:"code",type:"string",isIndexed:!0},{name:"name",type:"string"},{name:"type",type:"string",isIndexed:!0},{name:"currency",type:"string"},{name:"isActive",type:"boolean",isIndexed:!0},{name:"tenantId",type:"string",isOptional:!0,isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0},{name:"lastModified",type:"string"},{name:"deleted",type:"boolean",isIndexed:!0}]},{name:m.periods,columns:[{name:"id",type:"string"},{name:"tenantId",type:"string",isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0},{name:"code",type:"string",isIndexed:!0},{name:"startDate",type:"string",isIndexed:!0},{name:"endDate",type:"string",isIndexed:!0},{name:"status",type:"string",isIndexed:!0},{name:"lastModified",type:"string"},{name:"deleted",type:"boolean",isIndexed:!0}]},{name:m.journals,columns:[{name:"id",type:"string"},{name:"tenantId",type:"string",isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0},{name:"occurredAt",type:"string",isIndexed:!0},{name:"reference",type:"string",isOptional:!0,isIndexed:!0},{name:"status",type:"string",isIndexed:!0},{name:"postedAt",type:"string",isOptional:!0,isIndexed:!0},{name:"lastModified",type:"string"},{name:"deleted",type:"boolean",isIndexed:!0}]},{name:m.journalLines,columns:[{name:"id",type:"string"},{name:"journalId",type:"string",isIndexed:!0},{name:"accountId",type:"string",isIndexed:!0},{name:"debit",type:"number"},{name:"credit",type:"number"},{name:"memo",type:"string",isOptional:!0},{name:"tenantId",type:"string",isOptional:!0,isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0},{name:"lastModified",type:"string"},{name:"deleted",type:"boolean",isIndexed:!0}]},{name:m.histories,columns:[{name:"id",type:"string"},{name:"tableName",type:"string",isIndexed:!0},{name:"recordId",type:"string",isIndexed:!0},{name:"action",type:"string",isIndexed:!0},{name:"changes",type:"json",isOptional:!0},{name:"timestamp",type:"string",isIndexed:!0},{name:"tenantId",type:"string",isOptional:!0,isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0}]}];function L(){return G.map(n=>({...n,columns:n.columns.map(e=>({...e}))}))}var x=[{toVersion:1,up:async n=>{let e=L();for(let t of e)try{await n.addTable(t)}catch{}}}];var $={logInfo(){},logWarn(){},logError(){}};async function _(n,e=$){e.logInfo("[offinance] starting database migration process");let t=[...x].sort((o,c)=>o.toVersion-c.toVersion),a=t.length>0?t[t.length-1].toVersion:0,r=await n.getSchemaVersion();if((r==null||r<0)&&(r=0),r>=a){e.logInfo(`[offinance] database already up-to-date at v${r}`);return}for(let o of t)o.toVersion>r&&(e.logInfo(`[offinance] applying migration v${o.toVersion}`),await o.up(n),await n.setSchemaVersion(o.toVersion),r=o.toVersion);e.logInfo(`[offinance] migration completed at v${r}`)}var C=class{constructor(e,t){this.runtime=e;this.runtimeStateStore=t;this.runtimeStore=X(this.runtimeStateStore)}static builder(){return new D}get registry(){return this.runtime.registry}async start(e={}){this.runtimeStateStore.setState({phase:"starting",started:!1,lastError:null,lastTransitionAt:new Date().toISOString(),startCount:this.runtimeStateStore.getState().startCount,stopCount:this.runtimeStateStore.getState().stopCount});try{await this.runtime.start(e),this.domainServices=this.runtime.domainServices,this.runtimeStateStore.setState(t=>({...t,phase:"started",started:!0,startCount:t.startCount+1,lastError:null,lastTransitionAt:new Date().toISOString()}))}catch(t){throw this.runtimeStateStore.setState(a=>({...a,phase:"error",started:!1,lastError:t instanceof Error?t.message:String(t),lastTransitionAt:new Date().toISOString()})),t}}async stop(){this.runtimeStateStore.setState(e=>({...e,phase:"stopping",started:this.runtime.isStarted(),lastError:null,lastTransitionAt:new Date().toISOString()}));try{await this.runtime.stop(),this.runtimeStateStore.setState(e=>({...e,phase:"stopped",started:!1,stopCount:e.stopCount+1,lastError:null,lastTransitionAt:new Date().toISOString()}))}catch(e){throw this.runtimeStateStore.setState(t=>({...t,phase:"error",started:this.runtime.isStarted(),lastError:e instanceof Error?e.message:String(e),lastTransitionAt:new Date().toISOString()})),e}}isStarted(){return this.runtime.isStarted()}},D=class{constructor(){this.runtimeBuilder=z.builder();this.runtimeHooks={};this.runtimeBuilder.withDbAdapter(()=>new K)}withPlatformAdapter(e){return this.runtimeBuilder.withPlatformAdapter(e),this}withDbAdapter(e){return this.runtimeBuilder.withDbAdapter(e),this}withHttpAdapter(e){return this.runtimeBuilder.withHttpAdapter(e),this}withSocketAdapter(e){return this.runtimeBuilder.withSocketAdapter(e),this}withLoggerAdapter(e){return this.runtimeBuilder.withLoggerAdapter(e),this}withActivitySink(e){return this.runtimeBuilder.withActivitySink(e),this}withExtension(e,t){return this.runtimeBuilder.withExtension(e,t),this}withDomainServicesFactory(e){return this.domainServicesFactory=e,this}withRuntimeHooks(e){return this.runtimeHooks={...this.runtimeHooks,...e},this}build(){let e=Z({phase:"idle",started:!1,startCount:0,stopCount:0,lastError:null,lastTransitionAt:new Date().toISOString()}),t={runMigrations:async r=>{let o=r.registry?.dbAdapter;if(!o)throw new Error("OFINANCE_DB_ADAPTER_REQUIRED");await _(o,r.registry?.loggerAdapter),this.runtimeHooks.runMigrations&&await this.runtimeHooks.runMigrations(r)},...this.runtimeHooks,createDomainServices:async r=>{if(this.domainServicesFactory)return this.domainServicesFactory();let o=r.registry?.dbAdapter;if(!o)throw new Error("OFINANCE_DB_ADAPTER_REQUIRED");return w(o)}};this.runtimeBuilder.withHooks(t);let a=this.runtimeBuilder.build();return new C(a,e)}};function y(n,e,t){return{id:n.journalId,tenantId:n.tenantId,branchId:n.branchId,financeDomainId:n.financeDomainId,ledgerProfileId:n.ledgerProfileId,occurredAt:n.occurredAt,reference:t?`${n.reference??""}${t}`:n.reference,lines:e}}function A(n,e){if(!Number.isFinite(n)||n<=0)throw new Error(e)}function M(n,e){A(e.grossAmount,"POS_SALE_INVALID_GROSS_AMOUNT");let t=e.discountAmount??0,a=e.taxAmount??0;if(t<0||a<0)throw new Error("POS_SALE_NEGATIVE_COMPONENT");let r=e.grossAmount,o=r-t+a,c=[{accountId:e.paymentAccountId,debit:o,credit:0,memo:"POS sale payment"},{accountId:e.revenueAccountId,debit:0,credit:r,memo:"POS sale revenue"}];if(t>0){if(!e.discountAccountId)throw new Error("POS_SALE_DISCOUNT_ACCOUNT_REQUIRED");c.push({accountId:e.discountAccountId,debit:t,credit:0,memo:"POS sale discount"})}if(a>0){if(!e.taxPayableAccountId)throw new Error("POS_SALE_TAX_ACCOUNT_REQUIRED");c.push({accountId:e.taxPayableAccountId,debit:0,credit:a,memo:"POS sale tax payable"})}return y(n,c)}function N(n,e){return A(e.principalAmount,"COOP_LOAN_INVALID_PRINCIPAL"),y(n,[{accountId:e.receivableAccountId,debit:e.principalAmount,credit:0,memo:"Loan receivable principal"},{accountId:e.cashAccountId,debit:0,credit:e.principalAmount,memo:"Loan cash out"}])}function B(n,e){return A(e.amount,"COOP_SAVING_INVALID_AMOUNT"),y(n,[{accountId:e.cashAccountId,debit:e.amount,credit:0,memo:"Saving deposit cash in"},{accountId:e.savingLiabilityAccountId,debit:0,credit:e.amount,memo:"Saving liability increase"}])}function J(n,e){return A(e.amount,"COOP_SAVING_WITHDRAWAL_INVALID_AMOUNT"),y(n,[{accountId:e.savingLiabilityAccountId,debit:e.amount,credit:0,memo:"Saving liability decrease"},{accountId:e.cashAccountId,debit:0,credit:e.amount,memo:"Saving withdrawal cash out"}])}function j(n,e){A(e.principalAmount,"COOP_LOAN_REPAYMENT_INVALID_PRINCIPAL");let t=e.serviceAmount??0,a=e.penaltyAmount??0;if(t<0||a<0)throw new Error("COOP_LOAN_REPAYMENT_NEGATIVE_COMPONENT");if(t>0&&!e.serviceRevenueAccountId)throw new Error("COOP_LOAN_REPAYMENT_SERVICE_ACCOUNT_REQUIRED");if(a>0&&!e.penaltyRevenueAccountId)throw new Error("COOP_LOAN_REPAYMENT_PENALTY_ACCOUNT_REQUIRED");let r=e.principalAmount+t+a,o=[{accountId:e.cashAccountId,debit:r,credit:0,memo:"Loan repayment cash in"},{accountId:e.receivableAccountId,debit:0,credit:e.principalAmount,memo:"Loan receivable principal repayment"}];return t>0&&o.push({accountId:e.serviceRevenueAccountId,debit:0,credit:t,memo:"Loan service revenue"}),a>0&&o.push({accountId:e.penaltyRevenueAccountId,debit:0,credit:a,memo:"Loan penalty revenue"}),y(n,o)}function U(n,e){return e?{...n,...e}:{...n}}function b(n){let e=n.tenantId?n.scopedMapping.byTenant?.[n.tenantId]:void 0,t=n.tenantId&&n.branchId?`${n.tenantId}:${n.branchId}`:"",a=t?n.scopedMapping.byBranch?.[t]:void 0;return U(U(n.scopedMapping.default,e),a)}function V(n){return b(n)}function k(n){return b(n)}function H(n){return b(n)}function W(n){return b(n)}function q(n){return b(n)}function Fe(n,e){let{scopedAccountMapping:t,...a}=e,r=V({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return M(n,{...a,...r})}function Te(n,e){let{scopedAccountMapping:t,...a}=e,r=k({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return N(n,{...a,...r})}function we(n,e){let{scopedAccountMapping:t,...a}=e,r=W({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return B(n,{...a,...r})}function Le(n,e){let{scopedAccountMapping:t,...a}=e,r=q({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return J(n,{...a,...r})}function xe(n,e){let{scopedAccountMapping:t,...a}=e,r=H({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return j(n,{...a,...r})}function ee(n){switch(n.accountType){case"asset":case"expense":return n.debit-n.credit;case"liability":case"equity":case"revenue":return n.credit-n.debit;default:return 0}}function S(n,e){return n.filter(t=>t.accountType===e).reduce((t,a)=>t+ee(a),0)}function Me(n){let e=S(n,"revenue"),t=S(n,"expense");return{revenue:e,expense:t,netIncome:e-t}}function Ne(n){return{assets:S(n,"asset"),liabilities:S(n,"liability"),equity:S(n,"equity")}}function Q(n){return n.accountType==="revenue"?n.credit-n.debit:n.accountType==="expense"?n.debit-n.credit:0}function Je(n){let e=n.filter(a=>a.accountType==="revenue").reduce((a,r)=>a+Math.max(0,Q(r)),0),t=n.filter(a=>a.accountType==="expense").reduce((a,r)=>a+Math.max(0,Q(r)),0);return{operatingInflow:e,operatingOutflow:t,netOperatingCashflow:e-t}}function je(n,e){let t=[],a=0;for(let r of e){if(r.accountType==="revenue"){let o=r.credit-r.debit;o>0&&(t.push({accountId:r.accountId,debit:o,credit:0,memo:"Close revenue account"}),a+=o)}if(r.accountType==="expense"){let o=r.debit-r.credit;o>0&&(t.push({accountId:r.accountId,debit:0,credit:o,memo:"Close expense account"}),a-=o)}}return a>0?t.push({accountId:n.retainedEarningsAccountId,debit:0,credit:a,memo:"Retained earnings from period close"}):a<0&&t.push({accountId:n.retainedEarningsAccountId,debit:Math.abs(a),credit:0,memo:"Retained earnings from period close"}),{id:n.journalId,tenantId:n.tenantId,branchId:n.branchId,financeDomainId:n.financeDomainId,ledgerProfileId:n.ledgerProfileId,occurredAt:n.occurredAt,reference:n.reference,lines:t}}function Ve(n){return{version:"v1",generatedAt:n.generatedAt??new Date().toISOString(),payload:{trialBalance:n.trialBalance.map(e=>({...e})),incomeStatement:{...n.incomeStatement},balanceSheet:{...n.balanceSheet}}}}function te(n){return n.accountType==="revenue"?n.credit-n.debit:n.accountType==="expense"?n.debit-n.credit:0}function Y(n,e){return e===0?n===0?1:0:Number((n/e).toFixed(4))}function He(n,e){let t=e.map(r=>{let d=((r.accountCodes?.length?n.filter(u=>r.accountCodes?.includes(u.accountCode)):null)??n.filter(u=>r.accountType?u.accountType===r.accountType:!1)).reduce((u,h)=>u+te(h),0),g=d-r.plannedAmount;return{id:r.id,label:r.label,plannedAmount:r.plannedAmount,actualAmount:d,varianceAmount:g,achievementRatio:Y(d,r.plannedAmount)}}),a=t.reduce((r,o)=>(r.plannedAmount+=o.plannedAmount,r.actualAmount+=o.actualAmount,r.varianceAmount+=o.varianceAmount,r),{plannedAmount:0,actualAmount:0,varianceAmount:0});return{rows:t,totals:{...a,achievementRatio:Y(a.actualAmount,a.plannedAmount)}}}function ne(n,e){switch(e.type){case"finance.account.created":n.createAccount(e.payload);return;case"finance.period.opened":n.openFiscalPeriod(e.payload);return;case"finance.period.closed":n.closeFiscalPeriod(e.payload.periodId);return;case"finance.journal.drafted":n.createJournalDraft(e.payload);return;case"finance.journal.posted":n.postJournal(e.payload.journalId,e.payload.postedAt);return;case"finance.journal.reversed":n.reverseJournal(e.payload.journalId,e.payload.reversalId);return;default:throw new Error("FINANCE_EVENT_UNSUPPORTED")}}function Qe(n){let e=new l;for(let t of n)ne(e,t);return e}var ae=[{id:"coa-cash",code:"1101",name:"Kas",type:"asset"},{id:"coa-bank",code:"1102",name:"Bank",type:"asset"},{id:"coa-ar",code:"1201",name:"Piutang Usaha",type:"asset"},{id:"coa-ap",code:"2101",name:"Utang Usaha",type:"liability"},{id:"coa-retained",code:"3201",name:"Laba Ditahan",type:"equity"},{id:"coa-revenue",code:"4101",name:"Pendapatan Operasional",type:"revenue"},{id:"coa-expense",code:"5101",name:"Biaya Operasional",type:"expense"}];function Ge(n="IDR"){return ae.map(e=>({id:e.id,code:e.code,name:e.name,type:e.type,currency:n,isActive:!0}))}var re=[{id:"coop-coa-cash",code:"1101",name:"Kas",type:"asset"},{id:"coop-coa-bank",code:"1102",name:"Bank",type:"asset"},{id:"coop-coa-loan-ar",code:"1201",name:"Piutang Pinjaman Anggota",type:"asset"},{id:"coop-coa-other-ar",code:"1202",name:"Piutang Lain-lain",type:"asset"},{id:"coop-coa-prepaid",code:"1301",name:"Biaya Dibayar Dimuka",type:"asset"},{id:"coop-coa-fixed-asset",code:"1401",name:"Aset Tetap",type:"asset"},{id:"coop-coa-accum-depr",code:"1402",name:"Akumulasi Penyusutan",type:"asset"},{id:"coop-coa-simp-pokok",code:"2101",name:"Simpanan Pokok",type:"liability"},{id:"coop-coa-simp-wajib",code:"2102",name:"Simpanan Wajib",type:"liability"},{id:"coop-coa-simp-sukarela",code:"2103",name:"Simpanan Sukarela",type:"liability"},{id:"coop-coa-simp-berjangka",code:"2104",name:"Simpanan Berjangka",type:"liability"},{id:"coop-coa-ap",code:"2201",name:"Utang Usaha",type:"liability"},{id:"coop-coa-ext-loan",code:"2301",name:"Pinjaman Luar",type:"liability"},{id:"coop-coa-tax-payable",code:"2401",name:"Utang Pajak",type:"liability"},{id:"coop-coa-modal-awal",code:"3101",name:"Modal Awal",type:"equity"},{id:"coop-coa-cadangan-umum",code:"3201",name:"Dana Cadangan Umum",type:"equity"},{id:"coop-coa-cadangan-tujuan",code:"3202",name:"Dana Cadangan Tujuan",type:"equity"},{id:"coop-coa-shu-prior",code:"3301",name:"SHU Tahun Lalu",type:"equity"},{id:"coop-coa-shu-current",code:"3302",name:"SHU Berjalan",type:"equity"},{id:"coop-coa-shu-reserve",code:"3303",name:"Dana SHU Belum Dibagi",type:"equity"},{id:"coop-coa-jasa-pinjaman",code:"4101",name:"Pendapatan Jasa Pinjaman",type:"revenue"},{id:"coop-coa-provisi",code:"4102",name:"Pendapatan Provisi",type:"revenue"},{id:"coop-coa-rev-other",code:"4103",name:"Pendapatan Lain-lain",type:"revenue"},{id:"coop-coa-beban-bunga",code:"5101",name:"Beban Bunga Simpanan",type:"expense"},{id:"coop-coa-beban-ops",code:"5102",name:"Beban Operasional",type:"expense"},{id:"coop-coa-beban-gaji",code:"5103",name:"Beban Gaji dan Tunjangan",type:"expense"},{id:"coop-coa-beban-admin",code:"5104",name:"Beban Administrasi",type:"expense"},{id:"coop-coa-beban-depr",code:"5105",name:"Beban Penyusutan",type:"expense"},{id:"coop-coa-prov-kredit",code:"5106",name:"Cadangan Kerugian Piutang",type:"expense"}];function $e(n="IDR"){return re.map(e=>({id:e.id,code:e.code,name:e.name,type:e.type,currency:n,isActive:!0}))}function ze(n){let e=new Set,t=new Set;for(let a of n){if(!a.id||!a.code||!a.name)return{ok:!1,reason:"COA_REQUIRED_FIELDS"};if(e.has(a.id))return{ok:!1,reason:"COA_DUPLICATE_ID"};if(t.has(a.code))return{ok:!1,reason:"COA_DUPLICATE_CODE"};e.add(a.id),t.add(a.code)}return{ok:!0}}export{ae as CANONICAL_COA_TEMPLATES,re as COOP_COA_TEMPLATES,m as OFINANCE_TABLES,l as OffinanceCore,v as OffinanceEnvelopeService,C as OfinanceRuntimeCore,D as OfinanceRuntimeCoreBuilder,ne as applyFinanceDomainEvent,_ as applyPendingMigrations,Ne as buildBalanceSheet,He as buildBudgetVsRealization,Je as buildCashflowBaseline,je as buildClosingJournalDraft,Ve as buildFinanceExportEnvelope,Me as buildIncomeStatement,N as buildJournalFromCoopLoanDisbursement,j as buildJournalFromCoopLoanRepayment,B as buildJournalFromCoopSavingDeposit,J as buildJournalFromCoopSavingWithdrawal,M as buildJournalFromPosSale,Te as buildJournalFromScopedCoopLoanDisbursement,xe as buildJournalFromScopedCoopLoanRepayment,we as buildJournalFromScopedCoopSavingDeposit,Le as buildJournalFromScopedCoopSavingWithdrawal,Fe as buildJournalFromScopedPosSale,Ge as createCanonicalCoA,$e as createCoopCoA,w as createDbAdapterOfinanceServices,T as createOffinanceEnvelopeService,p as mapOffinanceErrorToEnvelope,Qe as replayFinanceDomainEvents,k as resolveCoopLoanDisbursementAccountMapping,H as resolveCoopLoanRepaymentAccountMapping,W as resolveCoopSavingDepositAccountMapping,q as resolveCoopSavingWithdrawalAccountMapping,V as resolvePosSaleAccountMapping,b as resolveScopedAccountMapping,F as toOffinanceFailureEnvelope,s as toOffinanceSuccessEnvelope,ze as validateCoaIntegrity,P as validateJournalBalance};
2
+ /*! For license information please see index.esm.js.LEGAL.txt */
File without changes
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var R=Object.defineProperty;var te=Object.getOwnPropertyDescriptor;var ne=Object.getOwnPropertyNames;var ae=Object.prototype.hasOwnProperty;var re=(n,e)=>{for(var t in e)R(n,t,{get:e[t],enumerable:!0})},oe=(n,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ne(e))!ae.call(n,r)&&r!==t&&R(n,r,{get:()=>e[r],enumerable:!(a=te(e,r))||a.enumerable});return n};var ie=n=>oe(R({},"__esModule",{value:!0}),n);var Re={};re(Re,{CANONICAL_COA_TEMPLATES:()=>Z,COOP_COA_TEMPLATES:()=>ee,OFINANCE_TABLES:()=>m,OffinanceCore:()=>l,OffinanceEnvelopeService:()=>v,OfinanceRuntimeCore:()=>C,OfinanceRuntimeCoreBuilder:()=>D,applyFinanceDomainEvent:()=>X,applyPendingMigrations:()=>w,buildBalanceSheet:()=>Ie,buildBudgetVsRealization:()=>he,buildCashflowBaseline:()=>ye,buildClosingJournalDraft:()=>Ae,buildFinanceExportEnvelope:()=>be,buildIncomeStatement:()=>fe,buildJournalFromCoopLoanDisbursement:()=>x,buildJournalFromCoopLoanRepayment:()=>N,buildJournalFromCoopSavingDeposit:()=>_,buildJournalFromCoopSavingWithdrawal:()=>M,buildJournalFromPosSale:()=>L,buildJournalFromScopedCoopLoanDisbursement:()=>de,buildJournalFromScopedCoopLoanRepayment:()=>me,buildJournalFromScopedCoopSavingDeposit:()=>ue,buildJournalFromScopedCoopSavingWithdrawal:()=>le,buildJournalFromScopedPosSale:()=>pe,createCanonicalCoA:()=>Ce,createCoopCoA:()=>De,createDbAdapterOfinanceServices:()=>T,createOffinanceEnvelopeService:()=>F,mapOffinanceErrorToEnvelope:()=>s,replayFinanceDomainEvents:()=>ve,resolveCoopLoanDisbursementAccountMapping:()=>J,resolveCoopLoanRepaymentAccountMapping:()=>j,resolveCoopSavingDepositAccountMapping:()=>U,resolveCoopSavingWithdrawalAccountMapping:()=>V,resolvePosSaleAccountMapping:()=>B,resolveScopedAccountMapping:()=>g,toOffinanceFailureEnvelope:()=>P,toOffinanceSuccessEnvelope:()=>c,validateCoaIntegrity:()=>Ee,validateJournalBalance:()=>O});module.exports=ie(Re);function O(n){if(!Array.isArray(n)||n.length===0)return{ok:!1,totalDebit:0,totalCredit:0,reason:"JOURNAL_LINES_EMPTY"};let e=0,t=0;for(let a of n){if(!a||!a.accountId)return{ok:!1,totalDebit:e,totalCredit:t,reason:"JOURNAL_ACCOUNT_REQUIRED"};if(a.debit<0||a.credit<0)return{ok:!1,totalDebit:e,totalCredit:t,reason:"JOURNAL_NEGATIVE_AMOUNT"};e+=a.debit,t+=a.credit}return Math.abs(e-t)>1e-6?{ok:!1,totalDebit:e,totalCredit:t,reason:"JOURNAL_NOT_BALANCED"}:{ok:!0,totalDebit:e,totalCredit:t}}var l=class{constructor(e){this.state={accounts:e?.accounts??new Map,periods:e?.periods??new Map,journals:e?.journals??new Map,balances:e?.balances??new Map,rapbPlans:e?.rapbPlans??new Map}}createAccount(e){if(!e.id||!e.code||!e.name)throw new Error("FINANCE_ACCOUNT_REQUIRED_FIELDS");if(this.state.accounts.has(e.id))throw new Error("FINANCE_ACCOUNT_ALREADY_EXISTS");if(Array.from(this.state.accounts.values()).some(a=>a.code===e.code))throw new Error("FINANCE_ACCOUNT_CODE_DUPLICATE");return this.state.accounts.set(e.id,{...e}),{...e}}listAccounts(){return Array.from(this.state.accounts.values()).map(e=>({...e})).sort((e,t)=>e.code.localeCompare(t.code))}openFiscalPeriod(e){if(!e.id||!e.tenantId||!e.code||!e.startDate||!e.endDate)throw new Error("FISCAL_PERIOD_REQUIRED_FIELDS");if(this.state.periods.has(e.id))throw new Error("FISCAL_PERIOD_ALREADY_EXISTS");if(Date.parse(e.startDate)>Date.parse(e.endDate))throw new Error("FISCAL_PERIOD_INVALID_RANGE");let t={...e,status:"open"};return this.state.periods.set(t.id,t),{...t}}closeFiscalPeriod(e){let t=this.state.periods.get(e);if(!t)throw new Error("FISCAL_PERIOD_NOT_FOUND");let a={...t,status:"closed"};return this.state.periods.set(e,a),{...a}}listFiscalPeriods(){return Array.from(this.state.periods.values()).map(e=>({...e})).sort((e,t)=>e.startDate.localeCompare(t.startDate))}createJournalDraft(e){if(!e.id||!e.tenantId||!e.occurredAt)throw new Error("JOURNAL_REQUIRED_FIELDS");if(this.state.journals.has(e.id))throw new Error("JOURNAL_ALREADY_EXISTS");this.assertJournalLinesKnownAccounts(e.lines),this.assertJournalPeriodOpen(e.occurredAt);let t=O(e.lines);if(!t.ok)throw new Error(t.reason??"JOURNAL_INVALID");let a={id:e.id,tenantId:e.tenantId,branchId:e.branchId,financeDomainId:e.financeDomainId,ledgerProfileId:e.ledgerProfileId,occurredAt:e.occurredAt,reference:e.reference,lines:e.lines.map(r=>({...r})),status:"draft"};return this.state.journals.set(a.id,a),{...a,lines:a.lines.map(r=>({...r}))}}postJournal(e,t=new Date().toISOString()){let a=this.state.journals.get(e);if(!a)throw new Error("JOURNAL_NOT_FOUND");if(a.status==="posted"||a.status==="reversed")return{...a,lines:a.lines.map(o=>({...o}))};this.applyLinesToBalance(a.lines);let r={...a,status:"posted",postedAt:t};return this.state.journals.set(e,r),{...r,lines:r.lines.map(o=>({...o}))}}reverseJournal(e,t){let a=this.state.journals.get(e);if(!a)throw new Error("JOURNAL_NOT_FOUND");if(a.status!=="posted")throw new Error("JOURNAL_NOT_POSTED");if(this.state.journals.has(t))throw new Error("JOURNAL_ALREADY_EXISTS");let r=a.lines.map(p=>({accountId:p.accountId,debit:p.credit,credit:p.debit,memo:p.memo})),o=this.createJournalDraft({id:t,tenantId:a.tenantId,branchId:a.branchId,financeDomainId:a.financeDomainId,ledgerProfileId:a.ledgerProfileId,occurredAt:a.occurredAt,reference:`reversal:${a.id}`,lines:r});return this.postJournal(o.id)}getTrialBalance(){return this.getTrialBalanceByScope()}getTrialBalanceByScope(e){let t=this.computeScopedBalances(e);return this.listAccounts().map(r=>{let o=t.get(r.id)??{debit:0,credit:0};return{accountId:r.id,accountCode:r.code,accountName:r.name,accountType:r.type,debit:o.debit,credit:o.credit,net:o.debit-o.credit}})}listJournals(e){return Array.from(this.state.journals.values()).filter(t=>this.matchesScope(t,e)).map(t=>({...t,lines:t.lines.map(a=>({...a}))})).sort((t,a)=>t.occurredAt.localeCompare(a.occurredAt))}assertJournalLinesKnownAccounts(e){for(let t of e)if(!this.state.accounts.has(t.accountId))throw new Error("JOURNAL_ACCOUNT_NOT_FOUND")}assertJournalPeriodOpen(e){if(this.state.periods.size===0)return;let t=this.findPeriodByDate(e);if(!t)throw new Error("FISCAL_PERIOD_NOT_FOUND_FOR_DATE");if(t.status!=="open")throw new Error("FISCAL_PERIOD_CLOSED")}findPeriodByDate(e){let t=Date.parse(e);for(let a of this.state.periods.values()){let r=Date.parse(a.startDate),o=Date.parse(a.endDate);if(t>=r&&t<=o)return a}return null}applyLinesToBalance(e){for(let t of e){let a=this.state.balances.get(t.accountId)??{debit:0,credit:0};a.debit+=t.debit,a.credit+=t.credit,this.state.balances.set(t.accountId,a)}}computeScopedBalances(e){if(!e||!e.tenantId&&!e.branchId&&!e.financeDomainId&&!e.ledgerProfileId&&!e.occurredFrom&&!e.occurredTo)return this.state.balances;let t=new Map;for(let a of this.state.journals.values())if(a.status==="posted"&&this.matchesScope(a,e))for(let r of a.lines){let o=t.get(r.accountId)??{debit:0,credit:0};o.debit+=r.debit,o.credit+=r.credit,t.set(r.accountId,o)}return t}matchesScope(e,t){return t?!(t.tenantId&&e.tenantId!==t.tenantId||t.branchId&&e.branchId!==t.branchId||t.financeDomainId&&e.financeDomainId!==t.financeDomainId||t.ledgerProfileId&&e.ledgerProfileId!==t.ledgerProfileId||t.occurredFrom&&e.occurredAt<t.occurredFrom||t.occurredTo&&e.occurredAt>t.occurredTo):!0}createRapb(e){if(!e.id||!e.tenantId||!e.periodCode||!e.title)throw new Error("RAPB_REQUIRED_FIELDS");if(!e.lines||e.lines.length===0)throw new Error("RAPB_LINES_REQUIRED");if(this.state.rapbPlans.has(e.id))throw new Error("RAPB_ALREADY_EXISTS");if(e.lines.some(r=>r.budgetedDebit<0||r.budgetedCredit<0))throw new Error("RAPB_NEGATIVE_AMOUNT");let t=e.lines.map(r=>{let o=this.state.accounts.get(r.accountId);if(!o)throw new Error(`RAPB_ACCOUNT_NOT_FOUND:${r.accountId}`);let p=r.budgetedDebit-r.budgetedCredit;return{accountId:r.accountId,accountCode:o.code,accountName:o.name,accountType:o.type,budgetedDebit:r.budgetedDebit,budgetedCredit:r.budgetedCredit,budgetedNet:p}}),a={id:e.id,tenantId:e.tenantId,branchId:e.branchId,financeDomainId:e.financeDomainId,ledgerProfileId:e.ledgerProfileId,periodCode:e.periodCode,title:e.title,status:"draft",lines:t,notes:e.notes,createdAt:new Date().toISOString()};return this.state.rapbPlans.set(a.id,a),a}getRapb(e){return this.state.rapbPlans.get(e)??null}listRapb(e){let t=Array.from(this.state.rapbPlans.values());return e&&(e.tenantId&&(t=t.filter(a=>a.tenantId===e.tenantId)),e.periodCode&&(t=t.filter(a=>a.periodCode===e.periodCode)),e.branchId&&(t=t.filter(a=>a.branchId===e.branchId)),e.financeDomainId&&(t=t.filter(a=>a.financeDomainId===e.financeDomainId)),e.ledgerProfileId&&(t=t.filter(a=>a.ledgerProfileId===e.ledgerProfileId))),t}approveRapb(e,t){let a=this.state.rapbPlans.get(e);if(!a)throw new Error("RAPB_NOT_FOUND");let r=typeof t=="string"?t:t.approvedBy;if(a.status==="approved")throw new Error("RAPB_ALREADY_APPROVED");let o={...a,status:"approved",approvedAt:new Date().toISOString(),approvedBy:r};return this.state.rapbPlans.set(e,o),o}evaluateVariance(e){let t=this.state.rapbPlans.get(e);if(!t)throw new Error("RAPB_NOT_FOUND");let a=this.getTrialBalance(),r=new Map(a.map(i=>[i.accountId,i])),o=0,p=0,d=0,f=0,u=t.lines.map(i=>{let H=r.get(i.accountId),I=H?.debit??0,y=H?.credit??0,W=I-y;return i.accountType==="revenue"?(o+=i.budgetedCredit-i.budgetedDebit,p+=y-I):i.accountType==="expense"&&(d+=i.budgetedDebit-i.budgetedCredit,f+=I-y),{accountId:i.accountId,accountCode:i.accountCode,accountName:i.accountName,accountType:i.accountType,budgetedDebit:i.budgetedDebit,budgetedCredit:i.budgetedCredit,budgetedNet:i.budgetedNet,actualDebit:I,actualCredit:y,actualNet:W,varianceDebit:I-i.budgetedDebit,varianceCredit:y-i.budgetedCredit,varianceNet:W-i.budgetedNet}}),h=o-d,k=p-f;return{rapbId:e,periodCode:t.periodCode,evaluatedAt:new Date().toISOString(),lines:u,totalBudgetedRevenue:o,totalActualRevenue:p,totalBudgetedExpense:d,totalActualExpense:f,budgetedNetIncome:h,actualNetIncome:k,netVariance:k-h}}};var Y=require("ofcore"),G=require("ofcore"),E=require("ofcore");function c(n,e){return{data:n,meta:{request_id:e??null,timestamp:new Date().toISOString()}}}function P(n,e,t={}){return{error:{code:n,message:e,details:t.details,retryable:t.retryable,correlationId:t.correlationId,timestamp:new Date().toISOString()},meta:{request_id:t.requestId??null,timestamp:new Date().toISOString()}}}function s(n,e="FINANCE_NOT_IMPLEMENTED",t="Unhandled offinance error."){return n instanceof Error?P(e,n.message||t):P(e,t)}var v=class{constructor(e){this.legacy=e;this.accountService={createAccount:async t=>{try{return c(this.legacy.accountService.createAccount(t))}catch(a){return s(a)}},listAccounts:async()=>{try{return c(this.legacy.accountService.listAccounts())}catch(t){return s(t)}}},this.journalService={createJournalDraft:async t=>{try{return c(this.legacy.journalService.createJournalDraft(t))}catch(a){return s(a)}},postJournal:async(t,a)=>{try{return c(this.legacy.journalService.postJournal(t,a))}catch(r){return s(r)}},reverseJournal:async(t,a)=>{try{return c(this.legacy.journalService.reverseJournal(t,a))}catch(r){return s(r)}},listJournals:async t=>{try{return c(this.legacy.journalService.listJournals(t))}catch(a){return s(a)}}},this.fiscalService={openFiscalPeriod:async t=>{try{return c(this.legacy.fiscalService.openFiscalPeriod(t))}catch(a){return s(a)}},closeFiscalPeriod:async t=>{try{return c(this.legacy.fiscalService.closeFiscalPeriod(t))}catch(a){return s(a)}},listFiscalPeriods:async()=>{try{return c(this.legacy.fiscalService.listFiscalPeriods())}catch(t){return s(t)}},getTrialBalance:async()=>{try{return c(this.legacy.fiscalService.getTrialBalance())}catch(t){return s(t)}},getTrialBalanceByScope:async t=>{try{return c(this.legacy.fiscalService.getTrialBalanceByScope(t))}catch(a){return s(a)}}},this.rapbService={createRapb:async t=>{try{return c(this.legacy.rapbService.createRapb(t))}catch(a){return s(a)}},getRapb:async t=>{try{return c(this.legacy.rapbService.getRapb(t))}catch(a){return s(a)}},listRapb:async t=>{try{return c(this.legacy.rapbService.listRapb(t))}catch(a){return s(a)}},approveRapb:async(t,a)=>{try{return c(this.legacy.rapbService.approveRapb(t,a))}catch(r){return s(r)}},evaluateVariance:async t=>{try{return c(this.legacy.rapbService.evaluateVariance(t))}catch(a){return s(a)}}}}};function F(n){return new v({accountService:n,journalService:n,fiscalService:n,rapbService:n})}async function T(n){let e=new l;return{financeService:F(e)}}var m={accounts:"ofinance_accounts",periods:"ofinance_periods",journals:"ofinance_journals",journalLines:"ofinance_journal_lines",histories:"ofinance_histories"},ce=[{name:m.accounts,columns:[{name:"id",type:"string"},{name:"code",type:"string",isIndexed:!0},{name:"name",type:"string"},{name:"type",type:"string",isIndexed:!0},{name:"currency",type:"string"},{name:"isActive",type:"boolean",isIndexed:!0},{name:"tenantId",type:"string",isOptional:!0,isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0},{name:"lastModified",type:"string"},{name:"deleted",type:"boolean",isIndexed:!0}]},{name:m.periods,columns:[{name:"id",type:"string"},{name:"tenantId",type:"string",isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0},{name:"code",type:"string",isIndexed:!0},{name:"startDate",type:"string",isIndexed:!0},{name:"endDate",type:"string",isIndexed:!0},{name:"status",type:"string",isIndexed:!0},{name:"lastModified",type:"string"},{name:"deleted",type:"boolean",isIndexed:!0}]},{name:m.journals,columns:[{name:"id",type:"string"},{name:"tenantId",type:"string",isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0},{name:"occurredAt",type:"string",isIndexed:!0},{name:"reference",type:"string",isOptional:!0,isIndexed:!0},{name:"status",type:"string",isIndexed:!0},{name:"postedAt",type:"string",isOptional:!0,isIndexed:!0},{name:"lastModified",type:"string"},{name:"deleted",type:"boolean",isIndexed:!0}]},{name:m.journalLines,columns:[{name:"id",type:"string"},{name:"journalId",type:"string",isIndexed:!0},{name:"accountId",type:"string",isIndexed:!0},{name:"debit",type:"number"},{name:"credit",type:"number"},{name:"memo",type:"string",isOptional:!0},{name:"tenantId",type:"string",isOptional:!0,isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0},{name:"lastModified",type:"string"},{name:"deleted",type:"boolean",isIndexed:!0}]},{name:m.histories,columns:[{name:"id",type:"string"},{name:"tableName",type:"string",isIndexed:!0},{name:"recordId",type:"string",isIndexed:!0},{name:"action",type:"string",isIndexed:!0},{name:"changes",type:"json",isOptional:!0},{name:"timestamp",type:"string",isIndexed:!0},{name:"tenantId",type:"string",isOptional:!0,isIndexed:!0},{name:"branchId",type:"string",isOptional:!0,isIndexed:!0},{name:"financeDomainId",type:"string",isOptional:!0,isIndexed:!0},{name:"ledgerProfileId",type:"string",isOptional:!0,isIndexed:!0}]}];function q(){return ce.map(n=>({...n,columns:n.columns.map(e=>({...e}))}))}var Q=[{toVersion:1,up:async n=>{let e=q();for(let t of e)try{await n.addTable(t)}catch{}}}];var se={logInfo(){},logWarn(){},logError(){}};async function w(n,e=se){e.logInfo("[offinance] starting database migration process");let t=[...Q].sort((o,p)=>o.toVersion-p.toVersion),a=t.length>0?t[t.length-1].toVersion:0,r=await n.getSchemaVersion();if((r==null||r<0)&&(r=0),r>=a){e.logInfo(`[offinance] database already up-to-date at v${r}`);return}for(let o of t)o.toVersion>r&&(e.logInfo(`[offinance] applying migration v${o.toVersion}`),await o.up(n),await n.setSchemaVersion(o.toVersion),r=o.toVersion);e.logInfo(`[offinance] migration completed at v${r}`)}var C=class{constructor(e,t){this.runtime=e;this.runtimeStateStore=t;this.runtimeStore=(0,E.asReadonlyStore)(this.runtimeStateStore)}static builder(){return new D}get registry(){return this.runtime.registry}async start(e={}){this.runtimeStateStore.setState({phase:"starting",started:!1,lastError:null,lastTransitionAt:new Date().toISOString(),startCount:this.runtimeStateStore.getState().startCount,stopCount:this.runtimeStateStore.getState().stopCount});try{await this.runtime.start(e),this.domainServices=this.runtime.domainServices,this.runtimeStateStore.setState(t=>({...t,phase:"started",started:!0,startCount:t.startCount+1,lastError:null,lastTransitionAt:new Date().toISOString()}))}catch(t){throw this.runtimeStateStore.setState(a=>({...a,phase:"error",started:!1,lastError:t instanceof Error?t.message:String(t),lastTransitionAt:new Date().toISOString()})),t}}async stop(){this.runtimeStateStore.setState(e=>({...e,phase:"stopping",started:this.runtime.isStarted(),lastError:null,lastTransitionAt:new Date().toISOString()}));try{await this.runtime.stop(),this.runtimeStateStore.setState(e=>({...e,phase:"stopped",started:!1,stopCount:e.stopCount+1,lastError:null,lastTransitionAt:new Date().toISOString()}))}catch(e){throw this.runtimeStateStore.setState(t=>({...t,phase:"error",started:this.runtime.isStarted(),lastError:e instanceof Error?e.message:String(e),lastTransitionAt:new Date().toISOString()})),e}}isStarted(){return this.runtime.isStarted()}},D=class{constructor(){this.runtimeBuilder=Y.CoreRuntime.builder();this.runtimeHooks={};this.runtimeBuilder.withDbAdapter(()=>new G.InMemoryDbAdapter)}withPlatformAdapter(e){return this.runtimeBuilder.withPlatformAdapter(e),this}withDbAdapter(e){return this.runtimeBuilder.withDbAdapter(e),this}withHttpAdapter(e){return this.runtimeBuilder.withHttpAdapter(e),this}withSocketAdapter(e){return this.runtimeBuilder.withSocketAdapter(e),this}withLoggerAdapter(e){return this.runtimeBuilder.withLoggerAdapter(e),this}withActivitySink(e){return this.runtimeBuilder.withActivitySink(e),this}withExtension(e,t){return this.runtimeBuilder.withExtension(e,t),this}withDomainServicesFactory(e){return this.domainServicesFactory=e,this}withRuntimeHooks(e){return this.runtimeHooks={...this.runtimeHooks,...e},this}build(){let e=(0,E.createStore)({phase:"idle",started:!1,startCount:0,stopCount:0,lastError:null,lastTransitionAt:new Date().toISOString()}),t={runMigrations:async r=>{let o=r.registry?.dbAdapter;if(!o)throw new Error("OFINANCE_DB_ADAPTER_REQUIRED");await w(o,r.registry?.loggerAdapter),this.runtimeHooks.runMigrations&&await this.runtimeHooks.runMigrations(r)},...this.runtimeHooks,createDomainServices:async r=>{if(this.domainServicesFactory)return this.domainServicesFactory();let o=r.registry?.dbAdapter;if(!o)throw new Error("OFINANCE_DB_ADAPTER_REQUIRED");return T(o)}};this.runtimeBuilder.withHooks(t);let a=this.runtimeBuilder.build();return new C(a,e)}};function A(n,e,t){return{id:n.journalId,tenantId:n.tenantId,branchId:n.branchId,financeDomainId:n.financeDomainId,ledgerProfileId:n.ledgerProfileId,occurredAt:n.occurredAt,reference:t?`${n.reference??""}${t}`:n.reference,lines:e}}function b(n,e){if(!Number.isFinite(n)||n<=0)throw new Error(e)}function L(n,e){b(e.grossAmount,"POS_SALE_INVALID_GROSS_AMOUNT");let t=e.discountAmount??0,a=e.taxAmount??0;if(t<0||a<0)throw new Error("POS_SALE_NEGATIVE_COMPONENT");let r=e.grossAmount,o=r-t+a,p=[{accountId:e.paymentAccountId,debit:o,credit:0,memo:"POS sale payment"},{accountId:e.revenueAccountId,debit:0,credit:r,memo:"POS sale revenue"}];if(t>0){if(!e.discountAccountId)throw new Error("POS_SALE_DISCOUNT_ACCOUNT_REQUIRED");p.push({accountId:e.discountAccountId,debit:t,credit:0,memo:"POS sale discount"})}if(a>0){if(!e.taxPayableAccountId)throw new Error("POS_SALE_TAX_ACCOUNT_REQUIRED");p.push({accountId:e.taxPayableAccountId,debit:0,credit:a,memo:"POS sale tax payable"})}return A(n,p)}function x(n,e){return b(e.principalAmount,"COOP_LOAN_INVALID_PRINCIPAL"),A(n,[{accountId:e.receivableAccountId,debit:e.principalAmount,credit:0,memo:"Loan receivable principal"},{accountId:e.cashAccountId,debit:0,credit:e.principalAmount,memo:"Loan cash out"}])}function _(n,e){return b(e.amount,"COOP_SAVING_INVALID_AMOUNT"),A(n,[{accountId:e.cashAccountId,debit:e.amount,credit:0,memo:"Saving deposit cash in"},{accountId:e.savingLiabilityAccountId,debit:0,credit:e.amount,memo:"Saving liability increase"}])}function M(n,e){return b(e.amount,"COOP_SAVING_WITHDRAWAL_INVALID_AMOUNT"),A(n,[{accountId:e.savingLiabilityAccountId,debit:e.amount,credit:0,memo:"Saving liability decrease"},{accountId:e.cashAccountId,debit:0,credit:e.amount,memo:"Saving withdrawal cash out"}])}function N(n,e){b(e.principalAmount,"COOP_LOAN_REPAYMENT_INVALID_PRINCIPAL");let t=e.serviceAmount??0,a=e.penaltyAmount??0;if(t<0||a<0)throw new Error("COOP_LOAN_REPAYMENT_NEGATIVE_COMPONENT");if(t>0&&!e.serviceRevenueAccountId)throw new Error("COOP_LOAN_REPAYMENT_SERVICE_ACCOUNT_REQUIRED");if(a>0&&!e.penaltyRevenueAccountId)throw new Error("COOP_LOAN_REPAYMENT_PENALTY_ACCOUNT_REQUIRED");let r=e.principalAmount+t+a,o=[{accountId:e.cashAccountId,debit:r,credit:0,memo:"Loan repayment cash in"},{accountId:e.receivableAccountId,debit:0,credit:e.principalAmount,memo:"Loan receivable principal repayment"}];return t>0&&o.push({accountId:e.serviceRevenueAccountId,debit:0,credit:t,memo:"Loan service revenue"}),a>0&&o.push({accountId:e.penaltyRevenueAccountId,debit:0,credit:a,memo:"Loan penalty revenue"}),A(n,o)}function $(n,e){return e?{...n,...e}:{...n}}function g(n){let e=n.tenantId?n.scopedMapping.byTenant?.[n.tenantId]:void 0,t=n.tenantId&&n.branchId?`${n.tenantId}:${n.branchId}`:"",a=t?n.scopedMapping.byBranch?.[t]:void 0;return $($(n.scopedMapping.default,e),a)}function B(n){return g(n)}function J(n){return g(n)}function j(n){return g(n)}function U(n){return g(n)}function V(n){return g(n)}function pe(n,e){let{scopedAccountMapping:t,...a}=e,r=B({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return L(n,{...a,...r})}function de(n,e){let{scopedAccountMapping:t,...a}=e,r=J({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return x(n,{...a,...r})}function ue(n,e){let{scopedAccountMapping:t,...a}=e,r=U({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return _(n,{...a,...r})}function le(n,e){let{scopedAccountMapping:t,...a}=e,r=V({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return M(n,{...a,...r})}function me(n,e){let{scopedAccountMapping:t,...a}=e,r=j({tenantId:n.tenantId,branchId:n.branchId,scopedMapping:t});return N(n,{...a,...r})}function ge(n){switch(n.accountType){case"asset":case"expense":return n.debit-n.credit;case"liability":case"equity":case"revenue":return n.credit-n.debit;default:return 0}}function S(n,e){return n.filter(t=>t.accountType===e).reduce((t,a)=>t+ge(a),0)}function fe(n){let e=S(n,"revenue"),t=S(n,"expense");return{revenue:e,expense:t,netIncome:e-t}}function Ie(n){return{assets:S(n,"asset"),liabilities:S(n,"liability"),equity:S(n,"equity")}}function z(n){return n.accountType==="revenue"?n.credit-n.debit:n.accountType==="expense"?n.debit-n.credit:0}function ye(n){let e=n.filter(a=>a.accountType==="revenue").reduce((a,r)=>a+Math.max(0,z(r)),0),t=n.filter(a=>a.accountType==="expense").reduce((a,r)=>a+Math.max(0,z(r)),0);return{operatingInflow:e,operatingOutflow:t,netOperatingCashflow:e-t}}function Ae(n,e){let t=[],a=0;for(let r of e){if(r.accountType==="revenue"){let o=r.credit-r.debit;o>0&&(t.push({accountId:r.accountId,debit:o,credit:0,memo:"Close revenue account"}),a+=o)}if(r.accountType==="expense"){let o=r.debit-r.credit;o>0&&(t.push({accountId:r.accountId,debit:0,credit:o,memo:"Close expense account"}),a-=o)}}return a>0?t.push({accountId:n.retainedEarningsAccountId,debit:0,credit:a,memo:"Retained earnings from period close"}):a<0&&t.push({accountId:n.retainedEarningsAccountId,debit:Math.abs(a),credit:0,memo:"Retained earnings from period close"}),{id:n.journalId,tenantId:n.tenantId,branchId:n.branchId,financeDomainId:n.financeDomainId,ledgerProfileId:n.ledgerProfileId,occurredAt:n.occurredAt,reference:n.reference,lines:t}}function be(n){return{version:"v1",generatedAt:n.generatedAt??new Date().toISOString(),payload:{trialBalance:n.trialBalance.map(e=>({...e})),incomeStatement:{...n.incomeStatement},balanceSheet:{...n.balanceSheet}}}}function Se(n){return n.accountType==="revenue"?n.credit-n.debit:n.accountType==="expense"?n.debit-n.credit:0}function K(n,e){return e===0?n===0?1:0:Number((n/e).toFixed(4))}function he(n,e){let t=e.map(r=>{let d=((r.accountCodes?.length?n.filter(u=>r.accountCodes?.includes(u.accountCode)):null)??n.filter(u=>r.accountType?u.accountType===r.accountType:!1)).reduce((u,h)=>u+Se(h),0),f=d-r.plannedAmount;return{id:r.id,label:r.label,plannedAmount:r.plannedAmount,actualAmount:d,varianceAmount:f,achievementRatio:K(d,r.plannedAmount)}}),a=t.reduce((r,o)=>(r.plannedAmount+=o.plannedAmount,r.actualAmount+=o.actualAmount,r.varianceAmount+=o.varianceAmount,r),{plannedAmount:0,actualAmount:0,varianceAmount:0});return{rows:t,totals:{...a,achievementRatio:K(a.actualAmount,a.plannedAmount)}}}function X(n,e){switch(e.type){case"finance.account.created":n.createAccount(e.payload);return;case"finance.period.opened":n.openFiscalPeriod(e.payload);return;case"finance.period.closed":n.closeFiscalPeriod(e.payload.periodId);return;case"finance.journal.drafted":n.createJournalDraft(e.payload);return;case"finance.journal.posted":n.postJournal(e.payload.journalId,e.payload.postedAt);return;case"finance.journal.reversed":n.reverseJournal(e.payload.journalId,e.payload.reversalId);return;default:throw new Error("FINANCE_EVENT_UNSUPPORTED")}}function ve(n){let e=new l;for(let t of n)X(e,t);return e}var Z=[{id:"coa-cash",code:"1101",name:"Kas",type:"asset"},{id:"coa-bank",code:"1102",name:"Bank",type:"asset"},{id:"coa-ar",code:"1201",name:"Piutang Usaha",type:"asset"},{id:"coa-ap",code:"2101",name:"Utang Usaha",type:"liability"},{id:"coa-retained",code:"3201",name:"Laba Ditahan",type:"equity"},{id:"coa-revenue",code:"4101",name:"Pendapatan Operasional",type:"revenue"},{id:"coa-expense",code:"5101",name:"Biaya Operasional",type:"expense"}];function Ce(n="IDR"){return Z.map(e=>({id:e.id,code:e.code,name:e.name,type:e.type,currency:n,isActive:!0}))}var ee=[{id:"coop-coa-cash",code:"1101",name:"Kas",type:"asset"},{id:"coop-coa-bank",code:"1102",name:"Bank",type:"asset"},{id:"coop-coa-loan-ar",code:"1201",name:"Piutang Pinjaman Anggota",type:"asset"},{id:"coop-coa-other-ar",code:"1202",name:"Piutang Lain-lain",type:"asset"},{id:"coop-coa-prepaid",code:"1301",name:"Biaya Dibayar Dimuka",type:"asset"},{id:"coop-coa-fixed-asset",code:"1401",name:"Aset Tetap",type:"asset"},{id:"coop-coa-accum-depr",code:"1402",name:"Akumulasi Penyusutan",type:"asset"},{id:"coop-coa-simp-pokok",code:"2101",name:"Simpanan Pokok",type:"liability"},{id:"coop-coa-simp-wajib",code:"2102",name:"Simpanan Wajib",type:"liability"},{id:"coop-coa-simp-sukarela",code:"2103",name:"Simpanan Sukarela",type:"liability"},{id:"coop-coa-simp-berjangka",code:"2104",name:"Simpanan Berjangka",type:"liability"},{id:"coop-coa-ap",code:"2201",name:"Utang Usaha",type:"liability"},{id:"coop-coa-ext-loan",code:"2301",name:"Pinjaman Luar",type:"liability"},{id:"coop-coa-tax-payable",code:"2401",name:"Utang Pajak",type:"liability"},{id:"coop-coa-modal-awal",code:"3101",name:"Modal Awal",type:"equity"},{id:"coop-coa-cadangan-umum",code:"3201",name:"Dana Cadangan Umum",type:"equity"},{id:"coop-coa-cadangan-tujuan",code:"3202",name:"Dana Cadangan Tujuan",type:"equity"},{id:"coop-coa-shu-prior",code:"3301",name:"SHU Tahun Lalu",type:"equity"},{id:"coop-coa-shu-current",code:"3302",name:"SHU Berjalan",type:"equity"},{id:"coop-coa-shu-reserve",code:"3303",name:"Dana SHU Belum Dibagi",type:"equity"},{id:"coop-coa-jasa-pinjaman",code:"4101",name:"Pendapatan Jasa Pinjaman",type:"revenue"},{id:"coop-coa-provisi",code:"4102",name:"Pendapatan Provisi",type:"revenue"},{id:"coop-coa-rev-other",code:"4103",name:"Pendapatan Lain-lain",type:"revenue"},{id:"coop-coa-beban-bunga",code:"5101",name:"Beban Bunga Simpanan",type:"expense"},{id:"coop-coa-beban-ops",code:"5102",name:"Beban Operasional",type:"expense"},{id:"coop-coa-beban-gaji",code:"5103",name:"Beban Gaji dan Tunjangan",type:"expense"},{id:"coop-coa-beban-admin",code:"5104",name:"Beban Administrasi",type:"expense"},{id:"coop-coa-beban-depr",code:"5105",name:"Beban Penyusutan",type:"expense"},{id:"coop-coa-prov-kredit",code:"5106",name:"Cadangan Kerugian Piutang",type:"expense"}];function De(n="IDR"){return ee.map(e=>({id:e.id,code:e.code,name:e.name,type:e.type,currency:n,isActive:!0}))}function Ee(n){let e=new Set,t=new Set;for(let a of n){if(!a.id||!a.code||!a.name)return{ok:!1,reason:"COA_REQUIRED_FIELDS"};if(e.has(a.id))return{ok:!1,reason:"COA_DUPLICATE_ID"};if(t.has(a.code))return{ok:!1,reason:"COA_DUPLICATE_CODE"};e.add(a.id),t.add(a.code)}return{ok:!0}}
2
+ /*! For license information please see index.js.LEGAL.txt */
File without changes
@@ -0,0 +1,27 @@
1
+ import type { FinanceAccountType } from '../contracts/AccountContract.js';
2
+ import type { TrialBalanceRow } from '../contracts/FiscalContract.js';
3
+ export interface BudgetPlanLine {
4
+ id: string;
5
+ label: string;
6
+ plannedAmount: number;
7
+ accountType?: Extract<FinanceAccountType, 'revenue' | 'expense'>;
8
+ accountCodes?: string[];
9
+ }
10
+ export interface BudgetRealizationRow {
11
+ id: string;
12
+ label: string;
13
+ plannedAmount: number;
14
+ actualAmount: number;
15
+ varianceAmount: number;
16
+ achievementRatio: number;
17
+ }
18
+ export interface BudgetVsRealizationSummary {
19
+ rows: BudgetRealizationRow[];
20
+ totals: {
21
+ plannedAmount: number;
22
+ actualAmount: number;
23
+ varianceAmount: number;
24
+ achievementRatio: number;
25
+ };
26
+ }
27
+ export declare function buildBudgetVsRealization(trialBalanceRows: TrialBalanceRow[], planLines: BudgetPlanLine[]): BudgetVsRealizationSummary;
@@ -0,0 +1,19 @@
1
+ import type { JournalDraftInput } from '../contracts/JournalContract.js';
2
+ import type { TrialBalanceRow } from '../contracts/FiscalContract.js';
3
+ export interface CashflowBaseline {
4
+ operatingInflow: number;
5
+ operatingOutflow: number;
6
+ netOperatingCashflow: number;
7
+ }
8
+ export interface ClosingContext {
9
+ journalId: string;
10
+ tenantId: string;
11
+ branchId?: string;
12
+ financeDomainId?: string;
13
+ ledgerProfileId: string;
14
+ occurredAt: string;
15
+ retainedEarningsAccountId: string;
16
+ reference?: string;
17
+ }
18
+ export declare function buildCashflowBaseline(rows: TrialBalanceRow[]): CashflowBaseline;
19
+ export declare function buildClosingJournalDraft(context: ClosingContext, rows: TrialBalanceRow[]): JournalDraftInput;
@@ -0,0 +1,17 @@
1
+ import type { BalanceSheet, IncomeStatement } from './financialStatements.js';
2
+ import type { TrialBalanceRow } from '../contracts/FiscalContract.js';
3
+ export interface FinanceExportEnvelope {
4
+ version: 'v1';
5
+ generatedAt: string;
6
+ payload: {
7
+ trialBalance: TrialBalanceRow[];
8
+ incomeStatement: IncomeStatement;
9
+ balanceSheet: BalanceSheet;
10
+ };
11
+ }
12
+ export declare function buildFinanceExportEnvelope(params: {
13
+ trialBalance: TrialBalanceRow[];
14
+ incomeStatement: IncomeStatement;
15
+ balanceSheet: BalanceSheet;
16
+ generatedAt?: string;
17
+ }): FinanceExportEnvelope;
@@ -0,0 +1,13 @@
1
+ import type { TrialBalanceRow } from '../contracts/FiscalContract.js';
2
+ export interface IncomeStatement {
3
+ revenue: number;
4
+ expense: number;
5
+ netIncome: number;
6
+ }
7
+ export interface BalanceSheet {
8
+ assets: number;
9
+ liabilities: number;
10
+ equity: number;
11
+ }
12
+ export declare function buildIncomeStatement(rows: TrialBalanceRow[]): IncomeStatement;
13
+ export declare function buildBalanceSheet(rows: TrialBalanceRow[]): BalanceSheet;
@@ -0,0 +1,66 @@
1
+ import type { FinanceAccount, FinanceAccountServiceContractV2 } from '../contracts/AccountContract.js';
2
+ import type { RapbServiceContractV2 } from '../contracts/BudgetContract.js';
3
+ import type { FinanceLedgerScope, FiscalPeriod, FiscalPeriodServiceContractV2 } from '../contracts/FiscalContract.js';
4
+ import type { FinanceJournalServiceContractV2, JournalDraftInput } from '../contracts/JournalContract.js';
5
+ export interface OffinanceLegacyServices {
6
+ accountService: {
7
+ createAccount(account: FinanceAccount): FinanceAccount;
8
+ listAccounts(): FinanceAccount[];
9
+ };
10
+ journalService: {
11
+ createJournalDraft(input: JournalDraftInput): any;
12
+ postJournal(journalId: string, postedAt?: string): any;
13
+ reverseJournal(journalId: string, reversalId: string): any;
14
+ listJournals(scope?: FinanceLedgerScope): any[];
15
+ };
16
+ fiscalService: {
17
+ openFiscalPeriod(period: FiscalPeriod): FiscalPeriod;
18
+ closeFiscalPeriod(periodId: string): FiscalPeriod;
19
+ listFiscalPeriods(): FiscalPeriod[];
20
+ getTrialBalance(): any[];
21
+ getTrialBalanceByScope(scope?: FinanceLedgerScope): any[];
22
+ };
23
+ rapbService: {
24
+ createRapb(input: any): any;
25
+ getRapb(id: string): any;
26
+ listRapb(scope?: any): any[];
27
+ approveRapb(id: string, options: string | {
28
+ approvedBy: string;
29
+ }): any;
30
+ evaluateVariance(rapbId: string): any;
31
+ };
32
+ }
33
+ export interface OffinanceEnvelopeServices {
34
+ accountService: FinanceAccountServiceContractV2;
35
+ journalService: FinanceJournalServiceContractV2;
36
+ fiscalService: FiscalPeriodServiceContractV2;
37
+ rapbService: RapbServiceContractV2;
38
+ }
39
+ export declare class OffinanceEnvelopeService implements OffinanceEnvelopeServices {
40
+ private readonly legacy;
41
+ readonly accountService: FinanceAccountServiceContractV2;
42
+ readonly journalService: FinanceJournalServiceContractV2;
43
+ readonly fiscalService: FiscalPeriodServiceContractV2;
44
+ readonly rapbService: RapbServiceContractV2;
45
+ constructor(legacy: OffinanceLegacyServices);
46
+ }
47
+ export declare function createOffinanceEnvelopeService(legacyService: {
48
+ createAccount(account: FinanceAccount): FinanceAccount;
49
+ listAccounts(): FinanceAccount[];
50
+ createJournalDraft(input: JournalDraftInput): any;
51
+ postJournal(journalId: string, postedAt?: string): any;
52
+ reverseJournal(journalId: string, reversalId: string): any;
53
+ listJournals(scope?: FinanceLedgerScope): any[];
54
+ openFiscalPeriod(period: FiscalPeriod): FiscalPeriod;
55
+ closeFiscalPeriod(periodId: string): FiscalPeriod;
56
+ listFiscalPeriods(): FiscalPeriod[];
57
+ getTrialBalance(): any[];
58
+ getTrialBalanceByScope(scope?: FinanceLedgerScope): any[];
59
+ createRapb(input: any): any;
60
+ getRapb(id: string): any;
61
+ listRapb(scope?: any): any[];
62
+ approveRapb(id: string, options: string | {
63
+ approvedBy: string;
64
+ }): any;
65
+ evaluateVariance(rapbId: string): any;
66
+ }): OffinanceEnvelopeServices;
@@ -0,0 +1,6 @@
1
+ import type { DbAdapter } from 'ofcore';
2
+ import { type OffinanceEnvelopeServices } from './OffinanceEnvelopeService.js';
3
+ export interface OfinanceDomainServices {
4
+ financeService: OffinanceEnvelopeServices;
5
+ }
6
+ export declare function createDbAdapterOfinanceServices(_dbAdapter: DbAdapter): Promise<OfinanceDomainServices>;
@@ -0,0 +1,10 @@
1
+ import type { ErrorDetail, ResponseEnvelope } from 'ofcore';
2
+ export type OffinanceEnvelopeErrorCode = 'FINANCE_VALIDATION_ERROR' | 'FINANCE_NOT_FOUND' | 'FINANCE_CONFLICT' | 'FINANCE_NOT_IMPLEMENTED';
3
+ export declare function toOffinanceSuccessEnvelope<T>(data: T, requestId?: string): ResponseEnvelope<T>;
4
+ export declare function toOffinanceFailureEnvelope<T = never>(code: OffinanceEnvelopeErrorCode, message: string, options?: {
5
+ details?: ErrorDetail[];
6
+ retryable?: boolean;
7
+ correlationId?: string;
8
+ requestId?: string;
9
+ }): ResponseEnvelope<T>;
10
+ export declare function mapOffinanceErrorToEnvelope<T = never>(error: unknown, fallbackCode?: OffinanceEnvelopeErrorCode, fallbackMessage?: string): ResponseEnvelope<T>;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "offinance-shared-core",
3
+ "version": "0.1.0-alpha.0",
4
+ "private": false,
5
+ "description": "Offline-first platform-agnostic db-agnostic finance shared core",
6
+ "author": {
7
+ "name": "Agus Made",
8
+ "email": "krisnaparta@gmail.com"
9
+ },
10
+ "license": "MIT",
11
+ "main": "dist/index.js",
12
+ "module": "dist/index.esm.js",
13
+ "types": "dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.esm.js",
18
+ "require": "./dist/index.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
27
+ "build": "npm run clean && tsc -p tsconfig.build.json && node esbuild.config.js",
28
+ "typecheck": "tsc -p tsconfig.json --noEmit",
29
+ "test": "vitest run",
30
+ "verify:boundary": "node scripts/verify-boundary-imports.cjs",
31
+ "verify:perf": "npm run build --silent && node scripts/perf-baseline.cjs",
32
+ "verify:exception-audit": "node scripts/verify-exception-audit.cjs",
33
+ "verify:ofcore-pattern": "node scripts/verify-ofcore-pattern.cjs",
34
+ "ci:check": "npm run verify:boundary && npm run verify:ofcore-pattern && npm run verify:exception-audit && npm test",
35
+ "prepublishOnly": "npm run ci:check",
36
+ "prepack": "npm run build"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^24.5.2",
40
+ "typescript": "^5.9.3",
41
+ "vitest": "^1.6.1"
42
+ },
43
+ "dependencies": {
44
+ "ofcore": "0.2.0-alpha.0"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://gitlab.com/rintisan/inti/offinance-shared-core.git"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }