@bizmap/sdk 0.0.120 → 0.0.122
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/main.d.ts +255 -912
- package/dist/main.js +414 -703
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -6,16 +6,16 @@ var companyUserRoles = z.enum([
|
|
|
6
6
|
"rcpst",
|
|
7
7
|
"cshr",
|
|
8
8
|
"a/r",
|
|
9
|
-
"
|
|
9
|
+
"ceo"
|
|
10
10
|
// "admin",
|
|
11
11
|
]);
|
|
12
|
-
var employeeRoles = companyUserRoles.exclude(["
|
|
13
|
-
var adminRoles = companyUserRoles.extract(["
|
|
12
|
+
var employeeRoles = companyUserRoles.exclude(["ceo"]);
|
|
13
|
+
var adminRoles = companyUserRoles.extract(["ceo"]);
|
|
14
14
|
var healthcareProviderRoles = companyUserRoles.extract([
|
|
15
15
|
"physAsst",
|
|
16
16
|
"doc"
|
|
17
17
|
]);
|
|
18
|
-
var
|
|
18
|
+
var taskDistAlgs = z.enum(["RR", "LOR"]);
|
|
19
19
|
var companyServiceSelectors = z.enum(["scheduler", "doctor"]);
|
|
20
20
|
|
|
21
21
|
// src/enums/Global.ts
|
|
@@ -36,56 +36,47 @@ var vitalKeys = z3.enum([
|
|
|
36
36
|
"bodyTemp"
|
|
37
37
|
]);
|
|
38
38
|
|
|
39
|
-
// src/enums/Appointments.ts
|
|
40
|
-
import * as z4 from "zod";
|
|
41
|
-
var serviceProviders = z4.enum([
|
|
42
|
-
"scheduler",
|
|
43
|
-
"doctor",
|
|
44
|
-
"physAsst",
|
|
45
|
-
"debtCollector"
|
|
46
|
-
]);
|
|
47
|
-
|
|
48
39
|
// src/schemas/company/CompanyDetails.ts
|
|
49
|
-
import * as
|
|
40
|
+
import * as z15 from "zod";
|
|
50
41
|
|
|
51
42
|
// src/schemas/company/components/Billing.ts
|
|
52
|
-
import * as
|
|
43
|
+
import * as z7 from "zod";
|
|
53
44
|
|
|
54
45
|
// src/schemas/utils/billing.ts
|
|
55
|
-
import * as
|
|
46
|
+
import * as z5 from "zod";
|
|
56
47
|
|
|
57
48
|
// src/schemas/utils/core.ts
|
|
58
|
-
import * as
|
|
59
|
-
var InvoiceNo =
|
|
49
|
+
import * as z4 from "zod";
|
|
50
|
+
var InvoiceNo = z4.string().trim().refine(
|
|
60
51
|
(d) => d && d.replace(
|
|
61
52
|
/^[0-9]{4}-(01|(0[2-9])|(1[0-2]))-(30|31|([1-2][0-9])|0[1-9])-[0-9]{7}/,
|
|
62
53
|
""
|
|
63
54
|
).length === 0,
|
|
64
55
|
'Invalid invoice no.: must match the pattern "yyyy-mm-dd-0000000"'
|
|
65
56
|
);
|
|
66
|
-
var Trn =
|
|
57
|
+
var Trn = z4.string().trim().refine(
|
|
67
58
|
(data) => data && data.replace(/^[0-9]{3}-[0-9]{3}-[0-9]{3}/, "").length === 0,
|
|
68
59
|
'Invalid trn: must match the pattern "000-000-000"'
|
|
69
60
|
);
|
|
70
|
-
var StandardTime =
|
|
61
|
+
var StandardTime = z4.string().trim().refine(
|
|
71
62
|
(d) => d && d.replace(/^(01|(0[2-9])|(1[0-2])):(([1-5][0-9])|0[0-9]) (am|pm)/i, "").length === 0,
|
|
72
63
|
'Invalid standard time: must match the pattern "hh:mm A"'
|
|
73
64
|
);
|
|
74
|
-
var Timestamp =
|
|
75
|
-
var InviteResponse =
|
|
76
|
-
var TimeLog =
|
|
65
|
+
var Timestamp = z4.iso.datetime();
|
|
66
|
+
var InviteResponse = z4.enum(["accepted", "declined"]);
|
|
67
|
+
var TimeLog = z4.object({
|
|
77
68
|
createdAt: Timestamp.readonly(),
|
|
78
69
|
lastModified: Timestamp.optional()
|
|
79
70
|
});
|
|
80
|
-
var TicketNo =
|
|
71
|
+
var TicketNo = z4.int().positive().refine(
|
|
81
72
|
(t) => t.toString().length <= 10,
|
|
82
73
|
"A ticket no. can only have (10) max digits."
|
|
83
74
|
).readonly();
|
|
84
|
-
var Reason =
|
|
85
|
-
value:
|
|
75
|
+
var Reason = z4.object({
|
|
76
|
+
value: z4.string().trim().min(3).max(100),
|
|
86
77
|
lastModified: Timestamp
|
|
87
78
|
});
|
|
88
|
-
var AlphaNumeric =
|
|
79
|
+
var AlphaNumeric = z4.string().trim().superRefine((data, ctx) => {
|
|
89
80
|
const invalidCharIdx = data.search(/[^a-z0-9]/i);
|
|
90
81
|
if (invalidCharIdx > -1) {
|
|
91
82
|
const char = `"'`.includes(data[invalidCharIdx]) ? `(${data[invalidCharIdx]})` : `"${data[invalidCharIdx]}"`;
|
|
@@ -94,38 +85,38 @@ var AlphaNumeric = z5.string().trim().superRefine((data, ctx) => {
|
|
|
94
85
|
});
|
|
95
86
|
|
|
96
87
|
// src/schemas/utils/billing.ts
|
|
97
|
-
var PriceMod =
|
|
98
|
-
description:
|
|
88
|
+
var PriceMod = z5.object({
|
|
89
|
+
description: z5.string().trim().min(3, "A description must have atleast (3) characters.").max(25, "A description can't have more that (25) characters.").superRefine((data, ctx) => {
|
|
99
90
|
const invalidCharIdx = data.search(/[^a-zA-Z0-9-\.:\(\)]/);
|
|
100
91
|
if (invalidCharIdx > -1) {
|
|
101
92
|
ctx.addIssue("");
|
|
102
93
|
}
|
|
103
94
|
}),
|
|
104
|
-
fixedAmount:
|
|
105
|
-
value:
|
|
95
|
+
fixedAmount: z5.object({
|
|
96
|
+
value: z5.number().min(0).max(1e6, "The max fixed amount is $1,000,000.00"),
|
|
106
97
|
currency: acceptedCurrencies
|
|
107
98
|
}),
|
|
108
|
-
percentage:
|
|
109
|
-
isOptional:
|
|
99
|
+
percentage: z5.number().min(0).max(1e3, "The max percentage is 1,000%"),
|
|
100
|
+
isOptional: z5.boolean(),
|
|
110
101
|
...TimeLog.shape
|
|
111
102
|
});
|
|
112
|
-
var PriceModList =
|
|
113
|
-
var PriceTag =
|
|
114
|
-
uid:
|
|
103
|
+
var PriceModList = z5.record(z5.uuidv4(), PriceMod);
|
|
104
|
+
var PriceTag = z5.object({
|
|
105
|
+
uid: z5.uuidv4(),
|
|
115
106
|
/**The name of the item that's being priced */
|
|
116
|
-
item:
|
|
117
|
-
cost:
|
|
107
|
+
item: z5.string().trim().min(3, "The price item must be atleast (3) characters long.").max(50, "The price item can't be more than (50) characters long."),
|
|
108
|
+
cost: z5.number().min(1, "The minimum allowed cost is $1.00").max(1e9, "The max allowed cost is $1,000,000,000.00."),
|
|
118
109
|
currency: acceptedCurrencies,
|
|
119
110
|
...TimeLog.shape
|
|
120
111
|
});
|
|
121
|
-
var Receipts =
|
|
112
|
+
var Receipts = z5.array(
|
|
122
113
|
PriceTag.omit({ uid: true, lastModified: true })
|
|
123
114
|
);
|
|
124
115
|
|
|
125
116
|
// src/schemas/company/components/State.ts
|
|
126
|
-
import * as
|
|
127
|
-
var CompanyState =
|
|
128
|
-
_id:
|
|
117
|
+
import * as z6 from "zod";
|
|
118
|
+
var CompanyState = z6.object({
|
|
119
|
+
_id: z6.string().toLowerCase().trim().min(3).max(63).transform(
|
|
129
120
|
(data) => data.replace(/-+/g, "-").replace(/_+/g, "_").replace(/\.+/g, ".").replace(/\s+/g, "")
|
|
130
121
|
).superRefine((data, ctx) => {
|
|
131
122
|
const invalidCharIdx = data.search(/[^a-z0-9\-_\.]/);
|
|
@@ -142,28 +133,26 @@ var CompanyState = z7.object({
|
|
|
142
133
|
ctx.addIssue("The last character must either be a letter or a number.");
|
|
143
134
|
}
|
|
144
135
|
}),
|
|
145
|
-
credits:
|
|
146
|
-
current:
|
|
136
|
+
credits: z6.object({
|
|
137
|
+
current: z6.number(),
|
|
147
138
|
lastModified: Timestamp.optional()
|
|
148
139
|
}),
|
|
149
|
-
|
|
150
|
-
current:
|
|
140
|
+
usedStorageInBytes: z6.object({
|
|
141
|
+
current: z6.number(),
|
|
151
142
|
lastModified: Timestamp.optional()
|
|
152
143
|
}),
|
|
153
|
-
|
|
154
|
-
current:
|
|
144
|
+
tierId: z6.object({
|
|
145
|
+
current: z6.string(),
|
|
155
146
|
lastModified: Timestamp.optional()
|
|
156
147
|
}),
|
|
148
|
+
count: z6.object({
|
|
149
|
+
invoiceNo: z6.int().positive(),
|
|
150
|
+
tktNo: z6.int().min(1)
|
|
151
|
+
}),
|
|
152
|
+
// distRoleIds: z.array(z.string()),
|
|
157
153
|
/** Data retention period in months */
|
|
158
154
|
// drpInMonths: z.int().min(1),
|
|
159
|
-
|
|
160
|
-
* @note This should only be null when the cycle resets
|
|
161
|
-
*/
|
|
162
|
-
invoiceNoCounter: z7.int().min(1),
|
|
163
|
-
/** A counter for the company's ticket no. */
|
|
164
|
-
tktNoCounter: z7.int().min(1),
|
|
165
|
-
lastModified: Timestamp.optional(),
|
|
166
|
-
createdAt: Timestamp
|
|
155
|
+
...TimeLog.shape
|
|
167
156
|
});
|
|
168
157
|
|
|
169
158
|
// src/schemas/company/components/Billing.ts
|
|
@@ -172,21 +161,21 @@ var createMod = (resource) => PriceModList.refine(
|
|
|
172
161
|
(d) => Object.values(d).length <= MAX_COMPANY_PRICE_MODS,
|
|
173
162
|
`Adding more than 100 ${resource}s is prohibited.`
|
|
174
163
|
);
|
|
175
|
-
var CompanyBilling =
|
|
164
|
+
var CompanyBilling = z7.object({
|
|
176
165
|
_id: CompanyState.shape._id,
|
|
177
|
-
additionalFees: createMod("additional fee"),
|
|
178
|
-
discounts: createMod("discount"),
|
|
166
|
+
additionalFees: createMod("additional fee").optional(),
|
|
167
|
+
discounts: createMod("discount").optional(),
|
|
179
168
|
/** Optionally deductable from bills */
|
|
180
|
-
prepayments: createMod("pre-payment"),
|
|
181
|
-
offeredServices:
|
|
169
|
+
prepayments: createMod("pre-payment").optional(),
|
|
170
|
+
offeredServices: z7.record(z7.uuidv4(), PriceTag.omit({ uid: true })).superRefine((data, ctx) => {
|
|
182
171
|
const services = Object.entries(data);
|
|
183
172
|
if (services.length > 150) {
|
|
184
173
|
ctx.addIssue(
|
|
185
174
|
"Creating more services than the max allowed services (150) is prohibited."
|
|
186
175
|
);
|
|
187
176
|
}
|
|
188
|
-
}),
|
|
189
|
-
|
|
177
|
+
}).optional(),
|
|
178
|
+
...TimeLog.shape
|
|
190
179
|
});
|
|
191
180
|
var MutableCompanyBilling = CompanyBilling.omit({
|
|
192
181
|
uid: true,
|
|
@@ -194,25 +183,32 @@ var MutableCompanyBilling = CompanyBilling.omit({
|
|
|
194
183
|
});
|
|
195
184
|
|
|
196
185
|
// src/schemas/company/components/Identity.ts
|
|
197
|
-
import * as
|
|
186
|
+
import * as z9 from "zod";
|
|
198
187
|
import { Address, PhoneNumber } from "@wavy/util";
|
|
199
188
|
|
|
200
189
|
// src/schemas/company/utils/CompanyIndustry.ts
|
|
201
|
-
import * as
|
|
202
|
-
var CompanyIndustry =
|
|
203
|
-
healthcare:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
190
|
+
import * as z8 from "zod";
|
|
191
|
+
var CompanyIndustry = z8.object({
|
|
192
|
+
healthcare: z8.object({
|
|
193
|
+
medicalCare: z8.object({
|
|
194
|
+
roles: z8.enum(["doc", "physAsst"])
|
|
195
|
+
})
|
|
196
|
+
// "pharmaceuticals": z.object({
|
|
197
|
+
// })
|
|
198
|
+
})
|
|
207
199
|
});
|
|
208
200
|
|
|
209
201
|
// src/schemas/company/utils/utils.ts
|
|
210
202
|
var getInvalidSectorError = (industry, sector) => `Invalid sector: "${sector}" isn't a sector under the "${industry}" industry.`;
|
|
203
|
+
var COMPANY_USER_RELATIONSHIP = {
|
|
204
|
+
doc: ["physAsst"],
|
|
205
|
+
physAsst: ["doc"]
|
|
206
|
+
};
|
|
211
207
|
|
|
212
208
|
// src/schemas/company/components/Identity.ts
|
|
213
|
-
var CompanyIdentity =
|
|
209
|
+
var CompanyIdentity = z9.object({
|
|
214
210
|
_id: CompanyState.shape._id,
|
|
215
|
-
alias:
|
|
211
|
+
alias: z9.string().trim().min(3).max(63).transform((data) => data.replace(/\s+/g, " ")).superRefine((data, ctx) => {
|
|
216
212
|
const invalidCharIdx = data.search(/[^a-z1-9 ]/i);
|
|
217
213
|
if (invalidCharIdx >= 0) {
|
|
218
214
|
ctx.addIssue(
|
|
@@ -220,33 +216,26 @@ var CompanyIdentity = z10.object({
|
|
|
220
216
|
);
|
|
221
217
|
}
|
|
222
218
|
}),
|
|
223
|
-
address: Address
|
|
224
|
-
logo:
|
|
225
|
-
industry: CompanyIndustry.keyof()
|
|
226
|
-
sector:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
tier: z10.object({
|
|
234
|
-
current: tiers,
|
|
235
|
-
lastModified: Timestamp.optional()
|
|
236
|
-
}),
|
|
237
|
-
isVerified: z10.boolean(),
|
|
238
|
-
legal: z10.object({
|
|
239
|
-
regNo: z10.string().max(20).readonly(),
|
|
219
|
+
address: Address,
|
|
220
|
+
logo: z9.string().optional(),
|
|
221
|
+
industry: CompanyIndustry.keyof(),
|
|
222
|
+
sector: z9.string(),
|
|
223
|
+
email: z9.email().max(45),
|
|
224
|
+
phoneNumber: PhoneNumber.optional(),
|
|
225
|
+
emailVerified: z9.boolean(),
|
|
226
|
+
verified: z9.boolean(),
|
|
227
|
+
legal: z9.object({
|
|
228
|
+
regNo: z9.string().max(20).readonly(),
|
|
240
229
|
// The company's registration number
|
|
241
|
-
trn:
|
|
242
|
-
gctRegNo:
|
|
230
|
+
trn: z9.string().regex(/[0-9]{3}-[0-9]{3}-[0-9]{3}/).nullish(),
|
|
231
|
+
gctRegNo: z9.string().max(15).nullish()
|
|
243
232
|
}).optional(),
|
|
244
|
-
|
|
233
|
+
...TimeLog.shape
|
|
245
234
|
}).superRefine((data, ctx) => {
|
|
246
235
|
const hasLegalInfo = "legal" in data;
|
|
247
|
-
if (data.
|
|
236
|
+
if (data.verified && !hasLegalInfo) {
|
|
248
237
|
ctx.addIssue("Verified companies must have the legal field defined.");
|
|
249
|
-
} else if (!data.
|
|
238
|
+
} else if (!data.verified && hasLegalInfo) {
|
|
250
239
|
ctx.addIssue("Unverified companies can't have the legal field defined.");
|
|
251
240
|
}
|
|
252
241
|
if (!CompanyIndustry.shape[data.industry].safeParse(data.sector).success) {
|
|
@@ -261,172 +250,53 @@ var MutableCompanyIdentity = CompanyIdentity.pick({
|
|
|
261
250
|
alias: true
|
|
262
251
|
});
|
|
263
252
|
|
|
264
|
-
// src/schemas/company/components/Notifications.ts
|
|
265
|
-
import * as z12 from "zod";
|
|
266
|
-
|
|
267
|
-
// src/schemas/Global.ts
|
|
268
|
-
import * as z11 from "zod";
|
|
269
|
-
|
|
270
|
-
// src/constants.ts
|
|
271
|
-
var NOTIF_ID_DELIM = "::";
|
|
272
|
-
var MAX_NAME_LENGTH = 21;
|
|
273
|
-
|
|
274
|
-
// src/schemas/Global.ts
|
|
275
|
-
var TierList = z11.record(
|
|
276
|
-
tiers,
|
|
277
|
-
z11.object({
|
|
278
|
-
cost: z11.number().min(0),
|
|
279
|
-
perks: z11.array(z11.string()).min(1)
|
|
280
|
-
})
|
|
281
|
-
).superRefine((data, ctx) => {
|
|
282
|
-
if (data.basic.cost !== 0) {
|
|
283
|
-
ctx.addIssue("The basic tier must always cost $0.00");
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
var Notification = z11.object({
|
|
287
|
-
_id: z11.string().superRefine((data, ctx) => {
|
|
288
|
-
const isUUidV4 = z11.uuidv4().safeParse(data).success;
|
|
289
|
-
if (isUUidV4) return;
|
|
290
|
-
let [from, to, nonce] = data.split(NOTIF_ID_DELIM);
|
|
291
|
-
nonce = nonce?.trim() || "";
|
|
292
|
-
const parsedFrom = z11.uuidv4().or(z11.uuidv7()).safeParse(from);
|
|
293
|
-
if (!parsedFrom.success) {
|
|
294
|
-
ctx.addIssue({
|
|
295
|
-
code: "custom",
|
|
296
|
-
message: "Invalid UUID",
|
|
297
|
-
path: ["from"]
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
const parsedTo = z11.uuidv4().safeParse(to);
|
|
301
|
-
if (!parsedTo.success) {
|
|
302
|
-
ctx.addIssue({
|
|
303
|
-
code: "custom",
|
|
304
|
-
message: "Invalid UUID",
|
|
305
|
-
path: ["to"]
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
if (nonce.length < 4 || nonce.length > 7) {
|
|
309
|
-
ctx.addIssue({
|
|
310
|
-
code: "custom",
|
|
311
|
-
message: "Invalid length",
|
|
312
|
-
path: ["nonce"]
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
}),
|
|
316
|
-
code: z11.literal([
|
|
317
|
-
"INVITE_RESPONSE",
|
|
318
|
-
"COMPANY_INVITE",
|
|
319
|
-
"DEV_MESSAGE",
|
|
320
|
-
"ALERT"
|
|
321
|
-
]),
|
|
322
|
-
payload: z11.string(),
|
|
323
|
-
isArchived: z11.boolean().optional(),
|
|
324
|
-
rcpt: z11.uuidv4().or(z11.uuidv7()).nullable(),
|
|
325
|
-
src: z11.object({
|
|
326
|
-
_id: z11.uuidv4().or(z11.uuidv7()),
|
|
327
|
-
name: z11.string(),
|
|
328
|
-
photoUrl: z11.string().nullish()
|
|
329
|
-
}),
|
|
330
|
-
createdAt: Timestamp
|
|
331
|
-
}).superRefine((data, ctx) => {
|
|
332
|
-
const expectMessage = () => {
|
|
333
|
-
if ((data.payload?.length ?? 0) < 3) {
|
|
334
|
-
ctx.addIssue("The payload must have at least (3) characters.");
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
switch (data.code) {
|
|
338
|
-
case "COMPANY_INVITE":
|
|
339
|
-
if (!z11.jwt().safeParse(data.payload).success) {
|
|
340
|
-
ctx.addIssue("The payload of company invite must be a valid jwt.");
|
|
341
|
-
}
|
|
342
|
-
break;
|
|
343
|
-
case "DEV_MESSAGE":
|
|
344
|
-
expectMessage();
|
|
345
|
-
break;
|
|
346
|
-
case "ALERT":
|
|
347
|
-
expectMessage();
|
|
348
|
-
break;
|
|
349
|
-
case "INVITE_RESPONSE":
|
|
350
|
-
if (!InviteResponse.safeParse(data.payload).success) {
|
|
351
|
-
ctx.addIssue(
|
|
352
|
-
`An invite response must be one of the following: ${InviteResponse.options.join()}.`
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
break;
|
|
356
|
-
default:
|
|
357
|
-
return data.code;
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
var CreditCurrency = z11.object({
|
|
361
|
-
uid: z11.uuidv4(),
|
|
362
|
-
cost: z11.object({ value: z11.number().min(0), currency: acceptedCurrencies }),
|
|
363
|
-
value: z11.int().min(0),
|
|
364
|
-
...TimeLog.shape
|
|
365
|
-
});
|
|
366
|
-
var PaymentDetails = z11.object({
|
|
367
|
-
// Used to track the payments recorded by each paymentCollector
|
|
368
|
-
uid: z11.uuidv4(),
|
|
369
|
-
method: paymentMethods,
|
|
370
|
-
currency: acceptedCurrencies,
|
|
371
|
-
amount: z11.number().min(1),
|
|
372
|
-
// Rate chart id (used to identify the rate chart used to convert the amount )
|
|
373
|
-
// rcid: z.uuidv4(),
|
|
374
|
-
...TimeLog.shape
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// src/schemas/company/components/Notifications.ts
|
|
378
|
-
var CompanyNotifications = z12.array(Notification).refine(
|
|
379
|
-
(data) => data.every((notif) => notif.code !== "COMPANY_INVITE"),
|
|
380
|
-
"A company can't receive a company invite."
|
|
381
|
-
);
|
|
382
|
-
|
|
383
253
|
// src/schemas/company/components/Preferences.ts
|
|
384
|
-
import * as
|
|
385
|
-
var CompanyPreferences =
|
|
254
|
+
import * as z10 from "zod";
|
|
255
|
+
var CompanyPreferences = z10.object({
|
|
386
256
|
_id: CompanyState.shape._id,
|
|
387
257
|
/** The user that's allowed to record the client's services. */
|
|
388
258
|
serviceSelector: companyServiceSelectors,
|
|
389
|
-
/**
|
|
390
|
-
|
|
391
|
-
* @note
|
|
392
|
-
* - only allowed when `serviceSelector === "scheduler"`
|
|
393
|
-
* */
|
|
394
|
-
enforcePaidAppointments: z13.boolean(),
|
|
259
|
+
/** Pay per task */
|
|
260
|
+
ppt: z10.boolean(),
|
|
395
261
|
/**
|
|
396
262
|
* @property RR (Round Robin): Even distribution.
|
|
397
263
|
* @property LOR (Least Outstanding Requests): Distribute based on availability.
|
|
398
264
|
*/
|
|
399
|
-
|
|
400
|
-
servicesDeployed:
|
|
401
|
-
|
|
265
|
+
taskDistAlg: taskDistAlgs,
|
|
266
|
+
servicesDeployed: z10.boolean(),
|
|
267
|
+
...TimeLog.shape
|
|
402
268
|
}).refine(
|
|
403
|
-
(data) => !data.
|
|
404
|
-
|
|
269
|
+
(data) => !data.ppt || data.ppt && data.serviceSelector === "scheduler",
|
|
270
|
+
`Pay per task is only allowed when the service selector is the "${"scheduler"}".`
|
|
405
271
|
);
|
|
406
272
|
var MutableCompanyPreferences = CompanyPreferences.omit({
|
|
407
|
-
|
|
273
|
+
_id: true,
|
|
408
274
|
lastModified: true
|
|
409
275
|
});
|
|
410
276
|
|
|
411
277
|
// src/schemas/company/components/Staff.ts
|
|
412
|
-
import
|
|
413
|
-
|
|
414
|
-
// src/schemas/company/components/User.ts
|
|
415
|
-
import * as z16 from "zod";
|
|
278
|
+
import { distinct } from "@wavy/fn";
|
|
279
|
+
import * as z14 from "zod";
|
|
416
280
|
|
|
417
281
|
// src/functions/helper-functions.ts
|
|
418
282
|
import { camelCaseToLetter, upperFirst } from "@wavy/fn";
|
|
419
283
|
|
|
420
284
|
// src/schemas/profiles/Client.ts
|
|
421
|
-
import * as
|
|
285
|
+
import * as z12 from "zod";
|
|
422
286
|
|
|
423
287
|
// src/schemas/profiles/User.ts
|
|
424
288
|
import { UserModel } from "@wavy/util";
|
|
425
|
-
import * as
|
|
426
|
-
|
|
289
|
+
import * as z11 from "zod";
|
|
290
|
+
|
|
291
|
+
// src/constants.ts
|
|
292
|
+
var NOTIF_ID_DELIM = "::";
|
|
293
|
+
var MAX_NAME_LENGTH = 21;
|
|
294
|
+
|
|
295
|
+
// src/schemas/profiles/User.ts
|
|
296
|
+
var UserDetails = z11.object({
|
|
427
297
|
...UserModel.shape,
|
|
428
|
-
_id:
|
|
429
|
-
name:
|
|
298
|
+
_id: z11.uuidv4(),
|
|
299
|
+
name: z11.string().transform((data) => {
|
|
430
300
|
const [firstName = "", lastName = ""] = data.split(" ");
|
|
431
301
|
return (firstName + " " + lastName).trim();
|
|
432
302
|
}).superRefine((data, ctx) => {
|
|
@@ -443,58 +313,59 @@ var UserDetails = z14.object({
|
|
|
443
313
|
});
|
|
444
314
|
}),
|
|
445
315
|
email: UserModel.shape.email,
|
|
446
|
-
publicKey:
|
|
316
|
+
publicKey: z11.string().optional(),
|
|
317
|
+
availableStorageInBytes: z11.number().optional(),
|
|
447
318
|
// notifications: z.array(Notification),
|
|
448
|
-
lastSignedIn:
|
|
319
|
+
lastSignedIn: z11.object({
|
|
449
320
|
auto: Timestamp,
|
|
450
321
|
manual: Timestamp
|
|
451
322
|
}).partial(),
|
|
452
|
-
companyUids:
|
|
323
|
+
companyUids: z11.array(CompanyState.shape._id).min(1).optional(),
|
|
453
324
|
// resumeToken: z.uuidv4().optional(),
|
|
454
325
|
...TimeLog.shape
|
|
455
326
|
}).omit({ uid: true });
|
|
456
327
|
|
|
457
328
|
// src/schemas/profiles/Client.ts
|
|
458
329
|
import { Address as Address2, PhoneNumber as PhoneNumber2 } from "@wavy/util";
|
|
459
|
-
var Name =
|
|
330
|
+
var Name = z12.string().trim().min(2).max(MAX_NAME_LENGTH).transform((d) => d.replace(/-+/g, "-").replace(/'+/g, "'")).superRefine((data, ctx) => {
|
|
460
331
|
const invalidCharIdx = data.search(/[^a-z'-]/i);
|
|
461
332
|
if (invalidCharIdx >= 0) {
|
|
462
333
|
const char = data[invalidCharIdx].includes('"') ? `(${data[invalidCharIdx]})` : `"${data[invalidCharIdx]}"`;
|
|
463
334
|
ctx.addIssue(`${char} is not an allowed character.`);
|
|
464
335
|
}
|
|
465
336
|
});
|
|
466
|
-
var
|
|
337
|
+
var ClientDetails = UserDetails.pick({
|
|
467
338
|
_id: true,
|
|
468
339
|
createdAt: true,
|
|
469
340
|
lastModified: true,
|
|
470
341
|
phoneNumber: true,
|
|
471
342
|
photoUrl: true
|
|
472
343
|
}).safeExtend(
|
|
473
|
-
|
|
344
|
+
z12.object({
|
|
474
345
|
firstName: Name,
|
|
475
346
|
middleName: Name,
|
|
476
347
|
lastName: Name,
|
|
477
348
|
email: UserDetails.shape.email.optional(),
|
|
478
349
|
dob: Timestamp,
|
|
479
350
|
sex: genders,
|
|
480
|
-
isGlobal:
|
|
351
|
+
isGlobal: z12.boolean(),
|
|
481
352
|
origin: CompanyIdentity.shape._id,
|
|
482
|
-
visits:
|
|
353
|
+
visits: z12.record(CompanyIdentity.shape._id, z12.array(Timestamp).min(1)).optional(),
|
|
483
354
|
phoneNumber: PhoneNumber2.optional(),
|
|
484
355
|
// aka. National id
|
|
485
|
-
nid:
|
|
356
|
+
nid: z12.object({
|
|
486
357
|
trn: Trn,
|
|
487
358
|
nin: AlphaNumeric.toLowerCase().min(5).max(20),
|
|
488
359
|
passportNo: AlphaNumeric.toLowerCase().min(5).max(15)
|
|
489
360
|
}).partial().optional(),
|
|
490
361
|
// Don't hash the attributes, they can be used to help accurately identify a client by whoever is searching for
|
|
491
362
|
// the client
|
|
492
|
-
attributes:
|
|
363
|
+
attributes: z12.object({
|
|
493
364
|
motherMaidenName: Name,
|
|
494
365
|
fatherMiddleName: Name,
|
|
495
366
|
motherMiddleName: Name,
|
|
496
367
|
oldestSiblingMiddleName: Name,
|
|
497
|
-
firstSchoolName:
|
|
368
|
+
firstSchoolName: z12.string().trim().min(3).max(60)
|
|
498
369
|
}).partial().refine(
|
|
499
370
|
(data) => Object.keys(data).length > 0,
|
|
500
371
|
"At least (1) attribute must be defined."
|
|
@@ -503,7 +374,7 @@ var ClientIdentity = UserDetails.pick({
|
|
|
503
374
|
linkedTo: UserDetails.shape._id.optional()
|
|
504
375
|
}).shape
|
|
505
376
|
);
|
|
506
|
-
var ClientForm =
|
|
377
|
+
var ClientForm = ClientDetails.pick({
|
|
507
378
|
firstName: true,
|
|
508
379
|
middleName: true,
|
|
509
380
|
lastName: true,
|
|
@@ -512,24 +383,6 @@ var ClientForm = ClientIdentity.pick({
|
|
|
512
383
|
}).required();
|
|
513
384
|
|
|
514
385
|
// src/functions/helper-functions.ts
|
|
515
|
-
var findConflictingPartners = (...partners) => {
|
|
516
|
-
let hasSingleDoctor = false;
|
|
517
|
-
const conflicts = [];
|
|
518
|
-
if (!partners) return partners;
|
|
519
|
-
for (const partner of partners) {
|
|
520
|
-
if (!partner.roles || partner.roles.some((r) => !(r in COMPANY_USER_RELATIONSHIP))) {
|
|
521
|
-
conflicts.push(partner);
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
if (partner.roles.includes("doc") && !hasSingleDoctor) {
|
|
525
|
-
hasSingleDoctor = true;
|
|
526
|
-
} else if (partner.roles.includes("doc")) {
|
|
527
|
-
conflicts.push(partner);
|
|
528
|
-
continue;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
return conflicts.length === 0 ? null : conflicts;
|
|
532
|
-
};
|
|
533
386
|
var getCompatibleRoles = (role) => {
|
|
534
387
|
return companyUserRoles.options.filter(
|
|
535
388
|
(r) => !COMPANY_USER_RELATIONSHIP[role]?.includes(r)
|
|
@@ -545,7 +398,7 @@ var normalizeCompanyId = (id) => {
|
|
|
545
398
|
return id.split("-").map((value) => upperFirst(value.toLowerCase())).join(" ");
|
|
546
399
|
};
|
|
547
400
|
var normalizeNidKey = (value) => {
|
|
548
|
-
value =
|
|
401
|
+
value = ClientDetails.shape.nid.unwrap().keyof().parse(value);
|
|
549
402
|
if (value === "nin") return "National Id No.";
|
|
550
403
|
if (value === "trn") return "Tax Registration No.";
|
|
551
404
|
return camelCaseToLetter(value.replace("No", "No."));
|
|
@@ -556,163 +409,103 @@ var parseClientName = ({
|
|
|
556
409
|
lastName
|
|
557
410
|
}) => [firstName, middleName[0] + ".", lastName].join(" ");
|
|
558
411
|
|
|
559
|
-
// src/schemas/company/
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
_id: z16.uuidv4(),
|
|
566
|
-
user: UserDetails.pick({ _id: true, name: true, email: true }),
|
|
412
|
+
// src/schemas/company/utils/CompanySession.ts
|
|
413
|
+
import * as z13 from "zod";
|
|
414
|
+
var CompanySession = z13.object({
|
|
415
|
+
_id: z13.uuidv4(),
|
|
416
|
+
userId: UserDetails.shape._id,
|
|
417
|
+
// user: UserDetails.pick({ _id: true, name: true, email: true }),
|
|
567
418
|
companyId: CompanyState.shape._id,
|
|
568
|
-
deviceId:
|
|
569
|
-
isAvailable:
|
|
570
|
-
value:
|
|
419
|
+
deviceId: z13.string().trim().min(15),
|
|
420
|
+
isAvailable: z13.object({
|
|
421
|
+
value: z13.boolean(),
|
|
571
422
|
lastModified: Timestamp.optional()
|
|
572
423
|
}),
|
|
573
|
-
isBusy:
|
|
574
|
-
value:
|
|
424
|
+
isBusy: z13.object({
|
|
425
|
+
value: z13.boolean(),
|
|
575
426
|
lastModified: Timestamp.optional()
|
|
576
427
|
}),
|
|
577
|
-
|
|
578
|
-
roles:
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
return newRoles.sort();
|
|
584
|
-
}).refine((roles) => {
|
|
585
|
-
return !roles.find(
|
|
586
|
-
(role) => getIncompatibleRoles(role).some((r) => roles.includes(r))
|
|
587
|
-
);
|
|
588
|
-
}, "A user is not allowed to have conflicting roles."),
|
|
589
|
-
ttl: z16.number(),
|
|
590
|
-
lastModified: Timestamp.optional(),
|
|
591
|
-
expiresAfter: z16.date()
|
|
592
|
-
});
|
|
593
|
-
var MiniCompanyUser2 = z16.object({
|
|
594
|
-
_id: UserDetails.shape._id,
|
|
595
|
-
...CompanyUserSession.pick({
|
|
596
|
-
roles: true,
|
|
597
|
-
/** Attached to help determine if a user is allowed to create a new session */
|
|
598
|
-
isBusy: true,
|
|
599
|
-
lastModified: true,
|
|
600
|
-
appointments: true
|
|
601
|
-
}).shape,
|
|
602
|
-
/** The time before the company session gets deleted */
|
|
603
|
-
lastActive: Timestamp.optional(),
|
|
604
|
-
inviteSent: Timestamp.optional(),
|
|
605
|
-
inviteAccepted: Timestamp.optional()
|
|
606
|
-
});
|
|
607
|
-
var CompanyUser2 = z16.object({
|
|
608
|
-
...MiniCompanyUser2.shape,
|
|
609
|
-
...CompanyUserSession.shape.user.shape,
|
|
610
|
-
// Only true when the user's data is retrieved from the session pool
|
|
611
|
-
// NB: This property is purely cosmetic (so no need to store this on the server)
|
|
612
|
-
isActive: z16.boolean()
|
|
428
|
+
taskCounter: z13.record(z13.literal(["undone", "done"]), z13.int().min(0)),
|
|
429
|
+
roles: CompanyMemberRoles,
|
|
430
|
+
ttl: z13.number(),
|
|
431
|
+
expiresAfter: z13.date(),
|
|
432
|
+
...TimeLog.shape
|
|
613
433
|
});
|
|
614
434
|
|
|
615
435
|
// src/schemas/company/components/Staff.ts
|
|
616
|
-
var
|
|
436
|
+
var CompanyMemberRoles = z14.array(companyUserRoles).transform((roles) => distinct(roles).sort()).refine((roles) => {
|
|
437
|
+
return !roles.find(
|
|
438
|
+
(role) => getIncompatibleRoles(role).some((r) => roles.includes(r))
|
|
439
|
+
);
|
|
440
|
+
}, "A user is not allowed to have conflicting roles.");
|
|
441
|
+
var CompanyStaff2 = z14.object({
|
|
617
442
|
_id: CompanyState.shape._id,
|
|
618
|
-
members:
|
|
619
|
-
|
|
620
|
-
|
|
443
|
+
members: z14.record(
|
|
444
|
+
UserDetails.shape._id,
|
|
445
|
+
z14.object({
|
|
446
|
+
invitedAt: Timestamp.optional(),
|
|
447
|
+
joinedAt: Timestamp,
|
|
448
|
+
lastActive: Timestamp,
|
|
449
|
+
roles: CompanyMemberRoles,
|
|
450
|
+
dailyTaskCounter: CompanySession.shape.taskCounter.optional()
|
|
451
|
+
})
|
|
621
452
|
),
|
|
622
|
-
// .superRefine(refineMembers),
|
|
623
453
|
/**
|
|
624
454
|
* @relationship one -> many
|
|
625
455
|
*@description A map of doctor `uids` to their assistants `uids` */
|
|
626
|
-
partnerMap:
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
),
|
|
630
|
-
updateQueue:
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
$REMOVE:
|
|
634
|
-
$CHANGE_ROLES:
|
|
635
|
-
newRoles:
|
|
456
|
+
partnerMap: z14.record(
|
|
457
|
+
UserDetails.shape._id,
|
|
458
|
+
z14.record(UserDetails.shape._id, z14.object({ addedAt: Timestamp }))
|
|
459
|
+
).optional(),
|
|
460
|
+
updateQueue: z14.record(
|
|
461
|
+
UserDetails.shape._id,
|
|
462
|
+
z14.object({
|
|
463
|
+
$REMOVE: z14.object({ addedAt: Timestamp }),
|
|
464
|
+
$CHANGE_ROLES: z14.object({
|
|
465
|
+
newRoles: CompanyMemberRoles,
|
|
636
466
|
addedAt: Timestamp
|
|
637
467
|
})
|
|
638
468
|
}).partial()
|
|
639
|
-
),
|
|
640
|
-
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
for (const uid of Object.keys(copy.updateQueue)) {
|
|
650
|
-
if (uid in copy.members) {
|
|
651
|
-
const userUpdates = copy.updateQueue[uid];
|
|
652
|
-
if ("$REMOVE" in userUpdates) {
|
|
653
|
-
copy.updateQueue[uid] = { $REMOVE: userUpdates.$REMOVE };
|
|
654
|
-
} else if (userUpdates.$CHANGE_ROLES && userUpdates.$CHANGE_ROLES.newRoles.length === copy.members[uid].roles.length && userUpdates.$CHANGE_ROLES.newRoles.every(
|
|
655
|
-
(role) => copy.members[uid].roles.includes(role)
|
|
656
|
-
)) {
|
|
657
|
-
delete copy.updateQueue[uid].$CHANGE_ROLES;
|
|
658
|
-
}
|
|
659
|
-
if (Object.keys(userUpdates).length < 1) delete copy.updateQueue[uid];
|
|
660
|
-
} else {
|
|
661
|
-
delete copy.updateQueue[uid];
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
for (const [docUid, assts] of Object.entries(copy.partnerMap)) {
|
|
665
|
-
if (docUid in d.members) {
|
|
666
|
-
for (const asstUid of Object.keys(assts)) {
|
|
667
|
-
if (asstUid in d.members) continue;
|
|
668
|
-
delete copy.partnerMap[docUid][asstUid];
|
|
469
|
+
).optional(),
|
|
470
|
+
...TimeLog.shape
|
|
471
|
+
}).superRefine((details, ctx) => {
|
|
472
|
+
for (const [_id, user] of Object.entries(details.members)) {
|
|
473
|
+
if (user.roles.includes("doc") && details.partnerMap && _id in details.partnerMap) {
|
|
474
|
+
for (const asstUid of Object.keys(details.partnerMap[_id])) {
|
|
475
|
+
if (!details.members[asstUid].roles.includes("physAsst")) {
|
|
476
|
+
ctx.addIssue(
|
|
477
|
+
`A partner group member must have the "physAsst" role (${_id}) -> (${asstUid}).`
|
|
478
|
+
);
|
|
669
479
|
}
|
|
670
|
-
} else {
|
|
671
|
-
delete copy.partnerMap[docUid];
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
return copy;
|
|
675
|
-
}).superRefine((details, ctx) => {
|
|
676
|
-
for (const [_id, user] of Object.entries(details.members)) {
|
|
677
|
-
if (user.roles.includes("doc") && _id in details.partnerMap) {
|
|
678
|
-
for (const asstUid of Object.keys(details.partnerMap[_id])) {
|
|
679
|
-
if (!details.members[asstUid].roles.includes("physAsst")) {
|
|
680
|
-
ctx.addIssue(
|
|
681
|
-
`A partner group member must have the "physAsst" role (${_id}) -> (${asstUid}).`
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
} else if (_id in details.partnerMap) {
|
|
686
|
-
ctx.addIssue(
|
|
687
|
-
`A partner group leader must have the "doc" role. (${_id})`
|
|
688
|
-
);
|
|
689
|
-
} else if (user.roles.includes("doc")) {
|
|
690
|
-
ctx.addIssue(
|
|
691
|
-
`All users that have the "doc" role must be the leader of a partner group. (${_id})`
|
|
692
|
-
);
|
|
693
480
|
}
|
|
481
|
+
} else if (details.partnerMap && _id in details.partnerMap) {
|
|
482
|
+
ctx.addIssue(
|
|
483
|
+
`A partner group leader must have the "doc" role. (${_id})`
|
|
484
|
+
);
|
|
485
|
+
} else if (user.roles.includes("doc")) {
|
|
486
|
+
ctx.addIssue(
|
|
487
|
+
`All users that have the "doc" role must be the leader of a partner group. (${_id})`
|
|
488
|
+
);
|
|
694
489
|
}
|
|
695
|
-
}
|
|
696
|
-
};
|
|
697
|
-
var
|
|
698
|
-
var MiniCompanyStaff = transformStaff(MiniRawCompanyStaff);
|
|
699
|
-
var MutableCompanyStaff = RawCompanyStaff.pick({
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
var MutableCompanyStaff = CompanyStaff2.pick({
|
|
700
493
|
updateQueue: true,
|
|
701
494
|
partnerMap: true
|
|
702
495
|
});
|
|
703
496
|
|
|
704
497
|
// src/schemas/company/CompanyDetails.ts
|
|
705
|
-
var CompanyDetails =
|
|
498
|
+
var CompanyDetails = z15.object({
|
|
706
499
|
_id: CompanyState.shape._id,
|
|
707
500
|
identity: CompanyIdentity.omit({ _id: true }),
|
|
708
|
-
notifications: CompanyNotifications,
|
|
709
|
-
receipts: Receipts,
|
|
501
|
+
// notifications: CompanyNotifications,
|
|
502
|
+
// receipts: Receipts,
|
|
710
503
|
state: CompanyState.omit({ _id: true }),
|
|
711
504
|
preferences: CompanyPreferences.omit({ _id: true }),
|
|
712
505
|
billing: CompanyBilling.omit({ _id: true }),
|
|
713
|
-
staff:
|
|
506
|
+
staff: CompanyStaff2.omit({ _id: true })
|
|
714
507
|
});
|
|
715
|
-
var MutableCompanyDetails =
|
|
508
|
+
var MutableCompanyDetails = z15.object({
|
|
716
509
|
_id: CompanyDetails.shape._id,
|
|
717
510
|
identity: MutableCompanyIdentity.partial().optional(),
|
|
718
511
|
preferences: MutableCompanyPreferences.partial().optional(),
|
|
@@ -720,108 +513,193 @@ var MutableCompanyDetails = z18.object({
|
|
|
720
513
|
staff: MutableCompanyStaff.partial().optional()
|
|
721
514
|
});
|
|
722
515
|
|
|
516
|
+
// src/schemas/company/components/Notifications.ts
|
|
517
|
+
import * as z17 from "zod";
|
|
518
|
+
|
|
519
|
+
// src/schemas/Global.ts
|
|
520
|
+
import * as z16 from "zod";
|
|
521
|
+
var TierList = z16.record(
|
|
522
|
+
tiers,
|
|
523
|
+
z16.object({
|
|
524
|
+
cost: z16.number().min(0),
|
|
525
|
+
perks: z16.array(z16.string()).min(1)
|
|
526
|
+
})
|
|
527
|
+
).superRefine((data, ctx) => {
|
|
528
|
+
if (data.basic.cost !== 0) {
|
|
529
|
+
ctx.addIssue("The basic tier must always cost $0.00");
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
var Notification = z16.object({
|
|
533
|
+
_id: z16.string().superRefine((data, ctx) => {
|
|
534
|
+
const isUUidV4 = z16.uuidv4().safeParse(data).success;
|
|
535
|
+
if (isUUidV4) return;
|
|
536
|
+
let [from, to, nonce] = data.split(NOTIF_ID_DELIM);
|
|
537
|
+
nonce = nonce?.trim() || "";
|
|
538
|
+
const parsedFrom = z16.uuidv4().or(z16.uuidv7()).safeParse(from);
|
|
539
|
+
if (!parsedFrom.success) {
|
|
540
|
+
ctx.addIssue({
|
|
541
|
+
code: "custom",
|
|
542
|
+
message: "Invalid UUID",
|
|
543
|
+
path: ["from"]
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
const parsedTo = z16.uuidv4().safeParse(to);
|
|
547
|
+
if (!parsedTo.success) {
|
|
548
|
+
ctx.addIssue({
|
|
549
|
+
code: "custom",
|
|
550
|
+
message: "Invalid UUID",
|
|
551
|
+
path: ["to"]
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
if (nonce.length < 4 || nonce.length > 7) {
|
|
555
|
+
ctx.addIssue({
|
|
556
|
+
code: "custom",
|
|
557
|
+
message: "Invalid length",
|
|
558
|
+
path: ["nonce"]
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}),
|
|
562
|
+
code: z16.literal([
|
|
563
|
+
"INVITE_RESPONSE",
|
|
564
|
+
"COMPANY_INVITE",
|
|
565
|
+
"DEV_MESSAGE",
|
|
566
|
+
"ALERT"
|
|
567
|
+
]),
|
|
568
|
+
payload: z16.string(),
|
|
569
|
+
isArchived: z16.boolean().optional(),
|
|
570
|
+
rcpt: z16.uuidv4().or(z16.uuidv7()).nullable(),
|
|
571
|
+
src: z16.object({
|
|
572
|
+
_id: z16.uuidv4().or(z16.uuidv7()),
|
|
573
|
+
name: z16.string(),
|
|
574
|
+
photoUrl: z16.string().nullish()
|
|
575
|
+
}),
|
|
576
|
+
createdAt: Timestamp
|
|
577
|
+
}).superRefine((data, ctx) => {
|
|
578
|
+
const expectMessage = () => {
|
|
579
|
+
if ((data.payload?.length ?? 0) < 3) {
|
|
580
|
+
ctx.addIssue("The payload must have at least (3) characters.");
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
switch (data.code) {
|
|
584
|
+
case "COMPANY_INVITE":
|
|
585
|
+
if (!z16.jwt().safeParse(data.payload).success) {
|
|
586
|
+
ctx.addIssue("The payload of company invite must be a valid jwt.");
|
|
587
|
+
}
|
|
588
|
+
break;
|
|
589
|
+
case "DEV_MESSAGE":
|
|
590
|
+
expectMessage();
|
|
591
|
+
break;
|
|
592
|
+
case "ALERT":
|
|
593
|
+
expectMessage();
|
|
594
|
+
break;
|
|
595
|
+
case "INVITE_RESPONSE":
|
|
596
|
+
if (!InviteResponse.safeParse(data.payload).success) {
|
|
597
|
+
ctx.addIssue(
|
|
598
|
+
`An invite response must be one of the following: ${InviteResponse.options.join()}.`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
break;
|
|
602
|
+
default:
|
|
603
|
+
return data.code;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
var CreditCurrency = z16.object({
|
|
607
|
+
uid: z16.uuidv4(),
|
|
608
|
+
cost: z16.object({ value: z16.number().min(0), currency: acceptedCurrencies }),
|
|
609
|
+
value: z16.int().min(0),
|
|
610
|
+
...TimeLog.shape
|
|
611
|
+
});
|
|
612
|
+
var PaymentDetails = z16.object({
|
|
613
|
+
// Used to track the payments recorded by each paymentCollector
|
|
614
|
+
uid: z16.uuidv4(),
|
|
615
|
+
method: paymentMethods,
|
|
616
|
+
currency: acceptedCurrencies,
|
|
617
|
+
amount: z16.number().min(1),
|
|
618
|
+
// Rate chart id (used to identify the rate chart used to convert the amount )
|
|
619
|
+
// rcid: z.uuidv4(),
|
|
620
|
+
...TimeLog.shape
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// src/schemas/company/components/Notifications.ts
|
|
624
|
+
var CompanyNotifications = z17.array(Notification).refine(
|
|
625
|
+
(data) => data.every((notif) => notif.code !== "COMPANY_INVITE"),
|
|
626
|
+
"A company can't receive a company invite."
|
|
627
|
+
);
|
|
628
|
+
|
|
723
629
|
// src/schemas/company/utils/CreateCompanyForm.ts
|
|
724
|
-
import * as
|
|
725
|
-
var CreateCompanyForm =
|
|
726
|
-
_id: CompanyIdentity.shape._id,
|
|
630
|
+
import * as z18 from "zod";
|
|
631
|
+
var CreateCompanyForm = z18.object({
|
|
727
632
|
alias: CompanyIdentity.shape.alias.optional(),
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
633
|
+
...CompanyIdentity.pick({
|
|
634
|
+
_id: true,
|
|
635
|
+
logo: true,
|
|
636
|
+
address: true,
|
|
637
|
+
email: true,
|
|
638
|
+
phoneNumber: true,
|
|
639
|
+
industry: true,
|
|
640
|
+
sector: true
|
|
641
|
+
}).shape
|
|
733
642
|
}).superRefine((data, ctx) => {
|
|
734
643
|
if (!CompanyIndustry.shape[data.industry].safeParse(data.sector).success) {
|
|
735
644
|
ctx.addIssue(getInvalidSectorError(data.industry, data.sector));
|
|
736
645
|
}
|
|
737
646
|
});
|
|
738
647
|
|
|
739
|
-
// src/schemas/
|
|
740
|
-
import * as
|
|
741
|
-
var Vitals =
|
|
648
|
+
// src/schemas/task/components/Medical.ts
|
|
649
|
+
import * as z19 from "zod";
|
|
650
|
+
var Vitals = z19.record(
|
|
742
651
|
vitalKeys,
|
|
743
|
-
|
|
744
|
-
value:
|
|
652
|
+
z19.object({
|
|
653
|
+
value: z19.string().trim().regex(/^\d{0,3}((\/|\.)\d{1,3})?/),
|
|
745
654
|
lastModified: Timestamp.nullable()
|
|
746
655
|
})
|
|
747
656
|
);
|
|
748
|
-
var Medicine =
|
|
749
|
-
brand:
|
|
657
|
+
var Medicine = z19.object({
|
|
658
|
+
brand: z19.string().trim(),
|
|
750
659
|
expiresAt: Timestamp.nullish(),
|
|
751
|
-
quantity:
|
|
660
|
+
quantity: z19.string().trim().regex(/^\d+(\.\d{1,3})? ?[a-zA-Z]{1,20}/, {
|
|
752
661
|
error: "Failed to match the pattern <number>+(.<number>{1,3})? ?[a-zA-Z]{1,20}"
|
|
753
662
|
}),
|
|
754
|
-
refills:
|
|
755
|
-
directions:
|
|
663
|
+
refills: z19.string().trim().regex(/^[0-8]/, { error: "Must be between 0 and 8" }),
|
|
664
|
+
directions: z19.string().trim().max(100, { error: "Must be 100 characters or less" }),
|
|
756
665
|
...TimeLog.shape
|
|
757
666
|
});
|
|
758
|
-
var MedicalDetails =
|
|
667
|
+
var MedicalDetails = z19.object({
|
|
759
668
|
vitals: Vitals,
|
|
760
|
-
prescriptions:
|
|
761
|
-
doctorNote:
|
|
762
|
-
value:
|
|
669
|
+
prescriptions: z19.array(z19.array(Medicine)).nullish(),
|
|
670
|
+
doctorNote: z19.object({
|
|
671
|
+
value: z19.string(),
|
|
763
672
|
lastModified: Timestamp.nullable()
|
|
764
673
|
}),
|
|
765
|
-
physAsstNotes:
|
|
766
|
-
|
|
767
|
-
title:
|
|
768
|
-
content:
|
|
674
|
+
physAsstNotes: z19.array(
|
|
675
|
+
z19.object({
|
|
676
|
+
title: z19.string(),
|
|
677
|
+
content: z19.string(),
|
|
769
678
|
...TimeLog.shape
|
|
770
679
|
})
|
|
771
680
|
).optional()
|
|
772
681
|
});
|
|
773
682
|
|
|
774
|
-
// src/schemas/
|
|
775
|
-
import
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
function calcBalance(components, options) {
|
|
781
|
-
const subtotal = sumOf(
|
|
782
|
-
Object.values(components.charges || {})?.map((c) => c.cost)
|
|
783
|
-
);
|
|
784
|
-
const discount = calcPriceMod(components.discounts, subtotal);
|
|
785
|
-
const additionalFees = calcPriceMod(components.additionalFees, subtotal);
|
|
786
|
-
const prepayments = calcPriceMod(components.prepayments, subtotal);
|
|
787
|
-
const payments = sumOf(components.payments?.map((p) => p.amount) ?? []);
|
|
788
|
-
const result = subtotal + additionalFees - (discount + prepayments + payments);
|
|
789
|
-
if (options?.logger) {
|
|
790
|
-
console.log({ subtotal, discount, additionalFees, prepayments, result });
|
|
791
|
-
}
|
|
792
|
-
return result;
|
|
793
|
-
}
|
|
794
|
-
var calcPriceMod = (mods, subtotal) => {
|
|
795
|
-
const castedAdjustments = !mods ? [] : Object.values(mods);
|
|
796
|
-
if (!mods || castedAdjustments.length < 1) return 0;
|
|
797
|
-
return sumOf(
|
|
798
|
-
castedAdjustments?.map(
|
|
799
|
-
(d) => (d.fixedAmount?.value || 0) + (d.percentage ? d.percentage * 0.01 * subtotal : 0)
|
|
800
|
-
)
|
|
801
|
-
);
|
|
802
|
-
};
|
|
803
|
-
|
|
804
|
-
// src/schemas/appointment/Appointment.ts
|
|
805
|
-
var TimelineActivity = z21.object({
|
|
806
|
-
postedAt: Timestamp.nullish(),
|
|
807
|
-
userUid: CompanyUser2.shape._id.nonoptional()
|
|
808
|
-
});
|
|
809
|
-
var AppointmentSeverity = z21.object({
|
|
810
|
-
"1": z21.literal("Emergency"),
|
|
811
|
-
"2": z21.literal("Important"),
|
|
812
|
-
"3": z21.literal("Average")
|
|
683
|
+
// src/schemas/task/Task.ts
|
|
684
|
+
import * as z20 from "zod";
|
|
685
|
+
var TaskSeverity = z20.object({
|
|
686
|
+
"1": z20.literal("Emergency"),
|
|
687
|
+
"2": z20.literal("Important"),
|
|
688
|
+
"3": z20.literal("Average")
|
|
813
689
|
});
|
|
814
|
-
var
|
|
690
|
+
var TaskDetails = z20.object({
|
|
815
691
|
/** A random uid that identifies the document. */
|
|
816
|
-
_id:
|
|
692
|
+
_id: z20.uuidv4(),
|
|
693
|
+
/** The amount of credits used to create the appointment */
|
|
694
|
+
_cost: z20.number().readonly(),
|
|
817
695
|
// This helps to identify the urgency of the appointment
|
|
818
|
-
severity:
|
|
696
|
+
severity: z20.string().refine((data) => TaskSeverity.keyof().safeParse(data).success),
|
|
819
697
|
/** The company's uid */
|
|
820
698
|
src: CompanyState.shape._id,
|
|
821
699
|
/** The ticket number */
|
|
822
700
|
tkt: TicketNo,
|
|
823
701
|
/**The reason for the service */
|
|
824
|
-
reason:
|
|
702
|
+
reason: z20.object({
|
|
825
703
|
value: Reason.shape.value,
|
|
826
704
|
...TimeLog.shape
|
|
827
705
|
}),
|
|
@@ -842,263 +720,105 @@ var MiniAppointmentDetails = z21.object({
|
|
|
842
720
|
/**Required to calculate the accurate grandTotal of the charges */
|
|
843
721
|
discounts: CompanyBilling.shape.discounts.optional(),
|
|
844
722
|
prepayments: CompanyBilling.shape.prepayments.optional(),
|
|
845
|
-
payments:
|
|
723
|
+
payments: z20.array(PaymentDetails).max(100).optional(),
|
|
846
724
|
/** The client's identity */
|
|
847
|
-
|
|
725
|
+
clientId: ClientDetails.shape._id,
|
|
848
726
|
/**
|
|
849
727
|
* Data that is specific to the entity (for now it's just medical data).
|
|
850
728
|
* It will only be defined for the participants that have access to it.
|
|
851
729
|
*/
|
|
852
|
-
payload:
|
|
730
|
+
payload: z20.object({
|
|
853
731
|
...MedicalDetails.shape,
|
|
854
|
-
lastModified:
|
|
732
|
+
lastModified: Timestamp.optional()
|
|
855
733
|
}).optional(),
|
|
856
734
|
/** Only defined when either the service provider or the client cancelled their appointment */
|
|
857
|
-
cancelled:
|
|
735
|
+
cancelled: z20.object({
|
|
858
736
|
/** The time that the confirm cancel button was clicked */
|
|
859
737
|
doneAt: Timestamp,
|
|
860
738
|
reason: Reason,
|
|
861
|
-
doneBy: companyUserRoles.extract(["doc", "physAsst"])
|
|
739
|
+
doneBy: companyUserRoles.extract(["doc", "physAsst", "rcpst"]).or(z20.literal("client"))
|
|
862
740
|
}).optional(),
|
|
863
741
|
//** Add this after consulting with company owners about payment expectations */
|
|
864
742
|
// paymentDueDate: Timestamp.nullish(),
|
|
865
743
|
// The user that is currently modifying the appointment
|
|
866
744
|
lockedBy: UserDetails.shape._id.optional(),
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
745
|
+
/** Role based access control
|
|
746
|
+
* If a role is:
|
|
747
|
+
* [1] Ommitted -> any user with that role that's from the same company can access the TaskDetails.
|
|
748
|
+
* [1] Addded -> only the users that have that role and an id in the respective array can access the TaskDetails.
|
|
749
|
+
*/
|
|
750
|
+
rbac: z20.record(companyUserRoles, z20.array(UserDetails.shape._id).optional()),
|
|
751
|
+
timeline: z20.record(
|
|
752
|
+
UserDetails.shape._id,
|
|
753
|
+
z20.object({
|
|
754
|
+
/** Removed to rely on a single source of truth (audit logs stored in s3)
|
|
755
|
+
* as it relates to a user's roles at any given time.
|
|
756
|
+
*/
|
|
757
|
+
// roles: CompanyMemberRoles,
|
|
758
|
+
postedAt: Timestamp,
|
|
759
|
+
// Use null as an indicator that this is intentionally undefined.
|
|
760
|
+
changes: z20.record(z20.string(), z20.any()).nullable()
|
|
761
|
+
}).optional()
|
|
762
|
+
),
|
|
763
|
+
// z.object({
|
|
764
|
+
// scheduler: TimelineActivity,
|
|
765
|
+
// physAsst: TimelineActivity.or(z.literal("none")),
|
|
766
|
+
// doctor: TimelineActivity,
|
|
767
|
+
// // Array used to accurately record payments made by the "a/r" and the "cshr"
|
|
768
|
+
// debtCollector: z
|
|
769
|
+
// .array(
|
|
770
|
+
// z.object({
|
|
771
|
+
// ...TimelineActivity.shape,
|
|
772
|
+
// // Payment uid -> collection time
|
|
773
|
+
// collectedPayments: z.record(z.uuidv4(), Timestamp),
|
|
774
|
+
// }),
|
|
775
|
+
// )
|
|
776
|
+
// .min(1)
|
|
777
|
+
// .nullable(),
|
|
778
|
+
// } satisfies Record<z.infer<typeof serviceProviders>, any>),
|
|
880
779
|
...TimeLog.shape
|
|
881
|
-
}).superRefine((data, ctx) => {
|
|
882
|
-
if (data.payments && data.charges && sumOf2(data.payments.map((p) => p.amount)) >= calcBalance(data)) {
|
|
883
|
-
const untrackedPayment = data.payments.find(
|
|
884
|
-
(p) => !data.timeline.debtCollector?.some(
|
|
885
|
-
(d) => p.uid in d.collectedPayments
|
|
886
|
-
)
|
|
887
|
-
);
|
|
888
|
-
if (untrackedPayment) {
|
|
889
|
-
ctx.addIssue(`Untracked payment found (${untrackedPayment.uid})`);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
780
|
});
|
|
893
|
-
var
|
|
894
|
-
|
|
895
|
-
}).extend(z21.object({ client: ClientIdentity }).shape);
|
|
896
|
-
var MutableAppointmentDetails = AppointmentDetails.safeExtend(
|
|
897
|
-
z21.object({
|
|
781
|
+
var MutableTaskDetails = TaskDetails.safeExtend(
|
|
782
|
+
z20.object({
|
|
898
783
|
/**
|
|
899
784
|
* @description An encrypted copy of the last state of the appointment.
|
|
900
785
|
* @note Used to accurately update the appointment without having to query the database
|
|
901
786
|
*/
|
|
902
|
-
_hash:
|
|
787
|
+
_hash: z20.string()
|
|
903
788
|
}).shape
|
|
904
789
|
);
|
|
905
|
-
var ScheduleAppointmentForm = z21.object({
|
|
906
|
-
clientId: ClientIdentity.shape._id,
|
|
907
|
-
serviceProviderId: CompanyUser2.shape._id.or(z21.literal("next")),
|
|
908
|
-
category: AppointmentDetails.shape.severity,
|
|
909
|
-
appointmentReason: AppointmentDetails.shape.reason,
|
|
910
|
-
charges: AppointmentDetails.shape.charges.optional(),
|
|
911
|
-
prepayments: AppointmentDetails.shape.prepayments.optional(),
|
|
912
|
-
payments: AppointmentDetails.shape.payments.optional(),
|
|
913
|
-
...TimeLog.shape
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
// src/functions/scheduleAppointment.ts
|
|
917
|
-
import { findChanges, omit, omitNils, TODO } from "@wavy/fn";
|
|
918
|
-
import { v4 } from "uuid";
|
|
919
790
|
|
|
920
|
-
// src/functions/
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
return !(uid in staff.updateQueue) && user.roles.includes(role) && user.isActive;
|
|
926
|
-
}
|
|
791
|
+
// src/functions/calc.ts
|
|
792
|
+
import { sumOf } from "@wavy/fn";
|
|
793
|
+
function calcBalance(components, options) {
|
|
794
|
+
const subtotal = sumOf(
|
|
795
|
+
Object.values(components.charges || {})?.map((c) => c.cost)
|
|
927
796
|
);
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
if (
|
|
934
|
-
|
|
935
|
-
case "RR": {
|
|
936
|
-
nextAvailable = userUids.find((uid) => {
|
|
937
|
-
const calcTotalAppts = (appointments) => appointments.ongoing + appointments.completed;
|
|
938
|
-
return calcTotalAppts(staff.members[uid].appointments) < calcTotalAppts(staff.members[userUids[0]].appointments);
|
|
939
|
-
}) || userUids[0];
|
|
940
|
-
break;
|
|
941
|
-
}
|
|
942
|
-
case "LOR": {
|
|
943
|
-
for (const uid of userUids) {
|
|
944
|
-
const user = staff.members[uid];
|
|
945
|
-
if (!nextAvailable || user.appointments.ongoing < staff.members[nextAvailable].appointments.ongoing) {
|
|
946
|
-
nextAvailable = uid;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
break;
|
|
950
|
-
}
|
|
951
|
-
default:
|
|
952
|
-
return algorithm;
|
|
797
|
+
const discount = calcPriceMod(components.discounts, subtotal);
|
|
798
|
+
const additionalFees = calcPriceMod(components.additionalFees, subtotal);
|
|
799
|
+
const prepayments = calcPriceMod(components.prepayments, subtotal);
|
|
800
|
+
const payments = sumOf(components.payments?.map((p) => p.amount) ?? []);
|
|
801
|
+
const result = subtotal + additionalFees - (discount + prepayments + payments);
|
|
802
|
+
if (options?.logger) {
|
|
803
|
+
console.log({ subtotal, discount, additionalFees, prepayments, result });
|
|
953
804
|
}
|
|
954
|
-
return
|
|
805
|
+
return result;
|
|
955
806
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
request.preferences = CompanyPreferences.omit({
|
|
963
|
-
uid: true,
|
|
964
|
-
lastModified: true
|
|
965
|
-
}).parse(request.preferences);
|
|
966
|
-
request.billing = CompanyBilling.omit({
|
|
967
|
-
uid: true,
|
|
968
|
-
prepayments: true,
|
|
969
|
-
lastModified: true
|
|
970
|
-
}).parse(request.billing);
|
|
971
|
-
const companyUser = request.staff?.members[request.sender._id];
|
|
972
|
-
const staffDetailsCopy = CompanyStaff.parse(structuredClone(request.staff));
|
|
973
|
-
const stateCopy = CompanyState.parse(structuredClone(request.state));
|
|
974
|
-
const castedFormCharges = Object.values(request.form?.charges || {});
|
|
975
|
-
let selectedDoctor = null;
|
|
976
|
-
let selectedPhysAsst = null;
|
|
977
|
-
const availableDoctors = findAvailableStaff("doc", staffDetailsCopy);
|
|
978
|
-
const today = new Date(Date.now()).toISOString();
|
|
979
|
-
if (!companyUser || !companyUser?.roles?.includes?.("rcpst")) {
|
|
980
|
-
throw new Error("Permissions missing.");
|
|
981
|
-
}
|
|
982
|
-
if (availableDoctors.length < 1) {
|
|
983
|
-
throw new Error("Insufficient staff available.");
|
|
984
|
-
}
|
|
985
|
-
if (request.form.serviceProviderId && availableDoctors.includes(request.form.serviceProviderId)) {
|
|
986
|
-
selectedDoctor = request.form.serviceProviderId;
|
|
987
|
-
} else if (request.form.serviceProviderId && staffDetailsCopy.members[request.form.serviceProviderId]?.roles?.includes?.(
|
|
988
|
-
"doc"
|
|
989
|
-
)) {
|
|
990
|
-
throw new Error("Unavailable doctor selected.");
|
|
991
|
-
} else if (request.form.serviceProviderId) {
|
|
992
|
-
throw new Error("Invalid doctor selected.");
|
|
993
|
-
}
|
|
994
|
-
if (request.form.serviceProviderId === null || request.form.serviceProviderId === void 0) {
|
|
995
|
-
selectedDoctor = findNextAvailableStaff(
|
|
996
|
-
availableDoctors,
|
|
997
|
-
request.preferences.apptDistAlg,
|
|
998
|
-
staffDetailsCopy
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
1001
|
-
if (!selectedDoctor) {
|
|
1002
|
-
throw new Error(
|
|
1003
|
-
"Internal error found. Failed to find an available doctor."
|
|
1004
|
-
);
|
|
1005
|
-
} else {
|
|
1006
|
-
selectedPhysAsst = findNextAvailableStaff(
|
|
1007
|
-
Object.keys(staffDetailsCopy.partnerMap[selectedDoctor]),
|
|
1008
|
-
"LOR",
|
|
1009
|
-
staffDetailsCopy
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
if (request.preferences.serviceSelector === "scheduler" && (!request.form.charges || castedFormCharges.length < 1)) {
|
|
1013
|
-
throw new Error("Insufficient services selected.");
|
|
1014
|
-
} else if (request.preferences.serviceSelector === "scheduler" && request.billing.offeredServices.deployed && castedFormCharges.find(
|
|
1015
|
-
(charge) => !!Object.values(request.billing.offeredServices).find(
|
|
1016
|
-
(createdCharge) => findChanges(charge, createdCharge).length > 0
|
|
807
|
+
var calcPriceMod = (mods, subtotal) => {
|
|
808
|
+
const castedAdjustments = !mods ? [] : Object.values(mods);
|
|
809
|
+
if (!mods || castedAdjustments.length < 1) return 0;
|
|
810
|
+
return sumOf(
|
|
811
|
+
castedAdjustments?.map(
|
|
812
|
+
(d) => (d.fixedAmount?.value || 0) + (d.percentage ? d.percentage * 0.01 * subtotal : 0)
|
|
1017
813
|
)
|
|
1018
|
-
)
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1021
|
-
if (request.preferences.enforcePaidAppointments && (!request.form.payments || request.form.payments.length < 1)) {
|
|
1022
|
-
throw new Error("Insufficient payment transactions recorded.");
|
|
1023
|
-
} else if (request.preferences.enforcePaidAppointments) {
|
|
1024
|
-
const maxIterations = Math.max(
|
|
1025
|
-
castedFormCharges.length,
|
|
1026
|
-
request.form.payments.length
|
|
1027
|
-
);
|
|
1028
|
-
let totalPaid = 0;
|
|
1029
|
-
let totalOwed = 0;
|
|
1030
|
-
for (let idx = 0; idx < maxIterations; idx++) {
|
|
1031
|
-
totalPaid += request.form.payments[idx]?.amount || 0;
|
|
1032
|
-
totalOwed += request.form.charges[idx]?.cost || 0;
|
|
1033
|
-
}
|
|
1034
|
-
if (totalPaid < totalOwed) throw new Error("Insufficient payments made.");
|
|
1035
|
-
}
|
|
1036
|
-
if (stateCopy.credits.current < request.cost) {
|
|
1037
|
-
if (companyUser.roles.some((role) => adminRoles.safeParse(role).success)) {
|
|
1038
|
-
throw new Error("Account balance has insufficient funds.");
|
|
1039
|
-
} else {
|
|
1040
|
-
throw new Error(
|
|
1041
|
-
"Something went wrong. Please contact an admin to rectify the problem."
|
|
1042
|
-
);
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
stateCopy.tktNoCounter++;
|
|
1046
|
-
stateCopy.credits.current -= request.cost;
|
|
1047
|
-
stateCopy.credits.lastModified = today;
|
|
1048
|
-
stateCopy.lastModified = today;
|
|
1049
|
-
staffDetailsCopy.members[selectedDoctor].appointments.ongoing++;
|
|
1050
|
-
if (selectedPhysAsst) {
|
|
1051
|
-
staffDetailsCopy.members[selectedPhysAsst].appointments.ongoing++;
|
|
1052
|
-
}
|
|
1053
|
-
staffDetailsCopy.lastModified = today;
|
|
1054
|
-
const appointment = omitNils({
|
|
1055
|
-
_id: v4(),
|
|
1056
|
-
src: stateCopy._id,
|
|
1057
|
-
tkt: request.state.tktNoCounter,
|
|
1058
|
-
severity: request.form.category,
|
|
1059
|
-
reason: request.form.appointmentReason,
|
|
1060
|
-
discounts: Object.fromEntries(
|
|
1061
|
-
Object.entries(request.billing.discounts || {}).filter(
|
|
1062
|
-
([_, disc]) => !disc.isOptional
|
|
1063
|
-
)
|
|
1064
|
-
),
|
|
1065
|
-
additionalFees: Object.fromEntries(
|
|
1066
|
-
Object.entries(request.billing.additionalFees || {}).filter(
|
|
1067
|
-
([_, fee]) => !fee.isOptional
|
|
1068
|
-
)
|
|
1069
|
-
),
|
|
1070
|
-
payments: request.preferences.enforcePaidAppointments ? request.form.payments : null,
|
|
1071
|
-
prepayments: request.preferences.enforcePaidAppointments ? request.form?.prepayments ?? null : null,
|
|
1072
|
-
charges: request.preferences.serviceSelector === "scheduler" ? request.form?.charges ?? null : null,
|
|
1073
|
-
client: await request.findClient(request.form.clientId),
|
|
1074
|
-
_hash: "aa.aa.aa",
|
|
1075
|
-
timeline: {
|
|
1076
|
-
scheduler: { userUid: request.sender._id, postedAt: today },
|
|
1077
|
-
doctor: { userUid: selectedDoctor },
|
|
1078
|
-
physAsst: selectedPhysAsst ? { userUid: selectedPhysAsst } : "none",
|
|
1079
|
-
get debtCollector() {
|
|
1080
|
-
if (request.preferences.enforcePaidAppointments)
|
|
1081
|
-
return [{ ...this.scheduler, collectedPayments: {} }];
|
|
1082
|
-
return null;
|
|
1083
|
-
}
|
|
1084
|
-
},
|
|
1085
|
-
createdAt: today
|
|
1086
|
-
});
|
|
1087
|
-
appointment._hash = request.createHash(omit(appointment, ["_hash"]));
|
|
1088
|
-
return {
|
|
1089
|
-
updates: {
|
|
1090
|
-
state: stateCopy,
|
|
1091
|
-
staffDetails: staffDetailsCopy
|
|
1092
|
-
},
|
|
1093
|
-
appointment: MutableAppointmentDetails.parse(appointment)
|
|
1094
|
-
};
|
|
1095
|
-
}
|
|
814
|
+
);
|
|
815
|
+
};
|
|
1096
816
|
|
|
1097
817
|
// src/functions/createNotifId.ts
|
|
1098
818
|
import { random } from "@wavy/fn";
|
|
1099
|
-
import { v4
|
|
819
|
+
import { v4 } from "uuid";
|
|
1100
820
|
function createNotifId(options) {
|
|
1101
|
-
let nid =
|
|
821
|
+
let nid = v4();
|
|
1102
822
|
if (options) {
|
|
1103
823
|
nid = Notification.shape._id.parse(
|
|
1104
824
|
[
|
|
@@ -1112,21 +832,17 @@ function createNotifId(options) {
|
|
|
1112
832
|
}
|
|
1113
833
|
export {
|
|
1114
834
|
AlphaNumeric,
|
|
1115
|
-
AppointmentDetails,
|
|
1116
|
-
AppointmentSeverity,
|
|
1117
835
|
COMPANY_USER_RELATIONSHIP,
|
|
836
|
+
ClientDetails,
|
|
1118
837
|
ClientForm,
|
|
1119
|
-
ClientIdentity,
|
|
1120
838
|
CompanyBilling,
|
|
1121
839
|
CompanyDetails,
|
|
1122
840
|
CompanyIdentity,
|
|
1123
841
|
CompanyIndustry,
|
|
1124
842
|
CompanyNotifications,
|
|
1125
843
|
CompanyPreferences,
|
|
1126
|
-
CompanyStaff,
|
|
844
|
+
CompanyStaff2 as CompanyStaff,
|
|
1127
845
|
CompanyState,
|
|
1128
|
-
CompanyUser2 as CompanyUser,
|
|
1129
|
-
CompanyUserSession,
|
|
1130
846
|
CreateCompanyForm,
|
|
1131
847
|
CreditCurrency,
|
|
1132
848
|
InviteResponse,
|
|
@@ -1134,13 +850,12 @@ export {
|
|
|
1134
850
|
MAX_COMPANY_PRICE_MODS,
|
|
1135
851
|
MedicalDetails,
|
|
1136
852
|
Medicine,
|
|
1137
|
-
MiniAppointmentDetails,
|
|
1138
|
-
MiniCompanyUser2 as MiniCompanyUser,
|
|
1139
|
-
MutableAppointmentDetails,
|
|
1140
853
|
MutableCompanyBilling,
|
|
1141
854
|
MutableCompanyDetails,
|
|
1142
855
|
MutableCompanyIdentity,
|
|
1143
856
|
MutableCompanyPreferences,
|
|
857
|
+
MutableCompanyStaff,
|
|
858
|
+
MutableTaskDetails,
|
|
1144
859
|
Notification,
|
|
1145
860
|
PaymentDetails,
|
|
1146
861
|
PriceMod,
|
|
@@ -1148,8 +863,9 @@ export {
|
|
|
1148
863
|
PriceTag,
|
|
1149
864
|
Reason,
|
|
1150
865
|
Receipts,
|
|
1151
|
-
ScheduleAppointmentForm,
|
|
1152
866
|
StandardTime,
|
|
867
|
+
TaskDetails,
|
|
868
|
+
TaskSeverity,
|
|
1153
869
|
TicketNo,
|
|
1154
870
|
TierList,
|
|
1155
871
|
TimeLog,
|
|
@@ -1159,16 +875,12 @@ export {
|
|
|
1159
875
|
Vitals,
|
|
1160
876
|
acceptedCurrencies,
|
|
1161
877
|
adminRoles,
|
|
1162
|
-
appointmentDistAlgs,
|
|
1163
878
|
calcBalance,
|
|
1164
879
|
calcPriceMod,
|
|
1165
880
|
companyServiceSelectors,
|
|
1166
881
|
companyUserRoles,
|
|
1167
882
|
createNotifId,
|
|
1168
883
|
employeeRoles,
|
|
1169
|
-
findAvailableStaff,
|
|
1170
|
-
findConflictingPartners,
|
|
1171
|
-
findNextAvailableStaff,
|
|
1172
884
|
genders,
|
|
1173
885
|
getCompatibleRoles,
|
|
1174
886
|
getIncompatibleRoles,
|
|
@@ -1177,8 +889,7 @@ export {
|
|
|
1177
889
|
normalizeNidKey,
|
|
1178
890
|
parseClientName,
|
|
1179
891
|
paymentMethods,
|
|
1180
|
-
|
|
1181
|
-
serviceProviders,
|
|
892
|
+
taskDistAlgs,
|
|
1182
893
|
tiers,
|
|
1183
894
|
vitalKeys
|
|
1184
895
|
};
|