@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 @@
|
|
|
1
|
+
{"version":3,"file":"service-invoices.d.ts","sourceRoot":"","sources":["../src/service-invoices.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMjC,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { financeInvoiceCoreService } from "./service-invoice-core.js";
|
|
2
|
+
import { financeInvoiceCreditNoteService } from "./service-invoice-credit-notes.js";
|
|
3
|
+
import { financeInvoiceFromBookingService } from "./service-invoice-from-booking.js";
|
|
4
|
+
import { financeInvoiceLineItemService } from "./service-invoice-line-items.js";
|
|
5
|
+
import { financeInvoicePaymentService } from "./service-invoice-payments.js";
|
|
6
|
+
export const financeInvoiceService = {
|
|
7
|
+
...financeInvoiceCoreService,
|
|
8
|
+
...financeInvoiceFromBookingService,
|
|
9
|
+
...financeInvoiceLineItemService,
|
|
10
|
+
...financeInvoicePaymentService,
|
|
11
|
+
...financeInvoiceCreditNoteService,
|
|
12
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import { bookingPaymentSchedules, invoices } from "./schema.js";
|
|
3
|
+
import { type CreateInvoiceFromBookingInput, type FinanceServiceRuntime, type InvoiceFromBookingData } from "./service.js";
|
|
4
|
+
/**
|
|
5
|
+
* Issue / proforma orchestration helpers that wrap the bare invoice
|
|
6
|
+
* creators with EventBus emissions so subscribers (SmartBill plugin,
|
|
7
|
+
* checkout-finalize workflow) can react.
|
|
8
|
+
*
|
|
9
|
+
* `createInvoice*` services in `service.ts` stay pure DB writers —
|
|
10
|
+
* the workflow / route layer chooses when to mark them as "issued"
|
|
11
|
+
* (a status change that's also a system signal).
|
|
12
|
+
*/
|
|
13
|
+
export interface InvoiceIssueRuntime extends FinanceServiceRuntime {
|
|
14
|
+
}
|
|
15
|
+
interface ExistingConvertedInvoicePointer {
|
|
16
|
+
id: string;
|
|
17
|
+
invoiceNumber: string;
|
|
18
|
+
}
|
|
19
|
+
export interface InvoiceIssuedEvent {
|
|
20
|
+
invoiceId: string;
|
|
21
|
+
invoiceNumber: string;
|
|
22
|
+
invoiceType: "invoice" | "proforma" | "credit_note";
|
|
23
|
+
bookingId: string | null;
|
|
24
|
+
totalCents: number;
|
|
25
|
+
currency: string;
|
|
26
|
+
/** Operator accounting/reporting currency when different from `currency`. */
|
|
27
|
+
baseCurrency?: string;
|
|
28
|
+
/** Rate set used to resolve `currency` -> `baseCurrency`, when available. */
|
|
29
|
+
fxRateSetId?: string;
|
|
30
|
+
/** Spot rate for `currency` → `baseCurrency`. */
|
|
31
|
+
fxRate?: number;
|
|
32
|
+
/** Provider or reference source for `fxRate`, for example `bnr`. */
|
|
33
|
+
fxRateSource?: string;
|
|
34
|
+
/** Provider timestamp for the quoted spot rate. */
|
|
35
|
+
fxRateQuotedAt?: string;
|
|
36
|
+
/** Provider timestamp after which the quoted spot rate is stale. */
|
|
37
|
+
fxRateValidUntil?: string;
|
|
38
|
+
/** Operator FX commission added on top of the spot rate. */
|
|
39
|
+
fxCommissionBps?: number;
|
|
40
|
+
/** `fxRate` after commission. Invoice providers should prefer this rate. */
|
|
41
|
+
effectiveRate?: number;
|
|
42
|
+
/** Optional invoice mention appended by providers when commission is non-zero. */
|
|
43
|
+
fxCommissionInvoiceMention?: string;
|
|
44
|
+
/** Linkage when this invoice replaced a proforma. */
|
|
45
|
+
convertedFromInvoiceId?: string | null;
|
|
46
|
+
clientName?: string;
|
|
47
|
+
clientEmail?: string | null;
|
|
48
|
+
clientPhone?: string | null;
|
|
49
|
+
clientAddress?: string | null;
|
|
50
|
+
clientCity?: string | null;
|
|
51
|
+
clientCounty?: string | null;
|
|
52
|
+
clientCountry?: string | null;
|
|
53
|
+
clientVatCode?: string | null;
|
|
54
|
+
clientRegCom?: string | null;
|
|
55
|
+
lineItems?: InvoiceIssuedLineItem[];
|
|
56
|
+
bookingNumber?: string | null;
|
|
57
|
+
issueDate?: string;
|
|
58
|
+
dueDate?: string;
|
|
59
|
+
externalAllocationRequired?: boolean;
|
|
60
|
+
externalProvider?: string | null;
|
|
61
|
+
externalConfigKey?: string | null;
|
|
62
|
+
externalSeriesId?: string | null;
|
|
63
|
+
externalPlaceholderNumber?: string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Per-issuance opt-out flag. When `true`, e-invoicing plugins
|
|
66
|
+
* (SmartBill etc.) ignore this event instead of pushing the document
|
|
67
|
+
* upstream. Origin: `invoiceFromBookingSchema.skipExternalSync`.
|
|
68
|
+
*/
|
|
69
|
+
skipExternalSync?: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface InvoiceIssuedLineItem {
|
|
72
|
+
description: string;
|
|
73
|
+
quantity: number;
|
|
74
|
+
unitPrice: number;
|
|
75
|
+
currency: string;
|
|
76
|
+
bookingPaymentScheduleId?: string;
|
|
77
|
+
scheduleType?: (typeof bookingPaymentSchedules.$inferSelect)["scheduleType"];
|
|
78
|
+
schedulePercent?: number;
|
|
79
|
+
taxPercentage?: number;
|
|
80
|
+
taxName?: string | null;
|
|
81
|
+
taxRegimeCode?: TaxRegimeCode | null;
|
|
82
|
+
isService?: boolean;
|
|
83
|
+
}
|
|
84
|
+
export type TaxRegimeCode = "standard" | "reduced" | "exempt" | "reverse_charge" | "margin_scheme_art311" | "zero_rated" | "out_of_scope" | "other";
|
|
85
|
+
export interface InvoiceProformaConvertedEvent extends InvoiceIssuedEvent {
|
|
86
|
+
id: string;
|
|
87
|
+
proformaId: string;
|
|
88
|
+
proformaInvoiceNumber: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create + emit an invoice from a booking. Returns the persisted row
|
|
92
|
+
* after flipping the status from `draft` to `issued`. Drafts shouldn't
|
|
93
|
+
* trigger SmartBill sync.
|
|
94
|
+
*/
|
|
95
|
+
export declare function issueInvoiceFromBooking(db: PostgresJsDatabase, input: CreateInvoiceFromBookingInput, bookingData: InvoiceFromBookingData, runtime?: InvoiceIssueRuntime): Promise<{
|
|
96
|
+
subtotalCents: number;
|
|
97
|
+
currency: string;
|
|
98
|
+
id: string;
|
|
99
|
+
createdAt: Date;
|
|
100
|
+
updatedAt: Date;
|
|
101
|
+
taxRegimeId: string | null;
|
|
102
|
+
taxCents: number;
|
|
103
|
+
totalCents: number;
|
|
104
|
+
status: "void" | "paid" | "draft" | "pending_external_allocation" | "issued" | "partially_paid" | "overdue";
|
|
105
|
+
bookingId: string;
|
|
106
|
+
notes: string | null;
|
|
107
|
+
templateId: string | null;
|
|
108
|
+
dueDate: string;
|
|
109
|
+
invoiceNumber: string;
|
|
110
|
+
invoiceType: "invoice" | "proforma" | "credit_note";
|
|
111
|
+
convertedFromInvoiceId: string | null;
|
|
112
|
+
seriesId: string | null;
|
|
113
|
+
sequence: number | null;
|
|
114
|
+
language: string | null;
|
|
115
|
+
personId: string | null;
|
|
116
|
+
organizationId: string | null;
|
|
117
|
+
baseCurrency: string | null;
|
|
118
|
+
fxRateSetId: string | null;
|
|
119
|
+
baseSubtotalCents: number | null;
|
|
120
|
+
baseTaxCents: number | null;
|
|
121
|
+
baseTotalCents: number | null;
|
|
122
|
+
paidCents: number;
|
|
123
|
+
basePaidCents: number | null;
|
|
124
|
+
balanceDueCents: number;
|
|
125
|
+
baseBalanceDueCents: number | null;
|
|
126
|
+
commissionPercent: number | null;
|
|
127
|
+
commissionAmountCents: number | null;
|
|
128
|
+
issueDate: string;
|
|
129
|
+
voidedAt: Date | null;
|
|
130
|
+
voidReason: string | null;
|
|
131
|
+
} | null>;
|
|
132
|
+
/**
|
|
133
|
+
* Create + emit a proforma from a booking. Same shape as
|
|
134
|
+
* `issueInvoiceFromBooking` but marks the row as `invoiceType:
|
|
135
|
+
* 'proforma'` and emits the proforma-specific event so the
|
|
136
|
+
* SmartBill plugin can route to its proforma endpoint.
|
|
137
|
+
*/
|
|
138
|
+
export declare function issueProformaFromBooking(db: PostgresJsDatabase, input: CreateInvoiceFromBookingInput, bookingData: InvoiceFromBookingData, runtime?: InvoiceIssueRuntime): Promise<{
|
|
139
|
+
subtotalCents: number;
|
|
140
|
+
currency: string;
|
|
141
|
+
id: string;
|
|
142
|
+
createdAt: Date;
|
|
143
|
+
updatedAt: Date;
|
|
144
|
+
taxRegimeId: string | null;
|
|
145
|
+
taxCents: number;
|
|
146
|
+
totalCents: number;
|
|
147
|
+
status: "void" | "paid" | "draft" | "pending_external_allocation" | "issued" | "partially_paid" | "overdue";
|
|
148
|
+
bookingId: string;
|
|
149
|
+
notes: string | null;
|
|
150
|
+
templateId: string | null;
|
|
151
|
+
dueDate: string;
|
|
152
|
+
invoiceNumber: string;
|
|
153
|
+
invoiceType: "invoice" | "proforma" | "credit_note";
|
|
154
|
+
convertedFromInvoiceId: string | null;
|
|
155
|
+
seriesId: string | null;
|
|
156
|
+
sequence: number | null;
|
|
157
|
+
language: string | null;
|
|
158
|
+
personId: string | null;
|
|
159
|
+
organizationId: string | null;
|
|
160
|
+
baseCurrency: string | null;
|
|
161
|
+
fxRateSetId: string | null;
|
|
162
|
+
baseSubtotalCents: number | null;
|
|
163
|
+
baseTaxCents: number | null;
|
|
164
|
+
baseTotalCents: number | null;
|
|
165
|
+
paidCents: number;
|
|
166
|
+
basePaidCents: number | null;
|
|
167
|
+
balanceDueCents: number;
|
|
168
|
+
baseBalanceDueCents: number | null;
|
|
169
|
+
commissionPercent: number | null;
|
|
170
|
+
commissionAmountCents: number | null;
|
|
171
|
+
issueDate: string;
|
|
172
|
+
voidedAt: Date | null;
|
|
173
|
+
voidReason: string | null;
|
|
174
|
+
} | null>;
|
|
175
|
+
export declare function buildInvoiceIssuedEvent(db: PostgresJsDatabase, invoice: typeof invoices.$inferSelect, runtime?: InvoiceIssueRuntime): Promise<InvoiceIssuedEvent>;
|
|
176
|
+
/**
|
|
177
|
+
* Convert an issued proforma into a final invoice. Copies the proforma's
|
|
178
|
+
* line items verbatim (totals + taxes already match the booking the
|
|
179
|
+
* customer accepted) and voids the proforma so it stops counting against
|
|
180
|
+
* outstanding balances. The new invoice carries `convertedFromInvoiceId`
|
|
181
|
+
* so the audit chain is preserved; downstream subscribers see the linkage
|
|
182
|
+
* on both the generic issued event and the conversion-specific event.
|
|
183
|
+
*
|
|
184
|
+
* Number derivation: `PRO-` prefix → `INV-`; otherwise the original
|
|
185
|
+
* number is suffixed with `-INV`. Callers can override via the optional
|
|
186
|
+
* `invoiceNumber` argument when they want a series-derived number.
|
|
187
|
+
*/
|
|
188
|
+
export declare function convertProformaToInvoice(db: PostgresJsDatabase, proformaId: string, options?: {
|
|
189
|
+
invoiceNumber?: string;
|
|
190
|
+
issueDate?: string;
|
|
191
|
+
dueDate?: string;
|
|
192
|
+
}, runtime?: InvoiceIssueRuntime): Promise<{
|
|
193
|
+
status: "ok";
|
|
194
|
+
invoice: typeof invoices.$inferSelect;
|
|
195
|
+
} | {
|
|
196
|
+
status: "not_found";
|
|
197
|
+
} | {
|
|
198
|
+
status: "not_proforma";
|
|
199
|
+
} | {
|
|
200
|
+
status: "already_converted";
|
|
201
|
+
invoice: ExistingConvertedInvoicePointer | null;
|
|
202
|
+
} | {
|
|
203
|
+
status: "duplicate_fiscal_invoice";
|
|
204
|
+
invoice: ExistingConvertedInvoicePointer;
|
|
205
|
+
}>;
|
|
206
|
+
export {};
|
|
207
|
+
//# sourceMappingURL=service-issue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-issue.d.ts","sourceRoot":"","sources":["../src/service-issue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
// agent-quality: file-size exception -- owner: finance; existing service module stays co-located until a dedicated split preserves behavior and tests.
|
|
2
|
+
import { appendActionLedgerMutation } from "@voyant-travel/action-ledger";
|
|
3
|
+
import { bookingItems, bookings } from "@voyant-travel/bookings/schema";
|
|
4
|
+
import { and, asc, eq, inArray, ne, sql } from "drizzle-orm";
|
|
5
|
+
import { resolveBookingSellTaxRate } from "./booking-tax.js";
|
|
6
|
+
import { resolveInvoiceFxContext } from "./invoice-fx.js";
|
|
7
|
+
import { isInvoiceNumberUniqueConstraintError } from "./invoice-number-errors.js";
|
|
8
|
+
import { bookingItemTaxLines, bookingPaymentSchedules, invoiceLineItems, invoiceNumberSeries, invoices, payments, } from "./schema.js";
|
|
9
|
+
import { buildInvoiceIssuedActionLedgerInput, financeService, InvoiceNumberConflictError, } from "./service.js";
|
|
10
|
+
const TAX_REGIME_CODES = new Set([
|
|
11
|
+
"standard",
|
|
12
|
+
"reduced",
|
|
13
|
+
"exempt",
|
|
14
|
+
"reverse_charge",
|
|
15
|
+
"margin_scheme_art311",
|
|
16
|
+
"zero_rated",
|
|
17
|
+
"out_of_scope",
|
|
18
|
+
"other",
|
|
19
|
+
]);
|
|
20
|
+
const ISSUED_EVENT = "invoice.issued";
|
|
21
|
+
const PROFORMA_ISSUED_EVENT = "invoice.proforma.issued";
|
|
22
|
+
const PROFORMA_CONVERTED_EVENT = "invoice.proforma.converted";
|
|
23
|
+
/**
|
|
24
|
+
* Create + emit an invoice from a booking. Returns the persisted row
|
|
25
|
+
* after flipping the status from `draft` to `issued`. Drafts shouldn't
|
|
26
|
+
* trigger SmartBill sync.
|
|
27
|
+
*/
|
|
28
|
+
export async function issueInvoiceFromBooking(db, input, bookingData, runtime = {}) {
|
|
29
|
+
const draft = await financeService.createInvoiceFromBooking(db, input, bookingData, runtime);
|
|
30
|
+
if (!draft)
|
|
31
|
+
return null;
|
|
32
|
+
const status = draft.status === "pending_external_allocation" ? draft.status : "issued";
|
|
33
|
+
const updateIssuedInvoice = (writer) => writer
|
|
34
|
+
.update(invoices)
|
|
35
|
+
.set({ status, updatedAt: new Date() })
|
|
36
|
+
.where(eq(invoices.id, draft.id))
|
|
37
|
+
.returning();
|
|
38
|
+
const actionLedgerContext = runtime.actionLedgerContext;
|
|
39
|
+
const issued = actionLedgerContext
|
|
40
|
+
? await db.transaction(async (tx) => {
|
|
41
|
+
const [row] = await updateIssuedInvoice(tx);
|
|
42
|
+
if (row) {
|
|
43
|
+
await appendActionLedgerMutation(tx, await buildInvoiceIssuedActionLedgerInput(actionLedgerContext, { invoice: row }, { authorizationSource: runtime.actionLedgerAuthorizationSource }));
|
|
44
|
+
}
|
|
45
|
+
return row;
|
|
46
|
+
})
|
|
47
|
+
: (await updateIssuedInvoice(db))[0];
|
|
48
|
+
const row = issued ?? draft;
|
|
49
|
+
await emitIssued(db, runtime, ISSUED_EVENT, row, { skipExternalSync: input.skipExternalSync });
|
|
50
|
+
return row;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create + emit a proforma from a booking. Same shape as
|
|
54
|
+
* `issueInvoiceFromBooking` but marks the row as `invoiceType:
|
|
55
|
+
* 'proforma'` and emits the proforma-specific event so the
|
|
56
|
+
* SmartBill plugin can route to its proforma endpoint.
|
|
57
|
+
*/
|
|
58
|
+
export async function issueProformaFromBooking(db, input, bookingData, runtime = {}) {
|
|
59
|
+
const draft = await financeService.createInvoiceFromBooking(db, input, bookingData, runtime);
|
|
60
|
+
if (!draft)
|
|
61
|
+
return null;
|
|
62
|
+
const status = draft.status === "pending_external_allocation" ? draft.status : "issued";
|
|
63
|
+
const updateIssuedInvoice = (writer) => writer
|
|
64
|
+
.update(invoices)
|
|
65
|
+
.set({ invoiceType: "proforma", status, updatedAt: new Date() })
|
|
66
|
+
.where(eq(invoices.id, draft.id))
|
|
67
|
+
.returning();
|
|
68
|
+
const actionLedgerContext = runtime.actionLedgerContext;
|
|
69
|
+
const issued = actionLedgerContext
|
|
70
|
+
? await db.transaction(async (tx) => {
|
|
71
|
+
const [row] = await updateIssuedInvoice(tx);
|
|
72
|
+
if (row) {
|
|
73
|
+
await appendActionLedgerMutation(tx, await buildInvoiceIssuedActionLedgerInput(actionLedgerContext, { invoice: row }, { authorizationSource: runtime.actionLedgerAuthorizationSource }));
|
|
74
|
+
}
|
|
75
|
+
return row;
|
|
76
|
+
})
|
|
77
|
+
: (await updateIssuedInvoice(db))[0];
|
|
78
|
+
const row = issued ?? draft;
|
|
79
|
+
await emitIssued(db, runtime, PROFORMA_ISSUED_EVENT, row, {
|
|
80
|
+
skipExternalSync: input.skipExternalSync,
|
|
81
|
+
});
|
|
82
|
+
return row;
|
|
83
|
+
}
|
|
84
|
+
async function emitIssued(db, runtime, eventName, invoice, options = {}) {
|
|
85
|
+
if (!runtime.eventBus)
|
|
86
|
+
return;
|
|
87
|
+
const payload = await buildInvoiceIssuedEvent(db, invoice, runtime);
|
|
88
|
+
if (options.skipExternalSync)
|
|
89
|
+
payload.skipExternalSync = true;
|
|
90
|
+
await runtime.eventBus.emit(eventName, payload);
|
|
91
|
+
}
|
|
92
|
+
async function emitProformaConverted(db, runtime, invoice, proforma) {
|
|
93
|
+
if (!runtime.eventBus)
|
|
94
|
+
return;
|
|
95
|
+
const issuedEvent = await buildInvoiceIssuedEvent(db, invoice, runtime);
|
|
96
|
+
const payload = {
|
|
97
|
+
...issuedEvent,
|
|
98
|
+
id: issuedEvent.invoiceId,
|
|
99
|
+
proformaId: proforma.id,
|
|
100
|
+
proformaInvoiceNumber: proforma.invoiceNumber,
|
|
101
|
+
};
|
|
102
|
+
await runtime.eventBus.emit(ISSUED_EVENT, issuedEvent);
|
|
103
|
+
await runtime.eventBus.emit(PROFORMA_CONVERTED_EVENT, payload);
|
|
104
|
+
}
|
|
105
|
+
export async function buildInvoiceIssuedEvent(db, invoice, runtime = {}) {
|
|
106
|
+
const [booking] = invoice.bookingId
|
|
107
|
+
? await db.select().from(bookings).where(eq(bookings.id, invoice.bookingId)).limit(1)
|
|
108
|
+
: [];
|
|
109
|
+
const [series] = invoice.seriesId && invoice.status === "pending_external_allocation"
|
|
110
|
+
? await db
|
|
111
|
+
.select()
|
|
112
|
+
.from(invoiceNumberSeries)
|
|
113
|
+
.where(eq(invoiceNumberSeries.id, invoice.seriesId))
|
|
114
|
+
.limit(1)
|
|
115
|
+
: [];
|
|
116
|
+
const lines = await db
|
|
117
|
+
.select()
|
|
118
|
+
.from(invoiceLineItems)
|
|
119
|
+
.where(eq(invoiceLineItems.invoiceId, invoice.id))
|
|
120
|
+
.orderBy(asc(invoiceLineItems.sortOrder));
|
|
121
|
+
const taxMetadataByBookingItemId = await loadLineTaxMetadata(db, lines);
|
|
122
|
+
const scheduleMetadataById = await loadLineScheduleMetadata(db, lines);
|
|
123
|
+
const payload = {
|
|
124
|
+
invoiceId: invoice.id,
|
|
125
|
+
invoiceNumber: invoice.invoiceNumber,
|
|
126
|
+
invoiceType: invoice.invoiceType,
|
|
127
|
+
bookingId: invoice.bookingId,
|
|
128
|
+
totalCents: invoice.totalCents,
|
|
129
|
+
currency: invoice.currency,
|
|
130
|
+
convertedFromInvoiceId: invoice.convertedFromInvoiceId,
|
|
131
|
+
clientName: buildClientName(booking),
|
|
132
|
+
clientEmail: booking?.contactEmail ?? null,
|
|
133
|
+
clientPhone: booking?.contactPhone ?? null,
|
|
134
|
+
clientAddress: [booking?.contactAddressLine1, booking?.contactAddressLine2].filter(Boolean).join("\n") ||
|
|
135
|
+
null,
|
|
136
|
+
clientCity: booking?.contactCity ?? null,
|
|
137
|
+
clientCounty: booking?.contactRegion ?? null,
|
|
138
|
+
clientCountry: booking?.contactCountry ?? null,
|
|
139
|
+
clientVatCode: null,
|
|
140
|
+
clientRegCom: null,
|
|
141
|
+
lineItems: lines.map((line) => {
|
|
142
|
+
const taxMetadata = line.bookingItemId == null ? undefined : taxMetadataByBookingItemId.get(line.bookingItemId);
|
|
143
|
+
const taxPercentage = line.taxRate ?? taxMetadata?.taxPercentage;
|
|
144
|
+
const schedule = line.bookingPaymentScheduleId == null
|
|
145
|
+
? undefined
|
|
146
|
+
: scheduleMetadataById.get(line.bookingPaymentScheduleId);
|
|
147
|
+
const schedulePercent = schedule && booking?.sellAmountCents && booking.sellAmountCents > 0
|
|
148
|
+
? Math.round((schedule.amountCents / booking.sellAmountCents) * 100)
|
|
149
|
+
: undefined;
|
|
150
|
+
return {
|
|
151
|
+
description: line.description,
|
|
152
|
+
quantity: line.quantity,
|
|
153
|
+
unitPrice: centsToMajor(line.unitPriceCents),
|
|
154
|
+
currency: invoice.currency,
|
|
155
|
+
...(line.bookingPaymentScheduleId == null
|
|
156
|
+
? {}
|
|
157
|
+
: { bookingPaymentScheduleId: line.bookingPaymentScheduleId }),
|
|
158
|
+
...(schedule?.scheduleType == null ? {} : { scheduleType: schedule.scheduleType }),
|
|
159
|
+
...(schedulePercent == null ? {} : { schedulePercent }),
|
|
160
|
+
...(taxPercentage == null ? {} : { taxPercentage }),
|
|
161
|
+
...(taxMetadata?.taxName == null ? {} : { taxName: taxMetadata.taxName }),
|
|
162
|
+
...(taxMetadata?.taxRegimeCode == null ? {} : { taxRegimeCode: taxMetadata.taxRegimeCode }),
|
|
163
|
+
isService: true,
|
|
164
|
+
};
|
|
165
|
+
}),
|
|
166
|
+
bookingNumber: booking?.bookingNumber ?? null,
|
|
167
|
+
issueDate: toDateString(invoice.issueDate),
|
|
168
|
+
dueDate: toDateString(invoice.dueDate),
|
|
169
|
+
};
|
|
170
|
+
if (series?.externalProvider) {
|
|
171
|
+
payload.externalAllocationRequired = true;
|
|
172
|
+
payload.externalProvider = series.externalProvider;
|
|
173
|
+
payload.externalConfigKey = series.externalConfigKey;
|
|
174
|
+
payload.externalSeriesId = series.id;
|
|
175
|
+
payload.externalPlaceholderNumber = invoice.invoiceNumber;
|
|
176
|
+
}
|
|
177
|
+
const fx = await resolveInvoiceFxContext(db, invoice, runtime);
|
|
178
|
+
if (fx)
|
|
179
|
+
Object.assign(payload, fx);
|
|
180
|
+
return payload;
|
|
181
|
+
}
|
|
182
|
+
async function loadLineTaxMetadata(db, lines) {
|
|
183
|
+
const bookingItemIds = [
|
|
184
|
+
...new Set(lines.map((line) => line.bookingItemId).filter((id) => Boolean(id))),
|
|
185
|
+
];
|
|
186
|
+
if (bookingItemIds.length === 0)
|
|
187
|
+
return new Map();
|
|
188
|
+
const taxLines = await db
|
|
189
|
+
.select({
|
|
190
|
+
bookingItemId: bookingItemTaxLines.bookingItemId,
|
|
191
|
+
name: bookingItemTaxLines.name,
|
|
192
|
+
code: bookingItemTaxLines.code,
|
|
193
|
+
scope: bookingItemTaxLines.scope,
|
|
194
|
+
rateBasisPoints: bookingItemTaxLines.rateBasisPoints,
|
|
195
|
+
})
|
|
196
|
+
.from(bookingItemTaxLines)
|
|
197
|
+
.where(inArray(bookingItemTaxLines.bookingItemId, bookingItemIds))
|
|
198
|
+
.orderBy(asc(bookingItemTaxLines.bookingItemId), asc(bookingItemTaxLines.sortOrder), asc(bookingItemTaxLines.createdAt));
|
|
199
|
+
const taxLinesByBookingItemId = new Map();
|
|
200
|
+
for (const taxLine of taxLines) {
|
|
201
|
+
const existing = taxLinesByBookingItemId.get(taxLine.bookingItemId) ?? [];
|
|
202
|
+
existing.push(taxLine);
|
|
203
|
+
taxLinesByBookingItemId.set(taxLine.bookingItemId, existing);
|
|
204
|
+
}
|
|
205
|
+
const metadataByBookingItemId = new Map();
|
|
206
|
+
for (const bookingItemId of bookingItemIds) {
|
|
207
|
+
const taxLine = selectEventTaxLine(taxLinesByBookingItemId.get(bookingItemId) ?? []);
|
|
208
|
+
if (!taxLine)
|
|
209
|
+
continue;
|
|
210
|
+
metadataByBookingItemId.set(bookingItemId, {
|
|
211
|
+
...(taxLine.rateBasisPoints == null
|
|
212
|
+
? {}
|
|
213
|
+
: { taxPercentage: Math.round(taxLine.rateBasisPoints / 100) }),
|
|
214
|
+
taxName: taxLine.name,
|
|
215
|
+
taxRegimeCode: parseTaxRegimeCode(taxLine.code),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
await backfillMissingLineTaxMetadata(db, bookingItemIds, metadataByBookingItemId);
|
|
219
|
+
return metadataByBookingItemId;
|
|
220
|
+
}
|
|
221
|
+
async function loadLineScheduleMetadata(db, lines) {
|
|
222
|
+
const scheduleIds = [
|
|
223
|
+
...new Set(lines.map((line) => line.bookingPaymentScheduleId).filter((id) => Boolean(id))),
|
|
224
|
+
];
|
|
225
|
+
if (scheduleIds.length === 0)
|
|
226
|
+
return new Map();
|
|
227
|
+
const scheduleRows = await db
|
|
228
|
+
.select()
|
|
229
|
+
.from(bookingPaymentSchedules)
|
|
230
|
+
.where(inArray(bookingPaymentSchedules.id, scheduleIds));
|
|
231
|
+
return new Map(scheduleRows.map((schedule) => [schedule.id, schedule]));
|
|
232
|
+
}
|
|
233
|
+
function selectEventTaxLine(taxLines) {
|
|
234
|
+
return (taxLines.find((taxLine) => taxLine.scope !== "withheld" && taxLine.rateBasisPoints != null) ??
|
|
235
|
+
taxLines.find((taxLine) => taxLine.scope !== "withheld") ??
|
|
236
|
+
null);
|
|
237
|
+
}
|
|
238
|
+
async function backfillMissingLineTaxMetadata(db, bookingItemIds, metadataByBookingItemId) {
|
|
239
|
+
const missingBookingItemIds = bookingItemIds.filter((id) => !metadataByBookingItemId.has(id));
|
|
240
|
+
if (missingBookingItemIds.length === 0)
|
|
241
|
+
return;
|
|
242
|
+
const productRows = await db
|
|
243
|
+
.select({
|
|
244
|
+
id: bookingItems.id,
|
|
245
|
+
productId: bookingItems.productId,
|
|
246
|
+
})
|
|
247
|
+
.from(bookingItems)
|
|
248
|
+
.where(inArray(bookingItems.id, missingBookingItemIds));
|
|
249
|
+
for (const row of productRows) {
|
|
250
|
+
if (!row.productId)
|
|
251
|
+
continue;
|
|
252
|
+
const taxRate = await resolveBookingSellTaxRate(db, { productId: row.productId });
|
|
253
|
+
if (!taxRate)
|
|
254
|
+
continue;
|
|
255
|
+
metadataByBookingItemId.set(row.id, {
|
|
256
|
+
taxPercentage: Math.round(taxRate.rate * 100),
|
|
257
|
+
taxName: taxRate.label,
|
|
258
|
+
taxRegimeCode: parseTaxRegimeCode(taxRate.code),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function parseTaxRegimeCode(code) {
|
|
263
|
+
const value = code?.split("/").at(-1);
|
|
264
|
+
return value && TAX_REGIME_CODES.has(value) ? value : null;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Convert an issued proforma into a final invoice. Copies the proforma's
|
|
268
|
+
* line items verbatim (totals + taxes already match the booking the
|
|
269
|
+
* customer accepted) and voids the proforma so it stops counting against
|
|
270
|
+
* outstanding balances. The new invoice carries `convertedFromInvoiceId`
|
|
271
|
+
* so the audit chain is preserved; downstream subscribers see the linkage
|
|
272
|
+
* on both the generic issued event and the conversion-specific event.
|
|
273
|
+
*
|
|
274
|
+
* Number derivation: `PRO-` prefix → `INV-`; otherwise the original
|
|
275
|
+
* number is suffixed with `-INV`. Callers can override via the optional
|
|
276
|
+
* `invoiceNumber` argument when they want a series-derived number.
|
|
277
|
+
*/
|
|
278
|
+
export async function convertProformaToInvoice(db, proformaId, options = {}, runtime = {}) {
|
|
279
|
+
const [proforma] = await db.select().from(invoices).where(eq(invoices.id, proformaId)).limit(1);
|
|
280
|
+
if (!proforma)
|
|
281
|
+
return { status: "not_found" };
|
|
282
|
+
if (proforma.invoiceType !== "proforma")
|
|
283
|
+
return { status: "not_proforma" };
|
|
284
|
+
const newInvoiceNumber = options.invoiceNumber ?? deriveInvoiceNumberFromProforma(proforma.invoiceNumber);
|
|
285
|
+
const todayIso = new Date().toISOString().slice(0, 10);
|
|
286
|
+
const issueDate = options.issueDate ?? todayIso;
|
|
287
|
+
const dueDate = options.dueDate ?? toDateString(proforma.dueDate);
|
|
288
|
+
const now = new Date();
|
|
289
|
+
const result = await db
|
|
290
|
+
.transaction(async (tx) => {
|
|
291
|
+
const guardKey = `finance:invoice:convert:${proforma.bookingId}`;
|
|
292
|
+
// agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
293
|
+
await tx.execute(sql `SELECT pg_advisory_xact_lock(hashtextextended(${guardKey}, 0))`);
|
|
294
|
+
const [lockedProforma] = await tx
|
|
295
|
+
.select()
|
|
296
|
+
.from(invoices)
|
|
297
|
+
.where(eq(invoices.id, proformaId))
|
|
298
|
+
.limit(1);
|
|
299
|
+
if (!lockedProforma)
|
|
300
|
+
return { status: "not_found" };
|
|
301
|
+
if (lockedProforma.invoiceType !== "proforma")
|
|
302
|
+
return { status: "not_proforma" };
|
|
303
|
+
const [existing] = await tx
|
|
304
|
+
.select({ id: invoices.id, invoiceNumber: invoices.invoiceNumber })
|
|
305
|
+
.from(invoices)
|
|
306
|
+
.where(eq(invoices.convertedFromInvoiceId, proformaId))
|
|
307
|
+
.limit(1);
|
|
308
|
+
if (existing)
|
|
309
|
+
return { status: "already_converted", invoice: existing };
|
|
310
|
+
if (lockedProforma.status === "void") {
|
|
311
|
+
return { status: "already_converted", invoice: null };
|
|
312
|
+
}
|
|
313
|
+
const [duplicateFiscalInvoice] = await tx
|
|
314
|
+
.select({ id: invoices.id, invoiceNumber: invoices.invoiceNumber })
|
|
315
|
+
.from(invoices)
|
|
316
|
+
.where(and(eq(invoices.bookingId, lockedProforma.bookingId), eq(invoices.invoiceType, "invoice"), ne(invoices.status, "void"), eq(invoices.totalCents, lockedProforma.totalCents), eq(invoices.currency, lockedProforma.currency)))
|
|
317
|
+
.limit(1);
|
|
318
|
+
if (duplicateFiscalInvoice) {
|
|
319
|
+
return { status: "duplicate_fiscal_invoice", invoice: duplicateFiscalInvoice };
|
|
320
|
+
}
|
|
321
|
+
const lineItems = await tx
|
|
322
|
+
.select()
|
|
323
|
+
.from(invoiceLineItems)
|
|
324
|
+
.where(eq(invoiceLineItems.invoiceId, proformaId))
|
|
325
|
+
.orderBy(asc(invoiceLineItems.sortOrder));
|
|
326
|
+
const [inserted] = await tx
|
|
327
|
+
.insert(invoices)
|
|
328
|
+
.values({
|
|
329
|
+
invoiceNumber: newInvoiceNumber,
|
|
330
|
+
invoiceType: "invoice",
|
|
331
|
+
convertedFromInvoiceId: lockedProforma.id,
|
|
332
|
+
seriesId: lockedProforma.seriesId,
|
|
333
|
+
templateId: lockedProforma.templateId,
|
|
334
|
+
taxRegimeId: lockedProforma.taxRegimeId,
|
|
335
|
+
language: lockedProforma.language,
|
|
336
|
+
bookingId: lockedProforma.bookingId,
|
|
337
|
+
personId: lockedProforma.personId,
|
|
338
|
+
organizationId: lockedProforma.organizationId,
|
|
339
|
+
status: "issued",
|
|
340
|
+
currency: lockedProforma.currency,
|
|
341
|
+
baseCurrency: lockedProforma.baseCurrency,
|
|
342
|
+
fxRateSetId: lockedProforma.fxRateSetId,
|
|
343
|
+
subtotalCents: lockedProforma.subtotalCents,
|
|
344
|
+
baseSubtotalCents: lockedProforma.baseSubtotalCents,
|
|
345
|
+
taxCents: lockedProforma.taxCents,
|
|
346
|
+
baseTaxCents: lockedProforma.baseTaxCents,
|
|
347
|
+
totalCents: lockedProforma.totalCents,
|
|
348
|
+
baseTotalCents: lockedProforma.baseTotalCents,
|
|
349
|
+
// Carry the proforma's settled amounts forward — a partially
|
|
350
|
+
// (or fully) paid proforma must convert to an invoice that
|
|
351
|
+
// reflects those payments, otherwise the new invoice shows the
|
|
352
|
+
// full total as outstanding and the payment rows reassigned
|
|
353
|
+
// below would orphan the balance.
|
|
354
|
+
paidCents: lockedProforma.paidCents,
|
|
355
|
+
basePaidCents: lockedProforma.basePaidCents,
|
|
356
|
+
balanceDueCents: lockedProforma.totalCents - lockedProforma.paidCents,
|
|
357
|
+
baseBalanceDueCents: lockedProforma.baseTotalCents !== null && lockedProforma.basePaidCents !== null
|
|
358
|
+
? lockedProforma.baseTotalCents - lockedProforma.basePaidCents
|
|
359
|
+
: lockedProforma.baseTotalCents,
|
|
360
|
+
commissionPercent: lockedProforma.commissionPercent,
|
|
361
|
+
commissionAmountCents: lockedProforma.commissionAmountCents,
|
|
362
|
+
issueDate,
|
|
363
|
+
dueDate,
|
|
364
|
+
notes: null,
|
|
365
|
+
})
|
|
366
|
+
.returning();
|
|
367
|
+
if (!inserted)
|
|
368
|
+
return { status: "not_found" };
|
|
369
|
+
if (lineItems.length > 0) {
|
|
370
|
+
await tx.insert(invoiceLineItems).values(lineItems.map((line) => ({
|
|
371
|
+
invoiceId: inserted.id,
|
|
372
|
+
bookingItemId: line.bookingItemId,
|
|
373
|
+
description: line.description,
|
|
374
|
+
quantity: line.quantity,
|
|
375
|
+
unitPriceCents: line.unitPriceCents,
|
|
376
|
+
totalCents: line.totalCents,
|
|
377
|
+
taxRate: line.taxRate,
|
|
378
|
+
sortOrder: line.sortOrder,
|
|
379
|
+
})));
|
|
380
|
+
}
|
|
381
|
+
// Reassign payments off the proforma so they don't sit attached to
|
|
382
|
+
// a void document. The proforma's payment rows become the final
|
|
383
|
+
// invoice's payment history.
|
|
384
|
+
await tx
|
|
385
|
+
.update(payments)
|
|
386
|
+
.set({ invoiceId: inserted.id, updatedAt: now })
|
|
387
|
+
.where(eq(payments.invoiceId, lockedProforma.id));
|
|
388
|
+
await tx
|
|
389
|
+
.update(invoices)
|
|
390
|
+
.set({
|
|
391
|
+
status: "void",
|
|
392
|
+
paidCents: 0,
|
|
393
|
+
basePaidCents: lockedProforma.basePaidCents == null ? null : 0,
|
|
394
|
+
balanceDueCents: 0,
|
|
395
|
+
baseBalanceDueCents: lockedProforma.baseBalanceDueCents == null ? null : 0,
|
|
396
|
+
voidedAt: now,
|
|
397
|
+
voidReason: `Converted to invoice ${inserted.invoiceNumber}`,
|
|
398
|
+
updatedAt: now,
|
|
399
|
+
})
|
|
400
|
+
.where(eq(invoices.id, lockedProforma.id));
|
|
401
|
+
return { status: "ok", invoice: inserted, proforma: lockedProforma };
|
|
402
|
+
})
|
|
403
|
+
.catch((error) => {
|
|
404
|
+
if (isInvoiceNumberUniqueConstraintError(error)) {
|
|
405
|
+
throw new InvoiceNumberConflictError(newInvoiceNumber);
|
|
406
|
+
}
|
|
407
|
+
throw error;
|
|
408
|
+
});
|
|
409
|
+
if (result.status !== "ok")
|
|
410
|
+
return result;
|
|
411
|
+
await emitProformaConverted(db, runtime, result.invoice, result.proforma);
|
|
412
|
+
return { status: "ok", invoice: result.invoice };
|
|
413
|
+
}
|
|
414
|
+
function deriveInvoiceNumberFromProforma(proformaNumber) {
|
|
415
|
+
if (/^PRO-/i.test(proformaNumber)) {
|
|
416
|
+
return proformaNumber.replace(/^PRO-/i, "INV-");
|
|
417
|
+
}
|
|
418
|
+
return `${proformaNumber}-INV`;
|
|
419
|
+
}
|
|
420
|
+
function buildClientName(booking) {
|
|
421
|
+
const name = [booking?.contactFirstName, booking?.contactLastName]
|
|
422
|
+
.filter((part) => typeof part === "string" && part.length > 0)
|
|
423
|
+
.join(" ");
|
|
424
|
+
return name || "Client";
|
|
425
|
+
}
|
|
426
|
+
function centsToMajor(cents) {
|
|
427
|
+
return cents / 100;
|
|
428
|
+
}
|
|
429
|
+
function toDateString(value) {
|
|
430
|
+
return typeof value === "string" ? value : value.toISOString().slice(0, 10);
|
|
431
|
+
}
|