@blackcode_sa/metaestetics-api 1.15.17-staging.7 → 1.15.17-staging.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +186 -32
- package/dist/index.mjs +186 -32
- package/package.json +1 -1
- package/src/config/tiers.config.ts +126 -21
- package/src/services/resource/resource.service.ts +85 -16
|
@@ -8,17 +8,30 @@ import type { PlanConfigDocument } from '../types/system/planConfig.types';
|
|
|
8
8
|
* what a given role (owner/admin/receptionist/assistant) can do.
|
|
9
9
|
* New roles can be added by extending the ClinicRole enum and adding
|
|
10
10
|
* a corresponding entry here.
|
|
11
|
+
*
|
|
12
|
+
* v2 (2026-03-31): Expanded from 23 to 40 permissions.
|
|
13
|
+
* - providers.manage → providers.create, providers.invite, providers.edit
|
|
14
|
+
* - staff.manage → staff.view, staff.edit, staff.invite, staff.delete, staff.viewTokens, staff.deleteTokens
|
|
15
|
+
* - Added: clinic.create, calendar.addEvent, calendar.editEvent, calendar.deleteEvent,
|
|
16
|
+
* appointments.reschedule, patients.viewDetails, patients.create, patients.manageTokens, billing.view
|
|
11
17
|
*/
|
|
12
18
|
export const PERMISSION_KEYS = {
|
|
13
|
-
//
|
|
19
|
+
// Clinic
|
|
14
20
|
'clinic.view': true,
|
|
15
21
|
'clinic.edit': true,
|
|
22
|
+
'clinic.create': true,
|
|
16
23
|
'reviews.view': true,
|
|
17
24
|
|
|
18
|
-
// Calendar
|
|
25
|
+
// Calendar
|
|
19
26
|
'calendar.view': true,
|
|
27
|
+
'calendar.addEvent': true,
|
|
28
|
+
'calendar.editEvent': true,
|
|
29
|
+
'calendar.deleteEvent': true,
|
|
30
|
+
|
|
31
|
+
// Appointments
|
|
20
32
|
'appointments.view': true,
|
|
21
33
|
'appointments.confirm': true,
|
|
34
|
+
'appointments.reschedule': true,
|
|
22
35
|
'appointments.cancel': true,
|
|
23
36
|
|
|
24
37
|
// Messaging
|
|
@@ -38,20 +51,31 @@ export const PERMISSION_KEYS = {
|
|
|
38
51
|
|
|
39
52
|
// Patients
|
|
40
53
|
'patients.view': true,
|
|
54
|
+
'patients.viewDetails': true,
|
|
55
|
+
'patients.create': true,
|
|
41
56
|
'patients.edit': true,
|
|
57
|
+
'patients.manageTokens': true,
|
|
42
58
|
|
|
43
59
|
// Providers (doctors, nurses, laser assistants, etc.)
|
|
44
60
|
'providers.view': true,
|
|
45
|
-
'providers.
|
|
61
|
+
'providers.create': true,
|
|
62
|
+
'providers.invite': true,
|
|
63
|
+
'providers.edit': true,
|
|
46
64
|
|
|
47
65
|
// Analytics
|
|
48
66
|
'analytics.view': true,
|
|
49
67
|
|
|
50
68
|
// Staff management
|
|
51
|
-
'staff.
|
|
69
|
+
'staff.view': true,
|
|
70
|
+
'staff.edit': true,
|
|
71
|
+
'staff.invite': true,
|
|
72
|
+
'staff.delete': true,
|
|
73
|
+
'staff.viewTokens': true,
|
|
74
|
+
'staff.deleteTokens': true,
|
|
52
75
|
|
|
53
76
|
// Settings / Admin
|
|
54
77
|
'settings.manage': true,
|
|
78
|
+
'billing.view': true,
|
|
55
79
|
'billing.manage': true,
|
|
56
80
|
} as const;
|
|
57
81
|
|
|
@@ -110,10 +134,15 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<ClinicRole, Record<string, boolean
|
|
|
110
134
|
[ClinicRole.OWNER]: {
|
|
111
135
|
'clinic.view': true,
|
|
112
136
|
'clinic.edit': true,
|
|
137
|
+
'clinic.create': true,
|
|
113
138
|
'reviews.view': true,
|
|
114
139
|
'calendar.view': true,
|
|
140
|
+
'calendar.addEvent': true,
|
|
141
|
+
'calendar.editEvent': true,
|
|
142
|
+
'calendar.deleteEvent': true,
|
|
115
143
|
'appointments.view': true,
|
|
116
144
|
'appointments.confirm': true,
|
|
145
|
+
'appointments.reschedule': true,
|
|
117
146
|
'appointments.cancel': true,
|
|
118
147
|
'messaging': true,
|
|
119
148
|
'procedures.view': true,
|
|
@@ -125,21 +154,37 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<ClinicRole, Record<string, boolean
|
|
|
125
154
|
'resources.edit': true,
|
|
126
155
|
'resources.delete': true,
|
|
127
156
|
'patients.view': true,
|
|
157
|
+
'patients.viewDetails': true,
|
|
158
|
+
'patients.create': true,
|
|
128
159
|
'patients.edit': true,
|
|
160
|
+
'patients.manageTokens': true,
|
|
129
161
|
'providers.view': true,
|
|
130
|
-
'providers.
|
|
162
|
+
'providers.create': true,
|
|
163
|
+
'providers.invite': true,
|
|
164
|
+
'providers.edit': true,
|
|
131
165
|
'analytics.view': true,
|
|
132
|
-
'staff.
|
|
166
|
+
'staff.view': true,
|
|
167
|
+
'staff.edit': true,
|
|
168
|
+
'staff.invite': true,
|
|
169
|
+
'staff.delete': true,
|
|
170
|
+
'staff.viewTokens': true,
|
|
171
|
+
'staff.deleteTokens': true,
|
|
133
172
|
'settings.manage': true,
|
|
173
|
+
'billing.view': true,
|
|
134
174
|
'billing.manage': true,
|
|
135
175
|
},
|
|
136
176
|
[ClinicRole.ADMIN]: {
|
|
137
177
|
'clinic.view': true,
|
|
138
178
|
'clinic.edit': true,
|
|
179
|
+
'clinic.create': false,
|
|
139
180
|
'reviews.view': true,
|
|
140
181
|
'calendar.view': true,
|
|
182
|
+
'calendar.addEvent': true,
|
|
183
|
+
'calendar.editEvent': true,
|
|
184
|
+
'calendar.deleteEvent': true,
|
|
141
185
|
'appointments.view': true,
|
|
142
186
|
'appointments.confirm': true,
|
|
187
|
+
'appointments.reschedule': true,
|
|
143
188
|
'appointments.cancel': true,
|
|
144
189
|
'messaging': true,
|
|
145
190
|
'procedures.view': true,
|
|
@@ -151,21 +196,37 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<ClinicRole, Record<string, boolean
|
|
|
151
196
|
'resources.edit': true,
|
|
152
197
|
'resources.delete': true,
|
|
153
198
|
'patients.view': true,
|
|
199
|
+
'patients.viewDetails': true,
|
|
200
|
+
'patients.create': true,
|
|
154
201
|
'patients.edit': true,
|
|
202
|
+
'patients.manageTokens': true,
|
|
155
203
|
'providers.view': true,
|
|
156
|
-
'providers.
|
|
204
|
+
'providers.create': true,
|
|
205
|
+
'providers.invite': true,
|
|
206
|
+
'providers.edit': true,
|
|
157
207
|
'analytics.view': true,
|
|
158
|
-
'staff.
|
|
208
|
+
'staff.view': true,
|
|
209
|
+
'staff.edit': false,
|
|
210
|
+
'staff.invite': false,
|
|
211
|
+
'staff.delete': false,
|
|
212
|
+
'staff.viewTokens': false,
|
|
213
|
+
'staff.deleteTokens': false,
|
|
159
214
|
'settings.manage': true,
|
|
215
|
+
'billing.view': true,
|
|
160
216
|
'billing.manage': false,
|
|
161
217
|
},
|
|
162
218
|
[ClinicRole.RECEPTIONIST]: {
|
|
163
219
|
'clinic.view': true,
|
|
164
220
|
'clinic.edit': false,
|
|
221
|
+
'clinic.create': false,
|
|
165
222
|
'reviews.view': true,
|
|
166
223
|
'calendar.view': true,
|
|
224
|
+
'calendar.addEvent': true,
|
|
225
|
+
'calendar.editEvent': false,
|
|
226
|
+
'calendar.deleteEvent': false,
|
|
167
227
|
'appointments.view': true,
|
|
168
228
|
'appointments.confirm': true,
|
|
229
|
+
'appointments.reschedule': true,
|
|
169
230
|
'appointments.cancel': false,
|
|
170
231
|
'messaging': true,
|
|
171
232
|
'procedures.view': true,
|
|
@@ -177,21 +238,37 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<ClinicRole, Record<string, boolean
|
|
|
177
238
|
'resources.edit': false,
|
|
178
239
|
'resources.delete': false,
|
|
179
240
|
'patients.view': true,
|
|
241
|
+
'patients.viewDetails': true,
|
|
242
|
+
'patients.create': true,
|
|
180
243
|
'patients.edit': false,
|
|
244
|
+
'patients.manageTokens': false,
|
|
181
245
|
'providers.view': true,
|
|
182
|
-
'providers.
|
|
246
|
+
'providers.create': false,
|
|
247
|
+
'providers.invite': false,
|
|
248
|
+
'providers.edit': false,
|
|
183
249
|
'analytics.view': false,
|
|
184
|
-
'staff.
|
|
250
|
+
'staff.view': false,
|
|
251
|
+
'staff.edit': false,
|
|
252
|
+
'staff.invite': false,
|
|
253
|
+
'staff.delete': false,
|
|
254
|
+
'staff.viewTokens': false,
|
|
255
|
+
'staff.deleteTokens': false,
|
|
185
256
|
'settings.manage': false,
|
|
257
|
+
'billing.view': false,
|
|
186
258
|
'billing.manage': false,
|
|
187
259
|
},
|
|
188
260
|
[ClinicRole.ASSISTANT]: {
|
|
189
261
|
'clinic.view': true,
|
|
190
262
|
'clinic.edit': false,
|
|
263
|
+
'clinic.create': false,
|
|
191
264
|
'reviews.view': true,
|
|
192
265
|
'calendar.view': true,
|
|
266
|
+
'calendar.addEvent': false,
|
|
267
|
+
'calendar.editEvent': false,
|
|
268
|
+
'calendar.deleteEvent': false,
|
|
193
269
|
'appointments.view': true,
|
|
194
270
|
'appointments.confirm': false,
|
|
271
|
+
'appointments.reschedule': false,
|
|
195
272
|
'appointments.cancel': false,
|
|
196
273
|
'messaging': false,
|
|
197
274
|
'procedures.view': true,
|
|
@@ -203,12 +280,23 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<ClinicRole, Record<string, boolean
|
|
|
203
280
|
'resources.edit': false,
|
|
204
281
|
'resources.delete': false,
|
|
205
282
|
'patients.view': true,
|
|
283
|
+
'patients.viewDetails': false,
|
|
284
|
+
'patients.create': false,
|
|
206
285
|
'patients.edit': false,
|
|
286
|
+
'patients.manageTokens': false,
|
|
207
287
|
'providers.view': true,
|
|
208
|
-
'providers.
|
|
288
|
+
'providers.create': false,
|
|
289
|
+
'providers.invite': false,
|
|
290
|
+
'providers.edit': false,
|
|
209
291
|
'analytics.view': false,
|
|
210
|
-
'staff.
|
|
292
|
+
'staff.view': false,
|
|
293
|
+
'staff.edit': false,
|
|
294
|
+
'staff.invite': false,
|
|
295
|
+
'staff.delete': false,
|
|
296
|
+
'staff.viewTokens': false,
|
|
297
|
+
'staff.deleteTokens': false,
|
|
211
298
|
'settings.manage': false,
|
|
299
|
+
'billing.view': false,
|
|
212
300
|
'billing.manage': false,
|
|
213
301
|
},
|
|
214
302
|
};
|
|
@@ -312,28 +400,44 @@ export const PERMISSION_LABELS: Record<
|
|
|
312
400
|
> = {
|
|
313
401
|
'clinic.view': { label: 'View Clinic', description: 'See clinic information and profile', category: 'Clinic' },
|
|
314
402
|
'clinic.edit': { label: 'Edit Clinic', description: 'Modify clinic settings and profile', category: 'Clinic' },
|
|
403
|
+
'clinic.create': { label: 'Create Clinic', description: 'Create new clinic branches', category: 'Clinic' },
|
|
315
404
|
'reviews.view': { label: 'View Reviews', description: 'See patient reviews and ratings', category: 'Clinic' },
|
|
316
405
|
'calendar.view': { label: 'View Calendar', description: 'See the clinic calendar and schedules', category: 'Calendar' },
|
|
406
|
+
'calendar.addEvent': { label: 'Add Events', description: 'Create appointments and blocking events on the calendar', category: 'Calendar' },
|
|
407
|
+
'calendar.editEvent': { label: 'Edit Events', description: 'Edit blocking events on the calendar', category: 'Calendar' },
|
|
408
|
+
'calendar.deleteEvent': { label: 'Delete Events', description: 'Delete blocking events from the calendar', category: 'Calendar' },
|
|
317
409
|
'appointments.view': { label: 'View Appointments', description: 'See appointment list and details', category: 'Appointments' },
|
|
318
|
-
'appointments.confirm': { label: 'Confirm Appointments', description: 'Confirm
|
|
410
|
+
'appointments.confirm': { label: 'Confirm Appointments', description: 'Confirm, reject, check in, and manage appointment status', category: 'Appointments' },
|
|
411
|
+
'appointments.reschedule': { label: 'Reschedule Appointments', description: 'Reschedule confirmed appointments to a new time', category: 'Appointments' },
|
|
319
412
|
'appointments.cancel': { label: 'Cancel Appointments', description: 'Cancel existing appointments', category: 'Appointments' },
|
|
320
413
|
'messaging': { label: 'Messaging', description: 'Send and receive messages with patients', category: 'Messaging' },
|
|
321
414
|
'procedures.view': { label: 'View Procedures', description: 'See the procedures catalog', category: 'Procedures' },
|
|
322
|
-
'procedures.create': { label: 'Create Procedures', description: 'Add new procedures to
|
|
415
|
+
'procedures.create': { label: 'Create Procedures', description: 'Add new procedures and assign to practitioners', category: 'Procedures' },
|
|
323
416
|
'procedures.edit': { label: 'Edit Procedures', description: 'Modify existing procedures', category: 'Procedures' },
|
|
324
417
|
'procedures.delete': { label: 'Delete Procedures', description: 'Remove procedures from the catalog', category: 'Procedures' },
|
|
325
418
|
'resources.view': { label: 'View Resources', description: 'See clinic resources and equipment', category: 'Resources' },
|
|
326
419
|
'resources.create': { label: 'Create Resources', description: 'Add new resources', category: 'Resources' },
|
|
327
|
-
'resources.edit': { label: 'Edit Resources', description: 'Modify existing resources', category: 'Resources' },
|
|
328
|
-
'resources.delete': { label: 'Delete Resources', description: '
|
|
329
|
-
'patients.view': { label: 'View Patients', description: 'See patient list
|
|
330
|
-
'patients.
|
|
420
|
+
'resources.edit': { label: 'Edit Resources', description: 'Modify existing resources and activate instances', category: 'Resources' },
|
|
421
|
+
'resources.delete': { label: 'Delete Resources', description: 'Deactivate resources and delete instances', category: 'Resources' },
|
|
422
|
+
'patients.view': { label: 'View Patients', description: 'See patient list', category: 'Patients' },
|
|
423
|
+
'patients.viewDetails': { label: 'View Patient Details', description: 'Open patient file and detailed profile', category: 'Patients' },
|
|
424
|
+
'patients.create': { label: 'Create Patients', description: 'Add new patients', category: 'Patients' },
|
|
425
|
+
'patients.edit': { label: 'Edit Patients', description: 'Modify patient records and schedule appointments', category: 'Patients' },
|
|
426
|
+
'patients.manageTokens': { label: 'Manage Patient Tokens', description: 'Create and manage patient invitation tokens', category: 'Patients' },
|
|
331
427
|
'providers.view': { label: 'View Providers', description: 'See the practitioners/providers list', category: 'Providers' },
|
|
332
|
-
'providers.
|
|
428
|
+
'providers.create': { label: 'Create Providers', description: 'Add new practitioners to the clinic', category: 'Providers' },
|
|
429
|
+
'providers.invite': { label: 'Invite Providers', description: 'Invite existing practitioners to the clinic', category: 'Providers' },
|
|
430
|
+
'providers.edit': { label: 'Edit Providers', description: 'Edit practitioner profiles and manage tokens', category: 'Providers' },
|
|
333
431
|
'analytics.view': { label: 'View Analytics', description: 'Access analytics dashboards and reports', category: 'Analytics' },
|
|
334
|
-
'staff.
|
|
432
|
+
'staff.view': { label: 'View Staff', description: 'See staff management page and team members', category: 'Staff Management' },
|
|
433
|
+
'staff.edit': { label: 'Edit Staff', description: 'Edit roles, permissions, and clinic assignments', category: 'Staff Management' },
|
|
434
|
+
'staff.invite': { label: 'Invite Staff', description: 'Invite new team members', category: 'Staff Management' },
|
|
435
|
+
'staff.delete': { label: 'Delete Staff', description: 'Remove team members and cancel invitations', category: 'Staff Management' },
|
|
436
|
+
'staff.viewTokens': { label: 'View Tokens', description: 'See registration tokens', category: 'Staff Management' },
|
|
437
|
+
'staff.deleteTokens': { label: 'Delete Tokens', description: 'Delete registration tokens', category: 'Staff Management' },
|
|
335
438
|
'settings.manage': { label: 'Manage Settings', description: 'Modify clinic group settings', category: 'Administration' },
|
|
336
|
-
'billing.
|
|
439
|
+
'billing.view': { label: 'View Billing', description: 'View subscription plans and billing history', category: 'Administration' },
|
|
440
|
+
'billing.manage': { label: 'Manage Billing', description: 'Manage subscriptions, upgrades, and payments', category: 'Administration' },
|
|
337
441
|
};
|
|
338
442
|
|
|
339
443
|
/** All unique permission categories in display order. */
|
|
@@ -347,6 +451,7 @@ export const PERMISSION_CATEGORIES = [
|
|
|
347
451
|
'Patients',
|
|
348
452
|
'Providers',
|
|
349
453
|
'Analytics',
|
|
454
|
+
'Staff Management',
|
|
350
455
|
'Administration',
|
|
351
456
|
] as const;
|
|
352
457
|
|
|
@@ -228,34 +228,61 @@ export class ResourceService extends BaseService {
|
|
|
228
228
|
// Handle quantity change
|
|
229
229
|
if (data.quantity !== undefined && data.quantity !== existing.quantity) {
|
|
230
230
|
if (data.quantity > existing.quantity) {
|
|
231
|
-
//
|
|
231
|
+
// Increasing quantity — first reactivate inactive instances, then create new ones if needed
|
|
232
|
+
const allInstances = await this.getResourceInstances(clinicBranchId, resourceId);
|
|
233
|
+
const activeInstances = allInstances.filter(i => i.status === ResourceStatus.ACTIVE);
|
|
234
|
+
const inactiveInstances = allInstances.filter(i => i.status === ResourceStatus.INACTIVE);
|
|
235
|
+
|
|
236
|
+
const needed = data.quantity - activeInstances.length;
|
|
232
237
|
const batch = writeBatch(this.db);
|
|
233
238
|
const now = serverTimestamp();
|
|
234
239
|
const resourceName = data.name || existing.name;
|
|
235
240
|
|
|
236
|
-
|
|
241
|
+
let reactivated = 0;
|
|
242
|
+
|
|
243
|
+
// Step 1: Reactivate inactive instances (sorted by index, lowest first)
|
|
244
|
+
const sortedInactive = [...inactiveInstances].sort((a, b) => a.index - b.index);
|
|
245
|
+
for (const instance of sortedInactive) {
|
|
246
|
+
if (reactivated >= needed) break;
|
|
237
247
|
const instanceRef = doc(
|
|
238
|
-
this.getInstancesRef(clinicBranchId, resourceId)
|
|
248
|
+
this.getInstancesRef(clinicBranchId, resourceId),
|
|
249
|
+
instance.id
|
|
239
250
|
);
|
|
240
|
-
|
|
241
|
-
ResourceInstance,
|
|
242
|
-
"createdAt" | "updatedAt"
|
|
243
|
-
> & { createdAt: any; updatedAt: any } = {
|
|
244
|
-
id: instanceRef.id,
|
|
245
|
-
resourceId,
|
|
246
|
-
clinicBranchId,
|
|
247
|
-
label: `${resourceName} #${i}`,
|
|
248
|
-
index: i,
|
|
251
|
+
batch.update(instanceRef, {
|
|
249
252
|
status: ResourceStatus.ACTIVE,
|
|
250
|
-
createdAt: now,
|
|
251
253
|
updatedAt: now,
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
+
});
|
|
255
|
+
reactivated++;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Step 2: Create new instances for any remaining needed
|
|
259
|
+
const remaining = needed - reactivated;
|
|
260
|
+
if (remaining > 0) {
|
|
261
|
+
// Find the highest existing index to continue numbering
|
|
262
|
+
const maxIndex = allInstances.reduce((max, i) => Math.max(max, i.index), 0);
|
|
263
|
+
for (let i = 1; i <= remaining; i++) {
|
|
264
|
+
const instanceRef = doc(
|
|
265
|
+
this.getInstancesRef(clinicBranchId, resourceId)
|
|
266
|
+
);
|
|
267
|
+
const instanceData: Omit<
|
|
268
|
+
ResourceInstance,
|
|
269
|
+
"createdAt" | "updatedAt"
|
|
270
|
+
> & { createdAt: any; updatedAt: any } = {
|
|
271
|
+
id: instanceRef.id,
|
|
272
|
+
resourceId,
|
|
273
|
+
clinicBranchId,
|
|
274
|
+
label: `${resourceName} #${maxIndex + i}`,
|
|
275
|
+
index: maxIndex + i,
|
|
276
|
+
status: ResourceStatus.ACTIVE,
|
|
277
|
+
createdAt: now,
|
|
278
|
+
updatedAt: now,
|
|
279
|
+
};
|
|
280
|
+
batch.set(instanceRef, instanceData);
|
|
281
|
+
}
|
|
254
282
|
}
|
|
255
283
|
|
|
256
284
|
updateData.quantity = data.quantity;
|
|
257
285
|
|
|
258
|
-
// Update resource doc in the batch too
|
|
259
286
|
const resourceDocRef = this.getResourceDocRef(
|
|
260
287
|
clinicBranchId,
|
|
261
288
|
resourceId
|
|
@@ -337,6 +364,48 @@ export class ResourceService extends BaseService {
|
|
|
337
364
|
});
|
|
338
365
|
}
|
|
339
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Hard deletes a specific instance and all its calendar events.
|
|
369
|
+
* Only works on INACTIVE instances with no future bookings.
|
|
370
|
+
*/
|
|
371
|
+
async hardDeleteInstance(
|
|
372
|
+
clinicBranchId: string,
|
|
373
|
+
resourceId: string,
|
|
374
|
+
instanceId: string
|
|
375
|
+
): Promise<void> {
|
|
376
|
+
// Verify instance exists and is inactive
|
|
377
|
+
const instanceRef = doc(
|
|
378
|
+
this.getInstancesRef(clinicBranchId, resourceId),
|
|
379
|
+
instanceId
|
|
380
|
+
);
|
|
381
|
+
const instanceSnap = await getDoc(instanceRef);
|
|
382
|
+
if (!instanceSnap.exists()) throw new Error("Instance not found");
|
|
383
|
+
|
|
384
|
+
const instance = instanceSnap.data() as ResourceInstance;
|
|
385
|
+
if (instance.status === ResourceStatus.ACTIVE) {
|
|
386
|
+
throw new Error("Cannot hard-delete an active instance. Deactivate it first.");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Check for future bookings
|
|
390
|
+
const hasFutureBookings = await this.instanceHasFutureBookings(
|
|
391
|
+
clinicBranchId, resourceId, instanceId
|
|
392
|
+
);
|
|
393
|
+
if (hasFutureBookings) {
|
|
394
|
+
throw new Error("Cannot delete instance with future bookings.");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Delete all calendar events for this instance
|
|
398
|
+
const calendarRef = this.getInstanceCalendarRef(
|
|
399
|
+
clinicBranchId, resourceId, instanceId
|
|
400
|
+
);
|
|
401
|
+
const calendarSnap = await getDocs(calendarRef);
|
|
402
|
+
|
|
403
|
+
const batch = writeBatch(this.db);
|
|
404
|
+
calendarSnap.docs.forEach(d => batch.delete(d.ref));
|
|
405
|
+
batch.delete(instanceRef);
|
|
406
|
+
await batch.commit();
|
|
407
|
+
}
|
|
408
|
+
|
|
340
409
|
/**
|
|
341
410
|
* Gets all instances for a resource
|
|
342
411
|
*/
|