@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 @@
1
+ {"version":3,"file":"service-bookings-dual-create.d.ts","sourceRoot":"","sources":["../src/service-bookings-dual-create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACtF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAGzB,MAAM,6BAA6B,CAAA;AAkBpC,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIlC,CAAA;AAEF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAI5E,MAAM,WAAW,wBAAyB,SAAQ,qBAAqB;CAAG;AAE1E,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,MAAM,CAAA;IACxB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,UAAU,EAAE,IAAI,CAAA;CACjB;AAID,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,mBAAmB,CAAA;IAC5B,SAAS,EAAE,mBAAmB,CAAA;IAC9B,KAAK,EAAE,YAAY,CAAA;IACnB,aAAa,EAAE,kBAAkB,CAAA;IACjC,eAAe,EAAE,kBAAkB,CAAA;CACpC;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GACjD;IACE,MAAM,EAAE,gBAAgB,GAAG,kBAAkB,CAAA;IAC7C,MAAM,EAAE,OAAO,CAAC,oBAAoB,EAAE;QAAE,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;CACxD,CAAA;AAqBL;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,kBAAkB,EACtB,QAAQ,EAAE,sBAAsB,EAChC,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,wBAAwB,CAAA;CAC9B,GACL,OAAO,CAAC,wBAAwB,CAAC,CAkFnC"}
@@ -0,0 +1,131 @@
1
+ import { bookingGroupsService } from "@voyant-travel/bookings";
2
+ import { z } from "zod";
3
+ import { bookingCreateSubSchema, createBooking, } from "./service-booking-create.js";
4
+ // ---------- validation ----------
5
+ /**
6
+ * Sub-booking input. Takes the full create payload minus `group
7
+ * Membership` — dual-create owns the group lifecycle (one new group, both
8
+ * bookings linked as members) so accepting a nested group override would
9
+ * just be an opportunity for the caller to desync.
10
+ */
11
+ const dualSubBookingSchema = bookingCreateSubSchema;
12
+ const dualCreateGroupSchema = z.object({
13
+ kind: z.enum(["shared_room", "other"]).default("shared_room"),
14
+ label: z.string().max(255).optional().nullable(),
15
+ optionUnitId: z.string().optional().nullable(),
16
+ });
17
+ export const dualCreateBookingSchema = z.object({
18
+ primary: dualSubBookingSchema,
19
+ secondary: dualSubBookingSchema,
20
+ group: dualCreateGroupSchema.default({ kind: "shared_room" }),
21
+ });
22
+ /**
23
+ * Thrown inside the outer tx to force drizzle to roll back both bookings +
24
+ * the group when one of the inner create calls returns non-ok.
25
+ * Drizzle doesn't abort on a non-throwing tx callback, so we convert the
26
+ * discriminated outcome into a throw here.
27
+ */
28
+ class DualCreateAbort extends Error {
29
+ outcome;
30
+ constructor(outcome) {
31
+ super(outcome.status === "ok"
32
+ ? "dual-create aborted: ok (unexpected)"
33
+ : `dual-create aborted: ${outcome.status}:${outcome.reason.status}`);
34
+ this.outcome = outcome;
35
+ this.name = "DualCreateAbort";
36
+ }
37
+ }
38
+ // ---------- service ----------
39
+ /**
40
+ * Create two bookings linked via a new `booking_group`, atomically. The
41
+ * canonical operator flow: two travelers book a shared room ("partaj"), each
42
+ * gets their own booking, and both are attached to a new shared_room group
43
+ * so subsequent payment / cancellation decisions can fan out across the
44
+ * pair.
45
+ *
46
+ * Transaction shape:
47
+ * - Outer tx opens via `db.transaction`.
48
+ * - Inner: two savepoint-scoped `createBooking(tx, ...)` calls — the
49
+ * nested transactions drizzle opens use SAVEPOINTs, so partial failures
50
+ * surface up to this layer as non-ok outcomes.
51
+ * - If either fails, the outer tx throws `DualCreateAbort` so the whole
52
+ * thing rolls back (no orphan booking, no orphan group).
53
+ * - Group creation + both memberships run last, inside the same outer tx.
54
+ *
55
+ * Event emission (`booking.dual-created`) is post-commit — subscribers only
56
+ * hear about successful pairs.
57
+ */
58
+ export async function dualCreateBooking(db, rawInput, options = {}) {
59
+ const { userId, runtime } = options;
60
+ const input = dualCreateBookingSchema.parse(rawInput);
61
+ let result;
62
+ try {
63
+ result = await db.transaction(async (tx) => {
64
+ const primaryOutcome = await createBooking(tx, input.primary, { userId, runtime });
65
+ if (primaryOutcome.status !== "ok") {
66
+ throw new DualCreateAbort({ status: "primary_failed", reason: primaryOutcome });
67
+ }
68
+ const secondaryOutcome = await createBooking(tx, input.secondary, { userId, runtime });
69
+ if (secondaryOutcome.status !== "ok") {
70
+ throw new DualCreateAbort({ status: "secondary_failed", reason: secondaryOutcome });
71
+ }
72
+ const primaryBooking = primaryOutcome.result.booking;
73
+ const secondaryBooking = secondaryOutcome.result.booking;
74
+ const group = await bookingGroupsService.createBookingGroup(tx, {
75
+ kind: input.group.kind,
76
+ label: input.group.label ??
77
+ `Shared — ${primaryBooking.bookingNumber} + ${secondaryBooking.bookingNumber}`,
78
+ productId: input.primary.productId,
79
+ optionUnitId: input.group.optionUnitId ?? null,
80
+ primaryBookingId: primaryBooking.id,
81
+ });
82
+ const primaryMemberResult = await bookingGroupsService.addGroupMember(tx, group.id, {
83
+ bookingId: primaryBooking.id,
84
+ role: "primary",
85
+ });
86
+ if (primaryMemberResult.status !== "ok") {
87
+ // Shouldn't happen — we just created both rows in this tx — but bail
88
+ // through the same abort path to unwind cleanly.
89
+ throw new DualCreateAbort({
90
+ status: "primary_failed",
91
+ reason: { status: "group_not_found" },
92
+ });
93
+ }
94
+ const secondaryMemberResult = await bookingGroupsService.addGroupMember(tx, group.id, {
95
+ bookingId: secondaryBooking.id,
96
+ role: "shared",
97
+ });
98
+ if (secondaryMemberResult.status !== "ok") {
99
+ throw new DualCreateAbort({
100
+ status: "secondary_failed",
101
+ reason: { status: "group_not_found" },
102
+ });
103
+ }
104
+ return {
105
+ primary: primaryOutcome.result,
106
+ secondary: secondaryOutcome.result,
107
+ group,
108
+ primaryMember: primaryMemberResult.member,
109
+ secondaryMember: secondaryMemberResult.member,
110
+ };
111
+ });
112
+ }
113
+ catch (error) {
114
+ if (error instanceof DualCreateAbort) {
115
+ return error.outcome;
116
+ }
117
+ throw error;
118
+ }
119
+ if (runtime?.eventBus) {
120
+ const event = {
121
+ groupId: result.group.id,
122
+ primaryBookingId: result.primary.booking.id,
123
+ secondaryBookingId: result.secondary.booking.id,
124
+ productId: input.primary.productId,
125
+ createdByUserId: userId ?? null,
126
+ occurredAt: new Date(),
127
+ };
128
+ await runtime.eventBus.emit("booking.dual-created", event);
129
+ }
130
+ return { status: "ok", result };
131
+ }
@@ -0,0 +1,6 @@
1
+ import { type SQL } from "drizzle-orm";
2
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
+ export declare function executeBoundaryRows<T extends object>(db: PostgresJsDatabase, query: SQL): Promise<T[]>;
4
+ export declare function sqlList(values: readonly string[]): SQL;
5
+ export declare function normalizeDateOnly(value: Date | string | null | undefined): string | null;
6
+ //# sourceMappingURL=service-boundary-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-boundary-sql.d.ts","sourceRoot":"","sources":["../src/service-boundary-sql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAO,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EACxD,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,GAAG,GACT,OAAO,CAAC,CAAC,EAAE,CAAC,CAId;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAMtD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGxF"}
@@ -0,0 +1,15 @@
1
+ import { sql } from "drizzle-orm";
2
+ export async function executeBoundaryRows(db, query) {
3
+ // biome-ignore lint/suspicious/noExplicitAny: #1141 keeps cross-package SQL boundary reads driver-agnostic.
4
+ const result = await db.execute(query);
5
+ return (Array.isArray(result) ? result : (result?.rows ?? []));
6
+ }
7
+ export function sqlList(values) {
8
+ // agent-quality: raw-sql reviewed -- owner: finance; callers pass only parameter-bound scalar ids into the joined SQL fragment.
9
+ return sql.join(values.map((value) => sql `${value}`), sql `, `);
10
+ }
11
+ export function normalizeDateOnly(value) {
12
+ if (!value)
13
+ return null;
14
+ return value instanceof Date ? value.toISOString().slice(0, 10) : value.slice(0, 10);
15
+ }
@@ -0,0 +1,26 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ export interface CostCategoryRecord {
3
+ id: string;
4
+ name: string;
5
+ sortOrder: number;
6
+ archived: boolean;
7
+ createdAt: string;
8
+ updatedAt: string;
9
+ }
10
+ export declare const costCategoriesService: {
11
+ list(db: PostgresJsDatabase, options?: {
12
+ includeArchived?: boolean;
13
+ }): Promise<CostCategoryRecord[]>;
14
+ create(db: PostgresJsDatabase, input: {
15
+ name: string;
16
+ sortOrder?: number;
17
+ }): Promise<CostCategoryRecord>;
18
+ update(db: PostgresJsDatabase, id: string, input: {
19
+ name?: string;
20
+ sortOrder?: number;
21
+ archived?: boolean;
22
+ }): Promise<CostCategoryRecord | null>;
23
+ /** Resolve id → name for a set of category ids (for breakdown labelling). */
24
+ nameMap(db: PostgresJsDatabase, ids: string[]): Promise<Map<string, string>>;
25
+ };
26
+ //# sourceMappingURL=service-cost-categories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-cost-categories.d.ts","sourceRoot":"","sources":["../src/service-cost-categories.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAkBjE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAaD,eAAO,MAAM,qBAAqB;aAE1B,kBAAkB,YACb;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACrC,OAAO,CAAC,kBAAkB,EAAE,CAAC;eAiB1B,kBAAkB,SACf;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1C,OAAO,CAAC,kBAAkB,CAAC;eAUxB,kBAAkB,MAClB,MAAM,SACH;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/D,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAarC,6EAA6E;gBAC3D,kBAAkB,OAAO,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CASnF,CAAA"}
@@ -0,0 +1,76 @@
1
+ import { asc, eq, isNull, sql } from "drizzle-orm";
2
+ import { costCategories } from "./schema.js";
3
+ /**
4
+ * Operator-configurable cost categories. Used to classify supplier-invoice
5
+ * lines and drive the per-category cost breakdown. Seeded lazily with sensible
6
+ * defaults the first time the list is read, so a fresh operator already has
7
+ * transportation / accommodation / guides / other to pick from.
8
+ */
9
+ const DEFAULT_CATEGORIES = [
10
+ "Transportation",
11
+ "Accommodation",
12
+ "Guides / touristic services",
13
+ "Other",
14
+ ];
15
+ function toRecord(row) {
16
+ return {
17
+ id: row.id,
18
+ name: row.name,
19
+ sortOrder: row.sortOrder,
20
+ archived: row.archivedAt != null,
21
+ createdAt: row.createdAt.toISOString(),
22
+ updatedAt: row.updatedAt.toISOString(),
23
+ };
24
+ }
25
+ export const costCategoriesService = {
26
+ async list(db, options = {}) {
27
+ const existing = await db.select().from(costCategories);
28
+ if (existing.length === 0) {
29
+ await db
30
+ .insert(costCategories)
31
+ .values(DEFAULT_CATEGORIES.map((name, index) => ({ name, sortOrder: index })))
32
+ .onConflictDoNothing();
33
+ }
34
+ const rows = await db
35
+ .select()
36
+ .from(costCategories)
37
+ .where(options.includeArchived ? undefined : isNull(costCategories.archivedAt))
38
+ .orderBy(asc(costCategories.sortOrder), asc(costCategories.name));
39
+ return rows.map(toRecord);
40
+ },
41
+ async create(db, input) {
42
+ const [row] = await db
43
+ .insert(costCategories)
44
+ .values({ name: input.name.trim(), sortOrder: input.sortOrder ?? 0 })
45
+ .returning();
46
+ if (!row)
47
+ throw new Error("Failed to create cost category");
48
+ return toRecord(row);
49
+ },
50
+ async update(db, id, input) {
51
+ const patch = { updatedAt: new Date() };
52
+ if (input.name !== undefined)
53
+ patch.name = input.name.trim();
54
+ if (input.sortOrder !== undefined)
55
+ patch.sortOrder = input.sortOrder;
56
+ if (input.archived !== undefined)
57
+ patch.archivedAt = input.archived ? new Date() : null;
58
+ const [row] = await db
59
+ .update(costCategories)
60
+ .set(patch)
61
+ .where(eq(costCategories.id, id))
62
+ .returning();
63
+ return row ? toRecord(row) : null;
64
+ },
65
+ /** Resolve id → name for a set of category ids (for breakdown labelling). */
66
+ async nameMap(db, ids) {
67
+ if (ids.length === 0)
68
+ return new Map();
69
+ const rows = await db
70
+ .select({ id: costCategories.id, name: costCategories.name })
71
+ .from(costCategories)
72
+ // agent-quality: raw-sql reviewed -- owner: finance; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
73
+ .where(sql `${costCategories.id} = any(${ids})`);
74
+ return new Map(rows.map((r) => [r.id, r.name]));
75
+ },
76
+ };
@@ -0,0 +1,80 @@
1
+ import type { EventBus } from "@voyant-travel/core";
2
+ import type { StorageProvider, StorageUploadBody } from "@voyant-travel/storage";
3
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
4
+ import { type invoiceLineItems, type invoiceRenditions, type invoices, invoiceTemplates, type payments } from "./schema.js";
5
+ import type { GenerateInvoiceDocumentInput } from "./validation.js";
6
+ export interface GeneratedInvoiceRenditionArtifact {
7
+ format?: "html" | "pdf" | "xml" | "json";
8
+ storageKey?: string | null;
9
+ contentType?: string | null;
10
+ fileSize?: number | null;
11
+ checksum?: string | null;
12
+ language?: string | null;
13
+ metadata?: Record<string, unknown> | null;
14
+ }
15
+ export interface InvoiceDocumentGeneratorContext {
16
+ db: PostgresJsDatabase;
17
+ invoice: typeof invoices.$inferSelect;
18
+ template: typeof invoiceTemplates.$inferSelect | null;
19
+ lineItems: Array<typeof invoiceLineItems.$inferSelect>;
20
+ payments: Array<typeof payments.$inferSelect>;
21
+ renderedBody: string;
22
+ renderedBodyFormat: "html" | "markdown" | "lexical_json";
23
+ variables: Record<string, unknown>;
24
+ bindings: Record<string, unknown>;
25
+ targetFormat: "html" | "pdf" | "xml" | "json";
26
+ language: string | null;
27
+ }
28
+ export type InvoiceDocumentGenerator = (context: InvoiceDocumentGeneratorContext) => Promise<GeneratedInvoiceRenditionArtifact>;
29
+ export interface InvoiceDocumentRuntimeOptions {
30
+ bindings?: Record<string, unknown>;
31
+ generator: InvoiceDocumentGenerator;
32
+ eventBus?: EventBus;
33
+ }
34
+ export interface StorageBackedInvoiceDocumentUpload {
35
+ body: StorageUploadBody;
36
+ format?: "html" | "pdf" | "xml" | "json";
37
+ key?: string | null;
38
+ metadata?: Record<string, unknown> | null;
39
+ language?: string | null;
40
+ }
41
+ export type StorageBackedInvoiceDocumentSerializer = (context: InvoiceDocumentGeneratorContext) => Promise<StorageBackedInvoiceDocumentUpload> | StorageBackedInvoiceDocumentUpload;
42
+ export interface StorageBackedInvoiceDocumentGeneratorOptions {
43
+ storage: StorageProvider;
44
+ keyPrefix?: string | ((context: InvoiceDocumentGeneratorContext) => Promise<string> | string);
45
+ serializer?: StorageBackedInvoiceDocumentSerializer;
46
+ }
47
+ export interface GeneratedInvoiceDocumentRecord {
48
+ invoiceId: string;
49
+ renderedBodyFormat: "html" | "markdown" | "lexical_json";
50
+ renderedBody: string;
51
+ rendition: typeof invoiceRenditions.$inferSelect;
52
+ }
53
+ export interface InvoiceDocumentGeneratedEvent {
54
+ invoiceId: string;
55
+ invoiceStatus: (typeof invoices.$inferSelect)["status"];
56
+ invoiceType: (typeof invoices.$inferSelect)["invoiceType"];
57
+ renditionId: string;
58
+ format: (typeof invoiceRenditions.$inferSelect)["format"];
59
+ renderedBodyFormat: "html" | "markdown" | "lexical_json";
60
+ regenerated: boolean;
61
+ }
62
+ export declare function defaultStorageBackedInvoiceDocumentSerializer(context: InvoiceDocumentGeneratorContext): Promise<StorageBackedInvoiceDocumentUpload> | StorageBackedInvoiceDocumentUpload;
63
+ export declare function defaultPdfInvoiceDocumentSerializer(context: InvoiceDocumentGeneratorContext): Promise<StorageBackedInvoiceDocumentUpload>;
64
+ export declare function createStorageBackedInvoiceDocumentGenerator(options: StorageBackedInvoiceDocumentGeneratorOptions): InvoiceDocumentGenerator;
65
+ export declare function createPdfInvoiceDocumentGenerator(options: Omit<StorageBackedInvoiceDocumentGeneratorOptions, "serializer">): InvoiceDocumentGenerator;
66
+ export declare const financeDocumentsService: {
67
+ generateInvoiceDocument(db: PostgresJsDatabase, invoiceId: string, input: GenerateInvoiceDocumentInput, runtime: InvoiceDocumentRuntimeOptions, options?: {
68
+ regenerated?: boolean;
69
+ }): Promise<{
70
+ status: "not_found" | "generator_failed";
71
+ } | ({
72
+ status: "generated";
73
+ } & GeneratedInvoiceDocumentRecord)>;
74
+ regenerateInvoiceDocument(db: PostgresJsDatabase, invoiceId: string, input: GenerateInvoiceDocumentInput, runtime: InvoiceDocumentRuntimeOptions): Promise<{
75
+ status: "not_found" | "generator_failed";
76
+ } | ({
77
+ status: "generated";
78
+ } & GeneratedInvoiceDocumentRecord)>;
79
+ };
80
+ //# sourceMappingURL=service-documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-documents.d.ts","sourceRoot":"","sources":["../src/service-documents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,228 @@
1
+ import { renderPdfDocument } from "@voyant-travel/utils/pdf-renderer";
2
+ import { and, desc, eq } from "drizzle-orm";
3
+ import { invoiceTemplates, } from "./schema.js";
4
+ import { financeService, renderInvoiceBody } from "./service.js";
5
+ function defaultInvoiceDocumentMimeType(format) {
6
+ switch (format) {
7
+ case "html":
8
+ return "text/html; charset=utf-8";
9
+ case "json":
10
+ return "application/json; charset=utf-8";
11
+ case "xml":
12
+ return "application/xml; charset=utf-8";
13
+ default:
14
+ return "application/pdf";
15
+ }
16
+ }
17
+ function encodeStringBody(value) {
18
+ return new TextEncoder().encode(value);
19
+ }
20
+ function getBodySize(body) {
21
+ if (body instanceof Uint8Array)
22
+ return body.byteLength;
23
+ if (body instanceof ArrayBuffer)
24
+ return body.byteLength;
25
+ return body.size;
26
+ }
27
+ function toUploadMetadata(metadata) {
28
+ const entries = Object.entries(metadata ?? {}).filter(([, value]) => ["string", "number", "boolean"].includes(typeof value));
29
+ return entries.length > 0
30
+ ? Object.fromEntries(entries.map(([key, value]) => [key, String(value)]))
31
+ : undefined;
32
+ }
33
+ export function defaultStorageBackedInvoiceDocumentSerializer(context) {
34
+ switch (context.targetFormat) {
35
+ case "html":
36
+ return {
37
+ body: encodeStringBody(context.renderedBody),
38
+ format: "html",
39
+ language: context.language,
40
+ metadata: { renderedBodyFormat: context.renderedBodyFormat },
41
+ };
42
+ case "json":
43
+ return {
44
+ body: encodeStringBody(JSON.stringify(context.variables, null, 2)),
45
+ format: "json",
46
+ language: context.language,
47
+ metadata: { renderedBodyFormat: context.renderedBodyFormat },
48
+ };
49
+ case "xml":
50
+ return {
51
+ body: encodeStringBody(context.renderedBody),
52
+ format: "xml",
53
+ language: context.language,
54
+ metadata: { renderedBodyFormat: context.renderedBodyFormat },
55
+ };
56
+ default:
57
+ return defaultPdfInvoiceDocumentSerializer(context);
58
+ }
59
+ }
60
+ export async function defaultPdfInvoiceDocumentSerializer(context) {
61
+ const body = await renderPdfDocument({
62
+ title: `Invoice ${context.invoice.id}`,
63
+ content: context.renderedBody,
64
+ format: context.renderedBodyFormat === "lexical_json"
65
+ ? "lexical_json"
66
+ : context.renderedBodyFormat === "html"
67
+ ? "html"
68
+ : "markdown",
69
+ metadataLines: [
70
+ `Invoice ID: ${context.invoice.id}`,
71
+ ...(context.language ? [`Language: ${context.language}`] : []),
72
+ ],
73
+ });
74
+ return {
75
+ body,
76
+ format: "pdf",
77
+ language: context.language,
78
+ metadata: {
79
+ renderedBodyFormat: context.renderedBodyFormat,
80
+ renderer: "voyant-basic-pdf",
81
+ },
82
+ };
83
+ }
84
+ export function createStorageBackedInvoiceDocumentGenerator(options) {
85
+ const serializer = options.serializer ?? defaultStorageBackedInvoiceDocumentSerializer;
86
+ return async (context) => {
87
+ const upload = await serializer(context);
88
+ const format = upload.format ?? context.targetFormat;
89
+ const keyPrefix = typeof options.keyPrefix === "function"
90
+ ? await options.keyPrefix(context)
91
+ : (options.keyPrefix ?? `invoices/${context.invoice.id}`);
92
+ const key = upload.key?.trim() || `${keyPrefix.replace(/\/$/, "")}/rendition.${format}`;
93
+ const uploaded = await options.storage.upload(upload.body, {
94
+ key,
95
+ contentType: defaultInvoiceDocumentMimeType(format),
96
+ metadata: toUploadMetadata(upload.metadata),
97
+ });
98
+ return {
99
+ format,
100
+ storageKey: uploaded.key,
101
+ contentType: defaultInvoiceDocumentMimeType(format),
102
+ fileSize: getBodySize(upload.body),
103
+ language: upload.language ?? context.language,
104
+ metadata: {
105
+ ...(upload.metadata ?? {}),
106
+ storageProvider: options.storage.name,
107
+ ...(uploaded.url ? { url: uploaded.url } : {}),
108
+ },
109
+ };
110
+ };
111
+ }
112
+ export function createPdfInvoiceDocumentGenerator(options) {
113
+ return createStorageBackedInvoiceDocumentGenerator({
114
+ ...options,
115
+ serializer: defaultPdfInvoiceDocumentSerializer,
116
+ });
117
+ }
118
+ async function prepareInvoiceDocument(db, invoiceId, input) {
119
+ const invoice = await financeService.getInvoiceById(db, invoiceId);
120
+ if (!invoice) {
121
+ return { status: "not_found" };
122
+ }
123
+ let templateId = input.templateId ?? invoice.templateId ?? null;
124
+ if (!templateId) {
125
+ const [defaultTemplate] = await db
126
+ .select()
127
+ .from(invoiceTemplates)
128
+ .where(and(eq(invoiceTemplates.isDefault, true), eq(invoiceTemplates.active, true)))
129
+ .orderBy(desc(invoiceTemplates.updatedAt))
130
+ .limit(1);
131
+ templateId = defaultTemplate?.id ?? null;
132
+ }
133
+ const [template, lineItems, paymentRows] = await Promise.all([
134
+ templateId ? financeService.getInvoiceTemplateById(db, templateId) : Promise.resolve(null),
135
+ financeService.listInvoiceLineItems(db, invoiceId),
136
+ financeService.listPayments(db, invoiceId),
137
+ ]);
138
+ const renderedBodyFormat = template?.bodyFormat ?? "html";
139
+ const variables = {
140
+ invoice,
141
+ lineItems,
142
+ payments: paymentRows,
143
+ };
144
+ const renderedBody = template
145
+ ? renderInvoiceBody(template.body, template.bodyFormat, variables)
146
+ : JSON.stringify(variables);
147
+ return {
148
+ status: "ready",
149
+ invoice,
150
+ template,
151
+ lineItems,
152
+ payments: paymentRows,
153
+ renderedBody,
154
+ renderedBodyFormat,
155
+ variables,
156
+ targetFormat: input.format,
157
+ language: input.language ?? invoice.language ?? template?.language ?? null,
158
+ };
159
+ }
160
+ export const financeDocumentsService = {
161
+ async generateInvoiceDocument(db, invoiceId, input, runtime, options = {}) {
162
+ const prepared = await prepareInvoiceDocument(db, invoiceId, input);
163
+ if (prepared.status === "not_found") {
164
+ return { status: "not_found" };
165
+ }
166
+ let artifact;
167
+ try {
168
+ artifact = await runtime.generator({
169
+ db,
170
+ invoice: prepared.invoice,
171
+ template: prepared.template,
172
+ lineItems: prepared.lineItems,
173
+ payments: prepared.payments,
174
+ renderedBody: prepared.renderedBody,
175
+ renderedBodyFormat: prepared.renderedBodyFormat,
176
+ variables: prepared.variables,
177
+ bindings: runtime.bindings ?? {},
178
+ targetFormat: prepared.targetFormat,
179
+ language: prepared.language,
180
+ });
181
+ }
182
+ catch {
183
+ return { status: "generator_failed" };
184
+ }
185
+ const format = artifact.format ?? prepared.targetFormat;
186
+ const bindResult = await financeService.bindInvoiceRendition(db, invoiceId, {
187
+ templateId: prepared.template?.id ?? null,
188
+ format,
189
+ storageKey: artifact.storageKey?.trim() || null,
190
+ contentType: artifact.contentType ?? defaultInvoiceDocumentMimeType(format),
191
+ fileSize: artifact.fileSize ?? null,
192
+ checksum: artifact.checksum ?? null,
193
+ language: artifact.language ?? prepared.language ?? null,
194
+ generatedAt: new Date().toISOString(),
195
+ metadata: {
196
+ ...(artifact.metadata ?? {}),
197
+ renderedBodyFormat: prepared.renderedBodyFormat,
198
+ },
199
+ replaceExisting: input.replaceExisting,
200
+ }, { eventBus: runtime.eventBus });
201
+ if (bindResult.status !== "bound") {
202
+ return { status: "not_found" };
203
+ }
204
+ const { rendition } = bindResult;
205
+ await runtime.eventBus?.emit("invoice.document.generated", {
206
+ invoiceId: prepared.invoice.id,
207
+ invoiceStatus: prepared.invoice.status,
208
+ invoiceType: prepared.invoice.invoiceType,
209
+ renditionId: rendition.id,
210
+ format: rendition.format,
211
+ renderedBodyFormat: prepared.renderedBodyFormat,
212
+ regenerated: options.regenerated ?? false,
213
+ }, {
214
+ category: "internal",
215
+ source: "service",
216
+ });
217
+ return {
218
+ status: "generated",
219
+ invoiceId: prepared.invoice.id,
220
+ renderedBodyFormat: prepared.renderedBodyFormat,
221
+ renderedBody: prepared.renderedBody,
222
+ rendition,
223
+ };
224
+ },
225
+ async regenerateInvoiceDocument(db, invoiceId, input, runtime) {
226
+ return this.generateInvoiceDocument(db, invoiceId, input, runtime, { regenerated: true });
227
+ },
228
+ };