@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.
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/route-runtime.d.ts +2 -0
- package/dist/contracts/route-runtime.d.ts.map +1 -1
- package/dist/contracts/route-runtime.js +1 -0
- package/dist/contracts/route-template-preview.d.ts +19 -0
- package/dist/contracts/route-template-preview.d.ts.map +1 -0
- package/dist/contracts/route-template-preview.js +20 -0
- package/dist/contracts/routes.d.ts +255 -22
- package/dist/contracts/routes.d.ts.map +1 -1
- package/dist/contracts/routes.js +124 -18
- package/dist/contracts/service-contracts.d.ts +4 -0
- package/dist/contracts/service-contracts.d.ts.map +1 -1
- package/dist/contracts/service-contracts.js +21 -6
- package/dist/contracts/service-documents.d.ts.map +1 -1
- package/dist/contracts/service-documents.js +20 -3
- package/dist/contracts/service-shared.d.ts +7 -0
- package/dist/contracts/service-shared.d.ts.map +1 -1
- package/dist/contracts/service-shared.js +22 -1
- package/dist/contracts/service-templates.d.ts.map +1 -1
- package/dist/contracts/service-templates.js +7 -1
- package/dist/contracts/service.d.ts +6 -2
- package/dist/contracts/service.d.ts.map +1 -1
- package/dist/contracts/service.js +2 -2
- package/dist/contracts/template-authoring.js +13 -13
- package/dist/contracts/validation.d.ts.map +1 -1
- package/dist/contracts/validation.js +14 -2
- package/package.json +9 -9
package/dist/contracts/routes.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
421
|
+
return renderPreviewResponse(c, {
|
|
312
422
|
variables: input.variables,
|
|
313
423
|
body: template.body,
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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":"
|
|
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 {
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
152
|
+
label: "Subtotal",
|
|
153
153
|
example: "210000",
|
|
154
154
|
type: "cents",
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
key: "booking.taxAmountCents",
|
|
158
|
-
label: "Tax total
|
|
158
|
+
label: "Tax total",
|
|
159
159
|
example: "39900",
|
|
160
160
|
type: "cents",
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
key: "booking.discountAmountCents",
|
|
164
|
-
label: "Discount
|
|
164
|
+
label: "Discount",
|
|
165
165
|
example: "0",
|
|
166
166
|
type: "cents",
|
|
167
167
|
},
|
|
168
168
|
{
|
|
169
169
|
key: "booking.totalAmountCents",
|
|
170
|
-
label: "Total (alias
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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":"
|
|
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"}
|