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