@voyant-travel/finance 0.119.5
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 +201 -0
- package/README.md +192 -0
- package/dist/action-ledger-drift.d.ts +29 -0
- package/dist/action-ledger-drift.d.ts.map +1 -0
- package/dist/action-ledger-drift.js +166 -0
- package/dist/booking-tax.d.ts +124 -0
- package/dist/booking-tax.d.ts.map +1 -0
- package/dist/booking-tax.js +264 -0
- package/dist/checkout-routes.d.ts +1154 -0
- package/dist/checkout-routes.d.ts.map +1 -0
- package/dist/checkout-routes.js +116 -0
- package/dist/checkout-service-plan.d.ts +137 -0
- package/dist/checkout-service-plan.d.ts.map +1 -0
- package/dist/checkout-service-plan.js +119 -0
- package/dist/checkout-service.d.ts +9 -0
- package/dist/checkout-service.d.ts.map +1 -0
- package/dist/checkout-service.js +324 -0
- package/dist/checkout-validation.d.ts +1682 -0
- package/dist/checkout-validation.d.ts.map +1 -0
- package/dist/checkout-validation.js +228 -0
- package/dist/document-download.d.ts +3 -0
- package/dist/document-download.d.ts.map +1 -0
- package/dist/document-download.js +1 -0
- package/dist/fx-money.d.ts +17 -0
- package/dist/fx-money.d.ts.map +1 -0
- package/dist/fx-money.js +194 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/invoice-fx.d.ts +134 -0
- package/dist/invoice-fx.d.ts.map +1 -0
- package/dist/invoice-fx.js +240 -0
- package/dist/invoice-number-errors.d.ts +2 -0
- package/dist/invoice-number-errors.d.ts.map +1 -0
- package/dist/invoice-number-errors.js +58 -0
- package/dist/markets-ref.d.ts +149 -0
- package/dist/markets-ref.d.ts.map +1 -0
- package/dist/markets-ref.js +17 -0
- package/dist/payment-link.d.ts +23 -0
- package/dist/payment-link.d.ts.map +1 -0
- package/dist/payment-link.js +30 -0
- package/dist/payment-policy.d.ts +113 -0
- package/dist/payment-policy.d.ts.map +1 -0
- package/dist/payment-policy.js +193 -0
- package/dist/route-runtime.d.ts +22 -0
- package/dist/route-runtime.d.ts.map +1 -0
- package/dist/route-runtime.js +18 -0
- package/dist/routes-action-ledger.d.ts +181 -0
- package/dist/routes-action-ledger.d.ts.map +1 -0
- package/dist/routes-action-ledger.js +142 -0
- package/dist/routes-booking-billing.d.ts +852 -0
- package/dist/routes-booking-billing.d.ts.map +1 -0
- package/dist/routes-booking-billing.js +223 -0
- package/dist/routes-booking-create.d.ts +3 -0
- package/dist/routes-booking-create.d.ts.map +1 -0
- package/dist/routes-booking-create.js +194 -0
- package/dist/routes-booking-reads.d.ts +46 -0
- package/dist/routes-booking-reads.d.ts.map +1 -0
- package/dist/routes-booking-reads.js +20 -0
- package/dist/routes-documents.d.ts +195 -0
- package/dist/routes-documents.d.ts.map +1 -0
- package/dist/routes-documents.js +93 -0
- package/dist/routes-invoice-core.d.ts +794 -0
- package/dist/routes-invoice-core.d.ts.map +1 -0
- package/dist/routes-invoice-core.js +238 -0
- package/dist/routes-invoice-documents.d.ts +401 -0
- package/dist/routes-invoice-documents.d.ts.map +1 -0
- package/dist/routes-invoice-documents.js +91 -0
- package/dist/routes-invoice-issue.d.ts +384 -0
- package/dist/routes-invoice-issue.d.ts.map +1 -0
- package/dist/routes-invoice-issue.js +208 -0
- package/dist/routes-payment-processing.d.ts +1193 -0
- package/dist/routes-payment-processing.d.ts.map +1 -0
- package/dist/routes-payment-processing.js +238 -0
- package/dist/routes-payments.d.ts +309 -0
- package/dist/routes-payments.d.ts.map +1 -0
- package/dist/routes-payments.js +94 -0
- package/dist/routes-public.d.ts +1948 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +275 -0
- package/dist/routes-reference-data.d.ts +977 -0
- package/dist/routes-reference-data.d.ts.map +1 -0
- package/dist/routes-reference-data.js +191 -0
- package/dist/routes-reports.d.ts +344 -0
- package/dist/routes-reports.d.ts.map +1 -0
- package/dist/routes-reports.js +93 -0
- package/dist/routes-runtime.d.ts +71 -0
- package/dist/routes-runtime.d.ts.map +1 -0
- package/dist/routes-runtime.js +59 -0
- package/dist/routes-settlement.d.ts +67 -0
- package/dist/routes-settlement.d.ts.map +1 -0
- package/dist/routes-settlement.js +23 -0
- package/dist/routes-shared.d.ts +35 -0
- package/dist/routes-shared.d.ts.map +1 -0
- package/dist/routes-shared.js +10 -0
- package/dist/routes-supplier-invoices.d.ts +778 -0
- package/dist/routes-supplier-invoices.d.ts.map +1 -0
- package/dist/routes-supplier-invoices.js +159 -0
- package/dist/routes-vouchers.d.ts +228 -0
- package/dist/routes-vouchers.d.ts.map +1 -0
- package/dist/routes-vouchers.js +54 -0
- package/dist/routes.d.ts +5577 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +44 -0
- package/dist/schema/booking-billing.d.ts +1006 -0
- package/dist/schema/booking-billing.d.ts.map +1 -0
- package/dist/schema/booking-billing.js +106 -0
- package/dist/schema/enums.d.ts +48 -0
- package/dist/schema/enums.d.ts.map +1 -0
- package/dist/schema/enums.js +237 -0
- package/dist/schema/invoice-documents.d.ts +1245 -0
- package/dist/schema/invoice-documents.d.ts.map +1 -0
- package/dist/schema/invoice-documents.js +140 -0
- package/dist/schema/payment-instruments.d.ts +418 -0
- package/dist/schema/payment-instruments.d.ts.map +1 -0
- package/dist/schema/payment-instruments.js +45 -0
- package/dist/schema/payment-processing.d.ts +563 -0
- package/dist/schema/payment-processing.d.ts.map +1 -0
- package/dist/schema/payment-processing.js +65 -0
- package/dist/schema/payment-sessions.d.ts +728 -0
- package/dist/schema/payment-sessions.d.ts.map +1 -0
- package/dist/schema/payment-sessions.js +79 -0
- package/dist/schema/receivables.d.ts +1474 -0
- package/dist/schema/receivables.d.ts.map +1 -0
- package/dist/schema/receivables.js +179 -0
- package/dist/schema/relations.d.ts +82 -0
- package/dist/schema/relations.d.ts.map +1 -0
- package/dist/schema/relations.js +144 -0
- package/dist/schema/supplier-invoices.d.ts +1619 -0
- package/dist/schema/supplier-invoices.d.ts.map +1 -0
- package/dist/schema/supplier-invoices.js +228 -0
- package/dist/schema/tax.d.ts +712 -0
- package/dist/schema/tax.d.ts.map +1 -0
- package/dist/schema/tax.js +98 -0
- package/dist/schema/vouchers.d.ts +444 -0
- package/dist/schema/vouchers.d.ts.map +1 -0
- package/dist/schema/vouchers.js +64 -0
- package/dist/schema.d.ts +12 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +11 -0
- package/dist/service-accountant-shares.d.ts +106 -0
- package/dist/service-accountant-shares.d.ts.map +1 -0
- package/dist/service-accountant-shares.js +331 -0
- package/dist/service-action-ledger-accounting.d.ts +104 -0
- package/dist/service-action-ledger-accounting.d.ts.map +1 -0
- package/dist/service-action-ledger-accounting.js +386 -0
- package/dist/service-action-ledger-booking-payments.d.ts +48 -0
- package/dist/service-action-ledger-booking-payments.d.ts.map +1 -0
- package/dist/service-action-ledger-booking-payments.js +178 -0
- package/dist/service-action-ledger-bookings.d.ts +44 -0
- package/dist/service-action-ledger-bookings.d.ts.map +1 -0
- package/dist/service-action-ledger-bookings.js +81 -0
- package/dist/service-action-ledger-payment-authorizations.d.ts +48 -0
- package/dist/service-action-ledger-payment-authorizations.d.ts.map +1 -0
- package/dist/service-action-ledger-payment-authorizations.js +209 -0
- package/dist/service-action-ledger-payment-sessions.d.ts +83 -0
- package/dist/service-action-ledger-payment-sessions.d.ts.map +1 -0
- package/dist/service-action-ledger-payment-sessions.js +294 -0
- package/dist/service-action-ledger-supplier-invoices.d.ts +27 -0
- package/dist/service-action-ledger-supplier-invoices.d.ts.map +1 -0
- package/dist/service-action-ledger-supplier-invoices.js +111 -0
- package/dist/service-action-ledger-supplier-payments.d.ts +21 -0
- package/dist/service-action-ledger-supplier-payments.d.ts.map +1 -0
- package/dist/service-action-ledger-supplier-payments.js +97 -0
- package/dist/service-action-ledger.d.ts +7 -0
- package/dist/service-action-ledger.d.ts.map +1 -0
- package/dist/service-action-ledger.js +6 -0
- package/dist/service-aggregates.d.ts +96 -0
- package/dist/service-aggregates.d.ts.map +1 -0
- package/dist/service-aggregates.js +294 -0
- package/dist/service-booking-billing.d.ts +2322 -0
- package/dist/service-booking-billing.d.ts.map +1 -0
- package/dist/service-booking-billing.js +8 -0
- package/dist/service-booking-create.d.ts +410 -0
- package/dist/service-booking-create.d.ts.map +1 -0
- package/dist/service-booking-create.js +1256 -0
- package/dist/service-booking-guarantees.d.ts +725 -0
- package/dist/service-booking-guarantees.d.ts.map +1 -0
- package/dist/service-booking-guarantees.js +153 -0
- package/dist/service-booking-item-billing.d.ts +1062 -0
- package/dist/service-booking-item-billing.d.ts.map +1 -0
- package/dist/service-booking-item-billing.js +77 -0
- package/dist/service-booking-payment-schedules.d.ts +557 -0
- package/dist/service-booking-payment-schedules.d.ts.map +1 -0
- package/dist/service-booking-payment-schedules.js +372 -0
- package/dist/service-bookings-dual-create.d.ts +308 -0
- package/dist/service-bookings-dual-create.d.ts.map +1 -0
- package/dist/service-bookings-dual-create.js +131 -0
- package/dist/service-boundary-sql.d.ts +6 -0
- package/dist/service-boundary-sql.d.ts.map +1 -0
- package/dist/service-boundary-sql.js +15 -0
- package/dist/service-cost-categories.d.ts +26 -0
- package/dist/service-cost-categories.d.ts.map +1 -0
- package/dist/service-cost-categories.js +76 -0
- package/dist/service-documents.d.ts +80 -0
- package/dist/service-documents.d.ts.map +1 -0
- package/dist/service-documents.js +228 -0
- package/dist/service-invoice-artifacts.d.ts +246 -0
- package/dist/service-invoice-artifacts.d.ts.map +1 -0
- package/dist/service-invoice-artifacts.js +277 -0
- package/dist/service-invoice-core.d.ts +405 -0
- package/dist/service-invoice-core.d.ts.map +1 -0
- package/dist/service-invoice-core.js +290 -0
- package/dist/service-invoice-credit-notes.d.ts +973 -0
- package/dist/service-invoice-credit-notes.d.ts.map +1 -0
- package/dist/service-invoice-credit-notes.js +142 -0
- package/dist/service-invoice-from-booking.d.ts +41 -0
- package/dist/service-invoice-from-booking.d.ts.map +1 -0
- package/dist/service-invoice-from-booking.js +267 -0
- package/dist/service-invoice-line-items.d.ts +432 -0
- package/dist/service-invoice-line-items.d.ts.map +1 -0
- package/dist/service-invoice-line-items.js +102 -0
- package/dist/service-invoice-numbering.d.ts +227 -0
- package/dist/service-invoice-numbering.d.ts.map +1 -0
- package/dist/service-invoice-numbering.js +260 -0
- package/dist/service-invoice-payments.d.ts +673 -0
- package/dist/service-invoice-payments.d.ts.map +1 -0
- package/dist/service-invoice-payments.js +398 -0
- package/dist/service-invoices.d.ts +2501 -0
- package/dist/service-invoices.d.ts.map +1 -0
- package/dist/service-invoices.js +12 -0
- package/dist/service-issue.d.ts +207 -0
- package/dist/service-issue.d.ts.map +1 -0
- package/dist/service-issue.js +431 -0
- package/dist/service-payment-authorizations.d.ts +164 -0
- package/dist/service-payment-authorizations.d.ts.map +1 -0
- package/dist/service-payment-authorizations.js +227 -0
- package/dist/service-payment-instruments.d.ts +116 -0
- package/dist/service-payment-instruments.d.ts.map +1 -0
- package/dist/service-payment-instruments.js +99 -0
- package/dist/service-payment-processing.d.ts +676 -0
- package/dist/service-payment-processing.d.ts.map +1 -0
- package/dist/service-payment-processing.js +10 -0
- package/dist/service-payment-session-completion.d.ts +48 -0
- package/dist/service-payment-session-completion.d.ts.map +1 -0
- package/dist/service-payment-session-completion.js +238 -0
- package/dist/service-payment-sessions.d.ts +361 -0
- package/dist/service-payment-sessions.d.ts.map +1 -0
- package/dist/service-payment-sessions.js +280 -0
- package/dist/service-profitability.d.ts +114 -0
- package/dist/service-profitability.d.ts.map +1 -0
- package/dist/service-profitability.js +794 -0
- package/dist/service-public.d.ts +553 -0
- package/dist/service-public.d.ts.map +1 -0
- package/dist/service-public.js +583 -0
- package/dist/service-reference-data.d.ts +272 -0
- package/dist/service-reference-data.d.ts.map +1 -0
- package/dist/service-reference-data.js +280 -0
- package/dist/service-rendition-wait.d.ts +38 -0
- package/dist/service-rendition-wait.d.ts.map +1 -0
- package/dist/service-rendition-wait.js +67 -0
- package/dist/service-reports.d.ts +37 -0
- package/dist/service-reports.d.ts.map +1 -0
- package/dist/service-reports.js +62 -0
- package/dist/service-settlement.d.ts +46 -0
- package/dist/service-settlement.d.ts.map +1 -0
- package/dist/service-settlement.js +185 -0
- package/dist/service-shared.d.ts +541 -0
- package/dist/service-shared.d.ts.map +1 -0
- package/dist/service-shared.js +764 -0
- package/dist/service-supplier-invoices.d.ts +871 -0
- package/dist/service-supplier-invoices.d.ts.map +1 -0
- package/dist/service-supplier-invoices.js +744 -0
- package/dist/service-supplier-payments.d.ts +69 -0
- package/dist/service-supplier-payments.d.ts.map +1 -0
- package/dist/service-supplier-payments.js +136 -0
- package/dist/service-vouchers-migration.d.ts +44 -0
- package/dist/service-vouchers-migration.d.ts.map +1 -0
- package/dist/service-vouchers-migration.js +148 -0
- package/dist/service-vouchers.d.ts +157 -0
- package/dist/service-vouchers.d.ts.map +1 -0
- package/dist/service-vouchers.js +191 -0
- package/dist/service.d.ts +6490 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +29 -0
- package/dist/validation-billing.d.ts +2 -0
- package/dist/validation-billing.d.ts.map +1 -0
- package/dist/validation-billing.js +1 -0
- package/dist/validation-payments.d.ts +2 -0
- package/dist/validation-payments.d.ts.map +1 -0
- package/dist/validation-payments.js +1 -0
- package/dist/validation-public.d.ts +2 -0
- package/dist/validation-public.d.ts.map +1 -0
- package/dist/validation-public.js +1 -0
- package/dist/validation-shared.d.ts +2 -0
- package/dist/validation-shared.d.ts.map +1 -0
- package/dist/validation-shared.js +1 -0
- package/dist/validation-vouchers.d.ts +2 -0
- package/dist/validation-vouchers.d.ts.map +1 -0
- package/dist/validation-vouchers.js +1 -0
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +1 -0
- package/package.json +121 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { and, appendActionLedgerMutation, assertInvoiceAcceptsNewPayment, assertPaymentCanSettleInvoice, buildPaymentDeleteActionLedgerInput, buildPaymentUpdateActionLedgerInput, buildRecordPaymentActionLedgerInput, desc, eq, getPaymentFromReplayedLedgerEntry, invoices, mapRawPayment, newId, paymentSettlementAmountSql, payments, recomputeInvoiceTotalsAfterPaymentChange, resolveFxMoneyBaseAmount, shouldNormalizeBaseAmount, sql, toRows, } from "./service-shared.js";
|
|
2
|
+
export const financeInvoicePaymentService = {
|
|
3
|
+
listPayments(db, invoiceId) {
|
|
4
|
+
return db
|
|
5
|
+
.select()
|
|
6
|
+
.from(payments)
|
|
7
|
+
.where(eq(payments.invoiceId, invoiceId))
|
|
8
|
+
.orderBy(desc(payments.paymentDate));
|
|
9
|
+
},
|
|
10
|
+
async listAllPayments(db, query) {
|
|
11
|
+
// The unified view UNIONs `payments` (customer-side, FK to invoices) and
|
|
12
|
+
// `supplier_payments` (FK to bookings + suppliers). Filters that only make
|
|
13
|
+
// sense for one side (invoiceId / supplierId) implicitly exclude the
|
|
14
|
+
// other; the explicit `kind` filter takes precedence.
|
|
15
|
+
const includeCustomer = (!query.kind || query.kind === "customer") && !query.supplierId;
|
|
16
|
+
const includeSupplier = (!query.kind || query.kind === "supplier") && !query.invoiceId;
|
|
17
|
+
if (!includeCustomer && !includeSupplier) {
|
|
18
|
+
return { data: [], total: 0, limit: query.limit, offset: query.offset };
|
|
19
|
+
}
|
|
20
|
+
const customerConditions = [sql `true`];
|
|
21
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
22
|
+
if (query.status)
|
|
23
|
+
customerConditions.push(sql `p.status = ${query.status}`);
|
|
24
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
25
|
+
if (query.paymentMethod)
|
|
26
|
+
customerConditions.push(sql `p.payment_method = ${query.paymentMethod}`);
|
|
27
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
28
|
+
if (query.currency)
|
|
29
|
+
customerConditions.push(sql `p.currency = ${query.currency}`);
|
|
30
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
31
|
+
if (query.invoiceId)
|
|
32
|
+
customerConditions.push(sql `p.invoice_id = ${query.invoiceId}`);
|
|
33
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
34
|
+
if (query.bookingId)
|
|
35
|
+
customerConditions.push(sql `i.booking_id = ${query.bookingId}`);
|
|
36
|
+
if (query.paymentDateFrom)
|
|
37
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
38
|
+
customerConditions.push(sql `p.payment_date >= ${query.paymentDateFrom}`);
|
|
39
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
40
|
+
if (query.paymentDateTo)
|
|
41
|
+
customerConditions.push(sql `p.payment_date <= ${query.paymentDateTo}`);
|
|
42
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
43
|
+
if (query.search)
|
|
44
|
+
customerConditions.push(sql `p.reference_number ILIKE ${`%${query.search}%`}`);
|
|
45
|
+
const customerWhere = sql.join(customerConditions, sql ` AND `);
|
|
46
|
+
const supplierConditions = [sql `true`];
|
|
47
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
48
|
+
if (query.status)
|
|
49
|
+
supplierConditions.push(sql `sp.status = ${query.status}`);
|
|
50
|
+
if (query.paymentMethod)
|
|
51
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
52
|
+
supplierConditions.push(sql `sp.payment_method = ${query.paymentMethod}`);
|
|
53
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
54
|
+
if (query.currency)
|
|
55
|
+
supplierConditions.push(sql `sp.currency = ${query.currency}`);
|
|
56
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
57
|
+
if (query.bookingId)
|
|
58
|
+
supplierConditions.push(sql `sp.booking_id = ${query.bookingId}`);
|
|
59
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
60
|
+
if (query.supplierId)
|
|
61
|
+
supplierConditions.push(sql `sp.supplier_id = ${query.supplierId}`);
|
|
62
|
+
if (query.paymentDateFrom)
|
|
63
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
64
|
+
supplierConditions.push(sql `sp.payment_date >= ${query.paymentDateFrom}`);
|
|
65
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
66
|
+
if (query.paymentDateTo)
|
|
67
|
+
supplierConditions.push(sql `sp.payment_date <= ${query.paymentDateTo}`);
|
|
68
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
69
|
+
if (query.search)
|
|
70
|
+
supplierConditions.push(sql `sp.reference_number ILIKE ${`%${query.search}%`}`);
|
|
71
|
+
const supplierWhere = sql.join(supplierConditions, sql ` AND `);
|
|
72
|
+
const customerSelect = sql `
|
|
73
|
+
SELECT
|
|
74
|
+
'customer'::text AS kind,
|
|
75
|
+
p.id AS id,
|
|
76
|
+
p.invoice_id AS invoice_id,
|
|
77
|
+
i.invoice_number AS invoice_number,
|
|
78
|
+
NULL::text AS booking_id,
|
|
79
|
+
NULL::text AS booking_number,
|
|
80
|
+
NULL::text AS supplier_id,
|
|
81
|
+
NULL::text AS supplier_name,
|
|
82
|
+
i.person_id AS person_id,
|
|
83
|
+
pe.first_name AS person_first_name,
|
|
84
|
+
pe.last_name AS person_last_name,
|
|
85
|
+
i.organization_id AS organization_id,
|
|
86
|
+
o.name AS organization_name,
|
|
87
|
+
p.amount_cents AS amount_cents,
|
|
88
|
+
p.currency AS currency,
|
|
89
|
+
p.base_currency AS base_currency,
|
|
90
|
+
p.base_amount_cents AS base_amount_cents,
|
|
91
|
+
p.payment_method::text AS payment_method,
|
|
92
|
+
p.status::text AS status,
|
|
93
|
+
p.reference_number AS reference_number,
|
|
94
|
+
p.payment_date AS payment_date,
|
|
95
|
+
p.notes AS notes,
|
|
96
|
+
p.created_at AS created_at,
|
|
97
|
+
p.updated_at AS updated_at
|
|
98
|
+
FROM payments p
|
|
99
|
+
LEFT JOIN invoices i ON i.id = p.invoice_id
|
|
100
|
+
LEFT JOIN people pe ON pe.id = i.person_id
|
|
101
|
+
LEFT JOIN organizations o ON o.id = i.organization_id
|
|
102
|
+
WHERE ${customerWhere}
|
|
103
|
+
`;
|
|
104
|
+
const supplierSelect = sql `
|
|
105
|
+
SELECT
|
|
106
|
+
'supplier'::text AS kind,
|
|
107
|
+
sp.id AS id,
|
|
108
|
+
NULL::text AS invoice_id,
|
|
109
|
+
NULL::text AS invoice_number,
|
|
110
|
+
sp.booking_id AS booking_id,
|
|
111
|
+
b.booking_number AS booking_number,
|
|
112
|
+
sp.supplier_id AS supplier_id,
|
|
113
|
+
s.name AS supplier_name,
|
|
114
|
+
NULL::text AS person_id,
|
|
115
|
+
NULL::text AS person_first_name,
|
|
116
|
+
NULL::text AS person_last_name,
|
|
117
|
+
NULL::text AS organization_id,
|
|
118
|
+
NULL::text AS organization_name,
|
|
119
|
+
sp.amount_cents AS amount_cents,
|
|
120
|
+
sp.currency AS currency,
|
|
121
|
+
sp.base_currency AS base_currency,
|
|
122
|
+
sp.base_amount_cents AS base_amount_cents,
|
|
123
|
+
sp.payment_method::text AS payment_method,
|
|
124
|
+
sp.status::text AS status,
|
|
125
|
+
sp.reference_number AS reference_number,
|
|
126
|
+
sp.payment_date AS payment_date,
|
|
127
|
+
sp.notes AS notes,
|
|
128
|
+
sp.created_at AS created_at,
|
|
129
|
+
sp.updated_at AS updated_at
|
|
130
|
+
FROM supplier_payments sp
|
|
131
|
+
LEFT JOIN bookings b ON b.id = sp.booking_id
|
|
132
|
+
LEFT JOIN suppliers s ON s.id = sp.supplier_id
|
|
133
|
+
WHERE ${supplierWhere}
|
|
134
|
+
`;
|
|
135
|
+
const unionParts = [];
|
|
136
|
+
if (includeCustomer)
|
|
137
|
+
unionParts.push(customerSelect);
|
|
138
|
+
if (includeSupplier)
|
|
139
|
+
unionParts.push(supplierSelect);
|
|
140
|
+
const unioned = sql.join(unionParts, sql ` UNION ALL `);
|
|
141
|
+
const sortColumn = (() => {
|
|
142
|
+
switch (query.sortBy) {
|
|
143
|
+
case "amountCents":
|
|
144
|
+
return sql.raw("amount_cents");
|
|
145
|
+
case "status":
|
|
146
|
+
return sql.raw("status");
|
|
147
|
+
case "paymentDate":
|
|
148
|
+
return sql.raw("payment_date");
|
|
149
|
+
default:
|
|
150
|
+
return sql.raw("created_at");
|
|
151
|
+
}
|
|
152
|
+
})();
|
|
153
|
+
const sortDirSql = query.sortDir === "asc" ? sql.raw("ASC") : sql.raw("DESC");
|
|
154
|
+
const dataResult = await db.execute(sql `
|
|
155
|
+
SELECT * FROM (${unioned}) all_payments
|
|
156
|
+
ORDER BY ${sortColumn} ${sortDirSql}, created_at DESC
|
|
157
|
+
LIMIT ${query.limit}
|
|
158
|
+
OFFSET ${query.offset}
|
|
159
|
+
`);
|
|
160
|
+
const countResult = await db.execute(sql `
|
|
161
|
+
SELECT COUNT(*)::int AS count FROM (${unioned}) all_payments
|
|
162
|
+
`);
|
|
163
|
+
const rows = toRows(dataResult);
|
|
164
|
+
const total = toRows(countResult)[0]?.count ?? 0;
|
|
165
|
+
const data = rows.map(mapRawPayment);
|
|
166
|
+
return {
|
|
167
|
+
data,
|
|
168
|
+
total,
|
|
169
|
+
limit: query.limit,
|
|
170
|
+
offset: query.offset,
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* Resolve a unified payment by id. Dispatches by typeid prefix:
|
|
175
|
+
* `pay_*` lives in `payments` (customer side), `spay_*` in `supplier_payments`.
|
|
176
|
+
* Returns the same enriched row shape as `listAllPayments` so callers can
|
|
177
|
+
* share a single record schema.
|
|
178
|
+
*/
|
|
179
|
+
async getPaymentById(db, id) {
|
|
180
|
+
if (id.startsWith("spay_")) {
|
|
181
|
+
const result = await db.execute(sql `
|
|
182
|
+
SELECT
|
|
183
|
+
'supplier'::text AS kind,
|
|
184
|
+
sp.id AS id,
|
|
185
|
+
NULL::text AS invoice_id,
|
|
186
|
+
NULL::text AS invoice_number,
|
|
187
|
+
sp.booking_id AS booking_id,
|
|
188
|
+
b.booking_number AS booking_number,
|
|
189
|
+
sp.supplier_id AS supplier_id,
|
|
190
|
+
s.name AS supplier_name,
|
|
191
|
+
NULL::text AS person_id,
|
|
192
|
+
NULL::text AS person_first_name,
|
|
193
|
+
NULL::text AS person_last_name,
|
|
194
|
+
NULL::text AS organization_id,
|
|
195
|
+
NULL::text AS organization_name,
|
|
196
|
+
sp.amount_cents AS amount_cents,
|
|
197
|
+
sp.currency AS currency,
|
|
198
|
+
sp.base_currency AS base_currency,
|
|
199
|
+
sp.base_amount_cents AS base_amount_cents,
|
|
200
|
+
sp.payment_method::text AS payment_method,
|
|
201
|
+
sp.status::text AS status,
|
|
202
|
+
sp.reference_number AS reference_number,
|
|
203
|
+
sp.payment_date AS payment_date,
|
|
204
|
+
sp.notes AS notes,
|
|
205
|
+
sp.created_at AS created_at,
|
|
206
|
+
sp.updated_at AS updated_at
|
|
207
|
+
FROM supplier_payments sp
|
|
208
|
+
LEFT JOIN bookings b ON b.id = sp.booking_id
|
|
209
|
+
LEFT JOIN suppliers s ON s.id = sp.supplier_id
|
|
210
|
+
WHERE sp.id = ${id}
|
|
211
|
+
LIMIT 1
|
|
212
|
+
`);
|
|
213
|
+
const row = toRows(result)[0];
|
|
214
|
+
return row ? mapRawPayment(row) : null;
|
|
215
|
+
}
|
|
216
|
+
const result = await db.execute(sql `
|
|
217
|
+
SELECT
|
|
218
|
+
'customer'::text AS kind,
|
|
219
|
+
p.id AS id,
|
|
220
|
+
p.invoice_id AS invoice_id,
|
|
221
|
+
i.invoice_number AS invoice_number,
|
|
222
|
+
NULL::text AS booking_id,
|
|
223
|
+
NULL::text AS booking_number,
|
|
224
|
+
NULL::text AS supplier_id,
|
|
225
|
+
NULL::text AS supplier_name,
|
|
226
|
+
i.person_id AS person_id,
|
|
227
|
+
pe.first_name AS person_first_name,
|
|
228
|
+
pe.last_name AS person_last_name,
|
|
229
|
+
i.organization_id AS organization_id,
|
|
230
|
+
o.name AS organization_name,
|
|
231
|
+
p.amount_cents AS amount_cents,
|
|
232
|
+
p.currency AS currency,
|
|
233
|
+
p.base_currency AS base_currency,
|
|
234
|
+
p.base_amount_cents AS base_amount_cents,
|
|
235
|
+
p.payment_method::text AS payment_method,
|
|
236
|
+
p.status::text AS status,
|
|
237
|
+
p.reference_number AS reference_number,
|
|
238
|
+
p.payment_date AS payment_date,
|
|
239
|
+
p.notes AS notes,
|
|
240
|
+
p.created_at AS created_at,
|
|
241
|
+
p.updated_at AS updated_at
|
|
242
|
+
FROM payments p
|
|
243
|
+
LEFT JOIN invoices i ON i.id = p.invoice_id
|
|
244
|
+
LEFT JOIN people pe ON pe.id = i.person_id
|
|
245
|
+
LEFT JOIN organizations o ON o.id = i.organization_id
|
|
246
|
+
WHERE p.id = ${id}
|
|
247
|
+
LIMIT 1
|
|
248
|
+
`);
|
|
249
|
+
const row = toRows(result)[0];
|
|
250
|
+
return row ? mapRawPayment(row) : null;
|
|
251
|
+
},
|
|
252
|
+
async createPayment(db, invoiceId, data, runtime = {}) {
|
|
253
|
+
const [invoice] = await db.select().from(invoices).where(eq(invoices.id, invoiceId)).limit(1);
|
|
254
|
+
if (!invoice) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
await assertInvoiceAcceptsNewPayment(db, invoice);
|
|
258
|
+
const { idempotencyKey: requestedIdempotencyKey, ...paymentInput } = data;
|
|
259
|
+
const paymentData = await resolveFxMoneyBaseAmount(db, paymentInput, {
|
|
260
|
+
...runtime,
|
|
261
|
+
targetBaseCurrency: invoice.currency,
|
|
262
|
+
fallbackFxRateSetId: invoice.fxRateSetId ?? null,
|
|
263
|
+
date: data.paymentDate,
|
|
264
|
+
});
|
|
265
|
+
assertPaymentCanSettleInvoice(invoice.currency, paymentData);
|
|
266
|
+
const paymentId = newId("payments");
|
|
267
|
+
return db.transaction(async (tx) => {
|
|
268
|
+
if (runtime.actionLedgerContext) {
|
|
269
|
+
const ledgerResult = await appendActionLedgerMutation(tx, await buildRecordPaymentActionLedgerInput(runtime.actionLedgerContext, {
|
|
270
|
+
invoice,
|
|
271
|
+
payment: {
|
|
272
|
+
...paymentData,
|
|
273
|
+
id: paymentId,
|
|
274
|
+
invoiceId,
|
|
275
|
+
},
|
|
276
|
+
}, {
|
|
277
|
+
authorizationSource: runtime.actionLedgerAuthorizationSource,
|
|
278
|
+
idempotencyKey: requestedIdempotencyKey,
|
|
279
|
+
}));
|
|
280
|
+
if (ledgerResult.replayed) {
|
|
281
|
+
return getPaymentFromReplayedLedgerEntry(tx, ledgerResult.entry.id);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const [payment] = await tx
|
|
285
|
+
.insert(payments)
|
|
286
|
+
.values({
|
|
287
|
+
id: paymentId,
|
|
288
|
+
...paymentData,
|
|
289
|
+
invoiceId,
|
|
290
|
+
paymentInstrumentId: paymentData.paymentInstrumentId ?? null,
|
|
291
|
+
paymentAuthorizationId: paymentData.paymentAuthorizationId ?? null,
|
|
292
|
+
paymentCaptureId: paymentData.paymentCaptureId ?? null,
|
|
293
|
+
})
|
|
294
|
+
.returning();
|
|
295
|
+
const [sumResult] = await tx
|
|
296
|
+
.select({ total: paymentSettlementAmountSql(invoice.currency) })
|
|
297
|
+
.from(payments)
|
|
298
|
+
.where(and(eq(payments.invoiceId, invoiceId), eq(payments.status, "completed")));
|
|
299
|
+
const paidCents = sumResult?.total ?? 0;
|
|
300
|
+
const balanceDueCents = Math.max(0, invoice.totalCents - paidCents);
|
|
301
|
+
let newStatus = invoice.status;
|
|
302
|
+
if (paidCents >= invoice.totalCents) {
|
|
303
|
+
newStatus = "paid";
|
|
304
|
+
}
|
|
305
|
+
else if (paidCents > 0) {
|
|
306
|
+
newStatus = "partially_paid";
|
|
307
|
+
}
|
|
308
|
+
await tx
|
|
309
|
+
.update(invoices)
|
|
310
|
+
.set({ paidCents, balanceDueCents, status: newStatus, updatedAt: new Date() })
|
|
311
|
+
.where(eq(invoices.id, invoiceId));
|
|
312
|
+
return payment;
|
|
313
|
+
});
|
|
314
|
+
},
|
|
315
|
+
async updatePayment(db, id, data, runtime = {}) {
|
|
316
|
+
const [existing] = await db.select().from(payments).where(eq(payments.id, id)).limit(1);
|
|
317
|
+
if (!existing) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const [invoice] = await db
|
|
321
|
+
.select()
|
|
322
|
+
.from(invoices)
|
|
323
|
+
.where(eq(invoices.id, existing.invoiceId))
|
|
324
|
+
.limit(1);
|
|
325
|
+
if (!invoice) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
// Merge the patch onto the existing row so FX validation sees the
|
|
329
|
+
// post-update settlement shape. Without this, a PATCH that flips a
|
|
330
|
+
// completed payment to a non-invoice currency without supplying a
|
|
331
|
+
// base amount would silently corrupt invoice totals (the row stays
|
|
332
|
+
// "completed" but contributes 0 to paid_cents).
|
|
333
|
+
const merged = {
|
|
334
|
+
amountCents: data.amountCents ?? existing.amountCents,
|
|
335
|
+
currency: data.currency ?? existing.currency,
|
|
336
|
+
baseCurrency: data.baseCurrency !== undefined ? data.baseCurrency : (existing.baseCurrency ?? null),
|
|
337
|
+
baseAmountCents: data.baseAmountCents !== undefined
|
|
338
|
+
? data.baseAmountCents
|
|
339
|
+
: (existing.baseAmountCents ?? null),
|
|
340
|
+
fxRateSetId: data.fxRateSetId !== undefined ? data.fxRateSetId : (existing.fxRateSetId ?? null),
|
|
341
|
+
paymentMethod: data.paymentMethod ?? existing.paymentMethod,
|
|
342
|
+
status: data.status ?? existing.status,
|
|
343
|
+
paymentDate: data.paymentDate ?? existing.paymentDate,
|
|
344
|
+
};
|
|
345
|
+
const normalized = shouldNormalizeBaseAmount(data)
|
|
346
|
+
? await resolveFxMoneyBaseAmount(db, merged, {
|
|
347
|
+
...runtime,
|
|
348
|
+
targetBaseCurrency: invoice.currency,
|
|
349
|
+
fallbackFxRateSetId: invoice.fxRateSetId ?? null,
|
|
350
|
+
date: merged.paymentDate,
|
|
351
|
+
})
|
|
352
|
+
: merged;
|
|
353
|
+
assertPaymentCanSettleInvoice(invoice.currency, normalized);
|
|
354
|
+
return db.transaction(async (tx) => {
|
|
355
|
+
const writePatch = { ...data, updatedAt: new Date() };
|
|
356
|
+
// resolveFxMoneyBaseAmount may have filled in baseCurrency / baseAmountCents /
|
|
357
|
+
// fxRateSetId — persist those even if the caller didn't include them.
|
|
358
|
+
writePatch.baseCurrency = normalized.baseCurrency ?? null;
|
|
359
|
+
writePatch.baseAmountCents = normalized.baseAmountCents ?? null;
|
|
360
|
+
writePatch.fxRateSetId = normalized.fxRateSetId ?? null;
|
|
361
|
+
const [payment] = await tx
|
|
362
|
+
.update(payments)
|
|
363
|
+
.set(writePatch)
|
|
364
|
+
.where(eq(payments.id, id))
|
|
365
|
+
.returning();
|
|
366
|
+
if (!payment) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
await recomputeInvoiceTotalsAfterPaymentChange(tx, invoice);
|
|
370
|
+
if (runtime.actionLedgerContext) {
|
|
371
|
+
await appendActionLedgerMutation(tx, buildPaymentUpdateActionLedgerInput(runtime.actionLedgerContext, { invoice, payment, changes: data }, { authorizationSource: runtime.actionLedgerAuthorizationSource }));
|
|
372
|
+
}
|
|
373
|
+
return payment;
|
|
374
|
+
});
|
|
375
|
+
},
|
|
376
|
+
async deletePayment(db, id, runtime = {}) {
|
|
377
|
+
return db.transaction(async (tx) => {
|
|
378
|
+
const [existing] = await tx.select().from(payments).where(eq(payments.id, id)).limit(1);
|
|
379
|
+
if (!existing) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
const [invoice] = await tx
|
|
383
|
+
.select()
|
|
384
|
+
.from(invoices)
|
|
385
|
+
.where(eq(invoices.id, existing.invoiceId))
|
|
386
|
+
.limit(1);
|
|
387
|
+
if (!invoice) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
await tx.delete(payments).where(eq(payments.id, id));
|
|
391
|
+
await recomputeInvoiceTotalsAfterPaymentChange(tx, invoice);
|
|
392
|
+
if (runtime.actionLedgerContext) {
|
|
393
|
+
await appendActionLedgerMutation(tx, buildPaymentDeleteActionLedgerInput(runtime.actionLedgerContext, { invoice, payment: existing }, { authorizationSource: runtime.actionLedgerAuthorizationSource }));
|
|
394
|
+
}
|
|
395
|
+
return existing;
|
|
396
|
+
});
|
|
397
|
+
},
|
|
398
|
+
};
|