@voyantjs/legal 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/contracts/routes.d.ts +94 -1
  2. package/dist/contracts/routes.d.ts.map +1 -1
  3. package/dist/contracts/routes.js +27 -1
  4. package/dist/contracts/service-contracts.d.ts +1234 -0
  5. package/dist/contracts/service-contracts.d.ts.map +1 -0
  6. package/dist/contracts/service-contracts.js +233 -0
  7. package/dist/contracts/service-series.d.ts +68 -0
  8. package/dist/contracts/service-series.d.ts.map +1 -0
  9. package/dist/contracts/service-series.js +34 -0
  10. package/dist/contracts/service-shared.d.ts +32 -0
  11. package/dist/contracts/service-shared.d.ts.map +1 -0
  12. package/dist/contracts/service-shared.js +142 -0
  13. package/dist/contracts/service-templates.d.ts +434 -0
  14. package/dist/contracts/service-templates.d.ts.map +1 -0
  15. package/dist/contracts/service-templates.js +132 -0
  16. package/dist/contracts/service.d.ts +882 -909
  17. package/dist/contracts/service.d.ts.map +1 -1
  18. package/dist/contracts/service.js +8 -568
  19. package/dist/contracts/validation.d.ts +14 -0
  20. package/dist/contracts/validation.d.ts.map +1 -1
  21. package/dist/contracts/validation.js +16 -0
  22. package/dist/policies/routes.d.ts +2 -2
  23. package/dist/policies/service-core.d.ts +1336 -0
  24. package/dist/policies/service-core.d.ts.map +1 -0
  25. package/dist/policies/service-core.js +357 -0
  26. package/dist/policies/service-shared.d.ts +43 -0
  27. package/dist/policies/service-shared.d.ts.map +1 -0
  28. package/dist/policies/service-shared.js +30 -0
  29. package/dist/policies/service.d.ts +28 -76
  30. package/dist/policies/service.d.ts.map +1 -1
  31. package/dist/policies/service.js +4 -436
  32. package/package.json +6 -6
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2CvC,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAQpD,kBAAkB,cACV,MAAM,QACZ,6BAA6B;;;;;;;;;;;;yBAe/B,kBAAkB,gBACR,MAAM,QACd,6BAA6B;;;;;;;;;;;;yBASV,kBAAkB,gBAAgB,MAAM;;;CAOpE,CAAA"}
@@ -0,0 +1,233 @@
1
+ import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
2
+ import { contractAttachments, contractSignatures, contracts, contractTemplateVersions, } from "./schema.js";
3
+ import { allocateContractNumber, paginate, renderTemplate, toTimestamp, } from "./service-shared.js";
4
+ export const contractRecordsService = {
5
+ async listContracts(db, query) {
6
+ const conditions = [];
7
+ if (query.scope)
8
+ conditions.push(eq(contracts.scope, query.scope));
9
+ if (query.status)
10
+ conditions.push(eq(contracts.status, query.status));
11
+ if (query.personId)
12
+ conditions.push(eq(contracts.personId, query.personId));
13
+ if (query.organizationId)
14
+ conditions.push(eq(contracts.organizationId, query.organizationId));
15
+ if (query.supplierId)
16
+ conditions.push(eq(contracts.supplierId, query.supplierId));
17
+ if (query.bookingId)
18
+ conditions.push(eq(contracts.bookingId, query.bookingId));
19
+ if (query.orderId)
20
+ 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)));
24
+ }
25
+ const where = conditions.length ? and(...conditions) : undefined;
26
+ return paginate(db
27
+ .select()
28
+ .from(contracts)
29
+ .where(where)
30
+ .limit(query.limit)
31
+ .offset(query.offset)
32
+ .orderBy(desc(contracts.createdAt)), db.select({ total: sql `count(*)::int` }).from(contracts).where(where), query.limit, query.offset);
33
+ },
34
+ async getContractById(db, id) {
35
+ const [row] = await db.select().from(contracts).where(eq(contracts.id, id)).limit(1);
36
+ return row ?? null;
37
+ },
38
+ async createContract(db, data) {
39
+ const [row] = await db
40
+ .insert(contracts)
41
+ .values({ ...data, expiresAt: toTimestamp(data.expiresAt) })
42
+ .returning();
43
+ return row ?? null;
44
+ },
45
+ async updateContract(db, id, data) {
46
+ const [row] = await db
47
+ .update(contracts)
48
+ .set({
49
+ ...data,
50
+ expiresAt: data.expiresAt === undefined ? undefined : toTimestamp(data.expiresAt),
51
+ updatedAt: new Date(),
52
+ })
53
+ .where(eq(contracts.id, id))
54
+ .returning();
55
+ return row ?? null;
56
+ },
57
+ async deleteContract(db, id) {
58
+ const [existing] = await db
59
+ .select({ id: contracts.id, status: contracts.status })
60
+ .from(contracts)
61
+ .where(eq(contracts.id, id))
62
+ .limit(1);
63
+ if (!existing)
64
+ return { status: "not_found" };
65
+ if (existing.status !== "draft")
66
+ return { status: "not_draft" };
67
+ await db.delete(contracts).where(eq(contracts.id, id));
68
+ return { status: "deleted" };
69
+ },
70
+ async issueContract(db, contractId) {
71
+ return db.transaction(async (tx) => {
72
+ const [contract] = await tx
73
+ .select()
74
+ .from(contracts)
75
+ .where(eq(contracts.id, contractId))
76
+ .limit(1);
77
+ if (!contract)
78
+ return { status: "not_found" };
79
+ if (contract.status !== "draft")
80
+ return { status: "not_draft" };
81
+ let renderedBody = contract.renderedBody;
82
+ let renderedBodyFormat = contract.renderedBodyFormat;
83
+ if (contract.templateVersionId) {
84
+ const [version] = await tx
85
+ .select()
86
+ .from(contractTemplateVersions)
87
+ .where(eq(contractTemplateVersions.id, contract.templateVersionId))
88
+ .limit(1);
89
+ if (version) {
90
+ const vars = contract.variables ?? {};
91
+ renderedBody = renderTemplate(version.body, version.bodyFormat, vars);
92
+ renderedBodyFormat = version.bodyFormat;
93
+ }
94
+ }
95
+ let contractNumber = contract.contractNumber;
96
+ if (!contractNumber && contract.seriesId) {
97
+ const allocated = await allocateContractNumber(tx, contract.seriesId);
98
+ if (allocated)
99
+ contractNumber = allocated.number;
100
+ }
101
+ const [updated] = await tx
102
+ .update(contracts)
103
+ .set({
104
+ status: "issued",
105
+ issuedAt: new Date(),
106
+ renderedBody,
107
+ renderedBodyFormat,
108
+ contractNumber,
109
+ updatedAt: new Date(),
110
+ })
111
+ .where(eq(contracts.id, contractId))
112
+ .returning();
113
+ return { status: "issued", contract: updated ?? null };
114
+ });
115
+ },
116
+ async sendContract(db, contractId) {
117
+ const [contract] = await db
118
+ .select({ id: contracts.id, status: contracts.status })
119
+ .from(contracts)
120
+ .where(eq(contracts.id, contractId))
121
+ .limit(1);
122
+ if (!contract)
123
+ return { status: "not_found" };
124
+ if (contract.status !== "issued" && contract.status !== "sent")
125
+ return { status: "not_issued" };
126
+ const [updated] = await db
127
+ .update(contracts)
128
+ .set({ status: "sent", sentAt: new Date(), updatedAt: new Date() })
129
+ .where(eq(contracts.id, contractId))
130
+ .returning();
131
+ return { status: "sent", contract: updated ?? null };
132
+ },
133
+ async voidContract(db, contractId) {
134
+ const [contract] = await db
135
+ .select({ id: contracts.id, status: contracts.status })
136
+ .from(contracts)
137
+ .where(eq(contracts.id, contractId))
138
+ .limit(1);
139
+ if (!contract)
140
+ return { status: "not_found" };
141
+ if (contract.status === "void")
142
+ return { status: "already_void" };
143
+ const [updated] = await db
144
+ .update(contracts)
145
+ .set({ status: "void", voidedAt: new Date(), updatedAt: new Date() })
146
+ .where(eq(contracts.id, contractId))
147
+ .returning();
148
+ return { status: "voided", contract: updated ?? null };
149
+ },
150
+ listSignatures(db, contractId) {
151
+ return db
152
+ .select()
153
+ .from(contractSignatures)
154
+ .where(eq(contractSignatures.contractId, contractId))
155
+ .orderBy(desc(contractSignatures.signedAt));
156
+ },
157
+ async signContract(db, contractId, data) {
158
+ return db.transaction(async (tx) => {
159
+ const [contract] = await tx
160
+ .select()
161
+ .from(contracts)
162
+ .where(eq(contracts.id, contractId))
163
+ .limit(1);
164
+ if (!contract)
165
+ return { status: "not_found" };
166
+ if (contract.status !== "issued" && contract.status !== "sent")
167
+ return { status: "not_signable" };
168
+ const [signature] = await tx
169
+ .insert(contractSignatures)
170
+ .values({ ...data, contractId })
171
+ .returning();
172
+ const [updated] = await tx
173
+ .update(contracts)
174
+ .set({ status: "signed", updatedAt: new Date() })
175
+ .where(eq(contracts.id, contractId))
176
+ .returning();
177
+ return { status: "signed", contract: updated ?? null, signature: signature ?? null };
178
+ });
179
+ },
180
+ async executeContract(db, contractId) {
181
+ const [contract] = await db
182
+ .select({ id: contracts.id, status: contracts.status })
183
+ .from(contracts)
184
+ .where(eq(contracts.id, contractId))
185
+ .limit(1);
186
+ if (!contract)
187
+ return { status: "not_found" };
188
+ if (contract.status !== "signed")
189
+ return { status: "not_signed" };
190
+ const [updated] = await db
191
+ .update(contracts)
192
+ .set({ status: "executed", executedAt: new Date(), updatedAt: new Date() })
193
+ .where(eq(contracts.id, contractId))
194
+ .returning();
195
+ return { status: "executed", contract: updated ?? null };
196
+ },
197
+ listAttachments(db, contractId) {
198
+ return db
199
+ .select()
200
+ .from(contractAttachments)
201
+ .where(eq(contractAttachments.contractId, contractId))
202
+ .orderBy(desc(contractAttachments.createdAt));
203
+ },
204
+ async createAttachment(db, contractId, data) {
205
+ const [contract] = await db
206
+ .select({ id: contracts.id })
207
+ .from(contracts)
208
+ .where(eq(contracts.id, contractId))
209
+ .limit(1);
210
+ if (!contract)
211
+ return null;
212
+ const [row] = await db
213
+ .insert(contractAttachments)
214
+ .values({ ...data, contractId })
215
+ .returning();
216
+ return row ?? null;
217
+ },
218
+ async updateAttachment(db, attachmentId, data) {
219
+ const [row] = await db
220
+ .update(contractAttachments)
221
+ .set(data)
222
+ .where(eq(contractAttachments.id, attachmentId))
223
+ .returning();
224
+ return row ?? null;
225
+ },
226
+ async deleteAttachment(db, attachmentId) {
227
+ const [row] = await db
228
+ .delete(contractAttachments)
229
+ .where(eq(contractAttachments.id, attachmentId))
230
+ .returning({ id: contractAttachments.id });
231
+ return row ?? null;
232
+ },
233
+ };
@@ -0,0 +1,68 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { CreateContractNumberSeriesInput, UpdateContractNumberSeriesInput } from "./service-shared.js";
3
+ export declare const contractSeriesService: {
4
+ listSeries(db: PostgresJsDatabase): Promise<{
5
+ id: string;
6
+ code: string;
7
+ name: string;
8
+ prefix: string;
9
+ separator: string;
10
+ padLength: number;
11
+ currentSequence: number;
12
+ resetStrategy: "never" | "annual" | "monthly";
13
+ resetAt: Date | null;
14
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
15
+ active: boolean;
16
+ createdAt: Date;
17
+ updatedAt: Date;
18
+ }[]>;
19
+ getSeriesById(db: PostgresJsDatabase, id: string): Promise<{
20
+ id: string;
21
+ code: string;
22
+ name: string;
23
+ prefix: string;
24
+ separator: string;
25
+ padLength: number;
26
+ currentSequence: number;
27
+ resetStrategy: "never" | "annual" | "monthly";
28
+ resetAt: Date | null;
29
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
30
+ active: boolean;
31
+ createdAt: Date;
32
+ updatedAt: Date;
33
+ } | null>;
34
+ createSeries(db: PostgresJsDatabase, data: CreateContractNumberSeriesInput): Promise<{
35
+ id: string;
36
+ name: string;
37
+ active: boolean;
38
+ createdAt: Date;
39
+ updatedAt: Date;
40
+ code: string;
41
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
42
+ prefix: string;
43
+ separator: string;
44
+ padLength: number;
45
+ currentSequence: number;
46
+ resetStrategy: "never" | "annual" | "monthly";
47
+ resetAt: Date | null;
48
+ } | null>;
49
+ updateSeries(db: PostgresJsDatabase, id: string, data: UpdateContractNumberSeriesInput): Promise<{
50
+ id: string;
51
+ code: string;
52
+ name: string;
53
+ prefix: string;
54
+ separator: string;
55
+ padLength: number;
56
+ currentSequence: number;
57
+ resetStrategy: "never" | "annual" | "monthly";
58
+ resetAt: Date | null;
59
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
60
+ active: boolean;
61
+ createdAt: Date;
62
+ updatedAt: Date;
63
+ } | null>;
64
+ deleteSeries(db: PostgresJsDatabase, id: string): Promise<{
65
+ id: string;
66
+ } | null>;
67
+ };
68
+ //# sourceMappingURL=service-series.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-series.d.ts","sourceRoot":"","sources":["../../src/contracts/service-series.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,KAAK,EACV,+BAA+B,EAC/B,+BAA+B,EAChC,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,qBAAqB;mBACX,kBAAkB;;;;;;;;;;;;;;;sBAGf,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;qBAQ/B,kBAAkB,QAAQ,+BAA+B;;;;;;;;;;;;;;;qBAIzD,kBAAkB,MAAM,MAAM,QAAQ,+BAA+B;;;;;;;;;;;;;;;qBAQrE,kBAAkB,MAAM,MAAM;;;CAOtD,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { desc, eq } from "drizzle-orm";
2
+ import { contractNumberSeries } from "./schema.js";
3
+ export const contractSeriesService = {
4
+ async listSeries(db) {
5
+ return db.select().from(contractNumberSeries).orderBy(desc(contractNumberSeries.updatedAt));
6
+ },
7
+ async getSeriesById(db, id) {
8
+ const [row] = await db
9
+ .select()
10
+ .from(contractNumberSeries)
11
+ .where(eq(contractNumberSeries.id, id))
12
+ .limit(1);
13
+ return row ?? null;
14
+ },
15
+ async createSeries(db, data) {
16
+ const [row] = await db.insert(contractNumberSeries).values(data).returning();
17
+ return row ?? null;
18
+ },
19
+ async updateSeries(db, id, data) {
20
+ const [row] = await db
21
+ .update(contractNumberSeries)
22
+ .set({ ...data, updatedAt: new Date() })
23
+ .where(eq(contractNumberSeries.id, id))
24
+ .returning();
25
+ return row ?? null;
26
+ },
27
+ async deleteSeries(db, id) {
28
+ const [row] = await db
29
+ .delete(contractNumberSeries)
30
+ .where(eq(contractNumberSeries.id, id))
31
+ .returning({ id: contractNumberSeries.id });
32
+ return row ?? null;
33
+ },
34
+ };
@@ -0,0 +1,32 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { z } from "zod";
3
+ import type { contractListQuerySchema, contractTemplateListQuerySchema, insertContractAttachmentSchema, insertContractNumberSeriesSchema, insertContractSchema, insertContractSignatureSchema, insertContractTemplateSchema, insertContractTemplateVersionSchema, renderTemplateInputSchema, updateContractAttachmentSchema, updateContractNumberSeriesSchema, updateContractSchema, updateContractTemplateSchema } from "./validation.js";
4
+ export type ContractListQuery = z.infer<typeof contractListQuerySchema>;
5
+ export type ContractTemplateListQuery = z.infer<typeof contractTemplateListQuerySchema>;
6
+ export type CreateContractTemplateInput = z.infer<typeof insertContractTemplateSchema>;
7
+ export type UpdateContractTemplateInput = z.infer<typeof updateContractTemplateSchema>;
8
+ export type CreateContractTemplateVersionInput = z.infer<typeof insertContractTemplateVersionSchema>;
9
+ export type CreateContractNumberSeriesInput = z.infer<typeof insertContractNumberSeriesSchema>;
10
+ export type UpdateContractNumberSeriesInput = z.infer<typeof updateContractNumberSeriesSchema>;
11
+ export type CreateContractInput = z.infer<typeof insertContractSchema>;
12
+ export type UpdateContractInput = z.infer<typeof updateContractSchema>;
13
+ export type CreateContractSignatureInput = z.infer<typeof insertContractSignatureSchema>;
14
+ export type CreateContractAttachmentInput = z.infer<typeof insertContractAttachmentSchema>;
15
+ export type UpdateContractAttachmentInput = z.infer<typeof updateContractAttachmentSchema>;
16
+ export type RenderTemplateInput = z.infer<typeof renderTemplateInputSchema>;
17
+ export declare function toTimestamp(value?: string | null): Date | null;
18
+ export declare function renderTemplate(body: string, bodyFormat: "markdown" | "html" | "lexical_json", variables: Record<string, unknown>): string;
19
+ export declare function validateTemplateVariables(variableSchema: unknown, values: Record<string, unknown>): string[];
20
+ export declare function allocateContractNumber(db: PostgresJsDatabase, seriesId: string): Promise<{
21
+ number: string;
22
+ sequence: number;
23
+ } | null>;
24
+ export declare function paginate<T extends object>(rowsQuery: Promise<T[]>, countQuery: Promise<Array<{
25
+ total: number;
26
+ }>>, limit: number, offset: number): Promise<{
27
+ data: T[];
28
+ total: number;
29
+ limit: number;
30
+ offset: number;
31
+ }>;
32
+ //# sourceMappingURL=service-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../../src/contracts/service-shared.ts"],"names":[],"mappings":"AACA,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;AA8DD,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,CAkBR;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,CAmCtD;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"}
@@ -0,0 +1,142 @@
1
+ import { sql } from "drizzle-orm";
2
+ import { contractNumberSeries } from "./schema.js";
3
+ export function toTimestamp(value) {
4
+ return value ? new Date(value) : null;
5
+ }
6
+ function resolvePath(obj, path) {
7
+ if (obj === null || obj === undefined)
8
+ return undefined;
9
+ const segments = [];
10
+ const parts = path.split(".");
11
+ for (const part of parts) {
12
+ if (!part)
13
+ continue;
14
+ const indexMatches = [...part.matchAll(/([^[\]]+)|\[(\d+)\]/g)];
15
+ for (const match of indexMatches) {
16
+ if (match[1] !== undefined)
17
+ segments.push(match[1]);
18
+ else if (match[2] !== undefined)
19
+ segments.push(Number.parseInt(match[2], 10));
20
+ }
21
+ }
22
+ let current = obj;
23
+ for (const seg of segments) {
24
+ if (current === null || current === undefined)
25
+ return undefined;
26
+ if (typeof seg === "number") {
27
+ if (!Array.isArray(current))
28
+ return undefined;
29
+ current = current[seg];
30
+ }
31
+ else {
32
+ if (typeof current !== "object")
33
+ return undefined;
34
+ current = current[seg];
35
+ }
36
+ }
37
+ return current;
38
+ }
39
+ function stringifyValue(value) {
40
+ if (value === null || value === undefined)
41
+ return "";
42
+ if (typeof value === "string")
43
+ return value;
44
+ if (typeof value === "number" || typeof value === "boolean")
45
+ return String(value);
46
+ return JSON.stringify(value);
47
+ }
48
+ const MUSTACHE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
49
+ function renderMustache(body, variables) {
50
+ return body.replace(MUSTACHE_RE, (_, path) => {
51
+ const resolved = resolvePath(variables, path.trim());
52
+ return stringifyValue(resolved);
53
+ });
54
+ }
55
+ function walkLexical(node, variables) {
56
+ const next = { ...node };
57
+ if (typeof next.text === "string") {
58
+ next.text = renderMustache(next.text, variables);
59
+ }
60
+ if (Array.isArray(next.children)) {
61
+ next.children = next.children.map((child) => walkLexical(child, variables));
62
+ }
63
+ return next;
64
+ }
65
+ export function renderTemplate(body, bodyFormat, variables) {
66
+ if (bodyFormat === "lexical_json") {
67
+ try {
68
+ const parsed = JSON.parse(body);
69
+ if (parsed && typeof parsed === "object") {
70
+ const obj = parsed;
71
+ if (obj.root && typeof obj.root === "object") {
72
+ const result = { ...obj, root: walkLexical(obj.root, variables) };
73
+ return JSON.stringify(result);
74
+ }
75
+ return JSON.stringify(walkLexical(obj, variables));
76
+ }
77
+ return body;
78
+ }
79
+ catch {
80
+ return renderMustache(body, variables);
81
+ }
82
+ }
83
+ return renderMustache(body, variables);
84
+ }
85
+ export function validateTemplateVariables(variableSchema, values) {
86
+ const issues = [];
87
+ if (!variableSchema || typeof variableSchema !== "object")
88
+ return issues;
89
+ const schema = variableSchema;
90
+ const requiredList = Array.isArray(schema.required) ? schema.required : [];
91
+ for (const key of requiredList) {
92
+ const resolved = resolvePath(values, key);
93
+ if (resolved === undefined || resolved === null || resolved === "") {
94
+ issues.push(`missing required variable: ${key}`);
95
+ }
96
+ }
97
+ return issues;
98
+ }
99
+ function currentPeriodBoundary(strategy, now) {
100
+ if (strategy === "never")
101
+ return null;
102
+ if (strategy === "annual")
103
+ return new Date(Date.UTC(now.getUTCFullYear(), 0, 1));
104
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
105
+ }
106
+ function formatNumber(prefix, separator, sequence, padLength) {
107
+ const padded = String(sequence).padStart(padLength, "0");
108
+ return `${prefix}${separator}${padded}`;
109
+ }
110
+ export async function allocateContractNumber(db, seriesId) {
111
+ return db.transaction(async (tx) => {
112
+ const rows = await tx.execute(sql `SELECT * FROM ${contractNumberSeries}
113
+ WHERE ${contractNumberSeries.id} = ${seriesId}
114
+ FOR UPDATE`);
115
+ const row = rows[0];
116
+ if (!row)
117
+ return null;
118
+ const strategy = row.reset_strategy;
119
+ const prefix = row.prefix ?? "";
120
+ const separator = row.separator ?? "";
121
+ const padLength = row.pad_length ?? 4;
122
+ const currentSequence = row.current_sequence ?? 0;
123
+ const resetAt = row.reset_at ? new Date(row.reset_at) : null;
124
+ const now = new Date();
125
+ const boundary = currentPeriodBoundary(strategy, now);
126
+ const shouldReset = boundary !== null && (resetAt === null || resetAt.getTime() < boundary.getTime());
127
+ const nextSequence = shouldReset ? 1 : currentSequence + 1;
128
+ const nextResetAt = strategy === "never" ? resetAt : boundary;
129
+ await tx
130
+ .update(contractNumberSeries)
131
+ .set({ currentSequence: nextSequence, resetAt: nextResetAt, updatedAt: new Date() })
132
+ .where(sql `${contractNumberSeries.id} = ${seriesId}`);
133
+ return {
134
+ number: formatNumber(prefix, separator, nextSequence, padLength),
135
+ sequence: nextSequence,
136
+ };
137
+ });
138
+ }
139
+ export async function paginate(rowsQuery, countQuery, limit, offset) {
140
+ const [data, countResult] = await Promise.all([rowsQuery, countQuery]);
141
+ return { data, total: countResult[0]?.total ?? 0, limit, offset };
142
+ }