@classytic/ledger-bd 0.6.0 → 0.7.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.
@@ -0,0 +1,457 @@
1
+ import { PostingRecipe } from "@classytic/ledger/posting-rules";
2
+
3
+ //#region src/posting-recipes/resolvers.d.ts
4
+ /**
5
+ * BD posting-recipe resolver context — the jurisdiction ports the recipes
6
+ * close over. Injected by the host (be-prod wires its existing
7
+ * `posting/tax.accounts.ts` + `withholding-amount.ts` + tax-resolver
8
+ * implementations); recipes NEVER import a host.
9
+ *
10
+ * Design: packages/ledger/docs/posting-rules-design.md §3.4 item 9 — tax
11
+ * resolvers COMPUTE, recipes PLACE.
12
+ */
13
+ type BdAccountingRegime = 'standard' | 'tot' | 'exempt' | 'importer' | (string & {});
14
+ interface BdImportStackResult {
15
+ /**
16
+ * Assessable value + CD + SD. The recipe capitalizes only the DUTIES portion
17
+ * (`landedCost − assessableValue`) — the AV itself is owed to the supplier
18
+ * and booked by the follow-up vendor bill (+ non-claimable VAT folds in).
19
+ */
20
+ landedCost: number;
21
+ /** RAW VAT computed by the stack — claimability is the RECIPE's decision via inputVatAccount. */
22
+ recoverableTax: number;
23
+ /** AT + AIT prepaid at the customs gate. */
24
+ prepaidTax: number;
25
+ /** Total paid at customs (bank credit) — duties + taxes ONLY, never the AV. */
26
+ totalPayable: number;
27
+ }
28
+ interface BdPostingResolvers {
29
+ /**
30
+ * Rate/regime-gated input-VAT account code. `null` = VAT not claimable
31
+ * (TOT/exempt regimes) — the recipe folds the amount into inventory.
32
+ * Mirrors be-prod `posting/tax.accounts.ts#inputVatAccount`.
33
+ */
34
+ inputVatAccount(rateCode: string, regime: BdAccountingRegime): string | null;
35
+ /** Rate → rate-code inference (be-prod `rateCodeForRate`). */
36
+ rateCodeForRate(rate: number): string;
37
+ /**
38
+ * VDS (VAT-deducted-at-source) withheld amount. Mirrors be-prod
39
+ * `posting/withholding-amount.ts#resolveVatWithheldAtSource`.
40
+ */
41
+ resolveVatWithheldAtSource(claimableVat: number, opts: {
42
+ applicable?: boolean | undefined;
43
+ rate?: number | undefined;
44
+ }): number;
45
+ /**
46
+ * Customs clearance stack (CD → SD → VAT → AT → AIT). Mirrors the active
47
+ * tax resolver's `computeImportStack` port.
48
+ *
49
+ * INVARIANT the implementation must satisfy (the import-clearance recipe
50
+ * balances on it): `(landedCost − assessableValue) + recoverableTax +
51
+ * prepaidTax` MUST equal `totalPayable` — i.e. `totalPayable` is what's paid
52
+ * at the customs gate (CD + SD + VAT + AT + AIT), never including the AV
53
+ * (recoverableTax is the raw VAT regardless of claimability). bd-tax's
54
+ * `calculateImportTaxStack.totalCustomsPayment` satisfies this natively.
55
+ */
56
+ computeImportStack(input: {
57
+ assessableValue: number;
58
+ cdRate: number;
59
+ date?: Date | undefined;
60
+ sdRate?: number | undefined;
61
+ atRate?: number | undefined;
62
+ vatRate?: number | undefined;
63
+ aitRate?: number | undefined;
64
+ regime?: BdAccountingRegime | undefined;
65
+ }): BdImportStackResult;
66
+ }
67
+ /** Inventory-type → slot name (mirrors be-prod INVENTORY_ACCOUNTS keys). */
68
+ declare const INVENTORY_SLOT: Record<string, string>;
69
+ declare function inventorySlotFor(inventoryType: string | undefined): string;
70
+ /** `displayRef` parity: human ref when present, else the id. */
71
+ declare function displayRef(referenceNumber: string | undefined, id: string): string;
72
+ //#endregion
73
+ //#region src/posting-recipes/pack.d.ts
74
+ /**
75
+ * Every chart slot any BD recipe references — statically (slot mode) or at
76
+ * evaluation time (route mode). The registry validates the host chart
77
+ * provides ALL of them at boot (fail-loud; anti-silent-default).
78
+ */
79
+ declare const BD_REQUIRED_POSTING_SLOTS: ReadonlyArray<string>;
80
+ declare function buildBdPostingRecipes(resolvers: BdPostingResolvers): ReadonlyArray<PostingRecipe<any>>;
81
+ //#endregion
82
+ //#region src/posting-recipes/purchases.recipes.d.ts
83
+ interface PurchaseRecipeInput {
84
+ purchaseId: string;
85
+ purchaseReferenceNumber?: string | undefined;
86
+ supplierId: string;
87
+ totalAmount: number;
88
+ tax: number;
89
+ vatRateCode?: string | undefined;
90
+ vatRate?: number | undefined;
91
+ regime?: BdAccountingRegime | undefined;
92
+ date: Date;
93
+ inventoryType?: string | undefined;
94
+ isPaid?: boolean | undefined;
95
+ description?: string | undefined;
96
+ currency?: string | undefined;
97
+ exchangeRate?: number | undefined;
98
+ foreignTotal?: number | undefined;
99
+ }
100
+ interface GoodsReceiptRecipeInput {
101
+ orderId: string;
102
+ orderNumber?: string | undefined;
103
+ receiptKey: string | number;
104
+ /** Net-of-VAT goods value in paisa (handler computes from lines or passes explicit). */
105
+ goodsValuePaisa: number;
106
+ date: Date;
107
+ }
108
+ interface VendorBillRecipeInput {
109
+ purchaseId: string;
110
+ supplierId: string;
111
+ totalAmount: number;
112
+ tax?: number | undefined;
113
+ vatRateCode?: string | undefined;
114
+ vatRate?: number | undefined;
115
+ regime?: BdAccountingRegime | undefined;
116
+ receivedAt: Date;
117
+ dueDate?: Date | undefined;
118
+ inventoryType?: string | undefined;
119
+ billNumber?: string | undefined;
120
+ withholdVds?: boolean | undefined;
121
+ vdsRate?: number | undefined;
122
+ /** Document-currency audit snapshot. GL amounts remain base minor units. */
123
+ currency?: string | undefined;
124
+ exchangeRate?: number | undefined;
125
+ foreignTotal?: number | undefined;
126
+ }
127
+ interface VendorPaymentRecipeInput {
128
+ purchaseId: string;
129
+ supplierId: string;
130
+ supplierName?: string | undefined;
131
+ amount: number;
132
+ /** Cash/bank amount at the settlement-date rate; defaults to `amount`. */
133
+ settlementAmount?: number | undefined;
134
+ currency?: string | undefined;
135
+ exchangeRate?: number | undefined;
136
+ foreignAmount?: number | undefined;
137
+ date: Date;
138
+ /** GL code — handler defaults via the 'cash' slot (Cash at Bank). */
139
+ fromAccountCode: string;
140
+ reference?: string | undefined;
141
+ realizedGainAccountCode?: string | undefined;
142
+ realizedLossAccountCode?: string | undefined;
143
+ }
144
+ interface VendorBillReversalRecipeInput extends Omit<VendorBillRecipeInput, 'receivedAt' | 'dueDate'> {
145
+ reason?: string | undefined;
146
+ /** Handler pre-defaults (original: `data.date ?? new Date()` — impure, so the seam owns it). */
147
+ date: Date;
148
+ }
149
+ interface SupplierReturnRecipeInput {
150
+ purchaseId: string;
151
+ supplierId: string;
152
+ moveGroupId: string;
153
+ /** Σ(qty × unitCost) in paisa — handler converts from major-unit lines. */
154
+ totalPaisa: number;
155
+ /** Handler pre-defaults (original: `data.date ?? new Date()`). */
156
+ date: Date;
157
+ reason?: string | undefined;
158
+ inventoryType?: string | undefined;
159
+ }
160
+ interface ImportClearanceRecipeInput {
161
+ clearanceId: string;
162
+ supplierId: string;
163
+ assessableValue: number;
164
+ cdRate: number;
165
+ sdRate?: number | undefined;
166
+ atRate?: number | undefined;
167
+ vatRate?: number | undefined;
168
+ aitRate?: number | undefined;
169
+ regime?: BdAccountingRegime | undefined;
170
+ date: Date;
171
+ inventoryType?: string | undefined;
172
+ billOfEntry?: string | undefined;
173
+ description?: string | undefined;
174
+ }
175
+ declare function buildPurchaseRecipes(resolvers: BdPostingResolvers): {
176
+ purchase: PostingRecipe<PurchaseRecipeInput>;
177
+ goodsReceipt: PostingRecipe<GoodsReceiptRecipeInput>;
178
+ vendorBill: PostingRecipe<VendorBillRecipeInput>;
179
+ vendorPayment: PostingRecipe<VendorPaymentRecipeInput>;
180
+ vendorBillReversal: PostingRecipe<VendorBillReversalRecipeInput>;
181
+ supplierReturn: PostingRecipe<SupplierReturnRecipeInput>;
182
+ importClearance: PostingRecipe<ImportClearanceRecipeInput>;
183
+ };
184
+ //#endregion
185
+ //#region src/posting-recipes/sales-cod.recipes.d.ts
186
+ /** sales.contract.ts:48-61 — method → activeCodes slot name. */
187
+ declare const PAYMENT_METHOD_SLOT: Record<string, string>;
188
+ /** restocking-fee.contract.ts:32-40 adds the 'cod' key (fee retained as cash). */
189
+ declare const RESTOCKING_METHOD_SLOT: Record<string, string>;
190
+ declare function paymentMethodSlot(method: string | undefined): string;
191
+ interface SalesTransactionRecipeInput {
192
+ transactionId: string;
193
+ amount: number;
194
+ tax: number;
195
+ method: string;
196
+ date: Date;
197
+ orderId?: string | undefined;
198
+ orderReferenceNumber?: string | undefined;
199
+ source?: string | undefined;
200
+ branchCode?: string | undefined;
201
+ description?: string | undefined;
202
+ promoDiscount?: number | undefined;
203
+ gatewayTransactionId?: string | undefined;
204
+ gatewayProvider?: string | undefined;
205
+ }
206
+ interface DailyPosSummaryRecipeInput {
207
+ branchId: string;
208
+ branchCode: string;
209
+ date: string;
210
+ byMethod: Array<{
211
+ method: string;
212
+ amount: number;
213
+ }>;
214
+ totalAmount: number;
215
+ totalTax: number;
216
+ transactionCount: number;
217
+ }
218
+ interface RefundRecipeInput {
219
+ transactionId: string;
220
+ refundAmount: number;
221
+ tax: number;
222
+ method: string;
223
+ date: Date;
224
+ orderId?: string | undefined;
225
+ reason?: string | undefined;
226
+ }
227
+ interface CodPlacementRecipeInput {
228
+ transactionId: string;
229
+ orderId: string;
230
+ orderReferenceNumber?: string | undefined;
231
+ customerId?: string | null | undefined;
232
+ amount: number;
233
+ tax: number;
234
+ date: Date;
235
+ branchCode?: string | undefined;
236
+ description?: string | undefined;
237
+ promoDiscount?: number | undefined;
238
+ /** Handler computes: addDays(date, expectedRemittanceDays ?? 14). Pure recipes take the Date. */
239
+ maturityDate: Date;
240
+ }
241
+ interface CodSettlementRecipeInput {
242
+ settlementId: string;
243
+ orderId: string;
244
+ orderReferenceNumber?: string | undefined;
245
+ grossAmount: number;
246
+ actualReceived: number;
247
+ courierCommission: number;
248
+ writeoff: number;
249
+ /** GL code — handler defaults via the 'cash' slot (Cash at Bank). */
250
+ cashAccountCode: string;
251
+ customerId?: string | null | undefined;
252
+ date: Date;
253
+ notes?: string | undefined;
254
+ }
255
+ interface CodCancellationRecipeInput {
256
+ orderId: string;
257
+ orderReferenceNumber?: string | undefined;
258
+ customerId?: string | null | undefined;
259
+ grossAmount: number;
260
+ tax: number;
261
+ promoDiscount?: number | undefined;
262
+ date: Date;
263
+ reason?: string | undefined;
264
+ }
265
+ interface RestockingFeeRecipeInput {
266
+ changeNumber: string;
267
+ orderId: string;
268
+ amount: number;
269
+ paymentMethod?: string | undefined;
270
+ date: Date;
271
+ reason?: string | undefined;
272
+ }
273
+ /**
274
+ * cod-settlement.contract.ts:121-134 — result-style pre-condition (ported
275
+ * verbatim, messages included). Handlers call BEFORE evaluate.
276
+ */
277
+ declare function validateCodSettlementInputs(data: Pick<CodSettlementRecipeInput, 'grossAmount' | 'actualReceived' | 'courierCommission' | 'writeoff'>): {
278
+ ok: true;
279
+ } | {
280
+ ok: false;
281
+ reason: string;
282
+ };
283
+ /** Throw-style form of the same pre-condition (for guard-clause call sites). */
284
+ declare function validateCodSettlement(i: CodSettlementRecipeInput): void;
285
+ declare function buildSalesCodRecipes(): {
286
+ salesTransaction: PostingRecipe<SalesTransactionRecipeInput>;
287
+ dailyPosSummary: PostingRecipe<DailyPosSummaryRecipeInput>;
288
+ refund: PostingRecipe<RefundRecipeInput>;
289
+ codPlacement: PostingRecipe<CodPlacementRecipeInput>;
290
+ codSettlement: PostingRecipe<CodSettlementRecipeInput>;
291
+ codCancellation: PostingRecipe<CodCancellationRecipeInput>;
292
+ restockingFee: PostingRecipe<RestockingFeeRecipeInput>;
293
+ };
294
+ //#endregion
295
+ //#region src/posting-recipes/inventory-transfer.recipes.d.ts
296
+ type StockAdjustmentType = 'loss' | 'gain' | 'correction' | 'opening';
297
+ interface StockAdjustmentRecipeInput {
298
+ adjustmentId: string;
299
+ referenceNumber?: string | undefined;
300
+ type: StockAdjustmentType;
301
+ amount: number;
302
+ date: Date;
303
+ reason?: string | undefined;
304
+ description?: string | undefined;
305
+ sourceModel?: string | undefined;
306
+ sourceId?: string | undefined;
307
+ }
308
+ interface CogsRecipeInput {
309
+ orderId: string;
310
+ orderReferenceNumber?: string | undefined;
311
+ costAmount: number;
312
+ date: Date;
313
+ metadata?: Record<string, unknown> | undefined;
314
+ }
315
+ interface CogsReversalRecipeInput {
316
+ returnId: string;
317
+ orderId: string;
318
+ returnReferenceNumber?: string | undefined;
319
+ costAmount: number;
320
+ date: Date;
321
+ description?: string | undefined;
322
+ metadata?: Record<string, unknown> | undefined;
323
+ }
324
+ interface TransferDispatchRecipeInput {
325
+ transferId: string;
326
+ documentNumber: string;
327
+ goodsCost: number;
328
+ date: Date;
329
+ /** Sender branch — stamped as the JE's organizationId. */
330
+ organizationId: string;
331
+ metadata?: Record<string, unknown> | undefined;
332
+ reason?: string | undefined;
333
+ }
334
+ interface TransferReceiveRecipeInput {
335
+ transferId: string;
336
+ documentNumber: string;
337
+ goodsCost: number;
338
+ transitCost?: number | undefined;
339
+ date: Date;
340
+ /** Receiver branch — stamped as the JE's organizationId. */
341
+ organizationId: string;
342
+ metadata?: Record<string, unknown> | undefined;
343
+ reason?: string | undefined;
344
+ }
345
+ interface LandedCostAllocationInput {
346
+ skuRef: string;
347
+ costLineCode: string;
348
+ /** Paisa — the handler converts from Flow's major-unit allocations. */
349
+ allocatedPaisa: number;
350
+ }
351
+ interface LandedCostRecipeInput {
352
+ landedCostId: string;
353
+ allocations: ReadonlyArray<LandedCostAllocationInput>;
354
+ date: Date;
355
+ reference?: string | undefined;
356
+ }
357
+ declare function landedCostClearingSlot(costLineCode: string): string;
358
+ declare function buildInventoryTransferRecipes(): {
359
+ stockAdjustment: PostingRecipe<StockAdjustmentRecipeInput>;
360
+ cogs: PostingRecipe<CogsRecipeInput>;
361
+ cogsReversal: PostingRecipe<CogsReversalRecipeInput>;
362
+ transferDispatch: PostingRecipe<TransferDispatchRecipeInput>;
363
+ transferReceive: PostingRecipe<TransferReceiveRecipeInput>;
364
+ transferDispatchReversal: PostingRecipe<TransferDispatchRecipeInput>;
365
+ transferReceiveReversal: PostingRecipe<TransferReceiveRecipeInput>;
366
+ landedCost: PostingRecipe<LandedCostRecipeInput>;
367
+ landedCostReversal: PostingRecipe<LandedCostRecipeInput>;
368
+ };
369
+ //#endregion
370
+ //#region src/posting-recipes/misc.recipes.d.ts
371
+ interface SettlementRecipeInput {
372
+ importId: string;
373
+ provider: string;
374
+ externalRef: string;
375
+ /** GL codes — the original passes explicit codes (host clearing config). */
376
+ clearingAccountCode: string;
377
+ bankAccountCode: string;
378
+ feeAccountCode: string;
379
+ writeoffAccountCode: string;
380
+ totalGross: number;
381
+ totalFee: number;
382
+ totalWriteoff: number;
383
+ totalNet: number;
384
+ date: Date;
385
+ notes?: string | undefined;
386
+ }
387
+ interface CustomerInvoiceRecipeInput {
388
+ orderId: string;
389
+ customerId: string;
390
+ totalAmount: number;
391
+ issuedAt: Date;
392
+ /** Handler computes dueDate ?? addDays(issuedAt, creditDays ?? 0). */
393
+ dueDate: Date;
394
+ invoiceNumber?: string | undefined;
395
+ vatAmount?: number | undefined;
396
+ withholdVds?: boolean | undefined;
397
+ vdsRate?: number | undefined;
398
+ }
399
+ interface CustomerReceiptRecipeInput {
400
+ orderId: string;
401
+ customerId: string;
402
+ customerName?: string | undefined;
403
+ amount: number;
404
+ date: Date;
405
+ /** GL code — handler defaults via the 'cash' slot. */
406
+ toAccountCode: string;
407
+ reference?: string | undefined;
408
+ }
409
+ interface VendorCreditNoteRecipeInput {
410
+ sourceId: string;
411
+ supplierId: string;
412
+ amount: number;
413
+ reason: string;
414
+ reference: string;
415
+ date: Date;
416
+ /** GL code override — handler defaults via the 'purchaseReturns' slot. */
417
+ contraAccountCode: string;
418
+ currency?: string | undefined;
419
+ exchangeRate?: number | undefined;
420
+ foreignAmount?: number | undefined;
421
+ }
422
+ interface CustomerDebitNoteRecipeInput {
423
+ sourceId: string;
424
+ customerId: string;
425
+ amount: number;
426
+ reason: string;
427
+ reference: string;
428
+ date: Date;
429
+ /** GL code override — handler defaults via the 'salesReturns' slot. */
430
+ contraAccountCode: string;
431
+ }
432
+ interface OpeningBalanceRecipeInput {
433
+ side: 'supplier' | 'customer';
434
+ partnerId: string;
435
+ partnerName?: string | undefined;
436
+ amount: number;
437
+ asOf: Date;
438
+ reason?: string | undefined;
439
+ }
440
+ /** settlement.contract.ts balance invariant. */
441
+ declare function validateSettlement(i: SettlementRecipeInput): void;
442
+ /** credit-debit-note.contract.ts fintech rules (positive int, reason, reference). */
443
+ declare function validateNoteInput(i: {
444
+ amount: number;
445
+ reason: string;
446
+ reference: string;
447
+ }): void;
448
+ declare function buildMiscRecipes(resolvers: BdPostingResolvers): {
449
+ settlement: PostingRecipe<SettlementRecipeInput>;
450
+ customerInvoice: PostingRecipe<CustomerInvoiceRecipeInput>;
451
+ customerReceipt: PostingRecipe<CustomerReceiptRecipeInput>;
452
+ vendorCreditNote: PostingRecipe<VendorCreditNoteRecipeInput>;
453
+ customerDebitNote: PostingRecipe<CustomerDebitNoteRecipeInput>;
454
+ openingBalance: PostingRecipe<OpeningBalanceRecipeInput>;
455
+ };
456
+ //#endregion
457
+ export { BD_REQUIRED_POSTING_SLOTS, type BdAccountingRegime, type BdImportStackResult, type BdPostingResolvers, type CodCancellationRecipeInput, type CodPlacementRecipeInput, type CodSettlementRecipeInput, type CogsRecipeInput, type CogsReversalRecipeInput, type CustomerDebitNoteRecipeInput, type CustomerInvoiceRecipeInput, type CustomerReceiptRecipeInput, type DailyPosSummaryRecipeInput, type GoodsReceiptRecipeInput, INVENTORY_SLOT, type ImportClearanceRecipeInput, type LandedCostAllocationInput, type LandedCostRecipeInput, type OpeningBalanceRecipeInput, PAYMENT_METHOD_SLOT, type PurchaseRecipeInput, RESTOCKING_METHOD_SLOT, type RefundRecipeInput, type RestockingFeeRecipeInput, type SalesTransactionRecipeInput, type SettlementRecipeInput, type StockAdjustmentRecipeInput, type StockAdjustmentType, type SupplierReturnRecipeInput, type TransferDispatchRecipeInput, type TransferReceiveRecipeInput, type VendorBillRecipeInput, type VendorBillReversalRecipeInput, type VendorCreditNoteRecipeInput, type VendorPaymentRecipeInput, buildBdPostingRecipes, buildInventoryTransferRecipes, buildMiscRecipes, buildPurchaseRecipes, buildSalesCodRecipes, displayRef, inventorySlotFor, landedCostClearingSlot, paymentMethodSlot, validateCodSettlement, validateCodSettlementInputs, validateNoteInput, validateSettlement };