@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.
Files changed (3) hide show
  1. package/dist/main.d.ts +251 -923
  2. package/dist/main.js +340 -628
  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";
@@ -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
- /** A counter for the company's invoice no.
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
- version: Version,
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.enum([
207
- "medicalCare"
208
- // "pharmaceuticals"
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.optional(),
228
+ address: Address,
227
229
  logo: z10.string().optional(),
228
- industry: CompanyIndustry.keyof().readonly(),
230
+ industry: CompanyIndustry.keyof(),
229
231
  sector: z10.string(),
230
- contact: z10.object({
231
- email: z10.email().max(45),
232
- phoneNumber: PhoneNumber.optional(),
233
- emailVerified: z10.boolean()
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
- version: Version,
248
- lastModified: Timestamp.optional()
242
+ ...TimeLog.shape
249
243
  }).superRefine((data, ctx) => {
250
244
  const hasLegalInfo = "legal" in data;
251
- if (data.isVerified && !hasLegalInfo) {
245
+ if (data.verified && !hasLegalInfo) {
252
246
  ctx.addIssue("Verified companies must have the legal field defined.");
253
- } else if (!data.isVerified && hasLegalInfo) {
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 z13 from "zod";
389
- var CompanyPreferences = z13.object({
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
- /** A record of the payments made must be attached before creating the
394
- * appointment
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
- apptDistAlg: appointmentDistAlgs,
404
- servicesDeployed: z13.boolean(),
405
- version: Version,
406
- lastModified: Timestamp.optional()
274
+ taskDistAlg: taskDistAlgs,
275
+ servicesDeployed: z11.boolean(),
276
+ ...TimeLog.shape
407
277
  }).refine(
408
- (data) => !data.enforcePaidAppointments || data.enforcePaidAppointments && data.serviceSelector === "scheduler",
409
- '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"}".`
410
280
  );
411
281
  var MutableCompanyPreferences = CompanyPreferences.omit({
412
- uid: true,
282
+ _id: true,
413
283
  lastModified: true
414
284
  });
415
285
 
416
286
  // src/schemas/company/components/Staff.ts
417
- import * as z17 from "zod";
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 z15 from "zod";
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 z14 from "zod";
431
- 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({
432
306
  ...UserModel.shape,
433
- _id: z14.uuidv4(),
434
- name: z14.string().transform((data) => {
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: z14.string().optional(),
325
+ publicKey: z12.string().optional(),
326
+ availableStorageInBytes: z12.number().optional(),
452
327
  // notifications: z.array(Notification),
453
- version: Version.default(1),
454
- lastSignedIn: z14.object({
328
+ lastSignedIn: z12.object({
455
329
  auto: Timestamp,
456
330
  manual: Timestamp
457
331
  }).partial(),
458
- companyUids: z14.array(CompanyState.shape._id).min(1).optional(),
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 = 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) => {
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 ClientIdentity = UserDetails.pick({
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
- z15.object({
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: z15.boolean(),
360
+ isGlobal: z13.boolean(),
488
361
  origin: CompanyIdentity.shape._id,
489
- 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(),
490
363
  phoneNumber: PhoneNumber2.optional(),
491
364
  // aka. National id
492
- nid: z15.object({
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: z15.object({
372
+ attributes: z13.object({
500
373
  motherMaidenName: Name,
501
374
  fatherMiddleName: Name,
502
375
  motherMiddleName: Name,
503
376
  oldestSiblingMiddleName: Name,
504
- firstSchoolName: z15.string().trim().min(3).max(60)
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 = ClientIdentity.pick({
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 = ClientIdentity.shape.nid.unwrap().keyof().parse(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/components/User.ts
567
- var COMPANY_USER_RELATIONSHIP = {
568
- doc: ["physAsst"],
569
- physAsst: ["doc"]
570
- };
571
- var CompanyUserSession = z16.object({
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: z16.string().trim().min(15),
576
- isAvailable: z16.object({
577
- value: z16.boolean(),
428
+ deviceId: z14.string().trim().min(15),
429
+ isAvailable: z14.object({
430
+ value: z14.boolean(),
578
431
  lastModified: Timestamp.optional()
579
432
  }),
580
- isBusy: z16.object({
581
- value: z16.boolean(),
433
+ isBusy: z14.object({
434
+ value: z14.boolean(),
582
435
  lastModified: Timestamp.optional()
583
436
  }),
584
- appointments: z16.record(z16.literal(["ongoing", "completed"]), z16.int().min(0)),
585
- roles: z16.array(companyUserRoles).transform((roles) => {
586
- const newRoles = [];
587
- for (const role of roles) {
588
- if (!!role && !newRoles.includes(role)) newRoles.push(role);
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 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({
624
451
  _id: CompanyState.shape._id,
625
- members: z17.record(
626
- MiniCompanyUser2.shape._id,
627
- 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
+ })
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: z17.record(
634
- CompanyUser2.shape._id,
635
- z17.record(CompanyUser2.shape._id, z17.object({ addedAt: Timestamp }))
636
- ),
637
- updateQueue: z17.record(
638
- CompanyUser2.shape._id,
639
- z17.object({
640
- $REMOVE: z17.object({ addedAt: Timestamp }),
641
- $CHANGE_ROLES: z17.object({
642
- 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,
643
475
  addedAt: Timestamp
644
476
  })
645
477
  }).partial()
646
- ),
647
- version: Version,
648
- lastModified: Timestamp.optional()
649
- });
650
- var RawCompanyStaff = z17.object({
651
- ...MiniRawCompanyStaff.shape,
652
- members: z17.record(CompanyUser2.shape._id, CompanyUser2.omit({ _id: true }))
653
- });
654
- var transformStaff = (object17) => {
655
- return object17.transform((d) => {
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 CompanyStaff = transformStaff(RawCompanyStaff);
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 = z18.object({
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: RawCompanyStaff.omit({ _id: true })
515
+ staff: CompanyStaff2.omit({ _id: true })
722
516
  });
723
- var MutableCompanyDetails = z18.object({
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
- logo: CompanyIdentity.shape.logo.optional(),
737
- ...CompanyIdentity.shape.contact.pick({ email: true, phoneNumber: true }).shape,
738
- industry: CompanyIdentity.shape.industry,
739
- sector: CompanyIdentity.shape.sector,
740
- 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
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/appointment/components/Medical.ts
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/appointment/Appointment.ts
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 MiniAppointmentDetails = z21.object({
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) => AppointmentSeverity.keyof().safeParse(data).success),
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
- clientUid: ClientIdentity.shape._id,
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: z21.number().nullable()
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
- timeline: z21.object({
876
- scheduler: TimelineActivity,
877
- physAsst: TimelineActivity.or(z21.literal("none")),
878
- doctor: TimelineActivity,
879
- // Array used to accurately record payments made by the "a/r" and the "cshr"
880
- debtCollector: z21.array(
881
- z21.object({
882
- ...TimelineActivity.shape,
883
- // Payment uid -> collection time
884
- collectedPayments: z21.record(z21.uuidv4(), Timestamp)
885
- })
886
- ).min(1).nullable()
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 AppointmentDetails = MiniAppointmentDetails.omit({
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/findAvailableStaff.ts
929
- function findAvailableStaff(role, staff) {
930
- return Object.keys(role === "doc" ? staff.partnerMap : staff.members).filter(
931
- (uid) => {
932
- const user = staff.members[uid];
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
- // src/functions/findNextAvailableStaff.ts
939
- function findNextAvailableStaff(userUids, algorithm, staff) {
940
- let nextAvailable = null;
941
- if (userUids.length <= 1) return userUids[0] ?? null;
942
- switch (algorithm) {
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 nextAvailable;
814
+ return result;
963
815
  }
964
-
965
- // src/functions/scheduleAppointment.ts
966
- async function scheduleAppointment(request) {
967
- TODO("Revise this fn (some things have changed in the form etc.)");
968
- request.form = ScheduleAppointmentForm.parse(request.form);
969
- request.sender = UserDetails.parse(request.sender);
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
- throw new Error("Invalid service(s) selected.");
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 as v42 } from "uuid";
828
+ import { v4 } from "uuid";
1108
829
  function createNotifId(options) {
1109
- let nid = v42();
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
  };