@voyantjs/legal 0.32.3 → 0.34.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.
@@ -1,6 +1,7 @@
1
1
  import { parseJsonBody, parseOptionalJsonBody, parseQuery } from "@voyantjs/hono";
2
2
  import { Hono } from "hono";
3
3
  import { buildContractsRouteRuntime, CONTRACTS_ROUTE_RUNTIME_CONTAINER_KEY, } from "./route-runtime.js";
4
+ import { renderPreviewResponse } from "./route-template-preview.js";
4
5
  import { contractsService } from "./service.js";
5
6
  import { contractListQuerySchema, contractTemplateDefaultQuerySchema, contractTemplateListQuerySchema, generateContractDocumentInputSchema, insertContractAttachmentSchema, insertContractNumberSeriesSchema, insertContractSchema, insertContractSignatureSchema, insertContractTemplateSchema, insertContractTemplateVersionSchema, publicRenderTemplatePreviewInputSchema, renderTemplateInputSchema, updateContractAttachmentSchema, updateContractNumberSeriesSchema, updateContractSchema, updateContractTemplateSchema, } from "./validation.js";
6
7
  function getRuntime(options, bindings, resolveFromContainer) {
@@ -23,6 +24,69 @@ function getFallbackDownloadUrl(metadata) {
23
24
  }
24
25
  return maybeUrl(record.url);
25
26
  }
27
+ function getMultipartString(value) {
28
+ const resolved = Array.isArray(value) ? value[0] : value;
29
+ return typeof resolved === "string" ? resolved : null;
30
+ }
31
+ function sanitizeStorageFileName(name) {
32
+ const normalized = name
33
+ .normalize("NFKD")
34
+ .replace(/[^\w.-]+/g, "-")
35
+ .replace(/^-+|-+$/g, "")
36
+ .slice(0, 160);
37
+ return normalized || "document";
38
+ }
39
+ function toHex(buffer) {
40
+ return Array.from(new Uint8Array(buffer))
41
+ .map((byte) => byte.toString(16).padStart(2, "0"))
42
+ .join("");
43
+ }
44
+ async function sha256Checksum(buffer) {
45
+ const digest = await crypto.subtle.digest("SHA-256", buffer.slice(0));
46
+ return `sha256:${toHex(digest)}`;
47
+ }
48
+ async function buildUploadedAttachmentInput({ storage, contractId, form, file, }) {
49
+ const originalName = file.name || "document";
50
+ const name = getMultipartString(form.name)?.trim() || originalName;
51
+ const kind = getMultipartString(form.kind)?.trim() || "document";
52
+ const mimeType = file.type || "application/octet-stream";
53
+ const body = await file.arrayBuffer();
54
+ const checksum = await sha256Checksum(body);
55
+ const storageName = sanitizeStorageFileName(originalName);
56
+ const key = `contracts/${contractId}/attachments/${crypto.randomUUID()}-${storageName}`;
57
+ const uploaded = await storage.upload(body, {
58
+ key,
59
+ contentType: mimeType,
60
+ metadata: {
61
+ checksum,
62
+ contractId,
63
+ kind,
64
+ name,
65
+ originalName,
66
+ },
67
+ });
68
+ return {
69
+ kind,
70
+ name,
71
+ mimeType,
72
+ fileSize: file.size,
73
+ storageKey: uploaded.key,
74
+ checksum,
75
+ metadata: {
76
+ originalName,
77
+ uploadedAt: new Date().toISOString(),
78
+ ...(uploaded.url ? { url: uploaded.url } : {}),
79
+ },
80
+ };
81
+ }
82
+ async function parseAttachmentUploadRequest(c) {
83
+ const form = (await c.req.parseBody());
84
+ const file = Array.isArray(form.file) ? form.file[0] : form.file;
85
+ if (!(file instanceof File)) {
86
+ return { error: c.json({ error: "Missing file field in multipart body" }, 400) };
87
+ }
88
+ return { form, file };
89
+ }
26
90
  export function createContractsAdminRoutes(options = {}) {
27
91
  return new Hono()
28
92
  .get("/templates", async (c) => {
@@ -64,8 +128,7 @@ export function createContractsAdminRoutes(options = {}) {
64
128
  if (!template)
65
129
  return c.json({ error: "Template not found" }, 404);
66
130
  const body = input.body ?? template.body;
67
- const rendered = contractsService.renderPreview({ ...input, body });
68
- return c.json({ data: { rendered } });
131
+ return renderPreviewResponse(c, { ...input, body });
69
132
  })
70
133
  .get("/templates/:id/versions", async (c) => {
71
134
  const rows = await contractsService.listTemplateVersions(c.get("db"), c.req.param("id"));
@@ -189,8 +252,7 @@ export function createContractsAdminRoutes(options = {}) {
189
252
  const contract = await contractsService.getContractById(c.get("db"), c.req.param("id"));
190
253
  if (!contract)
191
254
  return c.json({ error: "Contract not found" }, 404);
192
- const rendered = contractsService.renderPreview(input);
193
- return c.json({ data: { rendered } });
255
+ return renderPreviewResponse(c, input);
194
256
  })
195
257
  .post("/:id/generate-document", async (c) => {
196
258
  const runtime = getRuntime(options, c.env, (key) => c.var.container?.resolve(key));
@@ -245,12 +307,61 @@ export function createContractsAdminRoutes(options = {}) {
245
307
  if (!row)
246
308
  return c.json({ error: "Contract not found" }, 404);
247
309
  return c.json({ data: row }, 201);
310
+ })
311
+ .post("/:id/attachments/upload", async (c) => {
312
+ const runtime = getRuntime(options, c.env, (key) => c.var.container?.resolve(key));
313
+ const storage = runtime.documentStorage;
314
+ if (!storage) {
315
+ return c.json({ error: "Contract document storage is not configured" }, 501);
316
+ }
317
+ const parsed = await parseAttachmentUploadRequest(c);
318
+ if ("error" in parsed)
319
+ return parsed.error;
320
+ const row = await contractsService.createAttachment(c.get("db"), c.req.param("id"), await buildUploadedAttachmentInput({
321
+ storage,
322
+ contractId: c.req.param("id"),
323
+ form: parsed.form,
324
+ file: parsed.file,
325
+ }));
326
+ if (!row)
327
+ return c.json({ error: "Contract not found" }, 404);
328
+ return c.json({ data: row }, 201);
248
329
  })
249
330
  .patch("/attachments/:attachmentId", async (c) => {
250
331
  const row = await contractsService.updateAttachment(c.get("db"), c.req.param("attachmentId"), await parseJsonBody(c, updateContractAttachmentSchema));
251
332
  if (!row)
252
333
  return c.json({ error: "Attachment not found" }, 404);
253
334
  return c.json({ data: row });
335
+ })
336
+ .patch("/attachments/:attachmentId/upload", async (c) => {
337
+ const runtime = getRuntime(options, c.env, (key) => c.var.container?.resolve(key));
338
+ const storage = runtime.documentStorage;
339
+ if (!storage) {
340
+ return c.json({ error: "Contract document storage is not configured" }, 501);
341
+ }
342
+ const existing = await contractsService.getAttachmentById(c.get("db"), c.req.param("attachmentId"));
343
+ if (!existing)
344
+ return c.json({ error: "Attachment not found" }, 404);
345
+ const parsed = await parseAttachmentUploadRequest(c);
346
+ if ("error" in parsed)
347
+ return parsed.error;
348
+ const row = await contractsService.updateAttachment(c.get("db"), c.req.param("attachmentId"), await buildUploadedAttachmentInput({
349
+ storage,
350
+ contractId: existing.contractId,
351
+ form: parsed.form,
352
+ file: parsed.file,
353
+ }));
354
+ if (!row)
355
+ return c.json({ error: "Attachment not found" }, 404);
356
+ if (existing.storageKey && existing.storageKey !== row.storageKey) {
357
+ try {
358
+ await storage.delete(existing.storageKey);
359
+ }
360
+ catch (error) {
361
+ console.warn(`[legal] failed to delete replaced contract attachment object ${existing.storageKey}: ${error instanceof Error ? error.message : String(error)}`);
362
+ }
363
+ }
364
+ return c.json({ data: row });
254
365
  })
255
366
  .get("/attachments/:attachmentId/download", async (c) => {
256
367
  const attachment = await contractsService.getAttachmentById(c.get("db"), c.req.param("attachmentId"));
@@ -291,11 +402,10 @@ export function createContractsPublicRoutes() {
291
402
  const template = await contractsService.getTemplateById(c.get("db"), c.req.param("id"));
292
403
  if (!template?.active)
293
404
  return c.json({ error: "Template not found" }, 404);
294
- const rendered = contractsService.renderPreview({
405
+ return renderPreviewResponse(c, {
295
406
  variables: input.variables,
296
407
  body: template.body,
297
408
  });
298
- return c.json({ data: { rendered } });
299
409
  })
300
410
  /**
301
411
  * Slug-based variant — storefronts wire products to a contract
@@ -308,20 +418,16 @@ export function createContractsPublicRoutes() {
308
418
  const template = await contractsService.findTemplateBySlug(c.get("db"), c.req.param("slug"));
309
419
  if (!template?.active)
310
420
  return c.json({ error: "Template not found" }, 404);
311
- const rendered = contractsService.renderPreview({
421
+ return renderPreviewResponse(c, {
312
422
  variables: input.variables,
313
423
  body: template.body,
314
- });
315
- return c.json({
316
- data: {
317
- template: {
318
- id: template.id,
319
- slug: template.slug,
320
- name: template.name,
321
- language: template.language,
322
- scope: template.scope,
323
- },
324
- rendered,
424
+ }, {
425
+ template: {
426
+ id: template.id,
427
+ slug: template.slug,
428
+ name: template.name,
429
+ language: template.language,
430
+ scope: template.scope,
325
431
  },
326
432
  });
327
433
  })
@@ -3,6 +3,10 @@ import { type ContractListQuery, type CreateContractAttachmentInput, type Create
3
3
  export declare const contractRecordsService: {
4
4
  listContracts(db: PostgresJsDatabase, query: ContractListQuery): Promise<{
5
5
  data: {
6
+ personFirstName: string | null;
7
+ personLastName: string | null;
8
+ personEmail: string | null;
9
+ personPhone: string | null;
6
10
  id: string;
7
11
  contractNumber: string | null;
8
12
  scope: "customer" | "partner" | "supplier" | "other" | "channel";
@@ -1 +1 @@
1
- {"version":3,"file":"service-contracts.d.ts","sourceRoot":"","sources":["../../src/contracts/service-contracts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAQjE,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EACxB,KAAK,4BAA4B,EAIjC,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,sBAAsB;sBACT,kBAAkB,SAAS,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2B1C,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAI/B,kBAAkB,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAO7C,kBAAkB,MAAM,MAAM,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAYzD,kBAAkB,MAAM,MAAM;;;;;;;sBAW/B,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA4CvC,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgBtC,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAe1C,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAQnD,kBAAkB,cACV,MAAM,QACZ,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAuBV,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAe5C,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAO9B,kBAAkB,gBAAgB,MAAM;;;;;;;;;;;;yBAS9D,kBAAkB,cACV,MAAM,QACZ,6BAA6B;;;;;;;;;;;;yBAe/B,kBAAkB,gBACR,MAAM,QACd,6BAA6B;;;;;;;;;;;;yBASV,kBAAkB,gBAAgB,MAAM;;;CAOpE,CAAA"}
1
+ {"version":3,"file":"service-contracts.d.ts","sourceRoot":"","sources":["../../src/contracts/service-contracts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAQjE,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EACxB,KAAK,4BAA4B,EAIjC,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,sBAAsB;sBACT,kBAAkB,SAAS,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAmD1C,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAI/B,kBAAkB,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAO7C,kBAAkB,MAAM,MAAM,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAYzD,kBAAkB,MAAM,MAAM;;;;;;;sBAW/B,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA4CvC,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgBtC,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAe1C,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAQnD,kBAAkB,cACV,MAAM,QACZ,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAuBV,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAe5C,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAO9B,kBAAkB,gBAAgB,MAAM;;;;;;;;;;;;yBAS9D,kBAAkB,cACV,MAAM,QACZ,6BAA6B;;;;;;;;;;;;yBAe/B,kBAAkB,gBACR,MAAM,QACd,6BAA6B;;;;;;;;;;;;yBASV,kBAAkB,gBAAgB,MAAM;;;CAOpE,CAAA"}
@@ -1,4 +1,5 @@
1
- import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
1
+ import { people, personDirectoryView } from "@voyantjs/crm/schema";
2
+ import { and, desc, eq, getTableColumns, ilike, or, sql } from "drizzle-orm";
2
3
  import { contractAttachments, contractSignatures, contracts, contractTemplateVersions, } from "./schema.js";
3
4
  import { allocateContractNumber, paginate, renderTemplate, toTimestamp, } from "./service-shared.js";
4
5
  export const contractRecordsService = {
@@ -18,18 +19,32 @@ export const contractRecordsService = {
18
19
  conditions.push(eq(contracts.bookingId, query.bookingId));
19
20
  if (query.orderId)
20
21
  conditions.push(eq(contracts.orderId, query.orderId));
21
- if (query.search) {
22
- const term = `%${query.search}%`;
23
- conditions.push(or(ilike(contracts.title, term), ilike(contracts.contractNumber, term)));
22
+ const search = query.search?.trim();
23
+ if (search) {
24
+ const term = `%${search}%`;
25
+ conditions.push(or(ilike(contracts.title, term), ilike(contracts.contractNumber, term), ilike(people.firstName, term), ilike(people.lastName, term), sql `${people.firstName} || ' ' || ${people.lastName} ILIKE ${term}`, ilike(personDirectoryView.email, term), ilike(personDirectoryView.phone, term)));
24
26
  }
25
27
  const where = conditions.length ? and(...conditions) : undefined;
26
28
  return paginate(db
27
- .select()
29
+ .select({
30
+ ...getTableColumns(contracts),
31
+ personFirstName: people.firstName,
32
+ personLastName: people.lastName,
33
+ personEmail: personDirectoryView.email,
34
+ personPhone: personDirectoryView.phone,
35
+ })
28
36
  .from(contracts)
37
+ .leftJoin(people, eq(contracts.personId, people.id))
38
+ .leftJoin(personDirectoryView, eq(contracts.personId, personDirectoryView.personId))
29
39
  .where(where)
30
40
  .limit(query.limit)
31
41
  .offset(query.offset)
32
- .orderBy(desc(contracts.createdAt)), db.select({ total: sql `count(*)::int` }).from(contracts).where(where), query.limit, query.offset);
42
+ .orderBy(desc(contracts.createdAt)), db
43
+ .select({ total: sql `count(*)::int` })
44
+ .from(contracts)
45
+ .leftJoin(people, eq(contracts.personId, people.id))
46
+ .leftJoin(personDirectoryView, eq(contracts.personId, personDirectoryView.personId))
47
+ .where(where), query.limit, query.offset);
33
48
  },
34
49
  async getContractById(db, id) {
35
50
  const [row] = await db.select().from(contracts).where(eq(contracts.id, id)).limit(1);
@@ -1 +1 @@
1
- {"version":3,"file":"service-documents.d.ts","sourceRoot":"","sources":["../../src/contracts/service-documents.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service-documents.d.ts","sourceRoot":"","sources":["../../src/contracts/service-documents.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { renderPdfDocument } from "@voyantjs/utils/pdf-renderer";
2
2
  import { desc, eq } from "drizzle-orm";
3
3
  import { contractAttachments, contracts, contractTemplateVersions } from "./schema.js";
4
4
  import { contractRecordsService } from "./service-contracts.js";
5
- import { renderTemplate } from "./service-shared.js";
5
+ import { isContractTemplateSyntaxError, renderTemplate } from "./service-shared.js";
6
6
  function normalizeAttachmentInput(input, fallbackKind) {
7
7
  return {
8
8
  kind: input.kind ?? fallbackKind,
@@ -134,7 +134,16 @@ async function ensureRenderedContract(db, contractId, issueIfDraft) {
134
134
  return { status: "not_found" };
135
135
  }
136
136
  if (contract.status === "draft" && issueIfDraft) {
137
- const issued = await contractRecordsService.issueContract(db, contractId);
137
+ let issued;
138
+ try {
139
+ issued = await contractRecordsService.issueContract(db, contractId);
140
+ }
141
+ catch (error) {
142
+ if (isContractTemplateSyntaxError(error)) {
143
+ return { status: "render_unavailable", contract, templateVersion: null };
144
+ }
145
+ throw error;
146
+ }
138
147
  if (issued.status !== "issued" || !issued.contract) {
139
148
  if (issued.status === "not_found") {
140
149
  return { status: "not_found" };
@@ -148,7 +157,15 @@ async function ensureRenderedContract(db, contractId, issueIfDraft) {
148
157
  let renderedBodyFormat = contract.renderedBodyFormat;
149
158
  if ((!renderedBody || !renderedBodyFormat) && templateVersion) {
150
159
  const variables = contract.variables ?? {};
151
- renderedBody = renderTemplate(templateVersion.body, "html", variables);
160
+ try {
161
+ renderedBody = renderTemplate(templateVersion.body, "html", variables);
162
+ }
163
+ catch (error) {
164
+ if (isContractTemplateSyntaxError(error)) {
165
+ return { status: "render_unavailable", contract, templateVersion };
166
+ }
167
+ throw error;
168
+ }
152
169
  renderedBodyFormat = "html";
153
170
  const [updated] = await db
154
171
  .update(contracts)
@@ -1,3 +1,4 @@
1
+ import { type TemplateSyntaxIssue } from "@voyantjs/utils/template-renderer";
1
2
  import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
3
  import type { z } from "zod";
3
4
  import type { contractListQuerySchema, contractTemplateListQuerySchema, insertContractAttachmentSchema, insertContractNumberSeriesSchema, insertContractSchema, insertContractSignatureSchema, insertContractTemplateSchema, insertContractTemplateVersionSchema, renderTemplateInputSchema, updateContractAttachmentSchema, updateContractNumberSeriesSchema, updateContractSchema, updateContractTemplateSchema } from "./validation.js";
@@ -15,6 +16,12 @@ export type CreateContractAttachmentInput = z.infer<typeof insertContractAttachm
15
16
  export type UpdateContractAttachmentInput = z.infer<typeof updateContractAttachmentSchema>;
16
17
  export type RenderTemplateInput = z.infer<typeof renderTemplateInputSchema>;
17
18
  export declare function toTimestamp(value?: string | null): Date | null;
19
+ export declare class ContractTemplateSyntaxError extends Error {
20
+ readonly issues: TemplateSyntaxIssue[];
21
+ constructor(issues: TemplateSyntaxIssue[]);
22
+ }
23
+ export declare function isContractTemplateSyntaxError(error: unknown): error is ContractTemplateSyntaxError;
24
+ export declare function validateContractTemplateBody(body: string): void;
18
25
  export declare function renderTemplate(body: string, bodyFormat: "markdown" | "html" | "lexical_json", variables: Record<string, unknown>): string;
19
26
  export declare function validateTemplateVariables(variableSchema: unknown, values: Record<string, unknown>): string[];
20
27
  export declare function allocateContractNumber(db: PostgresJsDatabase, seriesId: string): Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../../src/contracts/service-shared.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAG5B,OAAO,KAAK,EACV,uBAAuB,EACvB,+BAA+B,EAC/B,8BAA8B,EAC9B,gCAAgC,EAChC,oBAAoB,EACpB,6BAA6B,EAC7B,4BAA4B,EAC5B,mCAAmC,EACnC,yBAAyB,EACzB,8BAA8B,EAC9B,gCAAgC,EAChC,oBAAoB,EACpB,4BAA4B,EAC7B,MAAM,iBAAiB,CAAA;AAExB,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AACvE,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAA;AACvF,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAA;AACtF,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAA;AACtF,MAAM,MAAM,kCAAkC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AACpG,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAC9F,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAC9F,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtE,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AACxF,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAA;AAC1F,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAA;AAC1F,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAE3E,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAE9D;AAyCD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,cAAc,EAChD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,MAAM,CAIR;AAED,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,OAAO,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,EAAE,CAYV;AAkBD,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAqDtD;AAED,wBAAsB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAC7C,SAAS,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,EACvB,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,EAC7C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM;;;;;GAIf"}
1
+ {"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../../src/contracts/service-shared.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { renderStructuredTemplate } from "@voyantjs/utils/template-renderer";
1
+ import { renderStructuredTemplate, validateStructuredTemplateSyntax, } from "@voyantjs/utils/template-renderer";
2
2
  import { sql } from "drizzle-orm";
3
3
  import { contractNumberSeries, contracts } from "./schema.js";
4
4
  export function toTimestamp(value) {
@@ -49,7 +49,28 @@ function resolvePath(obj, path) {
49
49
  * their explicit chain alone.
50
50
  */
51
51
  const CONTRACT_MISSING_VALUE_PLACEHOLDER = "-";
52
+ export class ContractTemplateSyntaxError extends Error {
53
+ issues;
54
+ constructor(issues) {
55
+ super("Contract template contains invalid Liquid syntax");
56
+ this.name = "ContractTemplateSyntaxError";
57
+ this.issues = issues;
58
+ }
59
+ }
60
+ export function isContractTemplateSyntaxError(error) {
61
+ return error instanceof ContractTemplateSyntaxError;
62
+ }
63
+ export function validateContractTemplateBody(body) {
64
+ const issues = validateStructuredTemplateSyntax(body, "html");
65
+ if (issues.length > 0) {
66
+ throw new ContractTemplateSyntaxError(issues);
67
+ }
68
+ }
52
69
  export function renderTemplate(body, bodyFormat, variables) {
70
+ const issues = validateStructuredTemplateSyntax(body, bodyFormat);
71
+ if (issues.length > 0) {
72
+ throw new ContractTemplateSyntaxError(issues);
73
+ }
53
74
  return renderStructuredTemplate(body, bodyFormat, variables, {
54
75
  missingValuePlaceholder: CONTRACT_MISSING_VALUE_PLACEHOLDER,
55
76
  });
@@ -1 +1 @@
1
- {"version":3,"file":"service-templates.d.ts","sourceRoot":"","sources":["../../src/contracts/service-templates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,kCAAkC,EAEvC,KAAK,mBAAmB,EAExB,KAAK,2BAA2B,EACjC,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,wBAAwB;sBACX,kBAAkB,SAAS,yBAAyB;;;;;;;;;;;;;;;;;;;wBA6BlD,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;IAQxD;;;OAGG;2BAC0B,kBAAkB,QAAQ,MAAM;;;;;;;;;;;;;;2BASvD,kBAAkB,SACf;QACL,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;QAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;KAC7B;;;;;;;;;;;;;;uBAgCsB,kBAAkB,QAAQ,2BAA2B;;;;;;;;;;;;;;IAI9E;;;;;;;;;;;;;;OAcG;uBACsB,kBAAkB,MAAM,MAAM,QAAQ,2BAA2B;;;;;;;;;;;;;;uBAqDjE,kBAAkB,MAAM,MAAM;;;6BAO9B,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAO9B,kBAAkB,MAAM,MAAM;;;;;;;;;;8BASzD,kBAAkB,cACV,MAAM,QACZ,kCAAkC;;;;;;;;;;yBAkCrB,mBAAmB,GAAG,MAAM;CAIlD,CAAA"}
1
+ {"version":3,"file":"service-templates.d.ts","sourceRoot":"","sources":["../../src/contracts/service-templates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,kCAAkC,EAEvC,KAAK,mBAAmB,EAExB,KAAK,2BAA2B,EAEjC,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,wBAAwB;sBACX,kBAAkB,SAAS,yBAAyB;;;;;;;;;;;;;;;;;;;wBA6BlD,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;IAQxD;;;OAGG;2BAC0B,kBAAkB,QAAQ,MAAM;;;;;;;;;;;;;;2BASvD,kBAAkB,SACf;QACL,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;QAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;KAC7B;;;;;;;;;;;;;;uBAgCsB,kBAAkB,QAAQ,2BAA2B;;;;;;;;;;;;;;IAK9E;;;;;;;;;;;;;;OAcG;uBACsB,kBAAkB,MAAM,MAAM,QAAQ,2BAA2B;;;;;;;;;;;;;;uBAyDjE,kBAAkB,MAAM,MAAM;;;6BAO9B,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAO9B,kBAAkB,MAAM,MAAM;;;;;;;;;;8BASzD,kBAAkB,cACV,MAAM,QACZ,kCAAkC;;;;;;;;;;yBAoCrB,mBAAmB,GAAG,MAAM;CAKlD,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
2
2
  import { contractTemplates, contractTemplateVersions } from "./schema.js";
3
- import { paginate, renderTemplate, } from "./service-shared.js";
3
+ import { paginate, renderTemplate, validateContractTemplateBody, } from "./service-shared.js";
4
4
  export const contractTemplatesService = {
5
5
  async listTemplates(db, query) {
6
6
  const conditions = [];
@@ -68,6 +68,7 @@ export const contractTemplatesService = {
68
68
  return rows[0] ?? null;
69
69
  },
70
70
  async createTemplate(db, data) {
71
+ validateContractTemplateBody(data.body);
71
72
  const [row] = await db.insert(contractTemplates).values(data).returning();
72
73
  return row ?? null;
73
74
  },
@@ -87,6 +88,9 @@ export const contractTemplatesService = {
87
88
  * out of sync with the versions table.
88
89
  */
89
90
  async updateTemplate(db, id, data) {
91
+ if (typeof data.body === "string") {
92
+ validateContractTemplateBody(data.body);
93
+ }
90
94
  return db.transaction(async (tx) => {
91
95
  // Read the current row first so we can detect whether the body
92
96
  // actually changed. Without this we'd version-bump on every
@@ -159,6 +163,7 @@ export const contractTemplatesService = {
159
163
  return row ?? null;
160
164
  },
161
165
  async createTemplateVersion(db, templateId, data) {
166
+ validateContractTemplateBody(data.body);
162
167
  return db.transaction(async (tx) => {
163
168
  const [template] = await tx
164
169
  .select({ id: contractTemplates.id })
@@ -194,6 +199,7 @@ export const contractTemplatesService = {
194
199
  },
195
200
  renderPreview(input) {
196
201
  const body = input.body ?? "";
202
+ validateContractTemplateBody(body);
197
203
  return renderTemplate(body, "html", input.variables);
198
204
  },
199
205
  };
@@ -1,5 +1,5 @@
1
- import { allocateContractNumber, renderTemplate, validateTemplateVariables } from "./service-shared.js";
2
- export { allocateContractNumber, renderTemplate, validateTemplateVariables };
1
+ import { allocateContractNumber, ContractTemplateSyntaxError, isContractTemplateSyntaxError, renderTemplate, validateContractTemplateBody, validateTemplateVariables } from "./service-shared.js";
2
+ export { allocateContractNumber, ContractTemplateSyntaxError, isContractTemplateSyntaxError, renderTemplate, validateContractTemplateBody, validateTemplateVariables, };
3
3
  export declare const contractsService: {
4
4
  generateContractDocument(db: import("drizzle-orm/postgres-js").PostgresJsDatabase, contractId: string, input: import("./validation.js").GenerateContractDocumentInput, runtime: import("./service-documents.js").ContractDocumentRuntimeOptions, options?: {
5
5
  regenerated?: boolean;
@@ -15,6 +15,10 @@ export declare const contractsService: {
15
15
  } & import("./service-documents.js").GeneratedContractDocumentRecord)>;
16
16
  listContracts(db: import("drizzle-orm/postgres-js").PostgresJsDatabase, query: import("./service-shared.js").ContractListQuery): Promise<{
17
17
  data: {
18
+ personFirstName: string | null;
19
+ personLastName: string | null;
20
+ personEmail: string | null;
21
+ personPhone: string | null;
18
22
  id: string;
19
23
  contractNumber: string | null;
20
24
  scope: "customer" | "partner" | "supplier" | "other" | "channel";
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/contracts/service.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,yBAAyB,EAC1B,MAAM,qBAAqB,CAAA;AAG5B,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,yBAAyB,EAAE,CAAA;AAE5E,eAAO,MAAM,gBAAgB;;mBAMk1S,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAAvrP,CAAC;yBAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAD1tD,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/contracts/service.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,6BAA6B,EAC7B,cAAc,EACd,4BAA4B,EAC5B,yBAAyB,EAC1B,MAAM,qBAAqB,CAAA;AAG5B,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,6BAA6B,EAC7B,cAAc,EACd,4BAA4B,EAC5B,yBAAyB,GAC1B,CAAA;AAED,eAAO,MAAM,gBAAgB;;mBAM2oT,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAAvpQ,CAAC;yBAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CADnjD,CAAA"}
@@ -1,9 +1,9 @@
1
1
  import { contractRecordsService } from "./service-contracts.js";
2
2
  import { contractDocumentsService } from "./service-documents.js";
3
3
  import { contractSeriesService } from "./service-series.js";
4
- import { allocateContractNumber, renderTemplate, validateTemplateVariables, } from "./service-shared.js";
4
+ import { allocateContractNumber, ContractTemplateSyntaxError, isContractTemplateSyntaxError, renderTemplate, validateContractTemplateBody, validateTemplateVariables, } from "./service-shared.js";
5
5
  import { contractTemplatesService } from "./service-templates.js";
6
- export { allocateContractNumber, renderTemplate, validateTemplateVariables };
6
+ export { allocateContractNumber, ContractTemplateSyntaxError, isContractTemplateSyntaxError, renderTemplate, validateContractTemplateBody, validateTemplateVariables, };
7
7
  export const contractsService = {
8
8
  ...contractTemplatesService,
9
9
  ...contractSeriesService,
@@ -142,45 +142,45 @@ export const contractTemplateVariableCatalog = [
142
142
  { key: "booking.sellCurrency", label: "Sell currency", example: "EUR", type: "string" },
143
143
  {
144
144
  key: "booking.sellAmountCents",
145
- label: "Sell total (cents)",
145
+ label: "Sell total",
146
146
  example: "249900",
147
147
  type: "cents",
148
148
  description: "Render with `| cents: booking.sellCurrency`.",
149
149
  },
150
150
  {
151
151
  key: "booking.subtotalAmountCents",
152
- label: "Subtotal (cents)",
152
+ label: "Subtotal",
153
153
  example: "210000",
154
154
  type: "cents",
155
155
  },
156
156
  {
157
157
  key: "booking.taxAmountCents",
158
- label: "Tax total (cents)",
158
+ label: "Tax total",
159
159
  example: "39900",
160
160
  type: "cents",
161
161
  },
162
162
  {
163
163
  key: "booking.discountAmountCents",
164
- label: "Discount (cents)",
164
+ label: "Discount",
165
165
  example: "0",
166
166
  type: "cents",
167
167
  },
168
168
  {
169
169
  key: "booking.totalAmountCents",
170
- label: "Total (alias, cents)",
170
+ label: "Total (alias)",
171
171
  example: "249900",
172
172
  type: "cents",
173
173
  },
174
174
  { key: "booking.currency", label: "Currency (alias)", example: "EUR", type: "string" },
175
175
  {
176
176
  key: "booking.paidAmountCents",
177
- label: "Paid so far (cents)",
177
+ label: "Paid so far",
178
178
  example: "0",
179
179
  type: "cents",
180
180
  },
181
181
  {
182
182
  key: "booking.balanceDueCents",
183
- label: "Balance due (cents)",
183
+ label: "Balance due",
184
184
  example: "249900",
185
185
  type: "cents",
186
186
  },
@@ -188,7 +188,7 @@ export const contractTemplateVariableCatalog = [
188
188
  // booking_payment_schedules)
189
189
  {
190
190
  key: "booking.depositAmountCents",
191
- label: "Deposit amount (cents)",
191
+ label: "Deposit amount",
192
192
  example: "50000",
193
193
  type: "cents",
194
194
  description: "Pulled from the `deposit` row in booking_payment_schedules. 0 when no schedule is set.",
@@ -201,7 +201,7 @@ export const contractTemplateVariableCatalog = [
201
201
  },
202
202
  {
203
203
  key: "booking.balanceAmountCents",
204
- label: "Balance amount (cents)",
204
+ label: "Balance amount",
205
205
  example: "199900",
206
206
  type: "cents",
207
207
  description: "Pulled from the `balance` row in booking_payment_schedules.",
@@ -399,13 +399,13 @@ export const contractTemplateVariableCatalog = [
399
399
  { key: "item.quantity", label: "Quantity", example: "2", type: "number" },
400
400
  {
401
401
  key: "item.unitAmountCents",
402
- label: "Unit amount (cents)",
402
+ label: "Unit amount",
403
403
  example: "124950",
404
404
  type: "cents",
405
405
  },
406
406
  {
407
407
  key: "item.totalAmountCents",
408
- label: "Total amount (cents)",
408
+ label: "Total amount",
409
409
  example: "249900",
410
410
  type: "cents",
411
411
  },
@@ -527,7 +527,7 @@ export const contractTemplateVariableCatalog = [
527
527
  { key: "payment.method", label: "Method label", example: "Card payment", type: "string" },
528
528
  {
529
529
  key: "payment.amountCents",
530
- label: "Amount (cents)",
530
+ label: "Amount",
531
531
  example: "249900",
532
532
  type: "cents",
533
533
  },
@@ -560,7 +560,7 @@ export const contractTemplateVariableCatalog = [
560
560
  type: "string",
561
561
  description: "deposit | installment | balance | hold | other",
562
562
  },
563
- { key: "line.amountCents", label: "Amount (cents)", example: "50000", type: "cents" },
563
+ { key: "line.amountCents", label: "Amount", example: "50000", type: "cents" },
564
564
  { key: "line.currency", label: "Currency", example: "EUR", type: "string" },
565
565
  { key: "line.dueDate", label: "Due date", example: "2026-05-10", type: "date" },
566
566
  {
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/contracts/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,mBAAmB;;;;;;EAAkE,CAAA;AAElG,eAAO,MAAM,oBAAoB;;;;;;;;EAQ/B,CAAA;AAEF,eAAO,MAAM,6BAA6B;;;;;EAAwD,CAAA;AAElG,eAAO,MAAM,iCAAiC;;;;EAAyC,CAAA;AAEvF,eAAO,MAAM,wBAAwB;;;;EAA+C,CAAA;AAwBpF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;iBAA6B,CAAA;AACtE,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;iBAAuC,CAAA;AAEhF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;iBAK1C,CAAA;AAEF,eAAO,MAAM,kCAAkC;;;;;;;;;;iBAc7C,CAAA;AAIF,eAAO,MAAM,mCAAmC;;;;;iBAK9C,CAAA;AAcF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;iBAAiC,CAAA;AAC9E,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;iBAA2C,CAAA;AAsBxF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAqB,CAAA;AACtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA+B,CAAA;AAEhE,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;iBASlC,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;iBAGpC,CAAA;AAEF,eAAO,MAAM,sCAAsC;;iBAEjD,CAAA;AAEF,eAAO,MAAM,mCAAmC;;;;iBAI9C,CAAA;AAEF,eAAO,MAAM,yCAAyC;;;;;;;;;;;iBAWpD,CAAA;AAEF,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAMhD,CAAA;AAkBF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;iBAA8B,CAAA;AAcxE,eAAO,MAAM,8BAA8B;;;;;;;;iBAA+B,CAAA;AAC1E,eAAO,MAAM,8BAA8B;;;;;;;;iBAAyC,CAAA;AAEpF,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/contracts/validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,mBAAmB;;;;;;EAAkE,CAAA;AAElG,eAAO,MAAM,oBAAoB;;;;;;;;EAQ/B,CAAA;AAEF,eAAO,MAAM,6BAA6B;;;;;EAAwD,CAAA;AAElG,eAAO,MAAM,iCAAiC;;;;EAAyC,CAAA;AAEvF,eAAO,MAAM,wBAAwB;;;;EAA+C,CAAA;AAoCpF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;iBAA6B,CAAA;AACtE,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;iBAAuC,CAAA;AAEhF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;iBAK1C,CAAA;AAEF,eAAO,MAAM,kCAAkC;;;;;;;;;;iBAc7C,CAAA;AAIF,eAAO,MAAM,mCAAmC;;;;;iBAK9C,CAAA;AAcF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;iBAAiC,CAAA;AAC9E,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;iBAA2C,CAAA;AAsBxF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAqB,CAAA;AACtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA+B,CAAA;AAEhE,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;iBASlC,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;iBAGpC,CAAA;AAEF,eAAO,MAAM,sCAAsC;;iBAEjD,CAAA;AAEF,eAAO,MAAM,mCAAmC;;;;iBAI9C,CAAA;AAEF,eAAO,MAAM,yCAAyC;;;;;;;;;;;iBAWpD,CAAA;AAEF,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAMhD,CAAA;AAkBF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;iBAA8B,CAAA;AAcxE,eAAO,MAAM,8BAA8B;;;;;;;;iBAA+B,CAAA;AAC1E,eAAO,MAAM,8BAA8B;;;;;;;;iBAAyC,CAAA;AAEpF,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA"}