@voyantjs/legal 0.2.0 → 0.3.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/routes.d.ts +1 -1
- package/dist/contracts/service-contracts.d.ts +1234 -0
- package/dist/contracts/service-contracts.d.ts.map +1 -0
- package/dist/contracts/service-contracts.js +233 -0
- package/dist/contracts/service-series.d.ts +68 -0
- package/dist/contracts/service-series.d.ts.map +1 -0
- package/dist/contracts/service-series.js +34 -0
- package/dist/contracts/service-shared.d.ts +32 -0
- package/dist/contracts/service-shared.d.ts.map +1 -0
- package/dist/contracts/service-shared.js +142 -0
- package/dist/contracts/service-templates.d.ts +415 -0
- package/dist/contracts/service-templates.d.ts.map +1 -0
- package/dist/contracts/service-templates.js +108 -0
- package/dist/contracts/service.d.ts +790 -836
- package/dist/contracts/service.d.ts.map +1 -1
- package/dist/contracts/service.js +8 -568
- package/dist/policies/routes.d.ts +2 -2
- package/dist/policies/service-core.d.ts +1336 -0
- package/dist/policies/service-core.d.ts.map +1 -0
- package/dist/policies/service-core.js +357 -0
- package/dist/policies/service-shared.d.ts +43 -0
- package/dist/policies/service-shared.d.ts.map +1 -0
- package/dist/policies/service-shared.js +30 -0
- package/dist/policies/service.d.ts +28 -76
- package/dist/policies/service.d.ts.map +1 -1
- package/dist/policies/service.js +4 -436
- package/package.json +6 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-core.d.ts","sourceRoot":"","sources":["../../src/policies/service-core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { and, desc, eq, gte, ilike, lte, or, sql } from "drizzle-orm";
|
|
2
|
+
import { policies, policyAcceptances, policyAssignments, policyRules, policyVersions, } from "./schema.js";
|
|
3
|
+
import { evaluateCancellationPolicy, paginate, toDateString, } from "./service-shared.js";
|
|
4
|
+
export const policiesCoreService = {
|
|
5
|
+
async listPolicies(db, query) {
|
|
6
|
+
const conditions = [];
|
|
7
|
+
if (query.kind)
|
|
8
|
+
conditions.push(eq(policies.kind, query.kind));
|
|
9
|
+
if (query.language)
|
|
10
|
+
conditions.push(eq(policies.language, query.language));
|
|
11
|
+
if (query.search) {
|
|
12
|
+
const term = `%${query.search}%`;
|
|
13
|
+
conditions.push(or(ilike(policies.name, term), ilike(policies.slug, term), ilike(policies.description, term)));
|
|
14
|
+
}
|
|
15
|
+
const where = conditions.length ? and(...conditions) : undefined;
|
|
16
|
+
return paginate(db
|
|
17
|
+
.select()
|
|
18
|
+
.from(policies)
|
|
19
|
+
.where(where)
|
|
20
|
+
.limit(query.limit)
|
|
21
|
+
.offset(query.offset)
|
|
22
|
+
.orderBy(desc(policies.updatedAt)), db.select({ total: sql `count(*)::int` }).from(policies).where(where), query.limit, query.offset);
|
|
23
|
+
},
|
|
24
|
+
async getPolicyById(db, id) {
|
|
25
|
+
const [row] = await db.select().from(policies).where(eq(policies.id, id)).limit(1);
|
|
26
|
+
return row ?? null;
|
|
27
|
+
},
|
|
28
|
+
async getPolicyBySlug(db, slug) {
|
|
29
|
+
const [row] = await db.select().from(policies).where(eq(policies.slug, slug)).limit(1);
|
|
30
|
+
return row ?? null;
|
|
31
|
+
},
|
|
32
|
+
async createPolicy(db, data) {
|
|
33
|
+
const [row] = await db.insert(policies).values(data).returning();
|
|
34
|
+
return row ?? null;
|
|
35
|
+
},
|
|
36
|
+
async updatePolicy(db, id, data) {
|
|
37
|
+
const [row] = await db
|
|
38
|
+
.update(policies)
|
|
39
|
+
.set({ ...data, updatedAt: new Date() })
|
|
40
|
+
.where(eq(policies.id, id))
|
|
41
|
+
.returning();
|
|
42
|
+
return row ?? null;
|
|
43
|
+
},
|
|
44
|
+
async deletePolicy(db, id) {
|
|
45
|
+
const [row] = await db
|
|
46
|
+
.delete(policies)
|
|
47
|
+
.where(eq(policies.id, id))
|
|
48
|
+
.returning({ id: policies.id });
|
|
49
|
+
return row ?? null;
|
|
50
|
+
},
|
|
51
|
+
listPolicyVersions(db, policyId) {
|
|
52
|
+
return db
|
|
53
|
+
.select()
|
|
54
|
+
.from(policyVersions)
|
|
55
|
+
.where(eq(policyVersions.policyId, policyId))
|
|
56
|
+
.orderBy(desc(policyVersions.version));
|
|
57
|
+
},
|
|
58
|
+
async getPolicyVersionById(db, id) {
|
|
59
|
+
const [row] = await db.select().from(policyVersions).where(eq(policyVersions.id, id)).limit(1);
|
|
60
|
+
return row ?? null;
|
|
61
|
+
},
|
|
62
|
+
async createPolicyVersion(db, policyId, data) {
|
|
63
|
+
return db.transaction(async (tx) => {
|
|
64
|
+
const [policy] = await tx
|
|
65
|
+
.select({ id: policies.id })
|
|
66
|
+
.from(policies)
|
|
67
|
+
.where(eq(policies.id, policyId))
|
|
68
|
+
.limit(1);
|
|
69
|
+
if (!policy)
|
|
70
|
+
return null;
|
|
71
|
+
const [maxRow] = await tx
|
|
72
|
+
.select({ max: sql `coalesce(max(${policyVersions.version}), 0)::int` })
|
|
73
|
+
.from(policyVersions)
|
|
74
|
+
.where(eq(policyVersions.policyId, policyId));
|
|
75
|
+
const nextVersion = (maxRow?.max ?? 0) + 1;
|
|
76
|
+
const [row] = await tx
|
|
77
|
+
.insert(policyVersions)
|
|
78
|
+
.values({
|
|
79
|
+
policyId,
|
|
80
|
+
version: nextVersion,
|
|
81
|
+
status: "draft",
|
|
82
|
+
title: data.title,
|
|
83
|
+
bodyFormat: data.bodyFormat,
|
|
84
|
+
body: data.body ?? null,
|
|
85
|
+
publishedBy: data.publishedBy ?? null,
|
|
86
|
+
metadata: data.metadata ?? null,
|
|
87
|
+
})
|
|
88
|
+
.returning();
|
|
89
|
+
return row ?? null;
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
async updatePolicyVersion(db, versionId, data) {
|
|
93
|
+
const [row] = await db
|
|
94
|
+
.update(policyVersions)
|
|
95
|
+
.set({ ...data, updatedAt: new Date() })
|
|
96
|
+
.where(and(eq(policyVersions.id, versionId), eq(policyVersions.status, "draft")))
|
|
97
|
+
.returning();
|
|
98
|
+
return row ?? null;
|
|
99
|
+
},
|
|
100
|
+
async publishPolicyVersion(db, versionId) {
|
|
101
|
+
return db.transaction(async (tx) => {
|
|
102
|
+
const [version] = await tx
|
|
103
|
+
.select()
|
|
104
|
+
.from(policyVersions)
|
|
105
|
+
.where(eq(policyVersions.id, versionId))
|
|
106
|
+
.limit(1);
|
|
107
|
+
if (!version)
|
|
108
|
+
return { status: "not_found" };
|
|
109
|
+
if (version.status !== "draft")
|
|
110
|
+
return { status: "not_draft" };
|
|
111
|
+
const now = new Date();
|
|
112
|
+
await tx
|
|
113
|
+
.update(policyVersions)
|
|
114
|
+
.set({ status: "retired", retiredAt: now, updatedAt: now })
|
|
115
|
+
.where(and(eq(policyVersions.policyId, version.policyId), eq(policyVersions.status, "published")));
|
|
116
|
+
const [published] = await tx
|
|
117
|
+
.update(policyVersions)
|
|
118
|
+
.set({ status: "published", publishedAt: now, updatedAt: now })
|
|
119
|
+
.where(eq(policyVersions.id, versionId))
|
|
120
|
+
.returning();
|
|
121
|
+
await tx
|
|
122
|
+
.update(policies)
|
|
123
|
+
.set({ currentVersionId: versionId, updatedAt: now })
|
|
124
|
+
.where(eq(policies.id, version.policyId));
|
|
125
|
+
return { status: "published", version: published ?? null };
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
async retirePolicyVersion(db, versionId) {
|
|
129
|
+
const [row] = await db
|
|
130
|
+
.update(policyVersions)
|
|
131
|
+
.set({ status: "retired", retiredAt: new Date(), updatedAt: new Date() })
|
|
132
|
+
.where(eq(policyVersions.id, versionId))
|
|
133
|
+
.returning();
|
|
134
|
+
return row ?? null;
|
|
135
|
+
},
|
|
136
|
+
listPolicyRules(db, versionId) {
|
|
137
|
+
return db
|
|
138
|
+
.select()
|
|
139
|
+
.from(policyRules)
|
|
140
|
+
.where(eq(policyRules.policyVersionId, versionId))
|
|
141
|
+
.orderBy(policyRules.sortOrder, policyRules.createdAt);
|
|
142
|
+
},
|
|
143
|
+
async createPolicyRule(db, versionId, data) {
|
|
144
|
+
const [version] = await db
|
|
145
|
+
.select({ id: policyVersions.id })
|
|
146
|
+
.from(policyVersions)
|
|
147
|
+
.where(eq(policyVersions.id, versionId))
|
|
148
|
+
.limit(1);
|
|
149
|
+
if (!version)
|
|
150
|
+
return null;
|
|
151
|
+
const [row] = await db
|
|
152
|
+
.insert(policyRules)
|
|
153
|
+
.values({
|
|
154
|
+
policyVersionId: versionId,
|
|
155
|
+
ruleType: data.ruleType,
|
|
156
|
+
label: data.label ?? null,
|
|
157
|
+
daysBeforeDeparture: data.daysBeforeDeparture ?? null,
|
|
158
|
+
refundPercent: data.refundPercent ?? null,
|
|
159
|
+
refundType: data.refundType ?? null,
|
|
160
|
+
flatAmountCents: data.flatAmountCents ?? null,
|
|
161
|
+
currency: data.currency ?? null,
|
|
162
|
+
validFrom: toDateString(data.validFrom),
|
|
163
|
+
validTo: toDateString(data.validTo),
|
|
164
|
+
conditions: data.conditions ?? null,
|
|
165
|
+
sortOrder: data.sortOrder,
|
|
166
|
+
})
|
|
167
|
+
.returning();
|
|
168
|
+
return row ?? null;
|
|
169
|
+
},
|
|
170
|
+
async updatePolicyRule(db, ruleId, data) {
|
|
171
|
+
const [row] = await db
|
|
172
|
+
.update(policyRules)
|
|
173
|
+
.set({ ...data, updatedAt: new Date() })
|
|
174
|
+
.where(eq(policyRules.id, ruleId))
|
|
175
|
+
.returning();
|
|
176
|
+
return row ?? null;
|
|
177
|
+
},
|
|
178
|
+
async deletePolicyRule(db, ruleId) {
|
|
179
|
+
const [row] = await db
|
|
180
|
+
.delete(policyRules)
|
|
181
|
+
.where(eq(policyRules.id, ruleId))
|
|
182
|
+
.returning({ id: policyRules.id });
|
|
183
|
+
return row ?? null;
|
|
184
|
+
},
|
|
185
|
+
async listPolicyAssignments(db, query) {
|
|
186
|
+
const conditions = [];
|
|
187
|
+
if (query.policyId)
|
|
188
|
+
conditions.push(eq(policyAssignments.policyId, query.policyId));
|
|
189
|
+
if (query.scope)
|
|
190
|
+
conditions.push(eq(policyAssignments.scope, query.scope));
|
|
191
|
+
if (query.productId)
|
|
192
|
+
conditions.push(eq(policyAssignments.productId, query.productId));
|
|
193
|
+
if (query.channelId)
|
|
194
|
+
conditions.push(eq(policyAssignments.channelId, query.channelId));
|
|
195
|
+
if (query.supplierId)
|
|
196
|
+
conditions.push(eq(policyAssignments.supplierId, query.supplierId));
|
|
197
|
+
if (query.marketId)
|
|
198
|
+
conditions.push(eq(policyAssignments.marketId, query.marketId));
|
|
199
|
+
if (query.organizationId)
|
|
200
|
+
conditions.push(eq(policyAssignments.organizationId, query.organizationId));
|
|
201
|
+
const where = conditions.length ? and(...conditions) : undefined;
|
|
202
|
+
return paginate(db
|
|
203
|
+
.select()
|
|
204
|
+
.from(policyAssignments)
|
|
205
|
+
.where(where)
|
|
206
|
+
.limit(query.limit)
|
|
207
|
+
.offset(query.offset)
|
|
208
|
+
.orderBy(desc(policyAssignments.priority), desc(policyAssignments.createdAt)), db.select({ total: sql `count(*)::int` }).from(policyAssignments).where(where), query.limit, query.offset);
|
|
209
|
+
},
|
|
210
|
+
async createPolicyAssignment(db, data) {
|
|
211
|
+
const [row] = await db
|
|
212
|
+
.insert(policyAssignments)
|
|
213
|
+
.values({
|
|
214
|
+
policyId: data.policyId,
|
|
215
|
+
scope: data.scope,
|
|
216
|
+
productId: data.productId ?? null,
|
|
217
|
+
channelId: data.channelId ?? null,
|
|
218
|
+
supplierId: data.supplierId ?? null,
|
|
219
|
+
marketId: data.marketId ?? null,
|
|
220
|
+
organizationId: data.organizationId ?? null,
|
|
221
|
+
validFrom: toDateString(data.validFrom),
|
|
222
|
+
validTo: toDateString(data.validTo),
|
|
223
|
+
priority: data.priority,
|
|
224
|
+
metadata: data.metadata ?? null,
|
|
225
|
+
})
|
|
226
|
+
.returning();
|
|
227
|
+
return row ?? null;
|
|
228
|
+
},
|
|
229
|
+
async updatePolicyAssignment(db, id, data) {
|
|
230
|
+
const [row] = await db
|
|
231
|
+
.update(policyAssignments)
|
|
232
|
+
.set({ ...data, updatedAt: new Date() })
|
|
233
|
+
.where(eq(policyAssignments.id, id))
|
|
234
|
+
.returning();
|
|
235
|
+
return row ?? null;
|
|
236
|
+
},
|
|
237
|
+
async deletePolicyAssignment(db, id) {
|
|
238
|
+
const [row] = await db
|
|
239
|
+
.delete(policyAssignments)
|
|
240
|
+
.where(eq(policyAssignments.id, id))
|
|
241
|
+
.returning({ id: policyAssignments.id });
|
|
242
|
+
return row ?? null;
|
|
243
|
+
},
|
|
244
|
+
async resolvePolicy(db, input) {
|
|
245
|
+
const conditions = [];
|
|
246
|
+
const atDate = input.at ?? new Date().toISOString().slice(0, 10);
|
|
247
|
+
const scopeConditions = [];
|
|
248
|
+
if (input.productId)
|
|
249
|
+
scopeConditions.push(or(eq(policyAssignments.productId, input.productId), sql `${policyAssignments.productId} IS NULL`));
|
|
250
|
+
if (input.channelId)
|
|
251
|
+
scopeConditions.push(or(eq(policyAssignments.channelId, input.channelId), sql `${policyAssignments.channelId} IS NULL`));
|
|
252
|
+
if (input.supplierId)
|
|
253
|
+
scopeConditions.push(or(eq(policyAssignments.supplierId, input.supplierId), sql `${policyAssignments.supplierId} IS NULL`));
|
|
254
|
+
if (input.marketId)
|
|
255
|
+
scopeConditions.push(or(eq(policyAssignments.marketId, input.marketId), sql `${policyAssignments.marketId} IS NULL`));
|
|
256
|
+
if (input.organizationId)
|
|
257
|
+
scopeConditions.push(or(eq(policyAssignments.organizationId, input.organizationId), sql `${policyAssignments.organizationId} IS NULL`));
|
|
258
|
+
const validity = and(or(sql `${policyAssignments.validFrom} IS NULL`, lte(policyAssignments.validFrom, atDate)), or(sql `${policyAssignments.validTo} IS NULL`, gte(policyAssignments.validTo, atDate)));
|
|
259
|
+
const candidates = await db
|
|
260
|
+
.select({ assignment: policyAssignments, policy: policies })
|
|
261
|
+
.from(policyAssignments)
|
|
262
|
+
.innerJoin(policies, eq(policyAssignments.policyId, policies.id))
|
|
263
|
+
.where(and(eq(policies.kind, input.kind), validity, ...conditions, ...(scopeConditions.length ? [and(...scopeConditions)] : [])))
|
|
264
|
+
.orderBy(desc(policyAssignments.priority), desc(policyAssignments.createdAt));
|
|
265
|
+
if (candidates.length === 0)
|
|
266
|
+
return null;
|
|
267
|
+
const scored = candidates.map((c) => {
|
|
268
|
+
const a = c.assignment;
|
|
269
|
+
const specificity = (a.productId ? 1 : 0) +
|
|
270
|
+
(a.channelId ? 1 : 0) +
|
|
271
|
+
(a.supplierId ? 1 : 0) +
|
|
272
|
+
(a.marketId ? 1 : 0) +
|
|
273
|
+
(a.organizationId ? 1 : 0);
|
|
274
|
+
return { ...c, specificity };
|
|
275
|
+
});
|
|
276
|
+
scored.sort((a, b) => {
|
|
277
|
+
if (b.assignment.priority !== a.assignment.priority)
|
|
278
|
+
return b.assignment.priority - a.assignment.priority;
|
|
279
|
+
if (b.specificity !== a.specificity)
|
|
280
|
+
return b.specificity - a.specificity;
|
|
281
|
+
return b.assignment.createdAt.getTime() - a.assignment.createdAt.getTime();
|
|
282
|
+
});
|
|
283
|
+
const winner = scored[0];
|
|
284
|
+
if (!winner)
|
|
285
|
+
return null;
|
|
286
|
+
if (!winner.policy.currentVersionId)
|
|
287
|
+
return { policy: winner.policy, assignment: winner.assignment, version: null, rules: [] };
|
|
288
|
+
const [version] = await db
|
|
289
|
+
.select()
|
|
290
|
+
.from(policyVersions)
|
|
291
|
+
.where(eq(policyVersions.id, winner.policy.currentVersionId))
|
|
292
|
+
.limit(1);
|
|
293
|
+
const rules = version
|
|
294
|
+
? await db
|
|
295
|
+
.select()
|
|
296
|
+
.from(policyRules)
|
|
297
|
+
.where(eq(policyRules.policyVersionId, version.id))
|
|
298
|
+
.orderBy(policyRules.sortOrder)
|
|
299
|
+
: [];
|
|
300
|
+
return { policy: winner.policy, assignment: winner.assignment, version: version ?? null, rules };
|
|
301
|
+
},
|
|
302
|
+
async evaluateCancellation(db, policyId, input) {
|
|
303
|
+
const [policy] = await db.select().from(policies).where(eq(policies.id, policyId)).limit(1);
|
|
304
|
+
if (!policy?.currentVersionId)
|
|
305
|
+
return null;
|
|
306
|
+
const rules = await db
|
|
307
|
+
.select()
|
|
308
|
+
.from(policyRules)
|
|
309
|
+
.where(eq(policyRules.policyVersionId, policy.currentVersionId));
|
|
310
|
+
const mapped = rules.map((r) => ({
|
|
311
|
+
id: r.id,
|
|
312
|
+
daysBeforeDeparture: r.daysBeforeDeparture,
|
|
313
|
+
refundPercent: r.refundPercent,
|
|
314
|
+
refundType: r.refundType,
|
|
315
|
+
flatAmountCents: r.flatAmountCents,
|
|
316
|
+
label: r.label,
|
|
317
|
+
}));
|
|
318
|
+
return evaluateCancellationPolicy(mapped, input);
|
|
319
|
+
},
|
|
320
|
+
async listPolicyAcceptances(db, query) {
|
|
321
|
+
const conditions = [];
|
|
322
|
+
if (query.policyVersionId)
|
|
323
|
+
conditions.push(eq(policyAcceptances.policyVersionId, query.policyVersionId));
|
|
324
|
+
if (query.personId)
|
|
325
|
+
conditions.push(eq(policyAcceptances.personId, query.personId));
|
|
326
|
+
if (query.bookingId)
|
|
327
|
+
conditions.push(eq(policyAcceptances.bookingId, query.bookingId));
|
|
328
|
+
if (query.orderId)
|
|
329
|
+
conditions.push(eq(policyAcceptances.orderId, query.orderId));
|
|
330
|
+
const where = conditions.length ? and(...conditions) : undefined;
|
|
331
|
+
return paginate(db
|
|
332
|
+
.select()
|
|
333
|
+
.from(policyAcceptances)
|
|
334
|
+
.where(where)
|
|
335
|
+
.limit(query.limit)
|
|
336
|
+
.offset(query.offset)
|
|
337
|
+
.orderBy(desc(policyAcceptances.acceptedAt)), db.select({ total: sql `count(*)::int` }).from(policyAcceptances).where(where), query.limit, query.offset);
|
|
338
|
+
},
|
|
339
|
+
async recordPolicyAcceptance(db, data) {
|
|
340
|
+
const [row] = await db
|
|
341
|
+
.insert(policyAcceptances)
|
|
342
|
+
.values({
|
|
343
|
+
policyVersionId: data.policyVersionId,
|
|
344
|
+
personId: data.personId ?? null,
|
|
345
|
+
bookingId: data.bookingId ?? null,
|
|
346
|
+
orderId: data.orderId ?? null,
|
|
347
|
+
offerId: data.offerId ?? null,
|
|
348
|
+
acceptedBy: data.acceptedBy ?? null,
|
|
349
|
+
method: data.method,
|
|
350
|
+
ipAddress: data.ipAddress ?? null,
|
|
351
|
+
userAgent: data.userAgent ?? null,
|
|
352
|
+
metadata: data.metadata ?? null,
|
|
353
|
+
})
|
|
354
|
+
.returning();
|
|
355
|
+
return row ?? null;
|
|
356
|
+
},
|
|
357
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { inArray } from "drizzle-orm";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import type { evaluateCancellationInputSchema, insertPolicyAcceptanceSchema, insertPolicyAssignmentSchema, insertPolicyRuleSchema, insertPolicySchema, insertPolicyVersionSchema, policyAcceptanceListQuerySchema, policyAssignmentListQuerySchema, policyListQuerySchema, resolvePolicyInputSchema, updatePolicyAssignmentSchema, updatePolicyRuleSchema, updatePolicySchema, updatePolicyVersionSchema } from "./validation.js";
|
|
4
|
+
export type PolicyListQuery = z.infer<typeof policyListQuerySchema>;
|
|
5
|
+
export type CreatePolicyInput = z.infer<typeof insertPolicySchema>;
|
|
6
|
+
export type UpdatePolicyInput = z.infer<typeof updatePolicySchema>;
|
|
7
|
+
export type CreatePolicyVersionInput = z.infer<typeof insertPolicyVersionSchema>;
|
|
8
|
+
export type UpdatePolicyVersionInput = z.infer<typeof updatePolicyVersionSchema>;
|
|
9
|
+
export type CreatePolicyRuleInput = z.infer<typeof insertPolicyRuleSchema>;
|
|
10
|
+
export type UpdatePolicyRuleInput = z.infer<typeof updatePolicyRuleSchema>;
|
|
11
|
+
export type CreatePolicyAssignmentInput = z.infer<typeof insertPolicyAssignmentSchema>;
|
|
12
|
+
export type UpdatePolicyAssignmentInput = z.infer<typeof updatePolicyAssignmentSchema>;
|
|
13
|
+
export type PolicyAssignmentListQuery = z.infer<typeof policyAssignmentListQuerySchema>;
|
|
14
|
+
export type CreatePolicyAcceptanceInput = z.infer<typeof insertPolicyAcceptanceSchema>;
|
|
15
|
+
export type PolicyAcceptanceListQuery = z.infer<typeof policyAcceptanceListQuerySchema>;
|
|
16
|
+
export type EvaluateCancellationInput = z.infer<typeof evaluateCancellationInputSchema>;
|
|
17
|
+
export type ResolvePolicyInput = z.infer<typeof resolvePolicyInputSchema>;
|
|
18
|
+
export declare function paginate<T extends object>(rowsQuery: Promise<T[]>, countQuery: Promise<Array<{
|
|
19
|
+
total: number;
|
|
20
|
+
}>>, limit: number, offset: number): Promise<{
|
|
21
|
+
data: T[];
|
|
22
|
+
total: number;
|
|
23
|
+
limit: number;
|
|
24
|
+
offset: number;
|
|
25
|
+
}>;
|
|
26
|
+
export declare function toDateString(value?: string | null): string | null;
|
|
27
|
+
export type CancellationRule = {
|
|
28
|
+
id?: string;
|
|
29
|
+
daysBeforeDeparture: number | null;
|
|
30
|
+
refundPercent: number | null;
|
|
31
|
+
refundType: "cash" | "credit" | "cash_or_credit" | "none" | null;
|
|
32
|
+
flatAmountCents: number | null;
|
|
33
|
+
label: string | null;
|
|
34
|
+
};
|
|
35
|
+
export type CancellationResult = {
|
|
36
|
+
refundPercent: number;
|
|
37
|
+
refundCents: number;
|
|
38
|
+
refundType: "cash" | "credit" | "cash_or_credit" | "none";
|
|
39
|
+
appliedRule: CancellationRule | null;
|
|
40
|
+
};
|
|
41
|
+
export declare function evaluateCancellationPolicy(rules: CancellationRule[], input: EvaluateCancellationInput): CancellationResult;
|
|
42
|
+
export declare const _unused: typeof inArray;
|
|
43
|
+
//# sourceMappingURL=service-shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../../src/policies/service-shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { inArray } from "drizzle-orm";
|
|
2
|
+
export async function paginate(rowsQuery, countQuery, limit, offset) {
|
|
3
|
+
const [data, countResult] = await Promise.all([rowsQuery, countQuery]);
|
|
4
|
+
return { data, total: countResult[0]?.total ?? 0, limit, offset };
|
|
5
|
+
}
|
|
6
|
+
export function toDateString(value) {
|
|
7
|
+
return value ?? null;
|
|
8
|
+
}
|
|
9
|
+
export function evaluateCancellationPolicy(rules, input) {
|
|
10
|
+
if (rules.length === 0) {
|
|
11
|
+
return { refundPercent: 0, refundCents: 0, refundType: "none", appliedRule: null };
|
|
12
|
+
}
|
|
13
|
+
const sorted = [...rules].sort((a, b) => {
|
|
14
|
+
const ad = a.daysBeforeDeparture ?? Number.NEGATIVE_INFINITY;
|
|
15
|
+
const bd = b.daysBeforeDeparture ?? Number.NEGATIVE_INFINITY;
|
|
16
|
+
return bd - ad;
|
|
17
|
+
});
|
|
18
|
+
const applied = sorted.find((rule) => rule.daysBeforeDeparture !== null && input.daysBeforeDeparture >= rule.daysBeforeDeparture) ??
|
|
19
|
+
sorted[sorted.length - 1] ??
|
|
20
|
+
null;
|
|
21
|
+
if (!applied) {
|
|
22
|
+
return { refundPercent: 0, refundCents: 0, refundType: "none", appliedRule: null };
|
|
23
|
+
}
|
|
24
|
+
const refundPercent = applied.refundPercent ?? 0;
|
|
25
|
+
const refundType = applied.refundType ?? "none";
|
|
26
|
+
const percentageRefundCents = Math.floor((input.totalCents * refundPercent) / 10000);
|
|
27
|
+
const refundCents = applied.flatAmountCents ?? percentageRefundCents;
|
|
28
|
+
return { refundPercent, refundCents, refundType, appliedRule: applied };
|
|
29
|
+
}
|
|
30
|
+
export const _unused = inArray;
|