@voyantjs/finance 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAgEjE,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAMD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6CA68BtB,CAAA;AAEJ,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAA"}
package/dist/routes.js ADDED
@@ -0,0 +1,632 @@
1
+ import { Hono } from "hono";
2
+ import { financeService } from "./service.js";
3
+ import { agingReportQuerySchema, insertBookingGuaranteeSchema, insertBookingItemCommissionSchema, insertBookingItemTaxLineSchema, insertBookingPaymentScheduleSchema, insertCreditNoteLineItemSchema, insertCreditNoteSchema, insertFinanceNoteSchema, insertInvoiceExternalRefSchema, insertInvoiceLineItemSchema, insertInvoiceNumberSeriesSchema, insertInvoiceSchema, insertInvoiceTemplateSchema, insertPaymentAuthorizationSchema, insertPaymentCaptureSchema, insertPaymentInstrumentSchema, insertPaymentSessionSchema, insertPaymentSchema, insertSupplierPaymentSchema, insertTaxRegimeSchema, invoiceListQuerySchema, invoiceNumberSeriesListQuerySchema, invoiceTemplateListQuerySchema, paymentAuthorizationListQuerySchema, paymentCaptureListQuerySchema, paymentInstrumentListQuerySchema, paymentSessionListQuerySchema, profitabilityQuerySchema, renderInvoiceInputSchema, revenueReportQuerySchema, supplierPaymentListQuerySchema, taxRegimeListQuerySchema, updateBookingGuaranteeSchema, updateBookingItemCommissionSchema, updateBookingItemTaxLineSchema, updateBookingPaymentScheduleSchema, updateCreditNoteSchema, updateInvoiceLineItemSchema, updateInvoiceNumberSeriesSchema, updateInvoiceSchema, updateInvoiceTemplateSchema, updatePaymentAuthorizationSchema, updatePaymentCaptureSchema, updatePaymentInstrumentSchema, updatePaymentSessionSchema, updateSupplierPaymentSchema, updateTaxRegimeSchema, createPaymentSessionFromGuaranteeSchema, createPaymentSessionFromInvoiceSchema, createPaymentSessionFromScheduleSchema, applyDefaultBookingPaymentPlanSchema, markPaymentSessionRequiresRedirectSchema, completePaymentSessionSchema, failPaymentSessionSchema, cancelPaymentSessionSchema, expirePaymentSessionSchema, } from "./validation.js";
4
+ // ==========================================================================
5
+ // Finance Routes — method-chained for Hono RPC type inference
6
+ // ==========================================================================
7
+ export const financeRoutes = new Hono()
8
+ // ========================================================================
9
+ // Payment Sessions
10
+ // ========================================================================
11
+ .get("/payment-sessions", async (c) => {
12
+ const query = paymentSessionListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
13
+ return c.json(await financeService.listPaymentSessions(c.get("db"), query));
14
+ })
15
+ .post("/payment-sessions", async (c) => {
16
+ return c.json({
17
+ data: await financeService.createPaymentSession(c.get("db"), insertPaymentSessionSchema.parse(await c.req.json())),
18
+ }, 201);
19
+ })
20
+ .get("/payment-sessions/:id", async (c) => {
21
+ const row = await financeService.getPaymentSessionById(c.get("db"), c.req.param("id"));
22
+ if (!row)
23
+ return c.json({ error: "Payment session not found" }, 404);
24
+ return c.json({ data: row });
25
+ })
26
+ .patch("/payment-sessions/:id", async (c) => {
27
+ const row = await financeService.updatePaymentSession(c.get("db"), c.req.param("id"), updatePaymentSessionSchema.parse(await c.req.json()));
28
+ if (!row)
29
+ return c.json({ error: "Payment session not found" }, 404);
30
+ return c.json({ data: row });
31
+ })
32
+ .post("/payment-sessions/:id/requires-redirect", async (c) => {
33
+ const row = await financeService.markPaymentSessionRequiresRedirect(c.get("db"), c.req.param("id"), markPaymentSessionRequiresRedirectSchema.parse(await c.req.json()));
34
+ if (!row)
35
+ return c.json({ error: "Payment session not found" }, 404);
36
+ return c.json({ data: row });
37
+ })
38
+ .post("/payment-sessions/:id/complete", async (c) => {
39
+ const row = await financeService.completePaymentSession(c.get("db"), c.req.param("id"), completePaymentSessionSchema.parse(await c.req.json()));
40
+ if (!row)
41
+ return c.json({ error: "Payment session not found" }, 404);
42
+ return c.json({ data: row });
43
+ })
44
+ .post("/payment-sessions/:id/fail", async (c) => {
45
+ const row = await financeService.failPaymentSession(c.get("db"), c.req.param("id"), failPaymentSessionSchema.parse(await c.req.json()));
46
+ if (!row)
47
+ return c.json({ error: "Payment session not found" }, 404);
48
+ return c.json({ data: row });
49
+ })
50
+ .post("/payment-sessions/:id/cancel", async (c) => {
51
+ const row = await financeService.cancelPaymentSession(c.get("db"), c.req.param("id"), cancelPaymentSessionSchema.parse(await c.req.json()));
52
+ if (!row)
53
+ return c.json({ error: "Payment session not found" }, 404);
54
+ return c.json({ data: row });
55
+ })
56
+ .post("/payment-sessions/:id/expire", async (c) => {
57
+ const row = await financeService.expirePaymentSession(c.get("db"), c.req.param("id"), expirePaymentSessionSchema.parse(await c.req.json()));
58
+ if (!row)
59
+ return c.json({ error: "Payment session not found" }, 404);
60
+ return c.json({ data: row });
61
+ })
62
+ // ========================================================================
63
+ // Payment Instruments
64
+ // ========================================================================
65
+ .get("/payment-instruments", async (c) => {
66
+ const query = paymentInstrumentListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
67
+ return c.json(await financeService.listPaymentInstruments(c.get("db"), query));
68
+ })
69
+ .post("/payment-instruments", async (c) => {
70
+ return c.json({
71
+ data: await financeService.createPaymentInstrument(c.get("db"), insertPaymentInstrumentSchema.parse(await c.req.json())),
72
+ }, 201);
73
+ })
74
+ .get("/payment-instruments/:id", async (c) => {
75
+ const row = await financeService.getPaymentInstrumentById(c.get("db"), c.req.param("id"));
76
+ if (!row)
77
+ return c.json({ error: "Payment instrument not found" }, 404);
78
+ return c.json({ data: row });
79
+ })
80
+ .patch("/payment-instruments/:id", async (c) => {
81
+ const row = await financeService.updatePaymentInstrument(c.get("db"), c.req.param("id"), updatePaymentInstrumentSchema.parse(await c.req.json()));
82
+ if (!row)
83
+ return c.json({ error: "Payment instrument not found" }, 404);
84
+ return c.json({ data: row });
85
+ })
86
+ .delete("/payment-instruments/:id", async (c) => {
87
+ const row = await financeService.deletePaymentInstrument(c.get("db"), c.req.param("id"));
88
+ if (!row)
89
+ return c.json({ error: "Payment instrument not found" }, 404);
90
+ return c.json({ success: true });
91
+ })
92
+ // ========================================================================
93
+ // Payment Authorizations
94
+ // ========================================================================
95
+ .get("/payment-authorizations", async (c) => {
96
+ const query = paymentAuthorizationListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
97
+ return c.json(await financeService.listPaymentAuthorizations(c.get("db"), query));
98
+ })
99
+ .post("/payment-authorizations", async (c) => {
100
+ return c.json({
101
+ data: await financeService.createPaymentAuthorization(c.get("db"), insertPaymentAuthorizationSchema.parse(await c.req.json())),
102
+ }, 201);
103
+ })
104
+ .get("/payment-authorizations/:id", async (c) => {
105
+ const row = await financeService.getPaymentAuthorizationById(c.get("db"), c.req.param("id"));
106
+ if (!row)
107
+ return c.json({ error: "Payment authorization not found" }, 404);
108
+ return c.json({ data: row });
109
+ })
110
+ .patch("/payment-authorizations/:id", async (c) => {
111
+ const row = await financeService.updatePaymentAuthorization(c.get("db"), c.req.param("id"), updatePaymentAuthorizationSchema.parse(await c.req.json()));
112
+ if (!row)
113
+ return c.json({ error: "Payment authorization not found" }, 404);
114
+ return c.json({ data: row });
115
+ })
116
+ .delete("/payment-authorizations/:id", async (c) => {
117
+ const row = await financeService.deletePaymentAuthorization(c.get("db"), c.req.param("id"));
118
+ if (!row)
119
+ return c.json({ error: "Payment authorization not found" }, 404);
120
+ return c.json({ success: true });
121
+ })
122
+ // ========================================================================
123
+ // Payment Captures
124
+ // ========================================================================
125
+ .get("/payment-captures", async (c) => {
126
+ const query = paymentCaptureListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
127
+ return c.json(await financeService.listPaymentCaptures(c.get("db"), query));
128
+ })
129
+ .post("/payment-captures", async (c) => {
130
+ return c.json({
131
+ data: await financeService.createPaymentCapture(c.get("db"), insertPaymentCaptureSchema.parse(await c.req.json())),
132
+ }, 201);
133
+ })
134
+ .get("/payment-captures/:id", async (c) => {
135
+ const row = await financeService.getPaymentCaptureById(c.get("db"), c.req.param("id"));
136
+ if (!row)
137
+ return c.json({ error: "Payment capture not found" }, 404);
138
+ return c.json({ data: row });
139
+ })
140
+ .patch("/payment-captures/:id", async (c) => {
141
+ const row = await financeService.updatePaymentCapture(c.get("db"), c.req.param("id"), updatePaymentCaptureSchema.parse(await c.req.json()));
142
+ if (!row)
143
+ return c.json({ error: "Payment capture not found" }, 404);
144
+ return c.json({ data: row });
145
+ })
146
+ .delete("/payment-captures/:id", async (c) => {
147
+ const row = await financeService.deletePaymentCapture(c.get("db"), c.req.param("id"));
148
+ if (!row)
149
+ return c.json({ error: "Payment capture not found" }, 404);
150
+ return c.json({ success: true });
151
+ })
152
+ // ========================================================================
153
+ // Reports (static paths first)
154
+ // ========================================================================
155
+ // GET /reports/revenue — Revenue by month
156
+ .get("/reports/revenue", async (c) => {
157
+ const query = revenueReportQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
158
+ return c.json({ data: await financeService.getRevenueReport(c.get("db"), query) });
159
+ })
160
+ // GET /reports/aging — Outstanding invoices by age buckets
161
+ .get("/reports/aging", async (c) => {
162
+ const query = agingReportQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
163
+ return c.json({ data: await financeService.getAgingReport(c.get("db"), query) });
164
+ })
165
+ // GET /reports/profitability — Per-booking margin summary
166
+ .get("/reports/profitability", async (c) => {
167
+ const query = profitabilityQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
168
+ return c.json({ data: await financeService.getProfitabilityReport(c.get("db"), query) });
169
+ })
170
+ // ========================================================================
171
+ // Booking Payment Schedules
172
+ // ========================================================================
173
+ .get("/bookings/:bookingId/payment-schedules", async (c) => {
174
+ return c.json({
175
+ data: await financeService.listBookingPaymentSchedules(c.get("db"), c.req.param("bookingId")),
176
+ });
177
+ })
178
+ .post("/bookings/:bookingId/payment-schedules", async (c) => {
179
+ const row = await financeService.createBookingPaymentSchedule(c.get("db"), c.req.param("bookingId"), insertBookingPaymentScheduleSchema.parse(await c.req.json()));
180
+ if (!row) {
181
+ return c.json({ error: "Booking not found" }, 404);
182
+ }
183
+ return c.json({ data: row }, 201);
184
+ })
185
+ .post("/bookings/:bookingId/payment-schedules/default-plan", async (c) => {
186
+ const rows = await financeService.applyDefaultBookingPaymentPlan(c.get("db"), c.req.param("bookingId"), applyDefaultBookingPaymentPlanSchema.parse(await c.req.json()));
187
+ if (!rows) {
188
+ return c.json({ error: "Booking not found" }, 404);
189
+ }
190
+ return c.json({ data: rows }, 201);
191
+ })
192
+ .patch("/bookings/:bookingId/payment-schedules/:scheduleId", async (c) => {
193
+ const row = await financeService.updateBookingPaymentSchedule(c.get("db"), c.req.param("scheduleId"), updateBookingPaymentScheduleSchema.parse(await c.req.json()));
194
+ if (!row) {
195
+ return c.json({ error: "Payment schedule not found" }, 404);
196
+ }
197
+ return c.json({ data: row });
198
+ })
199
+ .post("/bookings/:bookingId/payment-schedules/:scheduleId/payment-session", async (c) => {
200
+ try {
201
+ const row = await financeService.createPaymentSessionFromBookingSchedule(c.get("db"), c.req.param("scheduleId"), createPaymentSessionFromScheduleSchema.parse(await c.req.json()));
202
+ if (!row) {
203
+ return c.json({ error: "Payment schedule not found" }, 404);
204
+ }
205
+ return c.json({ data: row }, 201);
206
+ }
207
+ catch (error) {
208
+ const message = error instanceof Error ? error.message : "Unable to create payment session";
209
+ return c.json({ error: message }, 409);
210
+ }
211
+ })
212
+ .delete("/bookings/:bookingId/payment-schedules/:scheduleId", async (c) => {
213
+ const row = await financeService.deleteBookingPaymentSchedule(c.get("db"), c.req.param("scheduleId"));
214
+ if (!row) {
215
+ return c.json({ error: "Payment schedule not found" }, 404);
216
+ }
217
+ return c.json({ success: true }, 200);
218
+ })
219
+ // ========================================================================
220
+ // Booking Guarantees
221
+ // ========================================================================
222
+ .get("/bookings/:bookingId/guarantees", async (c) => {
223
+ return c.json({
224
+ data: await financeService.listBookingGuarantees(c.get("db"), c.req.param("bookingId")),
225
+ });
226
+ })
227
+ .post("/bookings/:bookingId/guarantees", async (c) => {
228
+ const row = await financeService.createBookingGuarantee(c.get("db"), c.req.param("bookingId"), insertBookingGuaranteeSchema.parse(await c.req.json()));
229
+ if (!row) {
230
+ return c.json({ error: "Booking not found" }, 404);
231
+ }
232
+ return c.json({ data: row }, 201);
233
+ })
234
+ .post("/bookings/:bookingId/guarantees/:guaranteeId/payment-session", async (c) => {
235
+ try {
236
+ const row = await financeService.createPaymentSessionFromBookingGuarantee(c.get("db"), c.req.param("guaranteeId"), createPaymentSessionFromGuaranteeSchema.parse(await c.req.json()));
237
+ if (!row) {
238
+ return c.json({ error: "Booking guarantee not found" }, 404);
239
+ }
240
+ return c.json({ data: row }, 201);
241
+ }
242
+ catch (error) {
243
+ const message = error instanceof Error ? error.message : "Unable to create payment session";
244
+ return c.json({ error: message }, 409);
245
+ }
246
+ })
247
+ .patch("/bookings/:bookingId/guarantees/:guaranteeId", async (c) => {
248
+ const row = await financeService.updateBookingGuarantee(c.get("db"), c.req.param("guaranteeId"), updateBookingGuaranteeSchema.parse(await c.req.json()));
249
+ if (!row) {
250
+ return c.json({ error: "Booking guarantee not found" }, 404);
251
+ }
252
+ return c.json({ data: row });
253
+ })
254
+ .delete("/bookings/:bookingId/guarantees/:guaranteeId", async (c) => {
255
+ const row = await financeService.deleteBookingGuarantee(c.get("db"), c.req.param("guaranteeId"));
256
+ if (!row) {
257
+ return c.json({ error: "Booking guarantee not found" }, 404);
258
+ }
259
+ return c.json({ success: true }, 200);
260
+ })
261
+ // ========================================================================
262
+ // Booking Item Taxes
263
+ // ========================================================================
264
+ .get("/booking-items/:bookingItemId/tax-lines", async (c) => {
265
+ return c.json({
266
+ data: await financeService.listBookingItemTaxLines(c.get("db"), c.req.param("bookingItemId")),
267
+ });
268
+ })
269
+ .post("/booking-items/:bookingItemId/tax-lines", async (c) => {
270
+ const row = await financeService.createBookingItemTaxLine(c.get("db"), c.req.param("bookingItemId"), insertBookingItemTaxLineSchema.parse(await c.req.json()));
271
+ if (!row) {
272
+ return c.json({ error: "Booking item not found" }, 404);
273
+ }
274
+ return c.json({ data: row }, 201);
275
+ })
276
+ .patch("/booking-items/:bookingItemId/tax-lines/:taxLineId", async (c) => {
277
+ const row = await financeService.updateBookingItemTaxLine(c.get("db"), c.req.param("taxLineId"), updateBookingItemTaxLineSchema.parse(await c.req.json()));
278
+ if (!row) {
279
+ return c.json({ error: "Booking item tax line not found" }, 404);
280
+ }
281
+ return c.json({ data: row });
282
+ })
283
+ .delete("/booking-items/:bookingItemId/tax-lines/:taxLineId", async (c) => {
284
+ const row = await financeService.deleteBookingItemTaxLine(c.get("db"), c.req.param("taxLineId"));
285
+ if (!row) {
286
+ return c.json({ error: "Booking item tax line not found" }, 404);
287
+ }
288
+ return c.json({ success: true }, 200);
289
+ })
290
+ // ========================================================================
291
+ // Booking Item Commissions
292
+ // ========================================================================
293
+ .get("/booking-items/:bookingItemId/commissions", async (c) => {
294
+ return c.json({
295
+ data: await financeService.listBookingItemCommissions(c.get("db"), c.req.param("bookingItemId")),
296
+ });
297
+ })
298
+ .post("/booking-items/:bookingItemId/commissions", async (c) => {
299
+ const row = await financeService.createBookingItemCommission(c.get("db"), c.req.param("bookingItemId"), insertBookingItemCommissionSchema.parse(await c.req.json()));
300
+ if (!row) {
301
+ return c.json({ error: "Booking item not found" }, 404);
302
+ }
303
+ return c.json({ data: row }, 201);
304
+ })
305
+ .patch("/booking-items/:bookingItemId/commissions/:commissionId", async (c) => {
306
+ const row = await financeService.updateBookingItemCommission(c.get("db"), c.req.param("commissionId"), updateBookingItemCommissionSchema.parse(await c.req.json()));
307
+ if (!row) {
308
+ return c.json({ error: "Booking item commission not found" }, 404);
309
+ }
310
+ return c.json({ data: row });
311
+ })
312
+ .delete("/booking-items/:bookingItemId/commissions/:commissionId", async (c) => {
313
+ const row = await financeService.deleteBookingItemCommission(c.get("db"), c.req.param("commissionId"));
314
+ if (!row) {
315
+ return c.json({ error: "Booking item commission not found" }, 404);
316
+ }
317
+ return c.json({ success: true }, 200);
318
+ })
319
+ // ========================================================================
320
+ // Supplier Payments
321
+ // ========================================================================
322
+ // GET /supplier-payments — List supplier payments
323
+ .get("/supplier-payments", async (c) => {
324
+ const query = supplierPaymentListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
325
+ return c.json(await financeService.listSupplierPayments(c.get("db"), query));
326
+ })
327
+ // POST /supplier-payments — Record supplier payment
328
+ .post("/supplier-payments", async (c) => {
329
+ return c.json({
330
+ data: await financeService.createSupplierPayment(c.get("db"), insertSupplierPaymentSchema.parse(await c.req.json())),
331
+ }, 201);
332
+ })
333
+ // PATCH /supplier-payments/:id — Update supplier payment
334
+ .patch("/supplier-payments/:id", async (c) => {
335
+ const row = await financeService.updateSupplierPayment(c.get("db"), c.req.param("id"), updateSupplierPaymentSchema.parse(await c.req.json()));
336
+ if (!row) {
337
+ return c.json({ error: "Supplier payment not found" }, 404);
338
+ }
339
+ return c.json({ data: row });
340
+ })
341
+ // ========================================================================
342
+ // Invoices CRUD
343
+ // ========================================================================
344
+ // GET /invoices — List invoices
345
+ .get("/invoices", async (c) => {
346
+ const query = invoiceListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
347
+ return c.json(await financeService.listInvoices(c.get("db"), query));
348
+ })
349
+ // POST /invoices — Create invoice
350
+ .post("/invoices", async (c) => {
351
+ return c.json({
352
+ data: await financeService.createInvoice(c.get("db"), insertInvoiceSchema.parse(await c.req.json())),
353
+ }, 201);
354
+ })
355
+ // GET /invoices/:id — Get single invoice
356
+ .get("/invoices/:id", async (c) => {
357
+ const row = await financeService.getInvoiceById(c.get("db"), c.req.param("id"));
358
+ if (!row) {
359
+ return c.json({ error: "Invoice not found" }, 404);
360
+ }
361
+ return c.json({ data: row });
362
+ })
363
+ // PATCH /invoices/:id — Update invoice
364
+ .patch("/invoices/:id", async (c) => {
365
+ const row = await financeService.updateInvoice(c.get("db"), c.req.param("id"), updateInvoiceSchema.parse(await c.req.json()));
366
+ if (!row) {
367
+ return c.json({ error: "Invoice not found" }, 404);
368
+ }
369
+ return c.json({ data: row });
370
+ })
371
+ // DELETE /invoices/:id — Delete invoice (draft only)
372
+ .delete("/invoices/:id", async (c) => {
373
+ const result = await financeService.deleteInvoice(c.get("db"), c.req.param("id"));
374
+ if (result.status === "not_found") {
375
+ return c.json({ error: "Invoice not found" }, 404);
376
+ }
377
+ if (result.status === "not_draft") {
378
+ return c.json({ error: "Only draft invoices can be deleted" }, 400);
379
+ }
380
+ return c.json({ success: true }, 200);
381
+ })
382
+ .post("/invoices/:id/payment-session", async (c) => {
383
+ try {
384
+ const row = await financeService.createPaymentSessionFromInvoice(c.get("db"), c.req.param("id"), createPaymentSessionFromInvoiceSchema.parse(await c.req.json()));
385
+ if (!row) {
386
+ return c.json({ error: "Invoice not found" }, 404);
387
+ }
388
+ return c.json({ data: row }, 201);
389
+ }
390
+ catch (error) {
391
+ const message = error instanceof Error ? error.message : "Unable to create payment session";
392
+ return c.json({ error: message }, 409);
393
+ }
394
+ })
395
+ // ========================================================================
396
+ // Invoice Line Items
397
+ // ========================================================================
398
+ // GET /invoices/:id/line-items — List line items
399
+ .get("/invoices/:id/line-items", async (c) => {
400
+ return c.json({
401
+ data: await financeService.listInvoiceLineItems(c.get("db"), c.req.param("id")),
402
+ });
403
+ })
404
+ // POST /invoices/:id/line-items — Add line item
405
+ .post("/invoices/:id/line-items", async (c) => {
406
+ const row = await financeService.createInvoiceLineItem(c.get("db"), c.req.param("id"), insertInvoiceLineItemSchema.parse(await c.req.json()));
407
+ if (!row) {
408
+ return c.json({ error: "Invoice not found" }, 404);
409
+ }
410
+ return c.json({ data: row }, 201);
411
+ })
412
+ // PATCH /invoices/:id/line-items/:lineId — Update line item
413
+ .patch("/invoices/:id/line-items/:lineId", async (c) => {
414
+ const row = await financeService.updateInvoiceLineItem(c.get("db"), c.req.param("lineId"), updateInvoiceLineItemSchema.parse(await c.req.json()));
415
+ if (!row) {
416
+ return c.json({ error: "Line item not found" }, 404);
417
+ }
418
+ return c.json({ data: row });
419
+ })
420
+ // DELETE /invoices/:id/line-items/:lineId — Delete line item
421
+ .delete("/invoices/:id/line-items/:lineId", async (c) => {
422
+ const row = await financeService.deleteInvoiceLineItem(c.get("db"), c.req.param("lineId"));
423
+ if (!row) {
424
+ return c.json({ error: "Line item not found" }, 404);
425
+ }
426
+ return c.json({ success: true }, 200);
427
+ })
428
+ // ========================================================================
429
+ // Payments
430
+ // ========================================================================
431
+ // GET /invoices/:id/payments — List payments
432
+ .get("/invoices/:id/payments", async (c) => {
433
+ return c.json({ data: await financeService.listPayments(c.get("db"), c.req.param("id")) });
434
+ })
435
+ // POST /invoices/:id/payments — Record payment (transaction)
436
+ .post("/invoices/:id/payments", async (c) => {
437
+ const row = await financeService.createPayment(c.get("db"), c.req.param("id"), insertPaymentSchema.parse(await c.req.json()));
438
+ if (!row) {
439
+ return c.json({ error: "Invoice not found" }, 404);
440
+ }
441
+ return c.json({ data: row }, 201);
442
+ })
443
+ // ========================================================================
444
+ // Credit Notes
445
+ // ========================================================================
446
+ // GET /invoices/:id/credit-notes — List credit notes
447
+ .get("/invoices/:id/credit-notes", async (c) => {
448
+ return c.json({
449
+ data: await financeService.listCreditNotes(c.get("db"), c.req.param("id")),
450
+ });
451
+ })
452
+ // POST /invoices/:id/credit-notes — Create credit note
453
+ .post("/invoices/:id/credit-notes", async (c) => {
454
+ const row = await financeService.createCreditNote(c.get("db"), c.req.param("id"), insertCreditNoteSchema.parse(await c.req.json()));
455
+ if (!row) {
456
+ return c.json({ error: "Invoice not found" }, 404);
457
+ }
458
+ return c.json({ data: row }, 201);
459
+ })
460
+ // PATCH /invoices/:id/credit-notes/:creditNoteId — Update credit note
461
+ .patch("/invoices/:id/credit-notes/:creditNoteId", async (c) => {
462
+ const row = await financeService.updateCreditNote(c.get("db"), c.req.param("creditNoteId"), updateCreditNoteSchema.parse(await c.req.json()));
463
+ if (!row) {
464
+ return c.json({ error: "Credit note not found" }, 404);
465
+ }
466
+ return c.json({ data: row });
467
+ })
468
+ // ========================================================================
469
+ // Credit Note Line Items
470
+ // ========================================================================
471
+ // GET /invoices/:id/credit-notes/:creditNoteId/line-items — List credit note line items
472
+ .get("/invoices/:id/credit-notes/:creditNoteId/line-items", async (c) => {
473
+ return c.json({
474
+ data: await financeService.listCreditNoteLineItems(c.get("db"), c.req.param("creditNoteId")),
475
+ });
476
+ })
477
+ // POST /invoices/:id/credit-notes/:creditNoteId/line-items — Add credit note line item
478
+ .post("/invoices/:id/credit-notes/:creditNoteId/line-items", async (c) => {
479
+ const row = await financeService.createCreditNoteLineItem(c.get("db"), c.req.param("creditNoteId"), insertCreditNoteLineItemSchema.parse(await c.req.json()));
480
+ if (!row) {
481
+ return c.json({ error: "Credit note not found" }, 404);
482
+ }
483
+ return c.json({ data: row }, 201);
484
+ })
485
+ // ========================================================================
486
+ // Finance Notes
487
+ // ========================================================================
488
+ // GET /invoices/:id/notes — List notes
489
+ .get("/invoices/:id/notes", async (c) => {
490
+ return c.json({ data: await financeService.listNotes(c.get("db"), c.req.param("id")) });
491
+ })
492
+ // POST /invoices/:id/notes — Add note
493
+ .post("/invoices/:id/notes", async (c) => {
494
+ const userId = c.get("userId");
495
+ if (!userId) {
496
+ return c.json({ error: "User ID required to create notes" }, 400);
497
+ }
498
+ const row = await financeService.createNote(c.get("db"), c.req.param("id"), userId, insertFinanceNoteSchema.parse(await c.req.json()));
499
+ if (!row) {
500
+ return c.json({ error: "Invoice not found" }, 404);
501
+ }
502
+ return c.json({ data: row }, 201);
503
+ })
504
+ // ========================================================================
505
+ // Invoice Number Series
506
+ // ========================================================================
507
+ .get("/invoice-number-series", async (c) => {
508
+ const query = invoiceNumberSeriesListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
509
+ return c.json(await financeService.listInvoiceNumberSeries(c.get("db"), query));
510
+ })
511
+ .post("/invoice-number-series", async (c) => {
512
+ const row = await financeService.createInvoiceNumberSeries(c.get("db"), insertInvoiceNumberSeriesSchema.parse(await c.req.json()));
513
+ return c.json({ data: row }, 201);
514
+ })
515
+ .get("/invoice-number-series/:id", async (c) => {
516
+ const row = await financeService.getInvoiceNumberSeriesById(c.get("db"), c.req.param("id"));
517
+ if (!row)
518
+ return c.json({ error: "Invoice number series not found" }, 404);
519
+ return c.json({ data: row });
520
+ })
521
+ .patch("/invoice-number-series/:id", async (c) => {
522
+ const row = await financeService.updateInvoiceNumberSeries(c.get("db"), c.req.param("id"), updateInvoiceNumberSeriesSchema.parse(await c.req.json()));
523
+ if (!row)
524
+ return c.json({ error: "Invoice number series not found" }, 404);
525
+ return c.json({ data: row });
526
+ })
527
+ .delete("/invoice-number-series/:id", async (c) => {
528
+ const row = await financeService.deleteInvoiceNumberSeries(c.get("db"), c.req.param("id"));
529
+ if (!row)
530
+ return c.json({ error: "Invoice number series not found" }, 404);
531
+ return c.json({ success: true });
532
+ })
533
+ .post("/invoice-number-series/:id/allocate", async (c) => {
534
+ const result = await financeService.allocateInvoiceNumber(c.get("db"), c.req.param("id"));
535
+ if (result.status === "not_found") {
536
+ return c.json({ error: "Invoice number series not found" }, 404);
537
+ }
538
+ if (result.status === "inactive") {
539
+ return c.json({ error: "Invoice number series is inactive" }, 409);
540
+ }
541
+ return c.json({
542
+ data: { sequence: result.sequence, formattedNumber: result.formattedNumber },
543
+ });
544
+ })
545
+ // ========================================================================
546
+ // Invoice Templates
547
+ // ========================================================================
548
+ .get("/invoice-templates", async (c) => {
549
+ const query = invoiceTemplateListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
550
+ return c.json(await financeService.listInvoiceTemplates(c.get("db"), query));
551
+ })
552
+ .post("/invoice-templates", async (c) => {
553
+ const row = await financeService.createInvoiceTemplate(c.get("db"), insertInvoiceTemplateSchema.parse(await c.req.json()));
554
+ return c.json({ data: row }, 201);
555
+ })
556
+ .get("/invoice-templates/:id", async (c) => {
557
+ const row = await financeService.getInvoiceTemplateById(c.get("db"), c.req.param("id"));
558
+ if (!row)
559
+ return c.json({ error: "Invoice template not found" }, 404);
560
+ return c.json({ data: row });
561
+ })
562
+ .patch("/invoice-templates/:id", async (c) => {
563
+ const row = await financeService.updateInvoiceTemplate(c.get("db"), c.req.param("id"), updateInvoiceTemplateSchema.parse(await c.req.json()));
564
+ if (!row)
565
+ return c.json({ error: "Invoice template not found" }, 404);
566
+ return c.json({ data: row });
567
+ })
568
+ .delete("/invoice-templates/:id", async (c) => {
569
+ const row = await financeService.deleteInvoiceTemplate(c.get("db"), c.req.param("id"));
570
+ if (!row)
571
+ return c.json({ error: "Invoice template not found" }, 404);
572
+ return c.json({ success: true });
573
+ })
574
+ // ========================================================================
575
+ // Tax Regimes
576
+ // ========================================================================
577
+ .get("/tax-regimes", async (c) => {
578
+ const query = taxRegimeListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
579
+ return c.json(await financeService.listTaxRegimes(c.get("db"), query));
580
+ })
581
+ .post("/tax-regimes", async (c) => {
582
+ const row = await financeService.createTaxRegime(c.get("db"), insertTaxRegimeSchema.parse(await c.req.json()));
583
+ return c.json({ data: row }, 201);
584
+ })
585
+ .get("/tax-regimes/:id", async (c) => {
586
+ const row = await financeService.getTaxRegimeById(c.get("db"), c.req.param("id"));
587
+ if (!row)
588
+ return c.json({ error: "Tax regime not found" }, 404);
589
+ return c.json({ data: row });
590
+ })
591
+ .patch("/tax-regimes/:id", async (c) => {
592
+ const row = await financeService.updateTaxRegime(c.get("db"), c.req.param("id"), updateTaxRegimeSchema.parse(await c.req.json()));
593
+ if (!row)
594
+ return c.json({ error: "Tax regime not found" }, 404);
595
+ return c.json({ data: row });
596
+ })
597
+ .delete("/tax-regimes/:id", async (c) => {
598
+ const row = await financeService.deleteTaxRegime(c.get("db"), c.req.param("id"));
599
+ if (!row)
600
+ return c.json({ error: "Tax regime not found" }, 404);
601
+ return c.json({ success: true });
602
+ })
603
+ // ========================================================================
604
+ // Invoice Renditions & External Refs (nested under invoice)
605
+ // ========================================================================
606
+ .get("/invoices/:id/renditions", async (c) => {
607
+ const rows = await financeService.listInvoiceRenditions(c.get("db"), c.req.param("id"));
608
+ return c.json({ data: rows });
609
+ })
610
+ .post("/invoices/:id/render", async (c) => {
611
+ const input = renderInvoiceInputSchema.parse(await c.req.json());
612
+ const result = await financeService.renderInvoice(c.get("db"), c.req.param("id"), input);
613
+ if (result.status === "not_found")
614
+ return c.json({ error: "Invoice not found" }, 404);
615
+ return c.json({ data: result.rendition }, 201);
616
+ })
617
+ .get("/invoices/:id/external-refs", async (c) => {
618
+ const rows = await financeService.listInvoiceExternalRefs(c.get("db"), c.req.param("id"));
619
+ return c.json({ data: rows });
620
+ })
621
+ .post("/invoices/:id/external-refs", async (c) => {
622
+ const row = await financeService.registerInvoiceExternalRef(c.get("db"), c.req.param("id"), insertInvoiceExternalRefSchema.parse(await c.req.json()));
623
+ if (!row)
624
+ return c.json({ error: "Invoice not found" }, 404);
625
+ return c.json({ data: row }, 201);
626
+ })
627
+ .delete("/invoices/:id/external-refs/:refId", async (c) => {
628
+ const row = await financeService.deleteInvoiceExternalRef(c.get("db"), c.req.param("refId"));
629
+ if (!row)
630
+ return c.json({ error: "External ref not found" }, 404);
631
+ return c.json({ success: true });
632
+ });