@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.
Files changed (294) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +192 -0
  3. package/dist/action-ledger-drift.d.ts +29 -0
  4. package/dist/action-ledger-drift.d.ts.map +1 -0
  5. package/dist/action-ledger-drift.js +166 -0
  6. package/dist/booking-tax.d.ts +124 -0
  7. package/dist/booking-tax.d.ts.map +1 -0
  8. package/dist/booking-tax.js +264 -0
  9. package/dist/checkout-routes.d.ts +1154 -0
  10. package/dist/checkout-routes.d.ts.map +1 -0
  11. package/dist/checkout-routes.js +116 -0
  12. package/dist/checkout-service-plan.d.ts +137 -0
  13. package/dist/checkout-service-plan.d.ts.map +1 -0
  14. package/dist/checkout-service-plan.js +119 -0
  15. package/dist/checkout-service.d.ts +9 -0
  16. package/dist/checkout-service.d.ts.map +1 -0
  17. package/dist/checkout-service.js +324 -0
  18. package/dist/checkout-validation.d.ts +1682 -0
  19. package/dist/checkout-validation.d.ts.map +1 -0
  20. package/dist/checkout-validation.js +228 -0
  21. package/dist/document-download.d.ts +3 -0
  22. package/dist/document-download.d.ts.map +1 -0
  23. package/dist/document-download.js +1 -0
  24. package/dist/fx-money.d.ts +17 -0
  25. package/dist/fx-money.d.ts.map +1 -0
  26. package/dist/fx-money.js +194 -0
  27. package/dist/index.d.ts +65 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +108 -0
  30. package/dist/invoice-fx.d.ts +134 -0
  31. package/dist/invoice-fx.d.ts.map +1 -0
  32. package/dist/invoice-fx.js +240 -0
  33. package/dist/invoice-number-errors.d.ts +2 -0
  34. package/dist/invoice-number-errors.d.ts.map +1 -0
  35. package/dist/invoice-number-errors.js +58 -0
  36. package/dist/markets-ref.d.ts +149 -0
  37. package/dist/markets-ref.d.ts.map +1 -0
  38. package/dist/markets-ref.js +17 -0
  39. package/dist/payment-link.d.ts +23 -0
  40. package/dist/payment-link.d.ts.map +1 -0
  41. package/dist/payment-link.js +30 -0
  42. package/dist/payment-policy.d.ts +113 -0
  43. package/dist/payment-policy.d.ts.map +1 -0
  44. package/dist/payment-policy.js +193 -0
  45. package/dist/route-runtime.d.ts +22 -0
  46. package/dist/route-runtime.d.ts.map +1 -0
  47. package/dist/route-runtime.js +18 -0
  48. package/dist/routes-action-ledger.d.ts +181 -0
  49. package/dist/routes-action-ledger.d.ts.map +1 -0
  50. package/dist/routes-action-ledger.js +142 -0
  51. package/dist/routes-booking-billing.d.ts +852 -0
  52. package/dist/routes-booking-billing.d.ts.map +1 -0
  53. package/dist/routes-booking-billing.js +223 -0
  54. package/dist/routes-booking-create.d.ts +3 -0
  55. package/dist/routes-booking-create.d.ts.map +1 -0
  56. package/dist/routes-booking-create.js +194 -0
  57. package/dist/routes-booking-reads.d.ts +46 -0
  58. package/dist/routes-booking-reads.d.ts.map +1 -0
  59. package/dist/routes-booking-reads.js +20 -0
  60. package/dist/routes-documents.d.ts +195 -0
  61. package/dist/routes-documents.d.ts.map +1 -0
  62. package/dist/routes-documents.js +93 -0
  63. package/dist/routes-invoice-core.d.ts +794 -0
  64. package/dist/routes-invoice-core.d.ts.map +1 -0
  65. package/dist/routes-invoice-core.js +238 -0
  66. package/dist/routes-invoice-documents.d.ts +401 -0
  67. package/dist/routes-invoice-documents.d.ts.map +1 -0
  68. package/dist/routes-invoice-documents.js +91 -0
  69. package/dist/routes-invoice-issue.d.ts +384 -0
  70. package/dist/routes-invoice-issue.d.ts.map +1 -0
  71. package/dist/routes-invoice-issue.js +208 -0
  72. package/dist/routes-payment-processing.d.ts +1193 -0
  73. package/dist/routes-payment-processing.d.ts.map +1 -0
  74. package/dist/routes-payment-processing.js +238 -0
  75. package/dist/routes-payments.d.ts +309 -0
  76. package/dist/routes-payments.d.ts.map +1 -0
  77. package/dist/routes-payments.js +94 -0
  78. package/dist/routes-public.d.ts +1948 -0
  79. package/dist/routes-public.d.ts.map +1 -0
  80. package/dist/routes-public.js +275 -0
  81. package/dist/routes-reference-data.d.ts +977 -0
  82. package/dist/routes-reference-data.d.ts.map +1 -0
  83. package/dist/routes-reference-data.js +191 -0
  84. package/dist/routes-reports.d.ts +344 -0
  85. package/dist/routes-reports.d.ts.map +1 -0
  86. package/dist/routes-reports.js +93 -0
  87. package/dist/routes-runtime.d.ts +71 -0
  88. package/dist/routes-runtime.d.ts.map +1 -0
  89. package/dist/routes-runtime.js +59 -0
  90. package/dist/routes-settlement.d.ts +67 -0
  91. package/dist/routes-settlement.d.ts.map +1 -0
  92. package/dist/routes-settlement.js +23 -0
  93. package/dist/routes-shared.d.ts +35 -0
  94. package/dist/routes-shared.d.ts.map +1 -0
  95. package/dist/routes-shared.js +10 -0
  96. package/dist/routes-supplier-invoices.d.ts +778 -0
  97. package/dist/routes-supplier-invoices.d.ts.map +1 -0
  98. package/dist/routes-supplier-invoices.js +159 -0
  99. package/dist/routes-vouchers.d.ts +228 -0
  100. package/dist/routes-vouchers.d.ts.map +1 -0
  101. package/dist/routes-vouchers.js +54 -0
  102. package/dist/routes.d.ts +5577 -0
  103. package/dist/routes.d.ts.map +1 -0
  104. package/dist/routes.js +44 -0
  105. package/dist/schema/booking-billing.d.ts +1006 -0
  106. package/dist/schema/booking-billing.d.ts.map +1 -0
  107. package/dist/schema/booking-billing.js +106 -0
  108. package/dist/schema/enums.d.ts +48 -0
  109. package/dist/schema/enums.d.ts.map +1 -0
  110. package/dist/schema/enums.js +237 -0
  111. package/dist/schema/invoice-documents.d.ts +1245 -0
  112. package/dist/schema/invoice-documents.d.ts.map +1 -0
  113. package/dist/schema/invoice-documents.js +140 -0
  114. package/dist/schema/payment-instruments.d.ts +418 -0
  115. package/dist/schema/payment-instruments.d.ts.map +1 -0
  116. package/dist/schema/payment-instruments.js +45 -0
  117. package/dist/schema/payment-processing.d.ts +563 -0
  118. package/dist/schema/payment-processing.d.ts.map +1 -0
  119. package/dist/schema/payment-processing.js +65 -0
  120. package/dist/schema/payment-sessions.d.ts +728 -0
  121. package/dist/schema/payment-sessions.d.ts.map +1 -0
  122. package/dist/schema/payment-sessions.js +79 -0
  123. package/dist/schema/receivables.d.ts +1474 -0
  124. package/dist/schema/receivables.d.ts.map +1 -0
  125. package/dist/schema/receivables.js +179 -0
  126. package/dist/schema/relations.d.ts +82 -0
  127. package/dist/schema/relations.d.ts.map +1 -0
  128. package/dist/schema/relations.js +144 -0
  129. package/dist/schema/supplier-invoices.d.ts +1619 -0
  130. package/dist/schema/supplier-invoices.d.ts.map +1 -0
  131. package/dist/schema/supplier-invoices.js +228 -0
  132. package/dist/schema/tax.d.ts +712 -0
  133. package/dist/schema/tax.d.ts.map +1 -0
  134. package/dist/schema/tax.js +98 -0
  135. package/dist/schema/vouchers.d.ts +444 -0
  136. package/dist/schema/vouchers.d.ts.map +1 -0
  137. package/dist/schema/vouchers.js +64 -0
  138. package/dist/schema.d.ts +12 -0
  139. package/dist/schema.d.ts.map +1 -0
  140. package/dist/schema.js +11 -0
  141. package/dist/service-accountant-shares.d.ts +106 -0
  142. package/dist/service-accountant-shares.d.ts.map +1 -0
  143. package/dist/service-accountant-shares.js +331 -0
  144. package/dist/service-action-ledger-accounting.d.ts +104 -0
  145. package/dist/service-action-ledger-accounting.d.ts.map +1 -0
  146. package/dist/service-action-ledger-accounting.js +386 -0
  147. package/dist/service-action-ledger-booking-payments.d.ts +48 -0
  148. package/dist/service-action-ledger-booking-payments.d.ts.map +1 -0
  149. package/dist/service-action-ledger-booking-payments.js +178 -0
  150. package/dist/service-action-ledger-bookings.d.ts +44 -0
  151. package/dist/service-action-ledger-bookings.d.ts.map +1 -0
  152. package/dist/service-action-ledger-bookings.js +81 -0
  153. package/dist/service-action-ledger-payment-authorizations.d.ts +48 -0
  154. package/dist/service-action-ledger-payment-authorizations.d.ts.map +1 -0
  155. package/dist/service-action-ledger-payment-authorizations.js +209 -0
  156. package/dist/service-action-ledger-payment-sessions.d.ts +83 -0
  157. package/dist/service-action-ledger-payment-sessions.d.ts.map +1 -0
  158. package/dist/service-action-ledger-payment-sessions.js +294 -0
  159. package/dist/service-action-ledger-supplier-invoices.d.ts +27 -0
  160. package/dist/service-action-ledger-supplier-invoices.d.ts.map +1 -0
  161. package/dist/service-action-ledger-supplier-invoices.js +111 -0
  162. package/dist/service-action-ledger-supplier-payments.d.ts +21 -0
  163. package/dist/service-action-ledger-supplier-payments.d.ts.map +1 -0
  164. package/dist/service-action-ledger-supplier-payments.js +97 -0
  165. package/dist/service-action-ledger.d.ts +7 -0
  166. package/dist/service-action-ledger.d.ts.map +1 -0
  167. package/dist/service-action-ledger.js +6 -0
  168. package/dist/service-aggregates.d.ts +96 -0
  169. package/dist/service-aggregates.d.ts.map +1 -0
  170. package/dist/service-aggregates.js +294 -0
  171. package/dist/service-booking-billing.d.ts +2322 -0
  172. package/dist/service-booking-billing.d.ts.map +1 -0
  173. package/dist/service-booking-billing.js +8 -0
  174. package/dist/service-booking-create.d.ts +410 -0
  175. package/dist/service-booking-create.d.ts.map +1 -0
  176. package/dist/service-booking-create.js +1256 -0
  177. package/dist/service-booking-guarantees.d.ts +725 -0
  178. package/dist/service-booking-guarantees.d.ts.map +1 -0
  179. package/dist/service-booking-guarantees.js +153 -0
  180. package/dist/service-booking-item-billing.d.ts +1062 -0
  181. package/dist/service-booking-item-billing.d.ts.map +1 -0
  182. package/dist/service-booking-item-billing.js +77 -0
  183. package/dist/service-booking-payment-schedules.d.ts +557 -0
  184. package/dist/service-booking-payment-schedules.d.ts.map +1 -0
  185. package/dist/service-booking-payment-schedules.js +372 -0
  186. package/dist/service-bookings-dual-create.d.ts +308 -0
  187. package/dist/service-bookings-dual-create.d.ts.map +1 -0
  188. package/dist/service-bookings-dual-create.js +131 -0
  189. package/dist/service-boundary-sql.d.ts +6 -0
  190. package/dist/service-boundary-sql.d.ts.map +1 -0
  191. package/dist/service-boundary-sql.js +15 -0
  192. package/dist/service-cost-categories.d.ts +26 -0
  193. package/dist/service-cost-categories.d.ts.map +1 -0
  194. package/dist/service-cost-categories.js +76 -0
  195. package/dist/service-documents.d.ts +80 -0
  196. package/dist/service-documents.d.ts.map +1 -0
  197. package/dist/service-documents.js +228 -0
  198. package/dist/service-invoice-artifacts.d.ts +246 -0
  199. package/dist/service-invoice-artifacts.d.ts.map +1 -0
  200. package/dist/service-invoice-artifacts.js +277 -0
  201. package/dist/service-invoice-core.d.ts +405 -0
  202. package/dist/service-invoice-core.d.ts.map +1 -0
  203. package/dist/service-invoice-core.js +290 -0
  204. package/dist/service-invoice-credit-notes.d.ts +973 -0
  205. package/dist/service-invoice-credit-notes.d.ts.map +1 -0
  206. package/dist/service-invoice-credit-notes.js +142 -0
  207. package/dist/service-invoice-from-booking.d.ts +41 -0
  208. package/dist/service-invoice-from-booking.d.ts.map +1 -0
  209. package/dist/service-invoice-from-booking.js +267 -0
  210. package/dist/service-invoice-line-items.d.ts +432 -0
  211. package/dist/service-invoice-line-items.d.ts.map +1 -0
  212. package/dist/service-invoice-line-items.js +102 -0
  213. package/dist/service-invoice-numbering.d.ts +227 -0
  214. package/dist/service-invoice-numbering.d.ts.map +1 -0
  215. package/dist/service-invoice-numbering.js +260 -0
  216. package/dist/service-invoice-payments.d.ts +673 -0
  217. package/dist/service-invoice-payments.d.ts.map +1 -0
  218. package/dist/service-invoice-payments.js +398 -0
  219. package/dist/service-invoices.d.ts +2501 -0
  220. package/dist/service-invoices.d.ts.map +1 -0
  221. package/dist/service-invoices.js +12 -0
  222. package/dist/service-issue.d.ts +207 -0
  223. package/dist/service-issue.d.ts.map +1 -0
  224. package/dist/service-issue.js +431 -0
  225. package/dist/service-payment-authorizations.d.ts +164 -0
  226. package/dist/service-payment-authorizations.d.ts.map +1 -0
  227. package/dist/service-payment-authorizations.js +227 -0
  228. package/dist/service-payment-instruments.d.ts +116 -0
  229. package/dist/service-payment-instruments.d.ts.map +1 -0
  230. package/dist/service-payment-instruments.js +99 -0
  231. package/dist/service-payment-processing.d.ts +676 -0
  232. package/dist/service-payment-processing.d.ts.map +1 -0
  233. package/dist/service-payment-processing.js +10 -0
  234. package/dist/service-payment-session-completion.d.ts +48 -0
  235. package/dist/service-payment-session-completion.d.ts.map +1 -0
  236. package/dist/service-payment-session-completion.js +238 -0
  237. package/dist/service-payment-sessions.d.ts +361 -0
  238. package/dist/service-payment-sessions.d.ts.map +1 -0
  239. package/dist/service-payment-sessions.js +280 -0
  240. package/dist/service-profitability.d.ts +114 -0
  241. package/dist/service-profitability.d.ts.map +1 -0
  242. package/dist/service-profitability.js +794 -0
  243. package/dist/service-public.d.ts +553 -0
  244. package/dist/service-public.d.ts.map +1 -0
  245. package/dist/service-public.js +583 -0
  246. package/dist/service-reference-data.d.ts +272 -0
  247. package/dist/service-reference-data.d.ts.map +1 -0
  248. package/dist/service-reference-data.js +280 -0
  249. package/dist/service-rendition-wait.d.ts +38 -0
  250. package/dist/service-rendition-wait.d.ts.map +1 -0
  251. package/dist/service-rendition-wait.js +67 -0
  252. package/dist/service-reports.d.ts +37 -0
  253. package/dist/service-reports.d.ts.map +1 -0
  254. package/dist/service-reports.js +62 -0
  255. package/dist/service-settlement.d.ts +46 -0
  256. package/dist/service-settlement.d.ts.map +1 -0
  257. package/dist/service-settlement.js +185 -0
  258. package/dist/service-shared.d.ts +541 -0
  259. package/dist/service-shared.d.ts.map +1 -0
  260. package/dist/service-shared.js +764 -0
  261. package/dist/service-supplier-invoices.d.ts +871 -0
  262. package/dist/service-supplier-invoices.d.ts.map +1 -0
  263. package/dist/service-supplier-invoices.js +744 -0
  264. package/dist/service-supplier-payments.d.ts +69 -0
  265. package/dist/service-supplier-payments.d.ts.map +1 -0
  266. package/dist/service-supplier-payments.js +136 -0
  267. package/dist/service-vouchers-migration.d.ts +44 -0
  268. package/dist/service-vouchers-migration.d.ts.map +1 -0
  269. package/dist/service-vouchers-migration.js +148 -0
  270. package/dist/service-vouchers.d.ts +157 -0
  271. package/dist/service-vouchers.d.ts.map +1 -0
  272. package/dist/service-vouchers.js +191 -0
  273. package/dist/service.d.ts +6490 -0
  274. package/dist/service.d.ts.map +1 -0
  275. package/dist/service.js +29 -0
  276. package/dist/validation-billing.d.ts +2 -0
  277. package/dist/validation-billing.d.ts.map +1 -0
  278. package/dist/validation-billing.js +1 -0
  279. package/dist/validation-payments.d.ts +2 -0
  280. package/dist/validation-payments.d.ts.map +1 -0
  281. package/dist/validation-payments.js +1 -0
  282. package/dist/validation-public.d.ts +2 -0
  283. package/dist/validation-public.d.ts.map +1 -0
  284. package/dist/validation-public.js +1 -0
  285. package/dist/validation-shared.d.ts +2 -0
  286. package/dist/validation-shared.d.ts.map +1 -0
  287. package/dist/validation-shared.js +1 -0
  288. package/dist/validation-vouchers.d.ts +2 -0
  289. package/dist/validation-vouchers.d.ts.map +1 -0
  290. package/dist/validation-vouchers.js +1 -0
  291. package/dist/validation.d.ts +2 -0
  292. package/dist/validation.d.ts.map +1 -0
  293. package/dist/validation.js +1 -0
  294. 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
+ };