@bizmap/sdk 0.0.120 → 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.
Files changed (3) hide show
  1. package/dist/main.d.ts +251 -901
  2. package/dist/main.js +340 -619
  3. 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
- "founder"
9
+ "ceo"
10
10
  // "admin",
11
11
  ]);
12
- var employeeRoles = companyUserRoles.exclude(["founder"]);
13
- var adminRoles = companyUserRoles.extract(["founder"]);
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 appointmentDistAlgs = z.enum(["RR", "LOR"]);
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 z18 from "zod";
49
+ import * as z16 from "zod";
50
50
 
51
51
  // src/schemas/company/components/Billing.ts
52
52
  import * as z8 from "zod";
@@ -146,24 +146,22 @@ var CompanyState = z7.object({
146
146
  current: z7.number(),
147
147
  lastModified: Timestamp.optional()
148
148
  }),
149
- maxStaff: z7.object({
150
- current: z7.int().min(1),
151
- lastModified: Timestamp.optional()
152
- }),
153
149
  usedStorageInBytes: z7.object({
154
150
  current: z7.number(),
155
151
  lastModified: Timestamp.optional()
156
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()),
157
162
  /** Data retention period in months */
158
163
  // drpInMonths: z.int().min(1),
159
- /** A counter for the company's invoice no.
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
164
+ ...TimeLog.shape
167
165
  });
168
166
 
169
167
  // src/schemas/company/components/Billing.ts
@@ -174,10 +172,10 @@ var createMod = (resource) => PriceModList.refine(
174
172
  );
175
173
  var CompanyBilling = z8.object({
176
174
  _id: CompanyState.shape._id,
177
- additionalFees: createMod("additional fee"),
178
- discounts: createMod("discount"),
175
+ additionalFees: createMod("additional fee").optional(),
176
+ discounts: createMod("discount").optional(),
179
177
  /** Optionally deductable from bills */
180
- prepayments: createMod("pre-payment"),
178
+ prepayments: createMod("pre-payment").optional(),
181
179
  offeredServices: z8.record(z8.uuidv4(), PriceTag.omit({ uid: true })).superRefine((data, ctx) => {
182
180
  const services = Object.entries(data);
183
181
  if (services.length > 150) {
@@ -185,8 +183,8 @@ var CompanyBilling = z8.object({
185
183
  "Creating more services than the max allowed services (150) is prohibited."
186
184
  );
187
185
  }
188
- }),
189
- lastModified: Timestamp.optional()
186
+ }).optional(),
187
+ ...TimeLog.shape
190
188
  });
191
189
  var MutableCompanyBilling = CompanyBilling.omit({
192
190
  uid: true,
@@ -200,14 +198,21 @@ import { Address, PhoneNumber } from "@wavy/util";
200
198
  // src/schemas/company/utils/CompanyIndustry.ts
201
199
  import * as z9 from "zod";
202
200
  var CompanyIndustry = z9.object({
203
- healthcare: z9.enum([
204
- "medicalCare"
205
- // "pharmaceuticals"
206
- ])
201
+ healthcare: z9.object({
202
+ medicalCare: z9.object({
203
+ roles: z9.enum(["doc", "physAsst"])
204
+ })
205
+ // "pharmaceuticals": z.object({
206
+ // })
207
+ })
207
208
  });
208
209
 
209
210
  // src/schemas/company/utils/utils.ts
210
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
+ };
211
216
 
212
217
  // src/schemas/company/components/Identity.ts
213
218
  var CompanyIdentity = z10.object({
@@ -220,33 +225,26 @@ var CompanyIdentity = z10.object({
220
225
  );
221
226
  }
222
227
  }),
223
- address: Address.optional(),
228
+ address: Address,
224
229
  logo: z10.string().optional(),
225
- industry: CompanyIndustry.keyof().readonly(),
230
+ industry: CompanyIndustry.keyof(),
226
231
  sector: z10.string(),
227
- contact: z10.object({
228
- email: z10.email().max(45),
229
- phoneNumber: PhoneNumber.optional(),
230
- emailVerified: z10.boolean()
231
- }),
232
- // Can only be changed by the server
233
- tier: z10.object({
234
- current: tiers,
235
- lastModified: Timestamp.optional()
236
- }),
237
- isVerified: z10.boolean(),
232
+ email: z10.email().max(45),
233
+ phoneNumber: PhoneNumber.optional(),
234
+ emailVerified: z10.boolean(),
235
+ verified: z10.boolean(),
238
236
  legal: z10.object({
239
237
  regNo: z10.string().max(20).readonly(),
240
238
  // The company's registration number
241
239
  trn: z10.string().regex(/[0-9]{3}-[0-9]{3}-[0-9]{3}/).nullish(),
242
240
  gctRegNo: z10.string().max(15).nullish()
243
241
  }).optional(),
244
- lastModified: Timestamp.optional()
242
+ ...TimeLog.shape
245
243
  }).superRefine((data, ctx) => {
246
244
  const hasLegalInfo = "legal" in data;
247
- if (data.isVerified && !hasLegalInfo) {
245
+ if (data.verified && !hasLegalInfo) {
248
246
  ctx.addIssue("Verified companies must have the legal field defined.");
249
- } else if (!data.isVerified && hasLegalInfo) {
247
+ } else if (!data.verified && hasLegalInfo) {
250
248
  ctx.addIssue("Unverified companies can't have the legal field defined.");
251
249
  }
252
250
  if (!CompanyIndustry.shape[data.industry].safeParse(data.sector).success) {
@@ -261,172 +259,53 @@ var MutableCompanyIdentity = CompanyIdentity.pick({
261
259
  alias: true
262
260
  });
263
261
 
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
262
  // src/schemas/company/components/Preferences.ts
384
- import * as z13 from "zod";
385
- var CompanyPreferences = z13.object({
263
+ import * as z11 from "zod";
264
+ var CompanyPreferences = z11.object({
386
265
  _id: CompanyState.shape._id,
387
266
  /** The user that's allowed to record the client's services. */
388
267
  serviceSelector: companyServiceSelectors,
389
- /** A record of the payments made must be attached before creating the
390
- * appointment
391
- * @note
392
- * - only allowed when `serviceSelector === "scheduler"`
393
- * */
394
- enforcePaidAppointments: z13.boolean(),
268
+ /** Pay per task */
269
+ ppt: z11.boolean(),
395
270
  /**
396
271
  * @property RR (Round Robin): Even distribution.
397
272
  * @property LOR (Least Outstanding Requests): Distribute based on availability.
398
273
  */
399
- apptDistAlg: appointmentDistAlgs,
400
- servicesDeployed: z13.boolean(),
401
- lastModified: Timestamp.optional()
274
+ taskDistAlg: taskDistAlgs,
275
+ servicesDeployed: z11.boolean(),
276
+ ...TimeLog.shape
402
277
  }).refine(
403
- (data) => !data.enforcePaidAppointments || data.enforcePaidAppointments && data.serviceSelector === "scheduler",
404
- 'Enforcing paid appointments is only allowed when the service selector is the "scheduler".'
278
+ (data) => !data.ppt || data.ppt && data.serviceSelector === "scheduler",
279
+ `Pay per task is only allowed when the service selector is the "${"scheduler"}".`
405
280
  );
406
281
  var MutableCompanyPreferences = CompanyPreferences.omit({
407
- uid: true,
282
+ _id: true,
408
283
  lastModified: true
409
284
  });
410
285
 
411
286
  // src/schemas/company/components/Staff.ts
412
- import * as z17 from "zod";
413
-
414
- // src/schemas/company/components/User.ts
415
- import * as z16 from "zod";
287
+ import { distinct } from "@wavy/fn";
288
+ import * as z15 from "zod";
416
289
 
417
290
  // src/functions/helper-functions.ts
418
291
  import { camelCaseToLetter, upperFirst } from "@wavy/fn";
419
292
 
420
293
  // src/schemas/profiles/Client.ts
421
- import * as z15 from "zod";
294
+ import * as z13 from "zod";
422
295
 
423
296
  // src/schemas/profiles/User.ts
424
297
  import { UserModel } from "@wavy/util";
425
- import * as z14 from "zod";
426
- var UserDetails = z14.object({
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({
427
306
  ...UserModel.shape,
428
- _id: z14.uuidv4(),
429
- name: z14.string().transform((data) => {
307
+ _id: z12.uuidv4(),
308
+ name: z12.string().transform((data) => {
430
309
  const [firstName = "", lastName = ""] = data.split(" ");
431
310
  return (firstName + " " + lastName).trim();
432
311
  }).superRefine((data, ctx) => {
@@ -443,58 +322,59 @@ var UserDetails = z14.object({
443
322
  });
444
323
  }),
445
324
  email: UserModel.shape.email,
446
- publicKey: z14.string().optional(),
325
+ publicKey: z12.string().optional(),
326
+ availableStorageInBytes: z12.number().optional(),
447
327
  // notifications: z.array(Notification),
448
- lastSignedIn: z14.object({
328
+ lastSignedIn: z12.object({
449
329
  auto: Timestamp,
450
330
  manual: Timestamp
451
331
  }).partial(),
452
- companyUids: z14.array(CompanyState.shape._id).min(1).optional(),
332
+ companyUids: z12.array(CompanyState.shape._id).min(1).optional(),
453
333
  // resumeToken: z.uuidv4().optional(),
454
334
  ...TimeLog.shape
455
335
  }).omit({ uid: true });
456
336
 
457
337
  // src/schemas/profiles/Client.ts
458
338
  import { Address as Address2, PhoneNumber as PhoneNumber2 } from "@wavy/util";
459
- var Name = z15.string().trim().min(2).max(MAX_NAME_LENGTH).transform((d) => d.replace(/-+/g, "-").replace(/'+/g, "'")).superRefine((data, ctx) => {
339
+ var Name = z13.string().trim().min(2).max(MAX_NAME_LENGTH).transform((d) => d.replace(/-+/g, "-").replace(/'+/g, "'")).superRefine((data, ctx) => {
460
340
  const invalidCharIdx = data.search(/[^a-z'-]/i);
461
341
  if (invalidCharIdx >= 0) {
462
342
  const char = data[invalidCharIdx].includes('"') ? `(${data[invalidCharIdx]})` : `"${data[invalidCharIdx]}"`;
463
343
  ctx.addIssue(`${char} is not an allowed character.`);
464
344
  }
465
345
  });
466
- var ClientIdentity = UserDetails.pick({
346
+ var ClientDetails = UserDetails.pick({
467
347
  _id: true,
468
348
  createdAt: true,
469
349
  lastModified: true,
470
350
  phoneNumber: true,
471
351
  photoUrl: true
472
352
  }).safeExtend(
473
- z15.object({
353
+ z13.object({
474
354
  firstName: Name,
475
355
  middleName: Name,
476
356
  lastName: Name,
477
357
  email: UserDetails.shape.email.optional(),
478
358
  dob: Timestamp,
479
359
  sex: genders,
480
- isGlobal: z15.boolean(),
360
+ isGlobal: z13.boolean(),
481
361
  origin: CompanyIdentity.shape._id,
482
- visits: z15.record(CompanyIdentity.shape._id, z15.array(Timestamp).min(1)).optional(),
362
+ visits: z13.record(CompanyIdentity.shape._id, z13.array(Timestamp).min(1)).optional(),
483
363
  phoneNumber: PhoneNumber2.optional(),
484
364
  // aka. National id
485
- nid: z15.object({
365
+ nid: z13.object({
486
366
  trn: Trn,
487
367
  nin: AlphaNumeric.toLowerCase().min(5).max(20),
488
368
  passportNo: AlphaNumeric.toLowerCase().min(5).max(15)
489
369
  }).partial().optional(),
490
370
  // Don't hash the attributes, they can be used to help accurately identify a client by whoever is searching for
491
371
  // the client
492
- attributes: z15.object({
372
+ attributes: z13.object({
493
373
  motherMaidenName: Name,
494
374
  fatherMiddleName: Name,
495
375
  motherMiddleName: Name,
496
376
  oldestSiblingMiddleName: Name,
497
- firstSchoolName: z15.string().trim().min(3).max(60)
377
+ firstSchoolName: z13.string().trim().min(3).max(60)
498
378
  }).partial().refine(
499
379
  (data) => Object.keys(data).length > 0,
500
380
  "At least (1) attribute must be defined."
@@ -503,7 +383,7 @@ var ClientIdentity = UserDetails.pick({
503
383
  linkedTo: UserDetails.shape._id.optional()
504
384
  }).shape
505
385
  );
506
- var ClientForm = ClientIdentity.pick({
386
+ var ClientForm = ClientDetails.pick({
507
387
  firstName: true,
508
388
  middleName: true,
509
389
  lastName: true,
@@ -512,24 +392,6 @@ var ClientForm = ClientIdentity.pick({
512
392
  }).required();
513
393
 
514
394
  // 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
395
  var getCompatibleRoles = (role) => {
534
396
  return companyUserRoles.options.filter(
535
397
  (r) => !COMPANY_USER_RELATIONSHIP[role]?.includes(r)
@@ -545,7 +407,7 @@ var normalizeCompanyId = (id) => {
545
407
  return id.split("-").map((value) => upperFirst(value.toLowerCase())).join(" ");
546
408
  };
547
409
  var normalizeNidKey = (value) => {
548
- value = ClientIdentity.shape.nid.unwrap().keyof().parse(value);
410
+ value = ClientDetails.shape.nid.unwrap().keyof().parse(value);
549
411
  if (value === "nin") return "National Id No.";
550
412
  if (value === "trn") return "Tax Registration No.";
551
413
  return camelCaseToLetter(value.replace("No", "No."));
@@ -556,163 +418,103 @@ var parseClientName = ({
556
418
  lastName
557
419
  }) => [firstName, middleName[0] + ".", lastName].join(" ");
558
420
 
559
- // src/schemas/company/components/User.ts
560
- var COMPANY_USER_RELATIONSHIP = {
561
- doc: ["physAsst"],
562
- physAsst: ["doc"]
563
- };
564
- var CompanyUserSession = z16.object({
565
- _id: z16.uuidv4(),
566
- 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 }),
567
427
  companyId: CompanyState.shape._id,
568
- deviceId: z16.string().trim().min(15),
569
- isAvailable: z16.object({
570
- value: z16.boolean(),
428
+ deviceId: z14.string().trim().min(15),
429
+ isAvailable: z14.object({
430
+ value: z14.boolean(),
571
431
  lastModified: Timestamp.optional()
572
432
  }),
573
- isBusy: z16.object({
574
- value: z16.boolean(),
433
+ isBusy: z14.object({
434
+ value: z14.boolean(),
575
435
  lastModified: Timestamp.optional()
576
436
  }),
577
- appointments: z16.record(z16.literal(["ongoing", "completed"]), z16.int().min(0)),
578
- roles: z16.array(companyUserRoles).transform((roles) => {
579
- const newRoles = [];
580
- for (const role of roles) {
581
- if (!!role && !newRoles.includes(role)) newRoles.push(role);
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()
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
613
442
  });
614
443
 
615
444
  // src/schemas/company/components/Staff.ts
616
- var MiniRawCompanyStaff = z17.object({
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({
617
451
  _id: CompanyState.shape._id,
618
- members: z17.record(
619
- MiniCompanyUser2.shape._id,
620
- MiniCompanyUser2.omit({ _id: true })
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
+ })
621
461
  ),
622
- // .superRefine(refineMembers),
623
462
  /**
624
463
  * @relationship one -> many
625
464
  *@description A map of doctor `uids` to their assistants `uids` */
626
- partnerMap: z17.record(
627
- CompanyUser2.shape._id,
628
- z17.record(CompanyUser2.shape._id, z17.object({ addedAt: Timestamp }))
629
- ),
630
- updateQueue: z17.record(
631
- CompanyUser2.shape._id,
632
- z17.object({
633
- $REMOVE: z17.object({ addedAt: Timestamp }),
634
- $CHANGE_ROLES: z17.object({
635
- newRoles: CompanyUser2.shape.roles,
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,
636
475
  addedAt: Timestamp
637
476
  })
638
477
  }).partial()
639
- ),
640
- lastModified: Timestamp.optional()
641
- });
642
- var RawCompanyStaff = z17.object({
643
- ...MiniRawCompanyStaff.shape,
644
- members: z17.record(CompanyUser2.shape._id, CompanyUser2.omit({ _id: true }))
645
- });
646
- var transformStaff = (object17) => {
647
- return object17.transform((d) => {
648
- const copy = structuredClone(d);
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];
669
- }
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
- }
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
+ );
684
488
  }
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
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
+ );
694
498
  }
695
- });
696
- };
697
- var CompanyStaff = transformStaff(RawCompanyStaff);
698
- var MiniCompanyStaff = transformStaff(MiniRawCompanyStaff);
699
- var MutableCompanyStaff = RawCompanyStaff.pick({
499
+ }
500
+ });
501
+ var MutableCompanyStaff = CompanyStaff2.pick({
700
502
  updateQueue: true,
701
503
  partnerMap: true
702
504
  });
703
505
 
704
506
  // src/schemas/company/CompanyDetails.ts
705
- var CompanyDetails = z18.object({
507
+ var CompanyDetails = z16.object({
706
508
  _id: CompanyState.shape._id,
707
509
  identity: CompanyIdentity.omit({ _id: true }),
708
- notifications: CompanyNotifications,
709
- receipts: Receipts,
510
+ // notifications: CompanyNotifications,
511
+ // receipts: Receipts,
710
512
  state: CompanyState.omit({ _id: true }),
711
513
  preferences: CompanyPreferences.omit({ _id: true }),
712
514
  billing: CompanyBilling.omit({ _id: true }),
713
- staff: RawCompanyStaff.omit({ _id: true })
515
+ staff: CompanyStaff2.omit({ _id: true })
714
516
  });
715
- var MutableCompanyDetails = z18.object({
517
+ var MutableCompanyDetails = z16.object({
716
518
  _id: CompanyDetails.shape._id,
717
519
  identity: MutableCompanyIdentity.partial().optional(),
718
520
  preferences: MutableCompanyPreferences.partial().optional(),
@@ -720,23 +522,139 @@ var MutableCompanyDetails = z18.object({
720
522
  staff: MutableCompanyStaff.partial().optional()
721
523
  });
722
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
+
723
638
  // src/schemas/company/utils/CreateCompanyForm.ts
724
639
  import * as z19 from "zod";
725
640
  var CreateCompanyForm = z19.object({
726
- _id: CompanyIdentity.shape._id,
727
641
  alias: CompanyIdentity.shape.alias.optional(),
728
- logo: CompanyIdentity.shape.logo.optional(),
729
- ...CompanyIdentity.shape.contact.pick({ email: true, phoneNumber: true }).shape,
730
- industry: CompanyIdentity.shape.industry,
731
- sector: CompanyIdentity.shape.sector,
732
- address: CompanyIdentity.shape.address.nonoptional()
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
733
651
  }).superRefine((data, ctx) => {
734
652
  if (!CompanyIndustry.shape[data.industry].safeParse(data.sector).success) {
735
653
  ctx.addIssue(getInvalidSectorError(data.industry, data.sector));
736
654
  }
737
655
  });
738
656
 
739
- // src/schemas/appointment/components/Medical.ts
657
+ // src/schemas/task/components/Medical.ts
740
658
  import * as z20 from "zod";
741
659
  var Vitals = z20.record(
742
660
  vitalKeys,
@@ -771,51 +689,20 @@ var MedicalDetails = z20.object({
771
689
  ).optional()
772
690
  });
773
691
 
774
- // src/schemas/appointment/Appointment.ts
775
- import { sumOf as sumOf2 } from "@wavy/fn";
692
+ // src/schemas/task/Task.ts
776
693
  import * as z21 from "zod";
777
-
778
- // src/functions/calc.ts
779
- import { sumOf } from "@wavy/fn";
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({
694
+ var TaskSeverity = z21.object({
810
695
  "1": z21.literal("Emergency"),
811
696
  "2": z21.literal("Important"),
812
697
  "3": z21.literal("Average")
813
698
  });
814
- var MiniAppointmentDetails = z21.object({
699
+ var TaskDetails = z21.object({
815
700
  /** A random uid that identifies the document. */
816
701
  _id: z21.uuidv4(),
702
+ /** The amount of credits used to create the appointment */
703
+ _cost: z21.number().readonly(),
817
704
  // This helps to identify the urgency of the appointment
818
- severity: z21.string().refine((data) => AppointmentSeverity.keyof().safeParse(data).success),
705
+ severity: z21.string().refine((data) => TaskSeverity.keyof().safeParse(data).success),
819
706
  /** The company's uid */
820
707
  src: CompanyState.shape._id,
821
708
  /** The ticket number */
@@ -844,56 +731,63 @@ var MiniAppointmentDetails = z21.object({
844
731
  prepayments: CompanyBilling.shape.prepayments.optional(),
845
732
  payments: z21.array(PaymentDetails).max(100).optional(),
846
733
  /** The client's identity */
847
- clientUid: ClientIdentity.shape._id,
734
+ clientId: ClientDetails.shape._id,
848
735
  /**
849
736
  * Data that is specific to the entity (for now it's just medical data).
850
737
  * It will only be defined for the participants that have access to it.
851
738
  */
852
739
  payload: z21.object({
853
740
  ...MedicalDetails.shape,
854
- lastModified: z21.number().nullable()
741
+ lastModified: Timestamp.optional()
855
742
  }).optional(),
856
743
  /** Only defined when either the service provider or the client cancelled their appointment */
857
744
  cancelled: z21.object({
858
745
  /** The time that the confirm cancel button was clicked */
859
746
  doneAt: Timestamp,
860
747
  reason: Reason,
861
- doneBy: companyUserRoles.extract(["doc", "physAsst"])
748
+ doneBy: companyUserRoles.extract(["doc", "physAsst", "rcpst"]).or(z21.literal("client"))
862
749
  }).optional(),
863
750
  //** Add this after consulting with company owners about payment expectations */
864
751
  // paymentDueDate: Timestamp.nullish(),
865
752
  // The user that is currently modifying the appointment
866
753
  lockedBy: UserDetails.shape._id.optional(),
867
- timeline: z21.object({
868
- scheduler: TimelineActivity,
869
- physAsst: TimelineActivity.or(z21.literal("none")),
870
- doctor: TimelineActivity,
871
- // Array used to accurately record payments made by the "a/r" and the "cshr"
872
- debtCollector: z21.array(
873
- z21.object({
874
- ...TimelineActivity.shape,
875
- // Payment uid -> collection time
876
- collectedPayments: z21.record(z21.uuidv4(), Timestamp)
877
- })
878
- ).min(1).nullable()
879
- }),
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>),
880
788
  ...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
789
  });
893
- var AppointmentDetails = MiniAppointmentDetails.omit({
894
- clientUid: true
895
- }).extend(z21.object({ client: ClientIdentity }).shape);
896
- var MutableAppointmentDetails = AppointmentDetails.safeExtend(
790
+ var MutableAppointmentDetails = TaskDetails.safeExtend(
897
791
  z21.object({
898
792
  /**
899
793
  * @description An encrypted copy of the last state of the appointment.
@@ -902,203 +796,38 @@ var MutableAppointmentDetails = AppointmentDetails.safeExtend(
902
796
  _hash: z21.string()
903
797
  }).shape
904
798
  );
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
799
 
920
- // src/functions/findAvailableStaff.ts
921
- function findAvailableStaff(role, staff) {
922
- return Object.keys(role === "doc" ? staff.partnerMap : staff.members).filter(
923
- (uid) => {
924
- const user = staff.members[uid];
925
- return !(uid in staff.updateQueue) && user.roles.includes(role) && user.isActive;
926
- }
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)
927
805
  );
928
- }
929
-
930
- // src/functions/findNextAvailableStaff.ts
931
- function findNextAvailableStaff(userUids, algorithm, staff) {
932
- let nextAvailable = null;
933
- if (userUids.length <= 1) return userUids[0] ?? null;
934
- switch (algorithm) {
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;
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 });
953
813
  }
954
- return nextAvailable;
814
+ return result;
955
815
  }
956
-
957
- // src/functions/scheduleAppointment.ts
958
- async function scheduleAppointment(request) {
959
- TODO("Revise this fn (some things have changed in the form etc.)");
960
- request.form = ScheduleAppointmentForm.parse(request.form);
961
- request.sender = UserDetails.parse(request.sender);
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
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)
1017
822
  )
1018
- )) {
1019
- throw new Error("Invalid service(s) selected.");
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
- }
823
+ );
824
+ };
1096
825
 
1097
826
  // src/functions/createNotifId.ts
1098
827
  import { random } from "@wavy/fn";
1099
- import { v4 as v42 } from "uuid";
828
+ import { v4 } from "uuid";
1100
829
  function createNotifId(options) {
1101
- let nid = v42();
830
+ let nid = v4();
1102
831
  if (options) {
1103
832
  nid = Notification.shape._id.parse(
1104
833
  [
@@ -1112,21 +841,17 @@ function createNotifId(options) {
1112
841
  }
1113
842
  export {
1114
843
  AlphaNumeric,
1115
- AppointmentDetails,
1116
- AppointmentSeverity,
1117
844
  COMPANY_USER_RELATIONSHIP,
845
+ ClientDetails,
1118
846
  ClientForm,
1119
- ClientIdentity,
1120
847
  CompanyBilling,
1121
848
  CompanyDetails,
1122
849
  CompanyIdentity,
1123
850
  CompanyIndustry,
1124
851
  CompanyNotifications,
1125
852
  CompanyPreferences,
1126
- CompanyStaff,
853
+ CompanyStaff2 as CompanyStaff,
1127
854
  CompanyState,
1128
- CompanyUser2 as CompanyUser,
1129
- CompanyUserSession,
1130
855
  CreateCompanyForm,
1131
856
  CreditCurrency,
1132
857
  InviteResponse,
@@ -1134,13 +859,12 @@ export {
1134
859
  MAX_COMPANY_PRICE_MODS,
1135
860
  MedicalDetails,
1136
861
  Medicine,
1137
- MiniAppointmentDetails,
1138
- MiniCompanyUser2 as MiniCompanyUser,
1139
862
  MutableAppointmentDetails,
1140
863
  MutableCompanyBilling,
1141
864
  MutableCompanyDetails,
1142
865
  MutableCompanyIdentity,
1143
866
  MutableCompanyPreferences,
867
+ MutableCompanyStaff,
1144
868
  Notification,
1145
869
  PaymentDetails,
1146
870
  PriceMod,
@@ -1148,8 +872,9 @@ export {
1148
872
  PriceTag,
1149
873
  Reason,
1150
874
  Receipts,
1151
- ScheduleAppointmentForm,
1152
875
  StandardTime,
876
+ TaskDetails,
877
+ TaskSeverity,
1153
878
  TicketNo,
1154
879
  TierList,
1155
880
  TimeLog,
@@ -1159,16 +884,12 @@ export {
1159
884
  Vitals,
1160
885
  acceptedCurrencies,
1161
886
  adminRoles,
1162
- appointmentDistAlgs,
1163
887
  calcBalance,
1164
888
  calcPriceMod,
1165
889
  companyServiceSelectors,
1166
890
  companyUserRoles,
1167
891
  createNotifId,
1168
892
  employeeRoles,
1169
- findAvailableStaff,
1170
- findConflictingPartners,
1171
- findNextAvailableStaff,
1172
893
  genders,
1173
894
  getCompatibleRoles,
1174
895
  getIncompatibleRoles,
@@ -1177,8 +898,8 @@ export {
1177
898
  normalizeNidKey,
1178
899
  parseClientName,
1179
900
  paymentMethods,
1180
- scheduleAppointment,
1181
901
  serviceProviders,
902
+ taskDistAlgs,
1182
903
  tiers,
1183
904
  vitalKeys
1184
905
  };