@bizmap/sdk 0.0.120 → 0.0.122

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