@fayz-ai/financial 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +34 -2
- package/dist/chunk-2XWEZXFT.js +2805 -0
- package/dist/chunk-2XWEZXFT.js.map +1 -0
- package/dist/chunk-4FWWKKZB.js +49 -0
- package/dist/chunk-4FWWKKZB.js.map +1 -0
- package/dist/chunk-4GXBOEI3.js +50 -0
- package/dist/chunk-4GXBOEI3.js.map +1 -0
- package/dist/chunk-4S5GK776.js +31 -0
- package/dist/chunk-4S5GK776.js.map +1 -0
- package/dist/chunk-56NQHYL7.js +20 -0
- package/dist/chunk-56NQHYL7.js.map +1 -0
- package/dist/chunk-7HISDBUG.js +84 -0
- package/dist/chunk-7HISDBUG.js.map +1 -0
- package/dist/chunk-FNXIAIUZ.js +81 -0
- package/dist/chunk-FNXIAIUZ.js.map +1 -0
- package/dist/chunk-HJPUHTI2.js +77 -0
- package/dist/chunk-HJPUHTI2.js.map +1 -0
- package/dist/chunk-IP5YH4S5.js +14 -0
- package/dist/chunk-IP5YH4S5.js.map +1 -0
- package/dist/chunk-J7BWATQU.js +10 -0
- package/dist/chunk-J7BWATQU.js.map +1 -0
- package/dist/chunk-MCSPCXQK.js +190 -0
- package/dist/chunk-MCSPCXQK.js.map +1 -0
- package/dist/chunk-T7WIM2V7.js +34 -0
- package/dist/chunk-T7WIM2V7.js.map +1 -0
- package/dist/chunk-TZZNVPBY.js +9 -0
- package/dist/chunk-TZZNVPBY.js.map +1 -0
- package/dist/chunk-UERJA7QH.js +893 -0
- package/dist/chunk-UERJA7QH.js.map +1 -0
- package/dist/chunk-XVELDCSL.js +11 -0
- package/dist/chunk-XVELDCSL.js.map +1 -0
- package/dist/chunk-YUNLQYOZ.js +26 -0
- package/dist/chunk-YUNLQYOZ.js.map +1 -0
- package/dist/chunk-ZFUQBYBG.js +124 -0
- package/dist/chunk-ZFUQBYBG.js.map +1 -0
- package/dist/data/fx-auto.d.ts +7 -0
- package/dist/data/fx-auto.d.ts.map +1 -0
- package/dist/data/fx-auto.js +3 -0
- package/dist/data/fx-auto.js.map +1 -0
- package/dist/data/idempotency.d.ts +2 -0
- package/dist/data/idempotency.d.ts.map +1 -0
- package/dist/data/idempotency.js +3 -0
- package/dist/data/idempotency.js.map +1 -0
- package/dist/data/mock.d.ts +29 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/mock.js +13 -0
- package/dist/data/mock.js.map +1 -0
- package/dist/data/provider.d.ts +265 -0
- package/dist/data/provider.d.ts.map +1 -0
- package/dist/data/provider.js +3 -0
- package/dist/data/provider.js.map +1 -0
- package/dist/data/reconcile.d.ts +37 -0
- package/dist/data/reconcile.d.ts.map +1 -0
- package/dist/data/reconcile.js +5 -0
- package/dist/data/reconcile.js.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/supabase.js +16 -0
- package/dist/data/supabase.js.map +1 -0
- package/dist/data/tables.d.ts +39 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/data/tables.js +3 -0
- package/dist/data/tables.js.map +1 -0
- package/dist/domain/account.d.ts +66 -0
- package/dist/domain/account.d.ts.map +1 -0
- package/dist/domain/account.js +3 -0
- package/dist/domain/account.js.map +1 -0
- package/dist/domain/card.d.ts +33 -0
- package/dist/domain/card.d.ts.map +1 -0
- package/dist/domain/card.js +3 -0
- package/dist/domain/card.js.map +1 -0
- package/dist/domain/cash-session.d.ts +80 -0
- package/dist/domain/cash-session.d.ts.map +1 -0
- package/dist/domain/cash-session.js +4 -0
- package/dist/domain/cash-session.js.map +1 -0
- package/dist/domain/chart-of-accounts.d.ts +57 -0
- package/dist/domain/chart-of-accounts.d.ts.map +1 -0
- package/dist/domain/chart-of-accounts.js +114 -0
- package/dist/domain/chart-of-accounts.js.map +1 -0
- package/dist/domain/client-account.d.ts +28 -0
- package/dist/domain/client-account.d.ts.map +1 -0
- package/dist/domain/client-account.js +3 -0
- package/dist/domain/client-account.js.map +1 -0
- package/dist/domain/cnab240.d.ts +182 -0
- package/dist/domain/cnab240.d.ts.map +1 -0
- package/dist/domain/cnab240.js +265 -0
- package/dist/domain/cnab240.js.map +1 -0
- package/dist/domain/commission.d.ts +142 -0
- package/dist/domain/commission.d.ts.map +1 -0
- package/dist/domain/commission.js +3 -0
- package/dist/domain/commission.js.map +1 -0
- package/dist/domain/counterparty.d.ts +13 -0
- package/dist/domain/counterparty.d.ts.map +1 -0
- package/dist/domain/counterparty.js +48 -0
- package/dist/domain/counterparty.js.map +1 -0
- package/dist/domain/gift-card.d.ts +35 -0
- package/dist/domain/gift-card.d.ts.map +1 -0
- package/dist/domain/gift-card.js +3 -0
- package/dist/domain/gift-card.js.map +1 -0
- package/dist/domain/goal.d.ts +26 -0
- package/dist/domain/goal.d.ts.map +1 -0
- package/dist/domain/goal.js +3 -0
- package/dist/domain/goal.js.map +1 -0
- package/dist/domain/invoice.d.ts +228 -0
- package/dist/domain/invoice.d.ts.map +1 -0
- package/dist/domain/invoice.js +4 -0
- package/dist/domain/invoice.js.map +1 -0
- package/dist/domain/money.d.ts +5 -0
- package/dist/domain/money.d.ts.map +1 -0
- package/dist/domain/money.js +3 -0
- package/dist/domain/money.js.map +1 -0
- package/dist/domain/movement.d.ts +114 -0
- package/dist/domain/movement.d.ts.map +1 -0
- package/dist/domain/movement.js +4 -0
- package/dist/domain/movement.js.map +1 -0
- package/dist/domain/nota-fiscal.d.ts +47 -0
- package/dist/domain/nota-fiscal.d.ts.map +1 -0
- package/dist/domain/nota-fiscal.js +3 -0
- package/dist/domain/nota-fiscal.js.map +1 -0
- package/dist/domain/payment-method.d.ts +54 -0
- package/dist/domain/payment-method.d.ts.map +1 -0
- package/dist/domain/payment-method.js +25 -0
- package/dist/domain/payment-method.js.map +1 -0
- package/dist/domain/query.d.ts +9 -0
- package/dist/domain/query.d.ts.map +1 -0
- package/dist/domain/query.js +3 -0
- package/dist/domain/query.js.map +1 -0
- package/dist/domain/reconciliation.d.ts +277 -0
- package/dist/domain/reconciliation.d.ts.map +1 -0
- package/dist/domain/reconciliation.js +4 -0
- package/dist/domain/reconciliation.js.map +1 -0
- package/dist/domain/recurring-bill.d.ts +42 -0
- package/dist/domain/recurring-bill.d.ts.map +1 -0
- package/dist/domain/recurring-bill.js +3 -0
- package/dist/domain/recurring-bill.js.map +1 -0
- package/dist/domain/report.d.ts +92 -0
- package/dist/domain/report.d.ts.map +1 -0
- package/dist/domain/report.js +6 -0
- package/dist/domain/report.js.map +1 -0
- package/dist/domain/statement-match.d.ts +36 -0
- package/dist/domain/statement-match.d.ts.map +1 -0
- package/dist/domain/statement-match.js +3 -0
- package/dist/domain/statement-match.js.map +1 -0
- package/dist/domain/statement.d.ts +75 -0
- package/dist/domain/statement.d.ts.map +1 -0
- package/dist/domain/statement.js +4 -0
- package/dist/domain/statement.js.map +1 -0
- package/dist/domain/transfer.d.ts +29 -0
- package/dist/domain/transfer.d.ts.map +1 -0
- package/dist/domain/transfer.js +3 -0
- package/dist/domain/transfer.js.map +1 -0
- package/dist/store.d.ts +66 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +287 -0
- package/dist/store.js.map +1 -0
- package/dist/views/zones.d.ts +40 -0
- package/dist/views/zones.d.ts.map +1 -0
- package/dist/views/zones.js +16 -0
- package/dist/views/zones.js.map +1 -0
- package/package.json +49 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/data/tables.ts
|
|
2
|
+
var T = {
|
|
3
|
+
movements: "plg_financial_movements",
|
|
4
|
+
// Statement evidence, which is not a ledger movement (financial 112).
|
|
5
|
+
statementLines: "plg_financial_bank_statement_lines",
|
|
6
|
+
// The accounts that have a statement feed (financial 113).
|
|
7
|
+
statementAccounts: "v_financial_statement_accounts",
|
|
8
|
+
statementSources: "plg_financial_account_statement_sources",
|
|
9
|
+
allocations: "plg_financial_allocations",
|
|
10
|
+
cashMovements: "v_financial_cash_movements",
|
|
11
|
+
accountBalances: "v_financial_account_balances",
|
|
12
|
+
chartOfAccounts: "plg_financial_chart_of_accounts",
|
|
13
|
+
bankAccounts: "plg_financial_bank_accounts",
|
|
14
|
+
cardBrands: "plg_financial_card_brands",
|
|
15
|
+
costCenters: "plg_financial_cost_centers",
|
|
16
|
+
cashRegisterSessions: "plg_financial_cash_register_sessions",
|
|
17
|
+
paymentMethods: "plg_financial_payment_methods",
|
|
18
|
+
paymentMethodTypes: "plg_financial_payment_method_types",
|
|
19
|
+
notas: "plg_financial_nfe",
|
|
20
|
+
// What only finance knows about a `public.items` line: chart account, cost centre, tax (financial 038).
|
|
21
|
+
invoiceItems: "plg_financial_invoice_items",
|
|
22
|
+
commissionRules: "plg_financial_commission_rules",
|
|
23
|
+
commissionRuleCriteria: "plg_financial_commission_rule_criteria",
|
|
24
|
+
commissionFunctions: "plg_financial_commission_functions",
|
|
25
|
+
// The installment is the obligation; this view gives it the legacy bill shape (financial 040, 044).
|
|
26
|
+
bills: "v_financial_installment_balances",
|
|
27
|
+
invoiceBalances: "v_financial_invoice_balances",
|
|
28
|
+
commissions: "v_financial_commissions",
|
|
29
|
+
commissionGenerations: "plg_financial_commission_generations",
|
|
30
|
+
periods: "plg_financial_periods",
|
|
31
|
+
trialBalance: "v_financial_trial_balance",
|
|
32
|
+
aging: "v_financial_aging",
|
|
33
|
+
agingSummary: "v_financial_aging_summary",
|
|
34
|
+
lineMargin: "v_financial_line_margin",
|
|
35
|
+
marginByMonth: "v_financial_margin_by_month",
|
|
36
|
+
recurringBills: "plg_financial_recurring_bills",
|
|
37
|
+
recurringOccurrences: "plg_financial_recurring_bill_occurrences",
|
|
38
|
+
discountsGiven: "v_financial_discounts_given",
|
|
39
|
+
cnabBatches: "plg_financial_cnab_batches",
|
|
40
|
+
cnabItems: "plg_financial_cnab_items",
|
|
41
|
+
// Where money goes out to: the payee's own account, never a ledger account (financial 149).
|
|
42
|
+
payeeAccounts: "payee_accounts",
|
|
43
|
+
// The unit's paying company, whose CNPJ heads a CNAB file.
|
|
44
|
+
unitCompanies: "v_unit_company_legal_entities"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { T };
|
|
48
|
+
//# sourceMappingURL=chunk-4FWWKKZB.js.map
|
|
49
|
+
//# sourceMappingURL=chunk-4FWWKKZB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/tables.ts"],"names":[],"mappings":";AACO,IAAM,CAAA,GAAI;AAAA,EACf,SAAA,EAAW,yBAAA;AAAA;AAAA,EAEX,cAAA,EAAgB,oCAAA;AAAA;AAAA,EAEhB,iBAAA,EAAmB,gCAAA;AAAA,EACnB,gBAAA,EAAkB,yCAAA;AAAA,EAClB,WAAA,EAAa,2BAAA;AAAA,EACb,aAAA,EAAe,4BAAA;AAAA,EACf,eAAA,EAAiB,8BAAA;AAAA,EACjB,eAAA,EAAiB,iCAAA;AAAA,EACjB,YAAA,EAAc,6BAAA;AAAA,EACd,UAAA,EAAY,2BAAA;AAAA,EACZ,WAAA,EAAa,4BAAA;AAAA,EACb,oBAAA,EAAsB,sCAAA;AAAA,EACtB,cAAA,EAAgB,+BAAA;AAAA,EAChB,kBAAA,EAAoB,oCAAA;AAAA,EACpB,KAAA,EAAO,mBAAA;AAAA;AAAA,EAEP,YAAA,EAAc,6BAAA;AAAA,EACd,eAAA,EAAiB,gCAAA;AAAA,EACjB,sBAAA,EAAwB,wCAAA;AAAA,EACxB,mBAAA,EAAqB,oCAAA;AAAA;AAAA,EAErB,KAAA,EAAO,kCAAA;AAAA,EACP,eAAA,EAAiB,8BAAA;AAAA,EACjB,WAAA,EAAa,yBAAA;AAAA,EACb,qBAAA,EAAuB,sCAAA;AAAA,EACvB,OAAA,EAAS,uBAAA;AAAA,EACT,YAAA,EAAc,2BAAA;AAAA,EACd,KAAA,EAAO,mBAAA;AAAA,EACP,YAAA,EAAc,2BAAA;AAAA,EACd,UAAA,EAAY,yBAAA;AAAA,EACZ,aAAA,EAAe,6BAAA;AAAA,EACf,cAAA,EAAgB,+BAAA;AAAA,EAChB,oBAAA,EAAsB,0CAAA;AAAA,EACtB,cAAA,EAAgB,6BAAA;AAAA,EAChB,WAAA,EAAa,4BAAA;AAAA,EACb,SAAA,EAAW,0BAAA;AAAA;AAAA,EAEX,aAAA,EAAe,gBAAA;AAAA;AAAA,EAEf,aAAA,EAAe;AACjB","file":"chunk-4FWWKKZB.js","sourcesContent":["// The physical names the financial adapters read and write, in one place.\nexport const T = {\n movements: 'plg_financial_movements',\n // Statement evidence, which is not a ledger movement (financial 112).\n statementLines: 'plg_financial_bank_statement_lines',\n // The accounts that have a statement feed (financial 113).\n statementAccounts: 'v_financial_statement_accounts',\n statementSources: 'plg_financial_account_statement_sources',\n allocations: 'plg_financial_allocations',\n cashMovements: 'v_financial_cash_movements',\n accountBalances: 'v_financial_account_balances',\n chartOfAccounts: 'plg_financial_chart_of_accounts',\n bankAccounts: 'plg_financial_bank_accounts',\n cardBrands: 'plg_financial_card_brands',\n costCenters: 'plg_financial_cost_centers',\n cashRegisterSessions: 'plg_financial_cash_register_sessions',\n paymentMethods: 'plg_financial_payment_methods',\n paymentMethodTypes: 'plg_financial_payment_method_types',\n notas: 'plg_financial_nfe',\n // What only finance knows about a `public.items` line: chart account, cost centre, tax (financial 038).\n invoiceItems: 'plg_financial_invoice_items',\n commissionRules: 'plg_financial_commission_rules',\n commissionRuleCriteria: 'plg_financial_commission_rule_criteria',\n commissionFunctions: 'plg_financial_commission_functions',\n // The installment is the obligation; this view gives it the legacy bill shape (financial 040, 044).\n bills: 'v_financial_installment_balances',\n invoiceBalances: 'v_financial_invoice_balances',\n commissions: 'v_financial_commissions',\n commissionGenerations: 'plg_financial_commission_generations',\n periods: 'plg_financial_periods',\n trialBalance: 'v_financial_trial_balance',\n aging: 'v_financial_aging',\n agingSummary: 'v_financial_aging_summary',\n lineMargin: 'v_financial_line_margin',\n marginByMonth: 'v_financial_margin_by_month',\n recurringBills: 'plg_financial_recurring_bills',\n recurringOccurrences: 'plg_financial_recurring_bill_occurrences',\n discountsGiven: 'v_financial_discounts_given',\n cnabBatches: 'plg_financial_cnab_batches',\n cnabItems: 'plg_financial_cnab_items',\n // Where money goes out to: the payee's own account, never a ledger account (financial 149).\n payeeAccounts: 'payee_accounts',\n // The unit's paying company, whose CNPJ heads a CNAB file.\n unitCompanies: 'v_unit_company_legal_entities',\n} as const\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { round2 } from './chunk-XVELDCSL.js';
|
|
2
|
+
|
|
3
|
+
// src/domain/movement.ts
|
|
4
|
+
function toLedgerDirection(direction) {
|
|
5
|
+
return direction === "debit" ? "payable" : "receivable";
|
|
6
|
+
}
|
|
7
|
+
function fromLedgerDirection(direction) {
|
|
8
|
+
return direction === "payable" ? "debit" : "credit";
|
|
9
|
+
}
|
|
10
|
+
function openAmount(movement) {
|
|
11
|
+
return round2((Number(movement.amount) || 0) - (Number(movement.paidAmount) || 0));
|
|
12
|
+
}
|
|
13
|
+
function paymentFee(amount, feePercent, direction) {
|
|
14
|
+
if (direction !== "credit" && direction !== "receivable") return 0;
|
|
15
|
+
return round2(amount * ((Number(feePercent) || 0) / 100));
|
|
16
|
+
}
|
|
17
|
+
function ledgerMethod(transactionType) {
|
|
18
|
+
if (!transactionType) return null;
|
|
19
|
+
if (transactionType === "bank_transfer") return "transfer";
|
|
20
|
+
if (transactionType === "voucher") return "other";
|
|
21
|
+
return transactionType;
|
|
22
|
+
}
|
|
23
|
+
function groupPaymentsByBill(bills, payments) {
|
|
24
|
+
const byBillId = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const bill of bills) byBillId.set(bill.id, []);
|
|
26
|
+
const unattributed = [];
|
|
27
|
+
for (const payment of payments) {
|
|
28
|
+
if (payment.settlesMovementId) {
|
|
29
|
+
const bucket = byBillId.get(payment.settlesMovementId);
|
|
30
|
+
if (bucket) bucket.push(payment);
|
|
31
|
+
else unattributed.push(payment);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const derived = bills.find(
|
|
35
|
+
(bill) => (bill.installmentNumber ?? null) === (payment.installmentNumber ?? null)
|
|
36
|
+
);
|
|
37
|
+
if (derived) byBillId.get(derived.id).push(payment);
|
|
38
|
+
else unattributed.push(payment);
|
|
39
|
+
}
|
|
40
|
+
return { byBillId, unattributed };
|
|
41
|
+
}
|
|
42
|
+
var reversalOf = (m) => m.metadata?.reversal_of;
|
|
43
|
+
function reversiblePayments(payments) {
|
|
44
|
+
const reversed = new Set(payments.map(reversalOf).filter(Boolean));
|
|
45
|
+
return payments.filter((m) => !reversalOf(m) && !reversed.has(m.id));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { fromLedgerDirection, groupPaymentsByBill, ledgerMethod, openAmount, paymentFee, reversiblePayments, toLedgerDirection };
|
|
49
|
+
//# sourceMappingURL=chunk-4GXBOEI3.js.map
|
|
50
|
+
//# sourceMappingURL=chunk-4GXBOEI3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/movement.ts"],"names":[],"mappings":";;;AA8FO,SAAS,kBAAkB,SAAA,EAAkD;AAClF,EAAA,OAAO,SAAA,KAAc,UAAU,SAAA,GAAY,YAAA;AAC7C;AAEO,SAAS,oBAAoB,SAAA,EAA4D;AAC9F,EAAA,OAAO,SAAA,KAAc,YAAY,OAAA,GAAU,QAAA;AAC7C;AAGO,SAAS,WAAW,QAAA,EAAoE;AAC7F,EAAA,OAAO,MAAA,CAAA,CAAQ,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,IAAK,MAAM,MAAA,CAAO,QAAA,CAAS,UAAU,CAAA,IAAK,CAAA,CAAE,CAAA;AACnF;AAGO,SAAS,UAAA,CAAW,MAAA,EAAgB,UAAA,EAAoB,SAAA,EAA2D;AACxH,EAAA,IAAI,SAAA,KAAc,QAAA,IAAY,SAAA,KAAc,YAAA,EAAc,OAAO,CAAA;AACjE,EAAA,OAAO,OAAO,MAAA,IAAA,CAAW,MAAA,CAAO,UAAU,CAAA,IAAK,KAAK,GAAA,CAAI,CAAA;AAC1D;AAMO,SAAS,aAAa,eAAA,EAA2D;AACtF,EAAA,IAAI,CAAC,iBAAiB,OAAO,IAAA;AAC7B,EAAA,IAAI,eAAA,KAAoB,iBAAiB,OAAO,UAAA;AAChD,EAAA,IAAI,eAAA,KAAoB,WAAW,OAAO,OAAA;AAC1C,EAAA,OAAO,eAAA;AACT;AAcO,SAAS,mBAAA,CACd,OACA,QAAA,EACoB;AACpB,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAiC;AACtD,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO,QAAA,CAAS,IAAI,IAAA,CAAK,EAAA,EAAI,EAAE,CAAA;AAClD,EAAA,MAAM,eAAoC,EAAC;AAE3C,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,QAAQ,iBAAA,EAAmB;AAC7B,MAAA,MAAM,MAAA,GAAS,QAAA,CAAS,GAAA,CAAI,OAAA,CAAQ,iBAAiB,CAAA;AACrD,MAAA,IAAI,MAAA,EAAQ,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAAA,WAC1B,YAAA,CAAa,KAAK,OAAO,CAAA;AAC9B,MAAA;AAAA,IACF;AACA,IAAA,MAAM,UAAU,KAAA,CAAM,IAAA;AAAA,MACpB,CAAC,IAAA,KAAA,CAAU,IAAA,CAAK,iBAAA,IAAqB,IAAA,OAAW,QAAQ,iBAAA,IAAqB,IAAA;AAAA,KAC/E;AACA,IAAA,IAAI,SAAS,QAAA,CAAS,GAAA,CAAI,QAAQ,EAAE,CAAA,CAAG,KAAK,OAAO,CAAA;AAAA,SAC9C,YAAA,CAAa,KAAK,OAAO,CAAA;AAAA,EAChC;AAEA,EAAA,OAAO,EAAE,UAAU,YAAA,EAAa;AAClC;AAEA,IAAM,UAAA,GAAa,CAAC,CAAA,KACjB,CAAA,CAAE,QAAA,EAAkD,WAAA;AAOhD,SAAS,mBAAmB,QAAA,EAAoD;AACrF,EAAA,MAAM,QAAA,GAAW,IAAI,GAAA,CAAI,QAAA,CAAS,IAAI,UAAU,CAAA,CAAE,MAAA,CAAO,OAAO,CAAa,CAAA;AAC7E,EAAA,OAAO,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,UAAA,CAAW,CAAC,CAAA,IAAK,CAAC,QAAA,CAAS,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,CAAA;AACrE","file":"chunk-4GXBOEI3.js","sourcesContent":["import { round2 } from './money'\nimport type { DateRange } from './query'\n\n/** `debit` is money going out (payable), `credit` money coming in (receivable). */\nexport type TransactionDirection = 'debit' | 'credit'\n\n/** How the database names a direction: `payable` and `receivable`. */\nexport type LedgerDirection = 'payable' | 'receivable'\n\nexport type MovementKind = 'bill' | 'payment' | 'transfer'\n\nexport type MovementStatus = 'pending' | 'paid' | 'partial' | 'overdue' | 'cancelled'\n\nexport interface FinancialMovement {\n id: string\n invoiceId?: string\n direction: TransactionDirection\n movementKind: MovementKind\n amount: number\n paidAmount: number\n status: MovementStatus\n /** For a payment: the bill it settles. */\n settlesMovementId?: string\n dueDate: string\n paymentDate?: string\n installmentNumber?: number\n paymentMethodId?: string\n paymentMethodTypeId?: string\n /** How the money moved (`pix`, `cash`, `credit_card`…), the code the payment RPCs write. */\n method?: string\n bankAccountId?: string\n cashSessionId?: string\n cardBrand?: string\n cardInstallments?: number\n /** Card fee withheld at settlement; a credit's net cash is `paidAmount - feeAmount`. */\n feeAmount?: number\n debitAccountId?: string\n creditAccountId?: string\n notes?: string\n metadata?: Record<string, unknown>\n /** Provider of an imported bank line. */\n externalSource?: string\n /** Provider-unique id of an imported bank line. */\n externalId?: string\n /** When the movement was reconciled; absent while pending. */\n reconciledAt?: string\n /** For an imported bank line: the internal movement it was matched to. */\n matchedMovementId?: string\n tenantId: string\n createdAt: string\n updatedAt: string\n}\n\nexport interface CreateMovementInput {\n direction: TransactionDirection\n movementKind: MovementKind\n amount: number\n dueDate: string\n installmentNumber?: number\n paymentMethodId?: string\n bankAccountId?: string\n notes?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface PayMovementInput {\n movementId: string\n amount: number\n paymentDate: string\n paymentMethodId?: string\n paymentMethodTypeId?: string\n bankAccountId?: string\n cashSessionId?: string\n cardBrand?: string\n cardInstallments?: number\n notes?: string\n /** What travels with this payment, e.g. `receiptUrl`. */\n metadata?: Record<string, unknown>\n /** Stable for every retry of the same payment attempt. */\n idempotencyKey?: string\n}\n\nexport interface MovementQuery {\n direction?: TransactionDirection\n status?: MovementStatus | MovementStatus[]\n bankAccountId?: string\n cashSessionId?: string\n /** Scope to one invoice's movements (bills and payments). */\n invoiceId?: string\n dateRange?: DateRange\n page?: number\n pageSize?: number\n}\n\nexport function toLedgerDirection(direction: TransactionDirection): LedgerDirection {\n return direction === 'debit' ? 'payable' : 'receivable'\n}\n\nexport function fromLedgerDirection(direction: string | null | undefined): TransactionDirection {\n return direction === 'payable' ? 'debit' : 'credit'\n}\n\n/** What is still owed on a bill. */\nexport function openAmount(movement: Pick<FinancialMovement, 'amount' | 'paidAmount'>): number {\n return round2((Number(movement.amount) || 0) - (Number(movement.paidAmount) || 0))\n}\n\n/** The card fee withheld from a payment. Only money coming in pays one. */\nexport function paymentFee(amount: number, feePercent: number, direction: TransactionDirection | LedgerDirection): number {\n if (direction !== 'credit' && direction !== 'receivable') return 0\n return round2(amount * ((Number(feePercent) || 0) / 100))\n}\n\n/**\n * The payment RPCs know `transfer` and `other`; the method catalogue says `bank_transfer` and\n * `voucher`. Translating here keeps a third vocabulary of money from appearing.\n */\nexport function ledgerMethod(transactionType: string | null | undefined): string | null {\n if (!transactionType) return null\n if (transactionType === 'bank_transfer') return 'transfer'\n if (transactionType === 'voucher') return 'other'\n return transactionType\n}\n\nexport interface SettlementGrouping {\n /** bill id → the cash events that settled it, oldest first. */\n byBillId: Map<string, FinancialMovement[]>\n /** Payments that name no bill we hold — surfaced, never silently dropped. */\n unattributed: FinancialMovement[]\n}\n\n/**\n * Which payments settled which bill. `settlesMovementId` wins whenever it is present; the\n * installment-number pairing only runs for payments that carry no link, because it\n * mis-attributes money when two payments share an installment number.\n */\nexport function groupPaymentsByBill(\n bills: FinancialMovement[],\n payments: FinancialMovement[],\n): SettlementGrouping {\n const byBillId = new Map<string, FinancialMovement[]>()\n for (const bill of bills) byBillId.set(bill.id, [])\n const unattributed: FinancialMovement[] = []\n\n for (const payment of payments) {\n if (payment.settlesMovementId) {\n const bucket = byBillId.get(payment.settlesMovementId)\n if (bucket) bucket.push(payment)\n else unattributed.push(payment)\n continue\n }\n const derived = bills.find(\n (bill) => (bill.installmentNumber ?? null) === (payment.installmentNumber ?? null),\n )\n if (derived) byBillId.get(derived.id)!.push(payment)\n else unattributed.push(payment)\n }\n\n return { byBillId, unattributed }\n}\n\nconst reversalOf = (m: FinancialMovement) =>\n (m.metadata as Record<string, unknown> | undefined)?.reversal_of as string | undefined\n\n/**\n * The payments an \"Estornar\" may still target: not a reversal itself and not\n * already reversed. Picking \"the last payment\" instead hit the previous\n * reversal, so each click reversed the reversal and the live payment stayed.\n */\nexport function reversiblePayments(payments: FinancialMovement[]): FinancialMovement[] {\n const reversed = new Set(payments.map(reversalOf).filter(Boolean) as string[])\n return payments.filter((m) => !reversalOf(m) && !reversed.has(m.id))\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/data/fx-auto.ts
|
|
2
|
+
var ENDPOINT = "https://api.frankfurter.dev/v1/latest";
|
|
3
|
+
var SOURCE = "BCE \xB7 refer\xEAncia do dia (autom\xE1tico)";
|
|
4
|
+
var attempted = /* @__PURE__ */ new Set();
|
|
5
|
+
async function ensureReferenceRates(pub, tenantId, house, currencies) {
|
|
6
|
+
const wanted = [...new Set(currencies.map((c) => c.toUpperCase()))].filter((c) => c !== house && !attempted.has(`${tenantId}:${house}:${c}`));
|
|
7
|
+
if (wanted.length === 0 || typeof fetch !== "function") return false;
|
|
8
|
+
wanted.forEach((c) => attempted.add(`${tenantId}:${house}:${c}`));
|
|
9
|
+
try {
|
|
10
|
+
const res = await fetch(`${ENDPOINT}?base=${encodeURIComponent(house)}&symbols=${wanted.map(encodeURIComponent).join(",")}`);
|
|
11
|
+
if (!res.ok) return false;
|
|
12
|
+
const body = await res.json();
|
|
13
|
+
const rows = Object.entries(body.rates ?? {}).filter(([, rate]) => typeof rate === "number" && rate > 0).map(([quote, rate]) => ({
|
|
14
|
+
tenant_id: tenantId,
|
|
15
|
+
rate_date: body.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
16
|
+
base_currency: house,
|
|
17
|
+
quote_currency: quote,
|
|
18
|
+
rate,
|
|
19
|
+
source: SOURCE
|
|
20
|
+
}));
|
|
21
|
+
if (rows.length === 0) return false;
|
|
22
|
+
const { error } = await pub.from("plg_financial_fx_rates").upsert(rows, { onConflict: "tenant_id,base_currency,quote_currency,rate_date", ignoreDuplicates: true });
|
|
23
|
+
return !error;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { ensureReferenceRates };
|
|
30
|
+
//# sourceMappingURL=chunk-4S5GK776.js.map
|
|
31
|
+
//# sourceMappingURL=chunk-4S5GK776.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/fx-auto.ts"],"names":[],"mappings":";AAQA,IAAM,QAAA,GAAW,uCAAA;AACjB,IAAM,MAAA,GAAS,+CAAA;AAEf,IAAM,SAAA,uBAAgB,GAAA,EAAY;AAOlC,eAAsB,oBAAA,CACpB,GAAA,EAAU,QAAA,EAAkB,KAAA,EAAe,UAAA,EACzB;AAClB,EAAA,MAAM,MAAA,GAAS,CAAC,GAAG,IAAI,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,WAAA,EAAa,CAAC,CAAC,CAAA,CAC/D,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,KAAM,KAAA,IAAS,CAAC,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAI,CAAC,EAAE,CAAC,CAAA;AAC3E,EAAA,IAAI,OAAO,MAAA,KAAW,CAAA,IAAK,OAAO,KAAA,KAAU,YAAY,OAAO,KAAA;AAG/D,EAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAA,KAAM,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAI,CAAC,CAAA,CAAE,CAAC,CAAA;AAChE,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,KAAA,CAAM,CAAA,EAAG,QAAQ,SAAS,kBAAA,CAAmB,KAAK,CAAC,CAAA,SAAA,EAAY,OAAO,GAAA,CAAI,kBAAkB,EAAE,IAAA,CAAK,GAAG,CAAC,CAAA,CAAE,CAAA;AAC3H,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,OAAO,KAAA;AACpB,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,KAAA,IAAS,EAAE,CAAA,CACzC,MAAA,CAAO,CAAC,GAAG,IAAI,CAAA,KAAM,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,GAAO,CAAC,CAAA,CACzD,GAAA,CAAI,CAAC,CAAC,KAAA,EAAO,IAAI,CAAA,MAAO;AAAA,MACvB,SAAA,EAAW,QAAA;AAAA,MAAU,SAAA,EAAW,IAAA,CAAK,IAAA,IAAA,iBAAQ,IAAI,IAAA,IAAO,WAAA,EAAY,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAAA,MACjF,aAAA,EAAe,KAAA;AAAA,MAAO,cAAA,EAAgB,KAAA;AAAA,MAAO,IAAA;AAAA,MAAM,MAAA,EAAQ;AAAA,KAC7D,CAAE,CAAA;AACJ,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA;AAC9B,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,IAAI,IAAA,CAAK,wBAAwB,CAAA,CACtD,MAAA,CAAO,MAAM,EAAE,UAAA,EAAY,kDAAA,EAAoD,gBAAA,EAAkB,MAAM,CAAA;AAC1G,IAAA,OAAO,CAAC,KAAA;AAAA,EACV,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF","file":"chunk-4S5GK776.js","sourcesContent":["// Reference rates, fetched when a currency has none on file.\n//\n// The ledger converts with the tenant's own rate table and refuses to guess.\n// Asking a person to type the euro's rate every morning is how that table\n// stays empty, so a missing pair is filled from the European Central Bank's\n// daily reference rates (served by frankfurter.dev) and recorded with its\n// source. A rate typed by hand for the same day is never overwritten.\n\nconst ENDPOINT = 'https://api.frankfurter.dev/v1/latest'\nconst SOURCE = 'BCE · referência do dia (automático)'\n\nconst attempted = new Set<string>()\n\n/**\n * Writes today's reference rate for `house → each currency` that has none.\n * Returns true when at least one rate was written. Never throws: without a\n * rate the amounts simply stay outside the totals, which the screen says.\n */\nexport async function ensureReferenceRates(\n pub: any, tenantId: string, house: string, currencies: string[],\n): Promise<boolean> {\n const wanted = [...new Set(currencies.map((c) => c.toUpperCase()))]\n .filter((c) => c !== house && !attempted.has(`${tenantId}:${house}:${c}`))\n if (wanted.length === 0 || typeof fetch !== 'function') return false\n // Once per pair per session: a currency the source does not quote would\n // otherwise be asked for on every screen.\n wanted.forEach((c) => attempted.add(`${tenantId}:${house}:${c}`))\n try {\n const res = await fetch(`${ENDPOINT}?base=${encodeURIComponent(house)}&symbols=${wanted.map(encodeURIComponent).join(',')}`)\n if (!res.ok) return false\n const body = await res.json() as { date?: string; rates?: Record<string, number> }\n const rows = Object.entries(body.rates ?? {})\n .filter(([, rate]) => typeof rate === 'number' && rate > 0)\n .map(([quote, rate]) => ({\n tenant_id: tenantId, rate_date: body.date ?? new Date().toISOString().slice(0, 10),\n base_currency: house, quote_currency: quote, rate, source: SOURCE,\n }))\n if (rows.length === 0) return false\n const { error } = await pub.from('plg_financial_fx_rates')\n .upsert(rows, { onConflict: 'tenant_id,base_currency,quote_currency,rate_date', ignoreDuplicates: true })\n return !error\n } catch {\n return false\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { round2 } from './chunk-XVELDCSL.js';
|
|
2
|
+
|
|
3
|
+
// src/domain/cash-session.ts
|
|
4
|
+
function sessionCashFlow(movements) {
|
|
5
|
+
let totalInflow = 0;
|
|
6
|
+
let totalOutflow = 0;
|
|
7
|
+
for (const m of movements) {
|
|
8
|
+
if (m.direction === "credit") totalInflow += Number(m.paidAmount) || 0;
|
|
9
|
+
else totalOutflow += Number(m.paidAmount) || 0;
|
|
10
|
+
}
|
|
11
|
+
return { movementCount: movements.length, totalInflow: round2(totalInflow), totalOutflow: round2(totalOutflow) };
|
|
12
|
+
}
|
|
13
|
+
function closingDifference(openingBalance, flow, counted) {
|
|
14
|
+
const expectedBalance = round2(openingBalance + flow.totalInflow - flow.totalOutflow);
|
|
15
|
+
return { expectedBalance, difference: round2(counted - expectedBalance) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { closingDifference, sessionCashFlow };
|
|
19
|
+
//# sourceMappingURL=chunk-56NQHYL7.js.map
|
|
20
|
+
//# sourceMappingURL=chunk-56NQHYL7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/cash-session.ts"],"names":[],"mappings":";;;AA0EO,SAAS,gBAAgB,SAAA,EAI9B;AACA,EAAA,IAAI,WAAA,GAAc,CAAA;AAClB,EAAA,IAAI,YAAA,GAAe,CAAA;AACnB,EAAA,KAAA,MAAW,KAAK,SAAA,EAAW;AACzB,IAAA,IAAI,EAAE,SAAA,KAAc,QAAA,iBAAyB,MAAA,CAAO,CAAA,CAAE,UAAU,CAAA,IAAK,CAAA;AAAA,SAChE,YAAA,IAAgB,MAAA,CAAO,CAAA,CAAE,UAAU,CAAA,IAAK,CAAA;AAAA,EAC/C;AACA,EAAA,OAAO,EAAE,aAAA,EAAe,SAAA,CAAU,MAAA,EAAQ,WAAA,EAAa,MAAA,CAAO,WAAW,CAAA,EAAG,YAAA,EAAc,MAAA,CAAO,YAAY,CAAA,EAAE;AACjH;AAGO,SAAS,iBAAA,CAAkB,cAAA,EAAwB,IAAA,EAAqD,OAAA,EAG7G;AACA,EAAA,MAAM,kBAAkB,MAAA,CAAO,cAAA,GAAiB,IAAA,CAAK,WAAA,GAAc,KAAK,YAAY,CAAA;AACpF,EAAA,OAAO,EAAE,eAAA,EAAiB,UAAA,EAAY,MAAA,CAAO,OAAA,GAAU,eAAe,CAAA,EAAE;AAC1E","file":"chunk-56NQHYL7.js","sourcesContent":["import { round2 } from './money'\nimport type { FinancialMovement } from './movement'\n\nexport type CashSessionStatus = 'open' | 'closed'\n\nexport interface CashSession {\n id: string\n bankAccountId: string\n bankAccountName?: string\n status: CashSessionStatus\n openedAt: string\n openedByUserId?: string\n openedByName?: string\n openingBalance: number\n closedAt?: string\n closedByUserId?: string\n closedByName?: string\n closingBalance?: number\n expectedBalance?: number\n /** Live expected cash of an open session. */\n expectedCash?: number\n /** What the session took in, per payment method. */\n receivedByMethod?: Record<string, number>\n paymentCount?: number\n difference?: number\n notes?: string\n unitId?: string\n tenantId: string\n createdAt: string\n}\n\nexport interface OpenCashSessionInput {\n bankAccountId: string\n openingBalance: number\n openedByUserId?: string\n openedByName?: string\n unitId?: string\n notes?: string\n}\n\nexport interface CloseCashSessionInput {\n sessionId: string\n closingBalance: number\n closedByUserId?: string\n closedByName?: string\n notes?: string\n}\n\nexport interface CashSessionSummary {\n session: CashSession\n movementCount: number\n totalInflow: number\n totalOutflow: number\n}\n\n/** A closed session waiting for review: whoever closes a register does not review it. */\nexport interface CashSessionReview {\n sessionId: string\n unitId?: string | null\n openedAt?: string | null\n closedAt?: string | null\n openedByName?: string | null\n closedByName?: string | null\n closedByUserId?: string | null\n openingBalance: number\n closingBalance: number\n difference: number\n reviewStatus: 'pending' | 'approved' | 'rejected'\n reviewedAt?: string | null\n reviewerName?: string | null\n reviewNotes?: string | null\n}\n\n/** What passed through a session: the paid movements in and out. */\nexport function sessionCashFlow(movements: Pick<FinancialMovement, 'direction' | 'paidAmount'>[]): {\n movementCount: number\n totalInflow: number\n totalOutflow: number\n} {\n let totalInflow = 0\n let totalOutflow = 0\n for (const m of movements) {\n if (m.direction === 'credit') totalInflow += Number(m.paidAmount) || 0\n else totalOutflow += Number(m.paidAmount) || 0\n }\n return { movementCount: movements.length, totalInflow: round2(totalInflow), totalOutflow: round2(totalOutflow) }\n}\n\n/** The cash a closing count should find, and how far the count is from it. */\nexport function closingDifference(openingBalance: number, flow: { totalInflow: number; totalOutflow: number }, counted: number): {\n expectedBalance: number\n difference: number\n} {\n const expectedBalance = round2(openingBalance + flow.totalInflow - flow.totalOutflow)\n return { expectedBalance, difference: round2(counted - expectedBalance) }\n}\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { round2 } from './chunk-XVELDCSL.js';
|
|
2
|
+
|
|
3
|
+
// src/domain/invoice.ts
|
|
4
|
+
function lineTotal(item) {
|
|
5
|
+
return round2(item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0));
|
|
6
|
+
}
|
|
7
|
+
function invoiceTotal(items) {
|
|
8
|
+
return round2(items.reduce((sum, item) => sum + lineTotal(item), 0));
|
|
9
|
+
}
|
|
10
|
+
function itemsDescription(items) {
|
|
11
|
+
return items.map((item) => item.description).filter(Boolean).join(", ");
|
|
12
|
+
}
|
|
13
|
+
function cancellationReason(input) {
|
|
14
|
+
const reason = input.reason.trim();
|
|
15
|
+
if (!reason) throw new Error("A cancellation reason is required");
|
|
16
|
+
return reason;
|
|
17
|
+
}
|
|
18
|
+
function invoiceStatusOf(invoice, bills, today) {
|
|
19
|
+
if (invoice.paidAmount >= invoice.totalAmount) return "paid";
|
|
20
|
+
if (invoice.paidAmount > 0) return "partial";
|
|
21
|
+
const late = bills.some((bill) => bill.status === "overdue" || bill.status === "pending" && bill.dueDate < today);
|
|
22
|
+
return late ? "overdue" : "open";
|
|
23
|
+
}
|
|
24
|
+
function splitInstallments(total, count) {
|
|
25
|
+
if (count < 1) return [];
|
|
26
|
+
const share = Math.floor(round2(total) * 100 / count) / 100;
|
|
27
|
+
const remainder = round2(total - share * count);
|
|
28
|
+
return Array.from({ length: count }, (_, i) => i === 0 ? round2(share + remainder) : share);
|
|
29
|
+
}
|
|
30
|
+
function scheduleCloses(amounts, total) {
|
|
31
|
+
return Math.round(amounts.reduce((sum, amount) => sum + amount, 0) * 100) === Math.round(total * 100);
|
|
32
|
+
}
|
|
33
|
+
function hasReceivedPayment(bills) {
|
|
34
|
+
return bills.some((bill) => (bill.paidAmount ?? 0) > 0);
|
|
35
|
+
}
|
|
36
|
+
function assertReschedule(invoice, currentBills, installments) {
|
|
37
|
+
if (hasReceivedPayment(currentBills)) throw new Error("Invoice already received; its schedule is history now");
|
|
38
|
+
const sum = round2(installments.reduce((total, installment) => total + installment.amount, 0));
|
|
39
|
+
if (sum !== round2(invoice.totalAmount)) {
|
|
40
|
+
throw new Error(`Installments sum to ${sum} but the invoice total is ${invoice.totalAmount}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function invoiceOpensForEditing(status) {
|
|
44
|
+
return status !== "paid" && status !== "cancelled";
|
|
45
|
+
}
|
|
46
|
+
function listsProvisioned(query) {
|
|
47
|
+
return !query.status && !query.fiscalNumber && !query.bankAccountId;
|
|
48
|
+
}
|
|
49
|
+
function withProvisioned(invoices, provisioned) {
|
|
50
|
+
if (provisioned.length === 0) return invoices;
|
|
51
|
+
return [...invoices, ...provisioned].sort((a, b) => (a.dueDate ?? "").localeCompare(b.dueDate ?? ""));
|
|
52
|
+
}
|
|
53
|
+
var RECEIVABLE_ONLY = /* @__PURE__ */ new Set(["service", "package", "gift_card"]);
|
|
54
|
+
function availableInvoiceItemTypes(itemTypes, lookupKinds, otherLabel, direction) {
|
|
55
|
+
const installed = new Set(lookupKinds);
|
|
56
|
+
const payable = direction === "debit" || direction === "payable";
|
|
57
|
+
const visible = itemTypes.filter((type) => (!payable || !RECEIVABLE_ONLY.has(type.value)) && (type.value === "other" || !["product", "service", "package"].includes(type.value) || installed.has(type.value)));
|
|
58
|
+
if (visible.some((type) => type.value === "other")) return visible;
|
|
59
|
+
return [...visible, { value: "other", label: otherLabel, icon: "MoreHorizontal" }];
|
|
60
|
+
}
|
|
61
|
+
function summarizeInvoiceLines(rows, visible = 2) {
|
|
62
|
+
const byInvoice = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
const invoiceId = row.invoice_id;
|
|
65
|
+
const label = String(row.name ?? row.description ?? "").trim();
|
|
66
|
+
if (!invoiceId || !label) continue;
|
|
67
|
+
const quantity = Number(row.quantity) || 1;
|
|
68
|
+
const rendered = quantity === 1 ? label : `${quantity} \xD7 ${label}`;
|
|
69
|
+
const current = byInvoice.get(invoiceId) ?? [];
|
|
70
|
+
current.push(rendered);
|
|
71
|
+
byInvoice.set(invoiceId, current);
|
|
72
|
+
}
|
|
73
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const [invoiceId, lines] of byInvoice) {
|
|
75
|
+
const shown = lines.slice(0, visible);
|
|
76
|
+
const remainder = lines.length - shown.length;
|
|
77
|
+
summaries.set(invoiceId, `${shown.join(" \xB7 ")}${remainder > 0 ? ` \xB7 +${remainder} itens` : ""}`);
|
|
78
|
+
}
|
|
79
|
+
return summaries;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { assertReschedule, availableInvoiceItemTypes, cancellationReason, hasReceivedPayment, invoiceOpensForEditing, invoiceStatusOf, invoiceTotal, itemsDescription, lineTotal, listsProvisioned, scheduleCloses, splitInstallments, summarizeInvoiceLines, withProvisioned };
|
|
83
|
+
//# sourceMappingURL=chunk-7HISDBUG.js.map
|
|
84
|
+
//# sourceMappingURL=chunk-7HISDBUG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/invoice.ts"],"names":[],"mappings":"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","file":"chunk-7HISDBUG.js","sourcesContent":["import { round2 } from './money'\nimport type { CreateMovementInput, FinancialMovement, TransactionDirection } from './movement'\nimport type { DateRange } from './query'\n\nexport type InvoiceStatus =\n | 'draft' | 'open' | 'partial' | 'paid' | 'cancelled' | 'overdue'\n /** A recurring occurrence not consolidated yet: forecast, not owed. No ledger entry, no charge. */\n | 'provisioned'\n\n/** What an invoice line is; the set is configured per vertical. */\nexport type InvoiceItemKind = string\n\nexport interface Invoice {\n id: string\n direction: TransactionDirection\n /** ISO code the document was written in. Absent = the house's currency. */\n currency?: string\n /** Free labels the house puts on the bill. */\n tags?: string[]\n /** Competence date: the axis of the ledger and the income statement. */\n invoiceDate: string\n /** The next open due date, or the last one once everything is paid. Lists sort by it. */\n dueDate?: string\n /** Lists are per installment: `id` is the installment and `invoiceId` the document it opens. */\n invoiceId?: string\n installmentNumber?: number\n attachmentCount?: number\n fiscalNumber?: string\n totalAmount: number\n paidAmount: number\n status: InvoiceStatus\n totalInstallments: number\n contactId?: string\n contactName?: string\n cashSessionId?: string\n unitId?: string\n observations?: string\n itemsSummary?: string\n paymentMethodId?: string\n paymentMethodTypeId?: string\n paymentAccountId?: string\n paymentCardInstallments?: number\n /** Start time of the booking the order came from, when it has one. */\n bookingStartsAt?: string\n metadata?: Record<string, unknown>\n tenantId: string\n createdAt: string\n updatedAt: string\n createdBy?: string\n /** A virtual row for a provisioned recurring occurrence; there is no invoice behind it. */\n isProvisionedOccurrence?: boolean\n occurrenceId?: string\n recurringBillId?: string\n}\n\nexport interface InvoiceItem {\n id: string\n invoiceId: string\n /** The picture of the product this line names, when it names one that has a picture. */\n imageUrl?: string\n itemKind: InvoiceItemKind\n referenceId?: string\n description: string\n quantity: number\n unitPrice: number\n totalAmount: number\n discount: number\n surcharge: number\n accountId?: string\n costCenterId?: string\n isExecuted?: boolean\n executionDate?: string\n executedByProfessionalId?: string\n metadata?: Record<string, unknown>\n /** Set when the line was cancelled on its own (cancel by item). */\n cancelledAt?: string\n cancelReason?: string\n createdAt: string\n}\n\nexport interface CreateInvoiceInput {\n direction: TransactionDirection\n invoiceDate: string\n fiscalNumber?: string\n contactId?: string\n contactName?: string\n cashSessionId?: string\n unitId?: string\n /** ISO code to write the document in. Absent = the unit's currency. */\n currency?: string\n tags?: string[]\n observations?: string\n metadata?: Record<string, unknown>\n items: CreateInvoiceItemInput[]\n installments: CreateMovementInput[]\n}\n\nexport interface CreateInvoiceItemInput {\n itemKind: InvoiceItemKind\n referenceId?: string\n description: string\n quantity: number\n unitPrice: number\n discount?: number\n surcharge?: number\n accountId?: string\n costCenterId?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface CancellationReason {\n id: string\n name: string\n}\n\n/** Goods a payable line brought into stock, still traceable to its note. */\nexport interface InvoiceStockEntry {\n itemId: string\n productId: string\n locationId: string\n /** Quantity that entered, in the product's stock unit. */\n entered: number\n /** What is still at that location — the most a return or loss can move. */\n available: number\n enteredOn: string\n}\n\nexport type StockDestination = 'devolver' | 'perda' | 'manter'\n\nexport interface CancelInvoiceItemsInput {\n invoiceId: string\n /** Empty = every live line (the whole invoice). */\n itemIds: string[]\n reasonId: string\n detail?: string\n stock: Array<{ itemId: string; destination: StockDestination; note?: string }>\n idempotencyKey?: string\n}\n\nexport interface CancelInvoiceItemsResult {\n whole: boolean\n amount: number\n /** What had been paid on the cancelled part and became counterparty credit. */\n creditAmount: number\n}\n\nexport interface InstallmentChargeInput {\n installmentId: string\n kind: 'interest' | 'fine' | 'discount'\n /** Always positive — `kind` carries the sign. */\n amount: number\n accountId?: string\n description?: string\n idempotencyKey?: string\n}\n\nexport interface CancelInvoiceInput {\n invoiceId: string\n reason: string\n /** Stable for every retry of the same cancellation attempt. */\n idempotencyKey?: string\n}\n\nexport interface InvoiceQuery {\n direction?: TransactionDirection\n status?: InvoiceStatus | InvoiceStatus[]\n contactId?: string\n unitId?: string\n accountId?: string\n costCenterId?: string\n dateRange?: DateRange\n search?: string\n fiscalNumber?: string\n /** The account the money left from or landed in. */\n bankAccountId?: string\n page?: number\n pageSize?: number\n}\n\n/** A receipt attached to an invoice. `pending` means the row exists but the file does not open yet. */\nexport interface InvoiceAttachment {\n id: string\n fileName: string\n fileSize?: number\n mimeType?: string\n url?: string\n pending: boolean\n createdAt?: string\n}\n\n/** One installment of a schedule, before it is written. */\nexport interface ScheduledInstallment {\n dueDate: string\n amount: number\n metadata?: Record<string, unknown>\n}\n\ntype PricedLine = Pick<CreateInvoiceItemInput, 'quantity' | 'unitPrice' | 'discount' | 'surcharge'>\n\nexport function lineTotal(item: PricedLine): number {\n return round2(item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0))\n}\n\n/** An invoice is worth the sum of its lines; storing one without the other bills one value and charges another. */\nexport function invoiceTotal(items: PricedLine[]): number {\n return round2(items.reduce((sum, item) => sum + lineTotal(item), 0))\n}\n\nexport function itemsDescription(items: Pick<CreateInvoiceItemInput, 'description'>[]): string {\n return items.map((item) => item.description).filter(Boolean).join(', ')\n}\n\n/** A cancellation without a reason is refused. */\nexport function cancellationReason(input: Pick<CancelInvoiceInput, 'reason'>): string {\n const reason = input.reason.trim()\n if (!reason) throw new Error('A cancellation reason is required')\n return reason\n}\n\n/** Status from what was paid, and whether any open bill is past due. */\nexport function invoiceStatusOf(\n invoice: Pick<Invoice, 'paidAmount' | 'totalAmount'>,\n bills: Pick<FinancialMovement, 'status' | 'dueDate'>[],\n today: string,\n): InvoiceStatus {\n if (invoice.paidAmount >= invoice.totalAmount) return 'paid'\n if (invoice.paidAmount > 0) return 'partial'\n const late = bills.some((bill) => bill.status === 'overdue' || (bill.status === 'pending' && bill.dueDate < today))\n return late ? 'overdue' : 'open'\n}\n\n/** Equal shares to the cent; the first installment absorbs the rounding remainder. */\nexport function splitInstallments(total: number, count: number): number[] {\n if (count < 1) return []\n const share = Math.floor(round2(total) * 100 / count) / 100\n const remainder = round2(total - share * count)\n return Array.from({ length: count }, (_, i) => (i === 0 ? round2(share + remainder) : share))\n}\n\n/** Does the schedule close on the invoice total? The database refuses a schedule that does not. */\nexport function scheduleCloses(amounts: number[], total: number): boolean {\n return Math.round(amounts.reduce((sum, amount) => sum + amount, 0) * 100) === Math.round(total * 100)\n}\n\n/** Once any money was received the schedule and the items are history. */\nexport function hasReceivedPayment(bills: Pick<FinancialMovement, 'paidAmount'>[]): boolean {\n return bills.some((bill) => (bill.paidAmount ?? 0) > 0)\n}\n\n/** A new schedule for an invoice, refused when it cannot be applied. */\nexport function assertReschedule(\n invoice: Pick<Invoice, 'totalAmount'>,\n currentBills: Pick<FinancialMovement, 'paidAmount'>[],\n installments: ScheduledInstallment[],\n): void {\n if (hasReceivedPayment(currentBills)) throw new Error('Invoice already received; its schedule is history now')\n const sum = round2(installments.reduce((total, installment) => total + installment.amount, 0))\n if (sum !== round2(invoice.totalAmount)) {\n throw new Error(`Installments sum to ${sum} but the invoice total is ${invoice.totalAmount}`)\n }\n}\n\n/**\n * Opening an invoice means editing it while it can still change. Paid and cancelled ones became\n * statement history; `partial` stays editable because its header can still be corrected.\n */\nexport function invoiceOpensForEditing(status: string | undefined): boolean {\n return status !== 'paid' && status !== 'cancelled'\n}\n\n/**\n * Forecast rows join a list only when they can match it: not when a status was asked for, and not\n * when the filter is a document number or a paying account, which a forecast never has.\n */\nexport function listsProvisioned(query: InvoiceQuery): boolean {\n return !query.status && !query.fiscalNumber && !query.bankAccountId\n}\n\n/** Real invoices and forecasts in one list, by due date. */\nexport function withProvisioned(invoices: Invoice[], provisioned: Invoice[]): Invoice[] {\n if (provisioned.length === 0) return invoices\n return [...invoices, ...provisioned].sort((a, b) => (a.dueDate ?? '').localeCompare(b.dueDate ?? ''))\n}\n\ninterface InvoiceItemType {\n value: string\n label: string\n icon?: string\n}\n\n// What the house sells is never offered on a payable: booking an expense against its own revenue line.\nconst RECEIVABLE_ONLY = new Set(['service', 'package', 'gift_card'])\n\n/** Catalogue-backed kinds need their catalogue installed; `other` is always offered. */\nexport function availableInvoiceItemTypes<T extends InvoiceItemType>(\n itemTypes: T[],\n lookupKinds: Iterable<string>,\n otherLabel: string,\n direction?: TransactionDirection | 'payable' | 'receivable',\n): T[] {\n const installed = new Set(lookupKinds)\n const payable = direction === 'debit' || direction === 'payable'\n const visible = itemTypes.filter((type) => (\n (!payable || !RECEIVABLE_ONLY.has(type.value))\n ) && (\n type.value === 'other'\n || !['product', 'service', 'package'].includes(type.value)\n || installed.has(type.value)\n ))\n if (visible.some((type) => type.value === 'other')) return visible\n return [...visible, { value: 'other', label: otherLabel, icon: 'MoreHorizontal' } as T]\n}\n\ninterface InvoiceLineSummarySource {\n invoice_id?: string | null\n name?: string | null\n description?: string | null\n quantity?: number | string | null\n}\n\n/** The billed lines compacted for a list row: two shown, the rest counted. */\nexport function summarizeInvoiceLines(rows: InvoiceLineSummarySource[], visible = 2): Map<string, string> {\n const byInvoice = new Map<string, string[]>()\n for (const row of rows) {\n const invoiceId = row.invoice_id\n const label = String(row.name ?? row.description ?? '').trim()\n if (!invoiceId || !label) continue\n const quantity = Number(row.quantity) || 1\n const rendered = quantity === 1 ? label : `${quantity} × ${label}`\n const current = byInvoice.get(invoiceId) ?? []\n current.push(rendered)\n byInvoice.set(invoiceId, current)\n }\n\n const summaries = new Map<string, string>()\n for (const [invoiceId, lines] of byInvoice) {\n const shown = lines.slice(0, visible)\n const remainder = lines.length - shown.length\n summaries.set(invoiceId, `${shown.join(' · ')}${remainder > 0 ? ` · +${remainder} itens` : ''}`)\n }\n return summaries\n}\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { buildBillInvoice, buildPayment, adjustmentDescription, adjustmentInvoice } from './chunk-MCSPCXQK.js';
|
|
2
|
+
import { round2 } from './chunk-XVELDCSL.js';
|
|
3
|
+
|
|
4
|
+
// src/data/reconcile.ts
|
|
5
|
+
async function billOf(provider, invoiceId) {
|
|
6
|
+
const { data } = await provider.getMovements({ invoiceId });
|
|
7
|
+
const bill = data.find((m) => m.movementKind === "bill");
|
|
8
|
+
if (!bill) throw new Error("A conta foi criada sem parcela");
|
|
9
|
+
return bill;
|
|
10
|
+
}
|
|
11
|
+
async function paymentIdOf(provider, settled) {
|
|
12
|
+
if (settled.movementKind === "payment") return settled.id;
|
|
13
|
+
const invoiceId = settled.invoiceId;
|
|
14
|
+
if (!invoiceId) throw new Error("A parcela quitada n\xE3o aponta para uma conta");
|
|
15
|
+
const { data } = await provider.getMovements({ invoiceId });
|
|
16
|
+
const payments = data.filter((m) => m.movementKind === "payment");
|
|
17
|
+
const last = payments[payments.length - 1];
|
|
18
|
+
if (!last) throw new Error("A conta foi quitada mas o pagamento n\xE3o foi encontrado");
|
|
19
|
+
return last.id;
|
|
20
|
+
}
|
|
21
|
+
async function runReconcileAsBill(provider, line, input) {
|
|
22
|
+
const invoice = await provider.createInvoice(buildBillInvoice(line, input));
|
|
23
|
+
const bill = await billOf(provider, invoice.id);
|
|
24
|
+
const settled = await provider.payMovement({
|
|
25
|
+
...buildPayment(line, bill.id, round2(line.amount), input.payment),
|
|
26
|
+
metadata: input.receiptUrl ? { receiptUrl: input.receiptUrl } : void 0
|
|
27
|
+
});
|
|
28
|
+
const paymentId = await paymentIdOf(provider, settled);
|
|
29
|
+
await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: paymentId });
|
|
30
|
+
return { invoiceId: invoice.id };
|
|
31
|
+
}
|
|
32
|
+
async function runReconcileLink(provider, line, target, options = {}) {
|
|
33
|
+
if (target.mode === "link_only") {
|
|
34
|
+
await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: target.movementId });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (options.partial) {
|
|
38
|
+
const amount = round2(line.amount);
|
|
39
|
+
if (amount <= 0 || amount >= round2(target.openAmount)) {
|
|
40
|
+
throw new Error("Pagamento parcial s\xF3 quando a linha \xE9 menor que o saldo em aberto");
|
|
41
|
+
}
|
|
42
|
+
const paga = await provider.payMovement(buildPayment(line, target.movementId, amount, options.payment));
|
|
43
|
+
const paymentId2 = await paymentIdOf(provider, paga);
|
|
44
|
+
await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: paymentId2 });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const adjustments = (options.adjustments ?? []).filter((adj) => round2(adj.amount) > 0);
|
|
48
|
+
if (provider.addInstallmentCharge && adjustments.length) {
|
|
49
|
+
let due = target.openAmount;
|
|
50
|
+
for (const adj of adjustments) {
|
|
51
|
+
await provider.addInstallmentCharge({
|
|
52
|
+
installmentId: target.movementId,
|
|
53
|
+
kind: adj.type,
|
|
54
|
+
amount: round2(adj.amount),
|
|
55
|
+
accountId: adj.accountId,
|
|
56
|
+
description: adjustmentDescription(adj)
|
|
57
|
+
});
|
|
58
|
+
due += adj.type === "discount" ? -round2(adj.amount) : round2(adj.amount);
|
|
59
|
+
}
|
|
60
|
+
const paid = await provider.payMovement(buildPayment(line, target.movementId, round2(due), options.payment));
|
|
61
|
+
const paidId = await paymentIdOf(provider, paid);
|
|
62
|
+
await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: paidId });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const settled = await provider.payMovement(
|
|
66
|
+
buildPayment(line, target.movementId, target.openAmount, options.payment)
|
|
67
|
+
);
|
|
68
|
+
const paymentId = await paymentIdOf(provider, settled);
|
|
69
|
+
await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: paymentId });
|
|
70
|
+
for (const adj of adjustments) {
|
|
71
|
+
const invoice = await provider.createInvoice(adjustmentInvoice(line, adj));
|
|
72
|
+
const bill = await billOf(provider, invoice.id);
|
|
73
|
+
await provider.payMovement(
|
|
74
|
+
buildPayment(line, bill.id, round2(adj.amount), options.payment, adjustmentDescription(adj))
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { runReconcileAsBill, runReconcileLink };
|
|
80
|
+
//# sourceMappingURL=chunk-FNXIAIUZ.js.map
|
|
81
|
+
//# sourceMappingURL=chunk-FNXIAIUZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/reconcile.ts"],"names":["paymentId"],"mappings":"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","file":"chunk-FNXIAIUZ.js","sourcesContent":["import type { CreateInvoiceInput, InstallmentChargeInput } from '../domain/invoice'\nimport { round2 } from '../domain/money'\nimport type { FinancialMovement, MovementQuery, PayMovementInput } from '../domain/movement'\nimport {\n adjustmentInvoice, buildBillInvoice, buildPayment, adjustmentDescription,\n type ReconcileAdjustment, type ReconcileAsBillInput, type ReconcileLine, type ReconcilePaymentInput,\n} from '../domain/reconciliation'\n\n/**\n * The provider primitives reconciling runs on. Both adapters lend theirs, so the workflow is written\n * once; `reconcileMovement` here is the raw mark, never the public method, which would re-enter.\n */\nexport interface ReconcileCapableProvider {\n createInvoice(input: CreateInvoiceInput): Promise<{ id: string }>\n getMovements(query: MovementQuery): Promise<{ data: FinancialMovement[] }>\n payMovement(input: PayMovementInput): Promise<FinancialMovement>\n reconcileMovement(input: { bankMovementId: string; matchedMovementId?: string }): Promise<void>\n /** When present, interest/fine/discount become a line of the bill being settled (V1). */\n addInstallmentCharge?(input: InstallmentChargeInput): Promise<void>\n}\n\nasync function billOf(provider: ReconcileCapableProvider, invoiceId: string): Promise<FinancialMovement> {\n const { data } = await provider.getMovements({ invoiceId })\n const bill = data.find((m) => m.movementKind === 'bill')\n if (!bill) throw new Error('A conta foi criada sem parcela')\n return bill\n}\n\n/**\n * The payment's id, not what `payMovement` returned. The Supabase adapter returns the settled\n * installment, and the line's `matched_movement_id` references movements: tying it to the\n * installment id is refused by the foreign key, leaving the bill paid and the line pending.\n */\nasync function paymentIdOf(provider: ReconcileCapableProvider, settled: FinancialMovement): Promise<string> {\n if (settled.movementKind === 'payment') return settled.id\n const invoiceId = settled.invoiceId\n if (!invoiceId) throw new Error('A parcela quitada não aponta para uma conta')\n const { data } = await provider.getMovements({ invoiceId })\n const payments = data.filter((m) => m.movementKind === 'payment')\n const last = payments[payments.length - 1]\n if (!last) throw new Error('A conta foi quitada mas o pagamento não foi encontrado')\n return last.id\n}\n\n/** \"Post as bill\": the bill is born classified, paid, and proven by the line. */\nexport async function runReconcileAsBill(\n provider: ReconcileCapableProvider,\n line: ReconcileLine,\n input: ReconcileAsBillInput,\n): Promise<{ invoiceId: string }> {\n const invoice = await provider.createInvoice(buildBillInvoice(line, input))\n const bill = await billOf(provider, invoice.id)\n const settled = await provider.payMovement({\n ...buildPayment(line, bill.id, round2(line.amount), input.payment),\n metadata: input.receiptUrl ? { receiptUrl: input.receiptUrl } : undefined,\n })\n const paymentId = await paymentIdOf(provider, settled)\n await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: paymentId })\n return { invoiceId: invoice.id }\n}\n\n/** \"Link\": an open bill is settled by the line; a paid one is only tied, never paid again. */\nexport async function runReconcileLink(\n provider: ReconcileCapableProvider,\n line: ReconcileLine,\n target: { movementId: string; mode: 'settle' | 'link_only'; openAmount: number },\n options: { payment?: ReconcilePaymentInput; adjustments?: ReconcileAdjustment[]; partial?: boolean } = {},\n): Promise<void> {\n if (target.mode === 'link_only') {\n await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: target.movementId })\n return\n }\n\n // Paid in parts: this line pays what it carries and the bill keeps the rest\n // open, so the next transfer reconciles against the same bill.\n if (options.partial) {\n const amount = round2(line.amount)\n if (amount <= 0 || amount >= round2(target.openAmount)) {\n throw new Error('Pagamento parcial só quando a linha é menor que o saldo em aberto')\n }\n const paga = await provider.payMovement(buildPayment(line, target.movementId, amount, options.payment))\n const paymentId = await paymentIdOf(provider, paga)\n await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: paymentId })\n return\n }\n\n // Interest, fine and discount go onto the bill itself, as in V1: the line is\n // added, the installment becomes what the bank charged, and one payment\n // settles it all. The stand-alone adjustment bill is only for a provider\n // that cannot amend.\n const adjustments = (options.adjustments ?? []).filter((adj) => round2(adj.amount) > 0)\n if (provider.addInstallmentCharge && adjustments.length) {\n let due = target.openAmount\n for (const adj of adjustments) {\n await provider.addInstallmentCharge({\n installmentId: target.movementId,\n kind: adj.type,\n amount: round2(adj.amount),\n accountId: adj.accountId,\n description: adjustmentDescription(adj),\n })\n due += adj.type === 'discount' ? -round2(adj.amount) : round2(adj.amount)\n }\n const paid = await provider.payMovement(buildPayment(line, target.movementId, round2(due), options.payment))\n const paidId = await paymentIdOf(provider, paid)\n await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: paidId })\n return\n }\n\n const settled = await provider.payMovement(\n buildPayment(line, target.movementId, target.openAmount, options.payment),\n )\n const paymentId = await paymentIdOf(provider, settled)\n await provider.reconcileMovement({ bankMovementId: line.id, matchedMovementId: paymentId })\n\n for (const adj of adjustments) {\n const invoice = await provider.createInvoice(adjustmentInvoice(line, adj))\n const bill = await billOf(provider, invoice.id)\n await provider.payMovement(\n buildPayment(line, bill.id, round2(adj.amount), options.payment, adjustmentDescription(adj)),\n )\n }\n}\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { round2 } from './chunk-XVELDCSL.js';
|
|
2
|
+
|
|
3
|
+
// src/domain/statement.ts
|
|
4
|
+
function affectsCash(movement) {
|
|
5
|
+
return !movement.externalSource || !!movement.reconciledAt && !movement.matchedMovementId;
|
|
6
|
+
}
|
|
7
|
+
function isRealized(movement) {
|
|
8
|
+
return movement.status !== "cancelled" && !!movement.paymentDate && affectsCash(movement);
|
|
9
|
+
}
|
|
10
|
+
function netCash(movement) {
|
|
11
|
+
const paid = Number(movement.paidAmount) || 0;
|
|
12
|
+
return movement.direction === "credit" ? paid - (Number(movement.feeAmount) || 0) : -paid;
|
|
13
|
+
}
|
|
14
|
+
function ledgerBalance(start, movements) {
|
|
15
|
+
return round2(movements.reduce((sum, m) => sum + netCash(m), start));
|
|
16
|
+
}
|
|
17
|
+
function emptyStatement(accountId) {
|
|
18
|
+
return { entries: [], openingBalance: 0, closingBalance: 0, totalCredits: 0, totalDebits: 0, totalFees: 0, net: 0, accountId };
|
|
19
|
+
}
|
|
20
|
+
function buildStatement(input) {
|
|
21
|
+
let balance = input.openingBalance;
|
|
22
|
+
let totalCredits = 0;
|
|
23
|
+
let totalDebits = 0;
|
|
24
|
+
let totalFees = 0;
|
|
25
|
+
const entries = input.movements.map((movement) => {
|
|
26
|
+
const gross = Number(movement.paidAmount) || 0;
|
|
27
|
+
const isCredit = movement.direction === "credit";
|
|
28
|
+
const fee = isCredit ? Number(movement.feeAmount) || 0 : 0;
|
|
29
|
+
const net = isCredit ? round2(gross - fee) : gross;
|
|
30
|
+
const isTransfer = movement.movementKind === "transfer";
|
|
31
|
+
const meta = movement.metadata ?? {};
|
|
32
|
+
const counterAccountId = isTransfer ? meta.counterAccountId : void 0;
|
|
33
|
+
balance = round2(isCredit ? balance + net : balance - net);
|
|
34
|
+
if (!(input.consolidated && isTransfer)) {
|
|
35
|
+
if (isCredit) totalCredits = round2(totalCredits + net);
|
|
36
|
+
else totalDebits = round2(totalDebits + net);
|
|
37
|
+
totalFees = round2(totalFees + fee);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
movement,
|
|
41
|
+
invoice: input.invoiceOf(movement),
|
|
42
|
+
runningBalance: balance,
|
|
43
|
+
gross,
|
|
44
|
+
fee,
|
|
45
|
+
net,
|
|
46
|
+
entryKind: isTransfer ? meta.transferRole === "in" ? "transfer-in" : "transfer-out" : "movement",
|
|
47
|
+
counterAccountId,
|
|
48
|
+
counterAccountName: counterAccountId ? input.accountNameOf(counterAccountId) : void 0
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
entries,
|
|
53
|
+
openingBalance: input.openingBalance,
|
|
54
|
+
closingBalance: balance,
|
|
55
|
+
totalCredits,
|
|
56
|
+
totalDebits,
|
|
57
|
+
totalFees,
|
|
58
|
+
net: round2(totalCredits - totalDebits)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function statementTimestamp(entry) {
|
|
62
|
+
const date = entry.movement.paymentDate ?? entry.movement.dueDate;
|
|
63
|
+
return Date.parse(date) || 0;
|
|
64
|
+
}
|
|
65
|
+
function newestStatementEntriesFirst(entries) {
|
|
66
|
+
return entries.map((entry, index) => ({ entry, index })).sort((left, right) => {
|
|
67
|
+
const byStatementDate = statementTimestamp(right.entry) - statementTimestamp(left.entry);
|
|
68
|
+
if (byStatementDate !== 0) return byStatementDate;
|
|
69
|
+
const byCreatedAt = (Date.parse(right.entry.movement.createdAt) || 0) - (Date.parse(left.entry.movement.createdAt) || 0);
|
|
70
|
+
if (byCreatedAt !== 0) return byCreatedAt;
|
|
71
|
+
return right.index - left.index;
|
|
72
|
+
}).map(({ entry }) => entry);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { affectsCash, buildStatement, emptyStatement, isRealized, ledgerBalance, netCash, newestStatementEntriesFirst };
|
|
76
|
+
//# sourceMappingURL=chunk-HJPUHTI2.js.map
|
|
77
|
+
//# sourceMappingURL=chunk-HJPUHTI2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/statement.ts"],"names":[],"mappings":"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","file":"chunk-HJPUHTI2.js","sourcesContent":["import type { Invoice } from './invoice'\nimport { round2 } from './money'\nimport type { FinancialMovement } from './movement'\nimport type { DateRange } from './query'\n\nexport interface StatementQuery {\n /** Omit for the consolidated statement of every account. */\n bankAccountId?: string\n dateRange: DateRange\n}\n\n/** What a statement row is: a movement, or one leg of a transfer. */\nexport type StatementEntryKind = 'movement' | 'transfer-in' | 'transfer-out'\n\nexport interface StatementEntry {\n movement: FinancialMovement\n invoice?: Invoice\n /** Net cash balance after this row. */\n runningBalance: number\n /** What settled the receivable or payable (the paid amount). */\n gross: number\n /** Card fee withheld, never negative. */\n fee: number\n /** Cash impact: gross minus fee for a credit, gross for a debit. */\n net: number\n entryKind: StatementEntryKind\n counterAccountId?: string\n counterAccountName?: string\n /** For a reconciled bank line, the internal movement it was matched to. */\n reconciledWith?: {\n movementId: string\n description?: string\n invoiceId?: string\n fiscalNumber?: string\n contactName?: string\n }\n}\n\nexport interface StatementResult {\n entries: StatementEntry[]\n /** Balance at the start of the first day, excluding the period's rows. */\n openingBalance: number\n /** Opening balance plus the net of every row in the period. */\n closingBalance: number\n totalCredits: number\n totalDebits: number\n totalFees: number\n /** Credits minus debits. */\n net: number\n /** The account asked for; absent for the consolidated statement. */\n accountId?: string\n /** The account's ledger balance as of today. */\n accountCurrentBalance?: number\n}\n\n/**\n * An imported bank line is evidence, not cash, until it is accepted on its own: once matched, the\n * internal movement carries the money and counting the line too would double it.\n */\nexport function affectsCash(movement: Pick<FinancialMovement, 'externalSource' | 'reconciledAt' | 'matchedMovementId'>): boolean {\n return !movement.externalSource || (!!movement.reconciledAt && !movement.matchedMovementId)\n}\n\n/** A cash event: not cancelled, with a payment date, and counted as cash. */\nexport function isRealized(movement: FinancialMovement): boolean {\n return movement.status !== 'cancelled' && !!movement.paymentDate && affectsCash(movement)\n}\n\n/** Signed cash impact of a movement: a credit nets its fee, a debit leaves whole. */\nexport function netCash(movement: Pick<FinancialMovement, 'direction' | 'paidAmount' | 'feeAmount'>): number {\n const paid = Number(movement.paidAmount) || 0\n return movement.direction === 'credit' ? paid - (Number(movement.feeAmount) || 0) : -paid\n}\n\n/** A balance from a starting point and the movements after it. */\nexport function ledgerBalance(start: number, movements: Pick<FinancialMovement, 'direction' | 'paidAmount' | 'feeAmount'>[]): number {\n return round2(movements.reduce((sum, m) => sum + netCash(m), start))\n}\n\nexport function emptyStatement(accountId?: string): StatementResult {\n return { entries: [], openingBalance: 0, closingBalance: 0, totalCredits: 0, totalDebits: 0, totalFees: 0, net: 0, accountId }\n}\n\nexport interface BuildStatementInput {\n openingBalance: number\n /** The period's cash events, in chronological order. */\n movements: FinancialMovement[]\n /** Transfers net to zero across accounts, so the consolidated totals leave them out. */\n consolidated: boolean\n accountNameOf: (accountId: string) => string | undefined\n invoiceOf: (movement: FinancialMovement) => Invoice | undefined\n}\n\n/** Rows with a running net balance, and the period's totals. */\nexport function buildStatement(input: BuildStatementInput): Omit<StatementResult, 'accountId' | 'accountCurrentBalance'> {\n let balance = input.openingBalance\n let totalCredits = 0\n let totalDebits = 0\n let totalFees = 0\n const entries = input.movements.map((movement): StatementEntry => {\n const gross = Number(movement.paidAmount) || 0\n const isCredit = movement.direction === 'credit'\n const fee = isCredit ? (Number(movement.feeAmount) || 0) : 0\n const net = isCredit ? round2(gross - fee) : gross\n const isTransfer = movement.movementKind === 'transfer'\n const meta = (movement.metadata ?? {}) as { transferRole?: string; counterAccountId?: string }\n const counterAccountId = isTransfer ? meta.counterAccountId : undefined\n balance = round2(isCredit ? balance + net : balance - net)\n if (!(input.consolidated && isTransfer)) {\n if (isCredit) totalCredits = round2(totalCredits + net)\n else totalDebits = round2(totalDebits + net)\n totalFees = round2(totalFees + fee)\n }\n return {\n movement,\n invoice: input.invoiceOf(movement),\n runningBalance: balance,\n gross,\n fee,\n net,\n entryKind: isTransfer ? (meta.transferRole === 'in' ? 'transfer-in' : 'transfer-out') : 'movement',\n counterAccountId,\n counterAccountName: counterAccountId ? input.accountNameOf(counterAccountId) : undefined,\n }\n })\n return {\n entries,\n openingBalance: input.openingBalance,\n closingBalance: balance,\n totalCredits,\n totalDebits,\n totalFees,\n net: round2(totalCredits - totalDebits),\n }\n}\n\nfunction statementTimestamp(entry: StatementEntry) {\n const date = entry.movement.paymentDate ?? entry.movement.dueDate\n return Date.parse(date) || 0\n}\n\n/** Newest cash events first, keeping each row's balance. */\nexport function newestStatementEntriesFirst(entries: StatementEntry[]) {\n // The source index is the chronology: a batch import stamps every row with the same dates, and\n // only the provider's forward walk orders them.\n return entries\n .map((entry, index) => ({ entry, index }))\n .sort((left, right) => {\n const byStatementDate = statementTimestamp(right.entry) - statementTimestamp(left.entry)\n if (byStatementDate !== 0) return byStatementDate\n\n const byCreatedAt = (Date.parse(right.entry.movement.createdAt) || 0)\n - (Date.parse(left.entry.movement.createdAt) || 0)\n if (byCreatedAt !== 0) return byCreatedAt\n\n return right.index - left.index\n })\n .map(({ entry }) => entry)\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/data/idempotency.ts
|
|
2
|
+
function createIdempotencyKey(scope) {
|
|
3
|
+
let suffix;
|
|
4
|
+
try {
|
|
5
|
+
suffix = typeof globalThis.crypto?.randomUUID === "function" ? globalThis.crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
6
|
+
} catch {
|
|
7
|
+
suffix = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
8
|
+
}
|
|
9
|
+
return `${scope}:${suffix}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { createIdempotencyKey };
|
|
13
|
+
//# sourceMappingURL=chunk-IP5YH4S5.js.map
|
|
14
|
+
//# sourceMappingURL=chunk-IP5YH4S5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/idempotency.ts"],"names":[],"mappings":";AAAO,SAAS,qBAAqB,KAAA,EAAuB;AAC1D,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,OAAO,UAAA,CAAW,MAAA,EAAQ,UAAA,KAAe,UAAA,GAC9C,UAAA,CAAW,MAAA,CAAO,UAAA,EAAW,GAC7B,CAAA,EAAG,IAAA,CAAK,GAAA,EAAI,CAAE,SAAS,EAAE,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAAA,EAC3E,CAAA,CAAA,MAAQ;AACN,IAAA,MAAA,GAAS,GAAG,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAAA,EAChF;AACA,EAAA,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAC3B","file":"chunk-IP5YH4S5.js","sourcesContent":["export function createIdempotencyKey(scope: string): string {\n let suffix: string\n try {\n suffix = typeof globalThis.crypto?.randomUUID === 'function'\n ? globalThis.crypto.randomUUID()\n : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n } catch {\n suffix = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n }\n return `${scope}:${suffix}`\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/domain/commission.ts
|
|
2
|
+
var UNENFORCED_CRITERIA = ["profession", "professional_group", "payment_method_type"];
|
|
3
|
+
function hasCriteriaValue(value) {
|
|
4
|
+
if (!value) return false;
|
|
5
|
+
return Object.values(value).some((x) => Array.isArray(x) ? x.length > 0 : x != null && x !== "");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export { UNENFORCED_CRITERIA, hasCriteriaValue };
|
|
9
|
+
//# sourceMappingURL=chunk-J7BWATQU.js.map
|
|
10
|
+
//# sourceMappingURL=chunk-J7BWATQU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/commission.ts"],"names":[],"mappings":";AA8BO,IAAM,mBAAA,GACX,CAAC,YAAA,EAAc,oBAAA,EAAsB,qBAAqB;AA+GrD,SAAS,iBAAiB,KAAA,EAAqD;AACpF,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,OAAO,OAAO,MAAA,CAAO,KAAK,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAO,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAI,CAAA,CAAE,MAAA,GAAS,IAAI,CAAA,IAAK,IAAA,IAAQ,MAAM,EAAG,CAAA;AACnG","file":"chunk-J7BWATQU.js","sourcesContent":["/** A computed commission, with the sale it came from. */\nexport interface CommissionEntry {\n id: string\n personId: string\n personName?: string | null\n itemId: string\n itemName?: string | null\n orderId?: string | null\n invoiceId?: string | null\n invoiceNumber?: string | null\n competenceDate?: string | null\n ruleName?: string | null\n functionName?: string | null\n baseAmount: number\n rate: number\n amount: number\n share: number\n role?: string | null\n status: 'pending' | 'approved' | 'paid' | 'cancelled'\n}\n\nexport type CommissionCriteriaType =\n | 'service' | 'category' | 'professional' | 'professional_group'\n | 'profession' | 'unit' | 'payment_method_type' | 'weekday' | 'time_range'\n\n/**\n * Criteria the engine does not enforce yet: profession and group have no registry to point at, and\n * the payment method is only known after the line is computed. The screen says so instead of\n * offering a control that does nothing.\n */\nexport const UNENFORCED_CRITERIA: CommissionCriteriaType[] =\n ['profession', 'professional_group', 'payment_method_type']\n\nexport interface CommissionRuleCriteria {\n id?: string\n criteriaType: CommissionCriteriaType\n /** `{ids:[…]}` for lists, `{days:[0..6]}` for weekdays, `{ranges:[{from,to}]}` for hours. */\n criteriaValue: Record<string, unknown>\n}\n\n/** A production tier. An empty `to` is unbounded; it pays `fixed` or `percent`. */\nexport interface CommissionTier {\n from: number\n to?: number | null\n fixed?: number | null\n percent?: number | null\n}\n\nexport interface CommissionFunction {\n id?: string\n ruleId?: string\n functionName: string\n accountType: 'professional_executor' | 'professional' | 'supplier' | 'select_on_launch'\n personId?: string | null\n valueType: 'percentage' | 'fixed'\n valueAmount: number\n calculationBase: 'total' | 'net'\n tierMode: 'none' | 'marginal' | 'retroactive' | 'guaranteed_min'\n minGuaranteed?: number | null\n tiers: CommissionTier[]\n isVariable: boolean\n displayOrder: number\n isActive: boolean\n}\n\n/** The policy: who earns what, for which sales. The most specific matching rule wins. */\nexport interface CommissionRule {\n id: string\n name: string\n description?: string | null\n priority: number\n isDefault: boolean\n isActive: boolean\n unitId?: string | null\n /** Legacy defaults; they only seed the default function of a rule that has none. */\n valueType: 'percentage' | 'fixed'\n value: number\n criteria: CommissionRuleCriteria[]\n functions: CommissionFunction[]\n}\n\nexport interface CommissionRuleInput {\n id?: string\n name: string\n description?: string | null\n priority: number\n isDefault?: boolean\n isActive?: boolean\n unitId?: string | null\n criteria?: CommissionRuleCriteria[]\n}\n\n/** The registries criteria point at, in one load. */\nexport interface CommissionCatalogs {\n services: { id: string; name: string; categoryId?: string | null }[]\n categories: { id: string; name: string }[]\n professionals: { id: string; name: string }[]\n units: { id: string; name: string }[]\n}\n\nexport interface CommissionQuery {\n periodStart?: string\n periodEnd?: string\n personId?: string\n status?: string\n}\n\nexport interface GenerateCommissionsInput {\n periodStart: string\n periodEnd: string\n unitId?: string\n}\n\n/** A computed batch: approved and paid as one. */\nexport interface CommissionGeneration {\n id: string\n periodStart: string\n periodEnd: string\n status: 'draft' | 'approved' | 'paid' | 'cancelled'\n total: number\n entries: number\n}\n\nexport interface CommissionApproveResult { generationId: string; approved: number }\n\nexport interface CommissionPayRunResult {\n generationId: string\n payables: number\n total: number\n dueDate: string\n}\n\nexport interface CommissionGenerationResult {\n generationId: string\n entries: number\n total: number\n}\n\n/**\n * An empty criterion is not a criterion. The rule matching most criteria wins, so saving one\n * without a value would make a rule more specific without restricting anything.\n */\nexport function hasCriteriaValue(value: Record<string, unknown> | undefined): boolean {\n if (!value) return false\n return Object.values(value).some((x) => (Array.isArray(x) ? x.length > 0 : x != null && x !== ''))\n}\n"]}
|