@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,583 @@
1
+ // agent-quality: file-size exception -- owner: finance; existing service module stays co-located until a dedicated split preserves behavior and tests.
2
+ import { bookings } from "@voyant-travel/bookings/schema";
3
+ import { and, asc, desc, eq, or, sql } from "drizzle-orm";
4
+ import { bookingGuarantees, bookingPaymentSchedules, invoiceRenditions, invoices, paymentInstruments, payments, vouchers, } from "./schema.js";
5
+ import { financeService } from "./service.js";
6
+ function normalizeDateTime(value) {
7
+ if (!value) {
8
+ return null;
9
+ }
10
+ return value instanceof Date ? value.toISOString() : value;
11
+ }
12
+ function isDefaultInstrument(metadata) {
13
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
14
+ return false;
15
+ }
16
+ const record = metadata;
17
+ return record.default === true || record.isDefault === true;
18
+ }
19
+ function getMetadataRecord(metadata) {
20
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
21
+ return null;
22
+ }
23
+ return metadata;
24
+ }
25
+ function getMetadataString(record, key) {
26
+ const value = record?.[key];
27
+ return typeof value === "string" && value.length > 0 ? value : null;
28
+ }
29
+ function getMetadataNumber(record, key) {
30
+ const value = record?.[key];
31
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
32
+ }
33
+ function getMetadataStringArray(record, key) {
34
+ const value = record?.[key];
35
+ if (!Array.isArray(value)) {
36
+ return [];
37
+ }
38
+ return value.filter((entry) => typeof entry === "string" && entry.length > 0);
39
+ }
40
+ function maybeUrl(value) {
41
+ if (!value) {
42
+ return null;
43
+ }
44
+ return /^https?:\/\//i.test(value) ? value : null;
45
+ }
46
+ function getMetadataDownloadUrl(record) {
47
+ return maybeUrl(getMetadataString(record, "url"));
48
+ }
49
+ function normalizeDocumentLookupQuery(query) {
50
+ return typeof query === "string" ? { reference: query } : query;
51
+ }
52
+ function toPublicPaymentTarget(session) {
53
+ if (session.bookingPaymentScheduleId) {
54
+ return {
55
+ type: "booking_payment_schedule",
56
+ bookingPaymentScheduleId: session.bookingPaymentScheduleId,
57
+ };
58
+ }
59
+ if (session.bookingGuaranteeId) {
60
+ return { type: "booking_guarantee", bookingGuaranteeId: session.bookingGuaranteeId };
61
+ }
62
+ if (session.invoiceId) {
63
+ return { type: "invoice", invoiceId: session.invoiceId };
64
+ }
65
+ if (session.targetType === "flight_order" && session.targetId) {
66
+ return { type: "flight_order", flightOrderId: session.targetId };
67
+ }
68
+ if (session.bookingId) {
69
+ return { type: "booking", bookingId: session.bookingId };
70
+ }
71
+ if (session.orderId) {
72
+ return { type: "legacy_order", legacyOrderId: session.orderId };
73
+ }
74
+ if (session.provider && session.externalReference) {
75
+ return {
76
+ type: "provider_reference",
77
+ provider: session.provider,
78
+ reference: session.externalReference,
79
+ };
80
+ }
81
+ return null;
82
+ }
83
+ function toPublicPaymentSession(session) {
84
+ return {
85
+ id: session.id,
86
+ target: toPublicPaymentTarget(session),
87
+ provenance: null,
88
+ targetType: session.targetType,
89
+ targetId: session.targetId ?? null,
90
+ bookingId: session.bookingId ?? null,
91
+ legacyOrderId: session.orderId ?? null,
92
+ invoiceId: session.invoiceId ?? null,
93
+ bookingPaymentScheduleId: session.bookingPaymentScheduleId ?? null,
94
+ bookingGuaranteeId: session.bookingGuaranteeId ?? null,
95
+ status: session.status,
96
+ provider: session.provider ?? null,
97
+ providerSessionId: session.providerSessionId ?? null,
98
+ providerPaymentId: session.providerPaymentId ?? null,
99
+ externalReference: session.externalReference ?? null,
100
+ clientReference: session.clientReference ?? null,
101
+ currency: session.currency,
102
+ amountCents: session.amountCents,
103
+ paymentMethod: session.paymentMethod ?? null,
104
+ payerEmail: session.payerEmail ?? null,
105
+ payerName: session.payerName ?? null,
106
+ redirectUrl: session.redirectUrl ?? null,
107
+ returnUrl: session.returnUrl ?? null,
108
+ cancelUrl: session.cancelUrl ?? null,
109
+ expiresAt: normalizeDateTime(session.expiresAt),
110
+ completedAt: normalizeDateTime(session.completedAt),
111
+ failureCode: session.failureCode ?? null,
112
+ failureMessage: session.failureMessage ?? null,
113
+ notes: session.notes ?? null,
114
+ };
115
+ }
116
+ async function mapInvoiceDocument(invoice, renditions, runtime = {}) {
117
+ const selectedRendition = renditions.find((rendition) => rendition.status === "ready") ?? renditions[0] ?? null;
118
+ const metadata = getMetadataRecord(selectedRendition?.metadata ?? null);
119
+ const resolvedDownloadUrl = selectedRendition?.storageKey && runtime.resolveDocumentDownloadUrl
120
+ ? await runtime.resolveDocumentDownloadUrl(selectedRendition.storageKey)
121
+ : null;
122
+ const downloadUrl = resolvedDownloadUrl ?? getMetadataDownloadUrl(metadata);
123
+ return {
124
+ invoiceId: invoice.id,
125
+ invoiceNumber: invoice.invoiceNumber,
126
+ invoiceType: invoice.invoiceType,
127
+ invoiceStatus: invoice.status,
128
+ currency: invoice.currency,
129
+ totalCents: invoice.totalCents,
130
+ paidCents: invoice.paidCents,
131
+ balanceDueCents: invoice.balanceDueCents,
132
+ issueDate: invoice.issueDate,
133
+ dueDate: invoice.dueDate,
134
+ renditionId: selectedRendition?.id ?? null,
135
+ documentStatus: selectedRendition?.status ?? "missing",
136
+ format: selectedRendition?.format ?? null,
137
+ language: selectedRendition?.language ?? null,
138
+ generatedAt: normalizeDateTime(selectedRendition?.generatedAt),
139
+ fileSize: selectedRendition?.fileSize ?? null,
140
+ checksum: selectedRendition?.checksum ?? null,
141
+ downloadUrl,
142
+ };
143
+ }
144
+ export const publicFinanceService = {
145
+ async getBookingDocuments(db, bookingId, runtime = {}) {
146
+ const [booking] = await db
147
+ .select({ id: bookings.id })
148
+ .from(bookings)
149
+ .where(eq(bookings.id, bookingId))
150
+ .limit(1);
151
+ if (!booking) {
152
+ return null;
153
+ }
154
+ const invoiceRows = await db
155
+ .select()
156
+ .from(invoices)
157
+ .where(eq(invoices.bookingId, bookingId))
158
+ .orderBy(desc(invoices.createdAt));
159
+ if (invoiceRows.length === 0) {
160
+ return { bookingId, documents: [] };
161
+ }
162
+ const renditionRows = await db
163
+ .select()
164
+ .from(invoiceRenditions)
165
+ .where(or(...invoiceRows.map((invoice) => eq(invoiceRenditions.invoiceId, invoice.id))))
166
+ .orderBy(desc(invoiceRenditions.createdAt));
167
+ const renditionByInvoiceId = new Map();
168
+ for (const rendition of renditionRows) {
169
+ const existing = renditionByInvoiceId.get(rendition.invoiceId) ?? [];
170
+ existing.push(rendition);
171
+ renditionByInvoiceId.set(rendition.invoiceId, existing);
172
+ }
173
+ return {
174
+ bookingId,
175
+ documents: await Promise.all(invoiceRows.map((invoice) => mapInvoiceDocument(invoice, renditionByInvoiceId.get(invoice.id) ?? [], runtime))),
176
+ };
177
+ },
178
+ async getDocumentByReference(db, query, runtime = {}) {
179
+ const lookup = normalizeDocumentLookupQuery(query);
180
+ const invoiceConditions = [eq(invoices.invoiceNumber, lookup.reference)];
181
+ if (lookup.invoiceType) {
182
+ invoiceConditions.push(eq(invoices.invoiceType, lookup.invoiceType));
183
+ }
184
+ const [invoiceMatch, paymentMatch] = await Promise.all([
185
+ db
186
+ .select()
187
+ .from(invoices)
188
+ .where(and(...invoiceConditions))
189
+ .orderBy(desc(invoices.createdAt))
190
+ .limit(2),
191
+ db
192
+ .select({
193
+ invoiceId: payments.invoiceId,
194
+ })
195
+ .from(payments)
196
+ .where(eq(payments.referenceNumber, lookup.reference))
197
+ .orderBy(desc(payments.createdAt))
198
+ .limit(1),
199
+ ]);
200
+ if (invoiceMatch.length > 1) {
201
+ return null;
202
+ }
203
+ const invoiceId = invoiceMatch[0]?.id ?? paymentMatch[0]?.invoiceId ?? null;
204
+ if (!invoiceId) {
205
+ return null;
206
+ }
207
+ const [invoice] = await db.select().from(invoices).where(eq(invoices.id, invoiceId)).limit(1);
208
+ if (!invoice?.bookingId) {
209
+ return null;
210
+ }
211
+ const renditions = await db
212
+ .select()
213
+ .from(invoiceRenditions)
214
+ .where(eq(invoiceRenditions.invoiceId, invoice.id))
215
+ .orderBy(desc(invoiceRenditions.createdAt));
216
+ return {
217
+ bookingId: invoice.bookingId,
218
+ ...(await mapInvoiceDocument(invoice, renditions, runtime)),
219
+ };
220
+ },
221
+ async getBookingDocumentByReference(db, bookingId, query, runtime = {}) {
222
+ const lookup = normalizeDocumentLookupQuery(query);
223
+ const invoiceConditions = [
224
+ eq(invoices.bookingId, bookingId),
225
+ eq(invoices.invoiceNumber, lookup.reference),
226
+ ];
227
+ if (lookup.invoiceType) {
228
+ invoiceConditions.push(eq(invoices.invoiceType, lookup.invoiceType));
229
+ }
230
+ const [invoiceMatch, paymentMatch] = await Promise.all([
231
+ db
232
+ .select()
233
+ .from(invoices)
234
+ .where(and(...invoiceConditions))
235
+ .orderBy(desc(invoices.createdAt))
236
+ .limit(2),
237
+ db
238
+ .select({
239
+ invoiceId: payments.invoiceId,
240
+ })
241
+ .from(payments)
242
+ .innerJoin(invoices, eq(payments.invoiceId, invoices.id))
243
+ .where(and(eq(invoices.bookingId, bookingId), eq(payments.referenceNumber, lookup.reference)))
244
+ .orderBy(desc(payments.createdAt))
245
+ .limit(1),
246
+ ]);
247
+ if (invoiceMatch.length > 1) {
248
+ return null;
249
+ }
250
+ const invoiceId = invoiceMatch[0]?.id ?? paymentMatch[0]?.invoiceId ?? null;
251
+ if (!invoiceId) {
252
+ return null;
253
+ }
254
+ const [invoice] = await db
255
+ .select()
256
+ .from(invoices)
257
+ .where(and(eq(invoices.id, invoiceId), eq(invoices.bookingId, bookingId)))
258
+ .limit(1);
259
+ if (!invoice?.bookingId) {
260
+ return null;
261
+ }
262
+ const renditions = await db
263
+ .select()
264
+ .from(invoiceRenditions)
265
+ .where(eq(invoiceRenditions.invoiceId, invoice.id))
266
+ .orderBy(desc(invoiceRenditions.createdAt));
267
+ return {
268
+ bookingId: invoice.bookingId,
269
+ ...(await mapInvoiceDocument(invoice, renditions, runtime)),
270
+ };
271
+ },
272
+ async getBookingPaymentOptions(db, bookingId, query) {
273
+ const [booking] = await db
274
+ .select({ id: bookings.id })
275
+ .from(bookings)
276
+ .where(eq(bookings.id, bookingId))
277
+ .limit(1);
278
+ if (!booking) {
279
+ return null;
280
+ }
281
+ const instrumentConditions = [eq(paymentInstruments.ownerType, "client")];
282
+ if (!query.includeInactive) {
283
+ instrumentConditions.push(eq(paymentInstruments.status, "active"));
284
+ }
285
+ if (query.personId) {
286
+ instrumentConditions.push(eq(paymentInstruments.personId, query.personId));
287
+ }
288
+ if (query.organizationId) {
289
+ instrumentConditions.push(eq(paymentInstruments.organizationId, query.organizationId));
290
+ }
291
+ if (query.provider) {
292
+ instrumentConditions.push(eq(paymentInstruments.provider, query.provider));
293
+ }
294
+ if (query.instrumentType) {
295
+ instrumentConditions.push(eq(paymentInstruments.instrumentType, query.instrumentType));
296
+ }
297
+ const [accounts, schedules, guarantees] = await Promise.all([
298
+ db
299
+ .select()
300
+ .from(paymentInstruments)
301
+ .where(and(...instrumentConditions))
302
+ .orderBy(desc(paymentInstruments.updatedAt))
303
+ .limit(50),
304
+ db
305
+ .select()
306
+ .from(bookingPaymentSchedules)
307
+ .where(and(eq(bookingPaymentSchedules.bookingId, bookingId), or(eq(bookingPaymentSchedules.status, "pending"), eq(bookingPaymentSchedules.status, "due"))))
308
+ .orderBy(asc(bookingPaymentSchedules.dueDate), asc(bookingPaymentSchedules.createdAt)),
309
+ db
310
+ .select()
311
+ .from(bookingGuarantees)
312
+ .where(and(eq(bookingGuarantees.bookingId, bookingId), or(eq(bookingGuarantees.status, "pending"), eq(bookingGuarantees.status, "failed"), eq(bookingGuarantees.status, "expired"))))
313
+ .orderBy(desc(bookingGuarantees.createdAt)),
314
+ ]);
315
+ const recommendedSchedule = schedules[0] ?? null;
316
+ const recommendedGuarantee = recommendedSchedule === null ? (guarantees[0] ?? null) : null;
317
+ return {
318
+ bookingId,
319
+ accounts: accounts.map((account) => ({
320
+ id: account.id,
321
+ label: account.label,
322
+ provider: account.provider ?? null,
323
+ instrumentType: account.instrumentType,
324
+ status: account.status,
325
+ brand: account.brand ?? null,
326
+ last4: account.last4 ?? null,
327
+ expiryMonth: account.expiryMonth ?? null,
328
+ expiryYear: account.expiryYear ?? null,
329
+ isDefault: isDefaultInstrument(account.metadata),
330
+ })),
331
+ schedules: schedules.map((schedule) => ({
332
+ id: schedule.id,
333
+ scheduleType: schedule.scheduleType,
334
+ status: schedule.status,
335
+ dueDate: schedule.dueDate,
336
+ currency: schedule.currency,
337
+ amountCents: schedule.amountCents,
338
+ notes: schedule.notes ?? null,
339
+ })),
340
+ guarantees: guarantees.map((guarantee) => ({
341
+ id: guarantee.id,
342
+ bookingPaymentScheduleId: guarantee.bookingPaymentScheduleId ?? null,
343
+ guaranteeType: guarantee.guaranteeType,
344
+ status: guarantee.status,
345
+ currency: guarantee.currency ?? null,
346
+ amountCents: guarantee.amountCents ?? null,
347
+ provider: guarantee.provider ?? null,
348
+ referenceNumber: guarantee.referenceNumber ?? null,
349
+ expiresAt: normalizeDateTime(guarantee.expiresAt),
350
+ notes: guarantee.notes ?? null,
351
+ })),
352
+ recommendedTarget: recommendedSchedule || recommendedGuarantee
353
+ ? {
354
+ targetType: recommendedSchedule
355
+ ? "booking_payment_schedule"
356
+ : "booking_guarantee",
357
+ targetId: recommendedSchedule?.id ?? recommendedGuarantee?.id ?? null,
358
+ }
359
+ : null,
360
+ };
361
+ },
362
+ async getBookingPayments(db, bookingId) {
363
+ const [booking] = await db
364
+ .select({ id: bookings.id })
365
+ .from(bookings)
366
+ .where(eq(bookings.id, bookingId))
367
+ .limit(1);
368
+ if (!booking) {
369
+ return null;
370
+ }
371
+ const invoiceRows = await db
372
+ .select({
373
+ id: invoices.id,
374
+ invoiceNumber: invoices.invoiceNumber,
375
+ invoiceType: invoices.invoiceType,
376
+ })
377
+ .from(invoices)
378
+ .where(eq(invoices.bookingId, bookingId))
379
+ .orderBy(desc(invoices.createdAt));
380
+ if (invoiceRows.length === 0) {
381
+ return { bookingId, payments: [] };
382
+ }
383
+ const invoiceById = new Map(invoiceRows.map((invoice) => [invoice.id, invoice]));
384
+ const paymentRows = await db
385
+ .select()
386
+ .from(payments)
387
+ .where(or(...invoiceRows.map((invoice) => eq(payments.invoiceId, invoice.id))))
388
+ .orderBy(desc(payments.paymentDate), desc(payments.createdAt));
389
+ return {
390
+ bookingId,
391
+ payments: paymentRows.flatMap((payment) => {
392
+ const invoice = invoiceById.get(payment.invoiceId);
393
+ if (!invoice) {
394
+ return [];
395
+ }
396
+ return [
397
+ {
398
+ id: payment.id,
399
+ invoiceId: invoice.id,
400
+ invoiceNumber: invoice.invoiceNumber,
401
+ invoiceType: invoice.invoiceType,
402
+ status: payment.status,
403
+ paymentMethod: payment.paymentMethod,
404
+ amountCents: payment.amountCents,
405
+ currency: payment.currency,
406
+ baseCurrency: payment.baseCurrency ?? null,
407
+ baseAmountCents: payment.baseAmountCents ?? null,
408
+ paymentDate: payment.paymentDate,
409
+ referenceNumber: payment.referenceNumber ?? null,
410
+ notes: payment.notes ?? null,
411
+ },
412
+ ];
413
+ }),
414
+ };
415
+ },
416
+ async getPaymentSession(db, sessionId) {
417
+ const session = await financeService.getPaymentSessionById(db, sessionId);
418
+ return session ? toPublicPaymentSession(session) : null;
419
+ },
420
+ async getInvoiceBookingId(db, invoiceId) {
421
+ const [invoice] = await db
422
+ .select({ bookingId: invoices.bookingId })
423
+ .from(invoices)
424
+ .where(eq(invoices.id, invoiceId))
425
+ .limit(1);
426
+ return invoice?.bookingId ?? null;
427
+ },
428
+ async startBookingSchedulePaymentSession(db, bookingId, scheduleId, input) {
429
+ const [schedule] = await db
430
+ .select({
431
+ id: bookingPaymentSchedules.id,
432
+ })
433
+ .from(bookingPaymentSchedules)
434
+ .where(and(eq(bookingPaymentSchedules.id, scheduleId), eq(bookingPaymentSchedules.bookingId, bookingId)))
435
+ .limit(1);
436
+ if (!schedule) {
437
+ return null;
438
+ }
439
+ const session = await financeService.createPaymentSessionFromBookingSchedule(db, scheduleId, input);
440
+ return session ? toPublicPaymentSession(session) : null;
441
+ },
442
+ async startBookingGuaranteePaymentSession(db, bookingId, guaranteeId, input) {
443
+ const [guarantee] = await db
444
+ .select({
445
+ id: bookingGuarantees.id,
446
+ })
447
+ .from(bookingGuarantees)
448
+ .where(and(eq(bookingGuarantees.id, guaranteeId), eq(bookingGuarantees.bookingId, bookingId)))
449
+ .limit(1);
450
+ if (!guarantee) {
451
+ return null;
452
+ }
453
+ const session = await financeService.createPaymentSessionFromBookingGuarantee(db, guaranteeId, input);
454
+ return session ? toPublicPaymentSession(session) : null;
455
+ },
456
+ async startInvoicePaymentSession(db, invoiceId, input) {
457
+ const session = await financeService.createPaymentSessionFromInvoice(db, invoiceId, input);
458
+ return session ? toPublicPaymentSession(session) : null;
459
+ },
460
+ async validateVoucher(db, input) {
461
+ const normalizedCode = input.code.trim();
462
+ const normalizedCodeLower = normalizedCode.toLowerCase();
463
+ // New path: first-class `vouchers` table. Covers any voucher issued via
464
+ // POST /v1/finance/vouchers.
465
+ const resolvedFromNewTable = await resolveVoucherFromNewTable(db, normalizedCode);
466
+ if (resolvedFromNewTable) {
467
+ return evaluateVoucherValidity(resolvedFromNewTable, input);
468
+ }
469
+ // Fallback path: legacy payment_instruments rows with instrumentType =
470
+ // 'voucher' and balance carried in metadata JSONB. Kept working until the
471
+ // migration script flips remaining rows over to the new table.
472
+ const voucherConditions = [
473
+ eq(paymentInstruments.instrumentType, "voucher"),
474
+ or(
475
+ // agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
476
+ sql `lower(coalesce(${paymentInstruments.externalToken}, '')) = ${normalizedCodeLower}`,
477
+ // agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
478
+ sql `lower(coalesce(${paymentInstruments.directBillReference}, '')) = ${normalizedCodeLower}`,
479
+ // agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
480
+ sql `lower(coalesce(${paymentInstruments.metadata} ->> 'code', '')) = ${normalizedCodeLower}`),
481
+ ];
482
+ if (input.provider) {
483
+ voucherConditions.push(eq(paymentInstruments.provider, input.provider));
484
+ }
485
+ const [voucher] = await db
486
+ .select()
487
+ .from(paymentInstruments)
488
+ .where(and(...voucherConditions))
489
+ .orderBy(desc(paymentInstruments.updatedAt))
490
+ .limit(1);
491
+ if (!voucher) {
492
+ return { valid: false, reason: "not_found", voucher: null };
493
+ }
494
+ const metadata = getMetadataRecord(voucher.metadata);
495
+ const voucherCode = getMetadataString(metadata, "code") ??
496
+ voucher.externalToken ??
497
+ voucher.directBillReference ??
498
+ input.code;
499
+ const currency = getMetadataString(metadata, "currency");
500
+ const amountCents = getMetadataNumber(metadata, "amountCents");
501
+ const remainingAmountCents = getMetadataNumber(metadata, "remainingAmountCents") ?? amountCents;
502
+ const validFrom = getMetadataString(metadata, "validFrom");
503
+ const expiresAt = getMetadataString(metadata, "expiresAt");
504
+ const bookingIds = getMetadataStringArray(metadata, "bookingIds");
505
+ const bookingId = getMetadataString(metadata, "bookingId");
506
+ const appliesToBookingIds = bookingId ? [bookingId, ...bookingIds] : bookingIds;
507
+ return evaluateVoucherValidity({
508
+ id: voucher.id,
509
+ code: voucherCode,
510
+ label: voucher.label,
511
+ provider: voucher.provider ?? null,
512
+ currency,
513
+ amountCents,
514
+ remainingAmountCents,
515
+ validFrom,
516
+ expiresAt,
517
+ appliesToBookingIds,
518
+ status: voucher.status === "active" ? "active" : "inactive",
519
+ }, input);
520
+ },
521
+ };
522
+ async function resolveVoucherFromNewTable(db, code) {
523
+ const [row] = await db
524
+ .select()
525
+ .from(vouchers)
526
+ // agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
527
+ .where(sql `lower(${vouchers.code}) = ${code.toLowerCase()}`)
528
+ .limit(1);
529
+ if (!row)
530
+ return null;
531
+ return {
532
+ id: row.id,
533
+ code: row.code,
534
+ label: null,
535
+ provider: null,
536
+ currency: row.currency,
537
+ amountCents: row.initialAmountCents,
538
+ remainingAmountCents: row.remainingAmountCents,
539
+ validFrom: row.validFrom ? row.validFrom.toISOString() : null,
540
+ expiresAt: row.expiresAt ? row.expiresAt.toISOString() : null,
541
+ appliesToBookingIds: row.sourceBookingId ? [row.sourceBookingId] : [],
542
+ status: row.status === "active" ? "active" : "inactive",
543
+ };
544
+ }
545
+ function evaluateVoucherValidity(voucher, input) {
546
+ const publicVoucher = {
547
+ id: voucher.id,
548
+ code: voucher.code,
549
+ label: voucher.label,
550
+ provider: voucher.provider,
551
+ currency: voucher.currency,
552
+ amountCents: voucher.amountCents,
553
+ remainingAmountCents: voucher.remainingAmountCents,
554
+ expiresAt: voucher.expiresAt,
555
+ };
556
+ if (voucher.status !== "active") {
557
+ return { valid: false, reason: "inactive", voucher: publicVoucher };
558
+ }
559
+ if (voucher.validFrom && new Date(voucher.validFrom) > new Date()) {
560
+ return { valid: false, reason: "not_started", voucher: publicVoucher };
561
+ }
562
+ if (voucher.expiresAt && new Date(voucher.expiresAt) < new Date()) {
563
+ return { valid: false, reason: "expired", voucher: publicVoucher };
564
+ }
565
+ if (input.bookingId &&
566
+ voucher.appliesToBookingIds.length > 0 &&
567
+ !voucher.appliesToBookingIds.includes(input.bookingId)) {
568
+ return { valid: false, reason: "booking_mismatch", voucher: publicVoucher };
569
+ }
570
+ if (input.currency && voucher.currency && input.currency !== voucher.currency) {
571
+ return { valid: false, reason: "currency_mismatch", voucher: publicVoucher };
572
+ }
573
+ if (input.amountCents &&
574
+ voucher.remainingAmountCents !== null &&
575
+ input.amountCents > voucher.remainingAmountCents) {
576
+ return {
577
+ valid: false,
578
+ reason: "insufficient_balance",
579
+ voucher: publicVoucher,
580
+ };
581
+ }
582
+ return { valid: true, reason: null, voucher: publicVoucher };
583
+ }