@blackcode_sa/metaestetics-api 1.15.16 → 1.15.17-staging.1
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/admin/index.d.mts +377 -222
- package/dist/admin/index.d.ts +377 -222
- package/dist/admin/index.js +625 -206
- package/dist/admin/index.mjs +624 -206
- package/dist/backoffice/index.d.mts +24 -0
- package/dist/backoffice/index.d.ts +24 -0
- package/dist/index.d.mts +292 -4
- package/dist/index.d.ts +292 -4
- package/dist/index.js +1142 -630
- package/dist/index.mjs +1137 -617
- package/package.json +2 -1
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +151 -129
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +2137 -2091
- package/src/admin/aggregation/appointment/index.ts +1 -1
- package/src/admin/aggregation/clinic/README.md +52 -52
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +966 -966
- package/src/admin/aggregation/clinic/index.ts +1 -1
- package/src/admin/aggregation/forms/README.md +13 -13
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
- package/src/admin/aggregation/forms/index.ts +1 -1
- package/src/admin/aggregation/index.ts +8 -8
- package/src/admin/aggregation/patient/README.md +27 -27
- package/src/admin/aggregation/patient/index.ts +1 -1
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
- package/src/admin/aggregation/practitioner/README.md +42 -42
- package/src/admin/aggregation/practitioner/index.ts +1 -1
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
- package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
- package/src/admin/aggregation/procedure/README.md +43 -43
- package/src/admin/aggregation/procedure/index.ts +1 -1
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
- package/src/admin/aggregation/reviews/index.ts +1 -1
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
- package/src/admin/analytics/analytics.admin.service.ts +278 -278
- package/src/admin/analytics/index.ts +2 -2
- package/src/admin/booking/README.md +184 -125
- package/src/admin/booking/booking.admin.ts +1330 -1073
- package/src/admin/booking/booking.calculator.ts +850 -712
- package/src/admin/booking/booking.types.ts +76 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +62 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +2 -1
- package/src/admin/calendar/resource-calendar.admin.ts +198 -0
- package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
- package/src/admin/documentation-templates/index.ts +1 -1
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
- package/src/admin/free-consultation/index.ts +1 -1
- package/src/admin/index.ts +83 -83
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +139 -139
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +1253 -1253
- package/src/admin/mailing/appointment/index.ts +1 -1
- package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
- package/src/admin/mailing/base.mailing.service.ts +208 -208
- package/src/admin/mailing/clinicWelcome/clinicWelcome.mailing.ts +292 -292
- package/src/admin/mailing/clinicWelcome/index.ts +1 -1
- package/src/admin/mailing/clinicWelcome/templates/welcome.template.ts +225 -225
- package/src/admin/mailing/index.ts +5 -5
- package/src/admin/mailing/patientInvite/index.ts +2 -2
- package/src/admin/mailing/patientInvite/patientInvite.mailing.ts +415 -415
- package/src/admin/mailing/patientInvite/templates/invitation.template.ts +105 -105
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
- package/src/admin/mailing/practitionerInvite/index.ts +2 -2
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
- package/src/admin/notifications/index.ts +1 -1
- package/src/admin/notifications/notifications.admin.ts +818 -818
- package/src/admin/requirements/README.md +128 -128
- package/src/admin/requirements/index.ts +1 -1
- package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
- package/src/admin/users/index.ts +1 -1
- package/src/admin/users/user-profile.admin.ts +405 -405
- package/src/backoffice/constants/certification.constants.ts +13 -13
- package/src/backoffice/constants/index.ts +1 -1
- package/src/backoffice/errors/backoffice.errors.ts +181 -181
- package/src/backoffice/errors/index.ts +1 -1
- package/src/backoffice/expo-safe/README.md +26 -26
- package/src/backoffice/expo-safe/index.ts +41 -41
- package/src/backoffice/index.ts +5 -5
- package/src/backoffice/services/FIXES_README.md +102 -102
- package/src/backoffice/services/README.md +57 -57
- package/src/backoffice/services/analytics.service.proposal.md +863 -863
- package/src/backoffice/services/analytics.service.summary.md +143 -143
- package/src/backoffice/services/brand.service.ts +260 -260
- package/src/backoffice/services/category.service.ts +384 -384
- package/src/backoffice/services/constants.service.ts +385 -385
- package/src/backoffice/services/documentation-template.service.ts +202 -202
- package/src/backoffice/services/index.ts +10 -10
- package/src/backoffice/services/migrate-products.ts +116 -116
- package/src/backoffice/services/product.service.ts +557 -557
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +461 -461
- package/src/backoffice/services/technology.service.ts +1153 -1153
- package/src/backoffice/types/README.md +12 -12
- package/src/backoffice/types/admin-constants.types.ts +69 -69
- package/src/backoffice/types/brand.types.ts +29 -29
- package/src/backoffice/types/category.types.ts +67 -67
- package/src/backoffice/types/documentation-templates.types.ts +28 -28
- package/src/backoffice/types/index.ts +10 -10
- package/src/backoffice/types/procedure-product.types.ts +38 -38
- package/src/backoffice/types/product.types.ts +239 -239
- package/src/backoffice/types/requirement.types.ts +63 -63
- package/src/backoffice/types/static/README.md +18 -18
- package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
- package/src/backoffice/types/static/certification.types.ts +37 -37
- package/src/backoffice/types/static/contraindication.types.ts +19 -19
- package/src/backoffice/types/static/index.ts +6 -6
- package/src/backoffice/types/static/pricing.types.ts +16 -16
- package/src/backoffice/types/static/procedure-family.types.ts +14 -14
- package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
- package/src/backoffice/types/subcategory.types.ts +34 -34
- package/src/backoffice/types/technology.types.ts +168 -168
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -164
- package/src/config/__mocks__/firebase.ts +99 -99
- package/src/config/firebase.ts +78 -78
- package/src/config/index.ts +17 -17
- package/src/config/tiers.config.ts +255 -229
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +211 -211
- package/src/errors/clinic.errors.ts +32 -32
- package/src/errors/firebase.errors.ts +47 -47
- package/src/errors/user.errors.ts +99 -99
- package/src/index.backup.ts +407 -407
- package/src/index.ts +6 -6
- package/src/locales/en.ts +31 -31
- package/src/recommender/admin/index.ts +1 -1
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
- package/src/recommender/front/index.ts +1 -1
- package/src/recommender/front/services/onboarding.service.ts +5 -5
- package/src/recommender/front/services/recommender.service.ts +3 -3
- package/src/recommender/index.ts +1 -1
- package/src/services/PATIENTAUTH.MD +197 -197
- package/src/services/README.md +106 -106
- package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
- package/src/services/__tests__/auth/auth.setup.ts +298 -298
- package/src/services/__tests__/auth.service.test.ts +310 -310
- package/src/services/__tests__/base.service.test.ts +36 -36
- package/src/services/__tests__/user.service.test.ts +530 -530
- package/src/services/analytics/ARCHITECTURE.md +199 -199
- package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
- package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
- package/src/services/analytics/QUICK_START.md +393 -393
- package/src/services/analytics/README.md +304 -304
- package/src/services/analytics/SUMMARY.md +141 -141
- package/src/services/analytics/TRENDS.md +380 -380
- package/src/services/analytics/USAGE_GUIDE.md +518 -518
- package/src/services/analytics/analytics-cloud.service.ts +222 -222
- package/src/services/analytics/analytics.service.ts +2148 -2148
- package/src/services/analytics/index.ts +4 -4
- package/src/services/analytics/review-analytics.service.ts +941 -941
- package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
- package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
- package/src/services/analytics/utils/grouping.utils.ts +434 -434
- package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
- package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
- package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2943 -2941
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +620 -620
- package/src/services/appointment/utils/extended-procedure.utils.ts +354 -354
- package/src/services/appointment/utils/form-initialization.utils.ts +516 -516
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +468 -468
- package/src/services/appointment/utils/zone-photo.utils.ts +302 -302
- package/src/services/auth/auth.service.ts +1435 -1435
- package/src/services/auth/auth.v2.service.ts +961 -961
- package/src/services/auth/index.ts +7 -7
- package/src/services/auth/utils/error.utils.ts +90 -90
- package/src/services/auth/utils/firebase.utils.ts +49 -49
- package/src/services/auth/utils/index.ts +21 -21
- package/src/services/auth/utils/practitioner.utils.ts +125 -125
- package/src/services/base.service.ts +41 -41
- package/src/services/calendar/calendar.service.ts +1077 -1077
- package/src/services/calendar/calendar.v2.service.ts +1693 -1693
- package/src/services/calendar/calendar.v3.service.ts +313 -313
- package/src/services/calendar/externalCalendar.service.ts +178 -178
- package/src/services/calendar/index.ts +5 -5
- package/src/services/calendar/synced-calendars.service.ts +743 -743
- package/src/services/calendar/utils/appointment.utils.ts +265 -265
- package/src/services/calendar/utils/calendar-event.utils.ts +676 -676
- package/src/services/calendar/utils/clinic.utils.ts +237 -237
- package/src/services/calendar/utils/docs.utils.ts +157 -157
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
- package/src/services/calendar/utils/index.ts +8 -8
- package/src/services/calendar/utils/patient.utils.ts +198 -198
- package/src/services/calendar/utils/practitioner.utils.ts +221 -221
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
- package/src/services/clinic/README.md +204 -204
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +265 -265
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +222 -222
- package/src/services/clinic/__tests__/clinic.service.test.ts +302 -302
- package/src/services/clinic/billing-transactions.service.ts +217 -217
- package/src/services/clinic/clinic-admin.service.ts +202 -202
- package/src/services/clinic/clinic-group.service.ts +310 -310
- package/src/services/clinic/clinic.service.ts +720 -720
- package/src/services/clinic/index.ts +5 -5
- package/src/services/clinic/practitioner-invite.service.ts +519 -519
- package/src/services/clinic/utils/admin.utils.ts +551 -551
- package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
- package/src/services/clinic/utils/clinic.utils.ts +1023 -1023
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +462 -462
- package/src/services/clinic/utils/index.ts +10 -10
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +83 -83
- package/src/services/clinic/utils/tag.utils.ts +124 -124
- package/src/services/documentation-templates/documentation-template.service.ts +537 -537
- package/src/services/documentation-templates/filled-document.service.ts +597 -597
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +16 -15
- package/src/services/media/index.ts +1 -1
- package/src/services/media/media.service.ts +418 -418
- package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/notification.service.ts +215 -215
- package/src/services/patient/README.md +48 -48
- package/src/services/patient/To-Do.md +43 -43
- package/src/services/patient/__tests__/patient.service.test.ts +286 -286
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +1021 -1021
- package/src/services/patient/patientRequirements.service.ts +309 -309
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/body-assessment.utils.ts +159 -159
- package/src/services/patient/utils/clinic.utils.ts +159 -159
- package/src/services/patient/utils/docs.utils.ts +142 -142
- package/src/services/patient/utils/hair-scalp-assessment.utils.ts +158 -158
- package/src/services/patient/utils/index.ts +9 -9
- package/src/services/patient/utils/location.utils.ts +126 -126
- package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
- package/src/services/patient/utils/medical.utils.ts +458 -458
- package/src/services/patient/utils/practitioner.utils.ts +260 -260
- package/src/services/patient/utils/pre-surgical-assessment.utils.ts +161 -161
- package/src/services/patient/utils/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- package/src/services/patient/utils/skin-quality-assessment.utils.ts +160 -160
- package/src/services/patient/utils/token.utils.ts +211 -211
- package/src/services/practitioner/README.md +145 -145
- package/src/services/practitioner/index.ts +1 -1
- package/src/services/practitioner/practitioner.service.ts +2355 -2354
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +2521 -2521
- package/src/services/resource/README.md +119 -0
- package/src/services/resource/index.ts +1 -0
- package/src/services/resource/resource.service.ts +555 -0
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +745 -745
- package/src/services/tier-enforcement.ts +240 -240
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +533 -533
- package/src/services/user/user.v2.service.ts +467 -467
- package/src/types/analytics/analytics.types.ts +597 -597
- package/src/types/analytics/grouped-analytics.types.ts +173 -173
- package/src/types/analytics/index.ts +4 -4
- package/src/types/analytics/stored-analytics.types.ts +137 -137
- package/src/types/appointment/index.ts +524 -517
- package/src/types/calendar/index.ts +261 -260
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +530 -529
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/rbac.types.ts +64 -63
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +50 -47
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +300 -300
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/body-assessment.types.ts +93 -93
- package/src/types/patient/hair-scalp-assessment.types.ts +98 -98
- package/src/types/patient/index.ts +279 -279
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/pre-surgical-assessment.types.ts +95 -95
- package/src/types/patient/skin-quality-assessment.types.ts +105 -105
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +208 -208
- package/src/types/procedure/index.ts +189 -183
- package/src/types/profile/index.ts +39 -39
- package/src/types/resource/README.md +153 -0
- package/src/types/resource/index.ts +199 -0
- package/src/types/reviews/index.ts +132 -132
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +60 -60
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/README.md +94 -0
- package/src/validations/appointment.schema.ts +589 -589
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +494 -494
- package/src/validations/common.schema.ts +25 -25
- package/src/validations/documentation-templates/index.ts +1 -1
- package/src/validations/documentation-templates/template.schema.ts +220 -220
- package/src/validations/documentation-templates.schema.ts +10 -10
- package/src/validations/index.ts +21 -20
- package/src/validations/media.schema.ts +10 -10
- package/src/validations/notification.schema.ts +90 -90
- package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
- package/src/validations/patient/body-assessment.schema.ts +82 -82
- package/src/validations/patient/hair-scalp-assessment.schema.ts +70 -70
- package/src/validations/patient/medical-info.schema.ts +177 -177
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/pre-surgical-assessment.schema.ts +78 -78
- package/src/validations/patient/skin-quality-assessment.schema.ts +70 -70
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -217
- package/src/validations/practitioner.schema.ts +224 -224
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +136 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/resource.schema.ts +57 -0
- package/src/validations/reviews.schema.ts +195 -195
- package/src/validations/schemas.ts +109 -109
- package/src/validations/shared.schema.ts +78 -78
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Resource Service (`Api/src/services/resource/`)
|
|
2
|
+
|
|
3
|
+
Server-side service for managing clinic resources and their instances. Extends `BaseService` and provides full CRUD operations backed by Firestore.
|
|
4
|
+
|
|
5
|
+
## File: `resource.service.ts`
|
|
6
|
+
|
|
7
|
+
### Class: `ResourceService extends BaseService`
|
|
8
|
+
|
|
9
|
+
#### Constructor
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
constructor(db: Firestore, auth: Auth, app: FirebaseApp)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Inherits Firestore, Auth, and Firebase App from `BaseService`.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
### Methods
|
|
20
|
+
|
|
21
|
+
#### `createResource(data: CreateResourceData): Promise<Resource>`
|
|
22
|
+
|
|
23
|
+
Creates a new resource and auto-generates instances.
|
|
24
|
+
|
|
25
|
+
- Validates input with `createResourceSchema` (Zod)
|
|
26
|
+
- Creates the `Resource` document under `clinics/{clinicBranchId}/resources/{id}`
|
|
27
|
+
- Batch-creates `N` `ResourceInstance` subdocuments (where N = `data.quantity`)
|
|
28
|
+
- Each instance gets label `"{name} #{index}"` (1-based)
|
|
29
|
+
- Returns the created `Resource`
|
|
30
|
+
|
|
31
|
+
#### `getResource(clinicBranchId: string, resourceId: string): Promise<Resource | null>`
|
|
32
|
+
|
|
33
|
+
Fetches a single resource by ID.
|
|
34
|
+
|
|
35
|
+
#### `getResourcesByClinic(clinicBranchId: string): Promise<Resource[]>`
|
|
36
|
+
|
|
37
|
+
Fetches all resources for a clinic branch.
|
|
38
|
+
|
|
39
|
+
#### `getActiveResourcesByClinic(clinicBranchId: string): Promise<Resource[]>`
|
|
40
|
+
|
|
41
|
+
Fetches only active resources (`status === 'active'`) for a clinic branch.
|
|
42
|
+
|
|
43
|
+
#### `updateResource(clinicBranchId: string, resourceId: string, data: UpdateResourceData): Promise<Resource>`
|
|
44
|
+
|
|
45
|
+
Updates a resource. Handles quantity changes:
|
|
46
|
+
|
|
47
|
+
- **Quantity increase**: Creates new instances starting from `currentQuantity + 1`
|
|
48
|
+
- **Quantity decrease**: Checks if affected instances (from the end) have future bookings. If any do, the operation is **rejected** with an error. Otherwise, instances are deactivated (status set to `inactive`).
|
|
49
|
+
|
|
50
|
+
#### `deleteResource(clinicBranchId: string, resourceId: string): Promise<void>`
|
|
51
|
+
|
|
52
|
+
Soft-deletes a resource by setting `status: 'inactive'`. Does not remove documents from Firestore.
|
|
53
|
+
|
|
54
|
+
#### `getResourceInstances(clinicBranchId: string, resourceId: string): Promise<ResourceInstance[]>`
|
|
55
|
+
|
|
56
|
+
Fetches all instances for a resource, ordered by `index`.
|
|
57
|
+
|
|
58
|
+
#### `getActiveResourceInstances(clinicBranchId: string, resourceId: string): Promise<ResourceInstance[]>`
|
|
59
|
+
|
|
60
|
+
Fetches only active instances (`status === 'active'`), ordered by `index`.
|
|
61
|
+
|
|
62
|
+
#### `getResourceCalendarEvents(clinicBranchId, resourceId, instanceId, start, end): Promise<ResourceCalendarEvent[]>`
|
|
63
|
+
|
|
64
|
+
Fetches calendar events for a specific instance within a time range. Used by the booking engine to check instance availability.
|
|
65
|
+
|
|
66
|
+
#### `instanceHasFutureBookings(clinicBranchId, resourceId, instanceId): Promise<boolean>`
|
|
67
|
+
|
|
68
|
+
Checks if an instance has any future booking events. Used when decreasing resource quantity to prevent deactivating instances with active bookings.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### Blocking Event Methods
|
|
73
|
+
|
|
74
|
+
CRUD operations for managing blocking events on resource instance calendars. Blocking events mark an instance as unavailable for a time period (maintenance, out of order, etc.).
|
|
75
|
+
|
|
76
|
+
#### `createResourceBlockingEvent(params: CreateResourceBlockingEventParams): Promise<ResourceCalendarEvent>`
|
|
77
|
+
|
|
78
|
+
Creates a blocking event on an instance's calendar.
|
|
79
|
+
|
|
80
|
+
- Validates input with `createResourceBlockingEventSchema` (Zod)
|
|
81
|
+
- Creates document at `clinics/{clinicBranchId}/resources/{resourceId}/instances/{instanceId}/calendar/{eventId}`
|
|
82
|
+
- Sets `eventType: BLOCKING`, `status: CONFIRMED`
|
|
83
|
+
- Appointment-specific fields are omitted
|
|
84
|
+
|
|
85
|
+
#### `updateResourceBlockingEvent(params: UpdateResourceBlockingEventParams): Promise<ResourceCalendarEvent>`
|
|
86
|
+
|
|
87
|
+
Updates an existing blocking event. Only provided fields are modified.
|
|
88
|
+
|
|
89
|
+
- Validates input with `updateResourceBlockingEventSchema`
|
|
90
|
+
- Verifies event exists before updating
|
|
91
|
+
- Returns the updated event
|
|
92
|
+
|
|
93
|
+
#### `deleteResourceBlockingEvent(clinicBranchId, resourceId, instanceId, eventId): Promise<void>`
|
|
94
|
+
|
|
95
|
+
Hard deletes a blocking event from the instance's calendar.
|
|
96
|
+
|
|
97
|
+
- Verifies event exists before deleting
|
|
98
|
+
|
|
99
|
+
#### `getResourceInstanceBlockingEvents(clinicBranchId, resourceId, instanceId): Promise<ResourceCalendarEvent[]>`
|
|
100
|
+
|
|
101
|
+
Fetches all blocking events for a specific instance, ordered by start time. Filters by `eventType == "blocking"`.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### Firestore Paths
|
|
106
|
+
|
|
107
|
+
| Entity | Path |
|
|
108
|
+
|--------|------|
|
|
109
|
+
| Resource | `clinics/{clinicBranchId}/resources/{resourceId}` |
|
|
110
|
+
| Instance | `clinics/{clinicBranchId}/resources/{resourceId}/instances/{instanceId}` |
|
|
111
|
+
| Calendar Event | `clinics/{clinicBranchId}/resources/{resourceId}/instances/{instanceId}/calendar/{eventId}` |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### Export
|
|
116
|
+
|
|
117
|
+
`index.ts` re-exports `ResourceService` from `resource.service.ts`. The service is also exported via `Api/src/services/index.ts`.
|
|
118
|
+
|
|
119
|
+
See [main Resource System README](../../../../docs/RESOURCE_SYSTEM.md) for the full system overview.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./resource.service";
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collection,
|
|
3
|
+
doc,
|
|
4
|
+
getDoc,
|
|
5
|
+
getDocs,
|
|
6
|
+
setDoc,
|
|
7
|
+
deleteDoc,
|
|
8
|
+
query,
|
|
9
|
+
where,
|
|
10
|
+
orderBy,
|
|
11
|
+
writeBatch,
|
|
12
|
+
updateDoc,
|
|
13
|
+
serverTimestamp,
|
|
14
|
+
Timestamp,
|
|
15
|
+
} from "firebase/firestore";
|
|
16
|
+
import { BaseService } from "../base.service";
|
|
17
|
+
import {
|
|
18
|
+
Resource,
|
|
19
|
+
ResourceInstance,
|
|
20
|
+
ResourceCalendarEvent,
|
|
21
|
+
ResourceStatus,
|
|
22
|
+
CreateResourceData,
|
|
23
|
+
UpdateResourceData,
|
|
24
|
+
CreateResourceBlockingEventParams,
|
|
25
|
+
UpdateResourceBlockingEventParams,
|
|
26
|
+
RESOURCES_COLLECTION,
|
|
27
|
+
RESOURCE_INSTANCES_SUBCOLLECTION,
|
|
28
|
+
RESOURCE_CALENDAR_SUBCOLLECTION,
|
|
29
|
+
} from "../../types/resource";
|
|
30
|
+
import { CalendarEventType, CalendarEventStatus } from "../../types/calendar";
|
|
31
|
+
import {
|
|
32
|
+
createResourceBlockingEventSchema,
|
|
33
|
+
updateResourceBlockingEventSchema,
|
|
34
|
+
} from "../../validations/resource.schema";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Service for managing clinic resources and their instances.
|
|
38
|
+
* Resources are stored as subcollections under clinics:
|
|
39
|
+
* clinics/{clinicId}/resources/{resourceId}
|
|
40
|
+
* clinics/{clinicId}/resources/{resourceId}/instances/{instanceId}
|
|
41
|
+
* clinics/{clinicId}/resources/{resourceId}/instances/{instanceId}/calendar/{eventId}
|
|
42
|
+
*/
|
|
43
|
+
export class ResourceService extends BaseService {
|
|
44
|
+
/**
|
|
45
|
+
* Gets reference to a clinic's resources collection
|
|
46
|
+
*/
|
|
47
|
+
private getResourcesRef(clinicBranchId: string) {
|
|
48
|
+
return collection(this.db, "clinics", clinicBranchId, RESOURCES_COLLECTION);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gets reference to a specific resource document
|
|
53
|
+
*/
|
|
54
|
+
private getResourceDocRef(clinicBranchId: string, resourceId: string) {
|
|
55
|
+
return doc(
|
|
56
|
+
this.db,
|
|
57
|
+
"clinics",
|
|
58
|
+
clinicBranchId,
|
|
59
|
+
RESOURCES_COLLECTION,
|
|
60
|
+
resourceId
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Gets reference to a resource's instances subcollection
|
|
66
|
+
*/
|
|
67
|
+
private getInstancesRef(clinicBranchId: string, resourceId: string) {
|
|
68
|
+
return collection(
|
|
69
|
+
this.db,
|
|
70
|
+
"clinics",
|
|
71
|
+
clinicBranchId,
|
|
72
|
+
RESOURCES_COLLECTION,
|
|
73
|
+
resourceId,
|
|
74
|
+
RESOURCE_INSTANCES_SUBCOLLECTION
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gets reference to an instance's calendar subcollection
|
|
80
|
+
*/
|
|
81
|
+
private getInstanceCalendarRef(
|
|
82
|
+
clinicBranchId: string,
|
|
83
|
+
resourceId: string,
|
|
84
|
+
instanceId: string
|
|
85
|
+
) {
|
|
86
|
+
return collection(
|
|
87
|
+
this.db,
|
|
88
|
+
"clinics",
|
|
89
|
+
clinicBranchId,
|
|
90
|
+
RESOURCES_COLLECTION,
|
|
91
|
+
resourceId,
|
|
92
|
+
RESOURCE_INSTANCES_SUBCOLLECTION,
|
|
93
|
+
instanceId,
|
|
94
|
+
RESOURCE_CALENDAR_SUBCOLLECTION
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Creates a new resource with its instances.
|
|
100
|
+
* Creates the resource document and N instance subdocuments in a single batch.
|
|
101
|
+
*/
|
|
102
|
+
async createResource(data: CreateResourceData): Promise<Resource> {
|
|
103
|
+
const batch = writeBatch(this.db);
|
|
104
|
+
|
|
105
|
+
const resourceRef = doc(this.getResourcesRef(data.clinicBranchId));
|
|
106
|
+
const resourceId = resourceRef.id;
|
|
107
|
+
|
|
108
|
+
const now = serverTimestamp();
|
|
109
|
+
|
|
110
|
+
const resourceData: Omit<Resource, "createdAt" | "updatedAt"> & {
|
|
111
|
+
createdAt: any;
|
|
112
|
+
updatedAt: any;
|
|
113
|
+
} = {
|
|
114
|
+
id: resourceId,
|
|
115
|
+
clinicBranchId: data.clinicBranchId,
|
|
116
|
+
name: data.name,
|
|
117
|
+
nameLower: data.name.toLowerCase(),
|
|
118
|
+
category: data.category,
|
|
119
|
+
description: data.description || undefined,
|
|
120
|
+
quantity: data.quantity,
|
|
121
|
+
status: ResourceStatus.ACTIVE,
|
|
122
|
+
linkedProcedureIds: [],
|
|
123
|
+
createdAt: now,
|
|
124
|
+
updatedAt: now,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
batch.set(resourceRef, resourceData);
|
|
128
|
+
|
|
129
|
+
// Create instance subdocuments
|
|
130
|
+
for (let i = 1; i <= data.quantity; i++) {
|
|
131
|
+
const instanceRef = doc(
|
|
132
|
+
this.getInstancesRef(data.clinicBranchId, resourceId)
|
|
133
|
+
);
|
|
134
|
+
const instanceData: Omit<ResourceInstance, "createdAt" | "updatedAt"> & {
|
|
135
|
+
createdAt: any;
|
|
136
|
+
updatedAt: any;
|
|
137
|
+
} = {
|
|
138
|
+
id: instanceRef.id,
|
|
139
|
+
resourceId,
|
|
140
|
+
clinicBranchId: data.clinicBranchId,
|
|
141
|
+
label: `${data.name} #${i}`,
|
|
142
|
+
index: i,
|
|
143
|
+
status: ResourceStatus.ACTIVE,
|
|
144
|
+
createdAt: now,
|
|
145
|
+
updatedAt: now,
|
|
146
|
+
};
|
|
147
|
+
batch.set(instanceRef, instanceData);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await batch.commit();
|
|
151
|
+
|
|
152
|
+
// Fetch and return the created resource
|
|
153
|
+
const created = await this.getResource(data.clinicBranchId, resourceId);
|
|
154
|
+
if (!created) throw new Error("Failed to read created resource");
|
|
155
|
+
return created;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Gets a single resource by ID
|
|
160
|
+
*/
|
|
161
|
+
async getResource(
|
|
162
|
+
clinicBranchId: string,
|
|
163
|
+
resourceId: string
|
|
164
|
+
): Promise<Resource | null> {
|
|
165
|
+
const docRef = this.getResourceDocRef(clinicBranchId, resourceId);
|
|
166
|
+
const docSnap = await getDoc(docRef);
|
|
167
|
+
if (!docSnap.exists()) return null;
|
|
168
|
+
return { id: docSnap.id, ...docSnap.data() } as Resource;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Gets all resources for a clinic branch
|
|
173
|
+
*/
|
|
174
|
+
async getResourcesByClinic(clinicBranchId: string): Promise<Resource[]> {
|
|
175
|
+
const q = query(
|
|
176
|
+
this.getResourcesRef(clinicBranchId),
|
|
177
|
+
orderBy("nameLower")
|
|
178
|
+
);
|
|
179
|
+
const snapshot = await getDocs(q);
|
|
180
|
+
return snapshot.docs.map(
|
|
181
|
+
(d) => ({ id: d.id, ...d.data() } as Resource)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Gets all active resources for a clinic branch
|
|
187
|
+
*/
|
|
188
|
+
async getActiveResourcesByClinic(
|
|
189
|
+
clinicBranchId: string
|
|
190
|
+
): Promise<Resource[]> {
|
|
191
|
+
const q = query(
|
|
192
|
+
this.getResourcesRef(clinicBranchId),
|
|
193
|
+
where("status", "==", ResourceStatus.ACTIVE),
|
|
194
|
+
orderBy("nameLower")
|
|
195
|
+
);
|
|
196
|
+
const snapshot = await getDocs(q);
|
|
197
|
+
return snapshot.docs.map(
|
|
198
|
+
(d) => ({ id: d.id, ...d.data() } as Resource)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Updates a resource. Handles quantity changes:
|
|
204
|
+
* - Increasing quantity: creates new instances
|
|
205
|
+
* - Decreasing quantity: blocked if affected instances have future bookings
|
|
206
|
+
*/
|
|
207
|
+
async updateResource(
|
|
208
|
+
clinicBranchId: string,
|
|
209
|
+
resourceId: string,
|
|
210
|
+
data: UpdateResourceData
|
|
211
|
+
): Promise<Resource> {
|
|
212
|
+
const existing = await this.getResource(clinicBranchId, resourceId);
|
|
213
|
+
if (!existing) throw new Error(`Resource ${resourceId} not found`);
|
|
214
|
+
|
|
215
|
+
const updateData: Record<string, any> = {
|
|
216
|
+
updatedAt: serverTimestamp(),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (data.name !== undefined) {
|
|
220
|
+
updateData.name = data.name;
|
|
221
|
+
updateData.nameLower = data.name.toLowerCase();
|
|
222
|
+
}
|
|
223
|
+
if (data.category !== undefined) updateData.category = data.category;
|
|
224
|
+
if (data.description !== undefined)
|
|
225
|
+
updateData.description = data.description;
|
|
226
|
+
if (data.status !== undefined) updateData.status = data.status;
|
|
227
|
+
|
|
228
|
+
// Handle quantity change
|
|
229
|
+
if (data.quantity !== undefined && data.quantity !== existing.quantity) {
|
|
230
|
+
if (data.quantity > existing.quantity) {
|
|
231
|
+
// Add new instances
|
|
232
|
+
const batch = writeBatch(this.db);
|
|
233
|
+
const now = serverTimestamp();
|
|
234
|
+
const resourceName = data.name || existing.name;
|
|
235
|
+
|
|
236
|
+
for (let i = existing.quantity + 1; i <= data.quantity; i++) {
|
|
237
|
+
const instanceRef = doc(
|
|
238
|
+
this.getInstancesRef(clinicBranchId, resourceId)
|
|
239
|
+
);
|
|
240
|
+
const instanceData: Omit<
|
|
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,
|
|
249
|
+
status: ResourceStatus.ACTIVE,
|
|
250
|
+
createdAt: now,
|
|
251
|
+
updatedAt: now,
|
|
252
|
+
};
|
|
253
|
+
batch.set(instanceRef, instanceData);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
updateData.quantity = data.quantity;
|
|
257
|
+
|
|
258
|
+
// Update resource doc in the batch too
|
|
259
|
+
const resourceDocRef = this.getResourceDocRef(
|
|
260
|
+
clinicBranchId,
|
|
261
|
+
resourceId
|
|
262
|
+
);
|
|
263
|
+
batch.update(resourceDocRef, updateData);
|
|
264
|
+
await batch.commit();
|
|
265
|
+
|
|
266
|
+
const updated = await this.getResource(clinicBranchId, resourceId);
|
|
267
|
+
if (!updated) throw new Error("Failed to read updated resource");
|
|
268
|
+
return updated;
|
|
269
|
+
} else {
|
|
270
|
+
// Decreasing quantity - check for future bookings on affected instances
|
|
271
|
+
const instances = await this.getResourceInstances(
|
|
272
|
+
clinicBranchId,
|
|
273
|
+
resourceId
|
|
274
|
+
);
|
|
275
|
+
const instancesToRemove = instances
|
|
276
|
+
.filter((inst) => inst.index > data.quantity!)
|
|
277
|
+
.filter((inst) => inst.status === ResourceStatus.ACTIVE);
|
|
278
|
+
|
|
279
|
+
for (const instance of instancesToRemove) {
|
|
280
|
+
const hasFutureBookings = await this.instanceHasFutureBookings(
|
|
281
|
+
clinicBranchId,
|
|
282
|
+
resourceId,
|
|
283
|
+
instance.id
|
|
284
|
+
);
|
|
285
|
+
if (hasFutureBookings) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Cannot reduce quantity: instance "${instance.label}" has future bookings. Cancel those bookings first.`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Safe to deactivate instances
|
|
293
|
+
const batch = writeBatch(this.db);
|
|
294
|
+
for (const instance of instancesToRemove) {
|
|
295
|
+
const instanceRef = doc(
|
|
296
|
+
this.getInstancesRef(clinicBranchId, resourceId),
|
|
297
|
+
instance.id
|
|
298
|
+
);
|
|
299
|
+
batch.update(instanceRef, {
|
|
300
|
+
status: ResourceStatus.INACTIVE,
|
|
301
|
+
updatedAt: serverTimestamp(),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
updateData.quantity = data.quantity;
|
|
306
|
+
const resourceDocRef = this.getResourceDocRef(
|
|
307
|
+
clinicBranchId,
|
|
308
|
+
resourceId
|
|
309
|
+
);
|
|
310
|
+
batch.update(resourceDocRef, updateData);
|
|
311
|
+
await batch.commit();
|
|
312
|
+
|
|
313
|
+
const updated = await this.getResource(clinicBranchId, resourceId);
|
|
314
|
+
if (!updated) throw new Error("Failed to read updated resource");
|
|
315
|
+
return updated;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Simple update (no quantity change)
|
|
320
|
+
const resourceDocRef = this.getResourceDocRef(clinicBranchId, resourceId);
|
|
321
|
+
await updateDoc(resourceDocRef, updateData);
|
|
322
|
+
|
|
323
|
+
const updated = await this.getResource(clinicBranchId, resourceId);
|
|
324
|
+
if (!updated) throw new Error("Failed to read updated resource");
|
|
325
|
+
return updated;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Soft deletes a resource by setting status to INACTIVE
|
|
330
|
+
*/
|
|
331
|
+
async deleteResource(
|
|
332
|
+
clinicBranchId: string,
|
|
333
|
+
resourceId: string
|
|
334
|
+
): Promise<void> {
|
|
335
|
+
await this.updateResource(clinicBranchId, resourceId, {
|
|
336
|
+
status: ResourceStatus.INACTIVE,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Gets all instances for a resource
|
|
342
|
+
*/
|
|
343
|
+
async getResourceInstances(
|
|
344
|
+
clinicBranchId: string,
|
|
345
|
+
resourceId: string
|
|
346
|
+
): Promise<ResourceInstance[]> {
|
|
347
|
+
const q = query(
|
|
348
|
+
this.getInstancesRef(clinicBranchId, resourceId),
|
|
349
|
+
orderBy("index")
|
|
350
|
+
);
|
|
351
|
+
const snapshot = await getDocs(q);
|
|
352
|
+
return snapshot.docs.map(
|
|
353
|
+
(d) => ({ id: d.id, ...d.data() } as ResourceInstance)
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Gets active instances for a resource
|
|
359
|
+
*/
|
|
360
|
+
async getActiveResourceInstances(
|
|
361
|
+
clinicBranchId: string,
|
|
362
|
+
resourceId: string
|
|
363
|
+
): Promise<ResourceInstance[]> {
|
|
364
|
+
const q = query(
|
|
365
|
+
this.getInstancesRef(clinicBranchId, resourceId),
|
|
366
|
+
where("status", "==", ResourceStatus.ACTIVE),
|
|
367
|
+
orderBy("index")
|
|
368
|
+
);
|
|
369
|
+
const snapshot = await getDocs(q);
|
|
370
|
+
return snapshot.docs.map(
|
|
371
|
+
(d) => ({ id: d.id, ...d.data() } as ResourceInstance)
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Gets calendar events for a specific resource instance within a time range
|
|
377
|
+
*/
|
|
378
|
+
async getResourceCalendarEvents(
|
|
379
|
+
clinicBranchId: string,
|
|
380
|
+
resourceId: string,
|
|
381
|
+
instanceId: string,
|
|
382
|
+
start: Timestamp,
|
|
383
|
+
end: Timestamp
|
|
384
|
+
): Promise<ResourceCalendarEvent[]> {
|
|
385
|
+
const calendarRef = this.getInstanceCalendarRef(
|
|
386
|
+
clinicBranchId,
|
|
387
|
+
resourceId,
|
|
388
|
+
instanceId
|
|
389
|
+
);
|
|
390
|
+
const q = query(
|
|
391
|
+
calendarRef,
|
|
392
|
+
where("eventTime.start", ">=", start),
|
|
393
|
+
where("eventTime.start", "<=", end),
|
|
394
|
+
orderBy("eventTime.start")
|
|
395
|
+
);
|
|
396
|
+
const snapshot = await getDocs(q);
|
|
397
|
+
return snapshot.docs.map(
|
|
398
|
+
(d) => ({ id: d.id, ...d.data() } as ResourceCalendarEvent)
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Checks if a resource instance has any future active bookings
|
|
404
|
+
*/
|
|
405
|
+
private async instanceHasFutureBookings(
|
|
406
|
+
clinicBranchId: string,
|
|
407
|
+
resourceId: string,
|
|
408
|
+
instanceId: string
|
|
409
|
+
): Promise<boolean> {
|
|
410
|
+
const now = Timestamp.now();
|
|
411
|
+
const calendarRef = this.getInstanceCalendarRef(
|
|
412
|
+
clinicBranchId,
|
|
413
|
+
resourceId,
|
|
414
|
+
instanceId
|
|
415
|
+
);
|
|
416
|
+
const q = query(
|
|
417
|
+
calendarRef,
|
|
418
|
+
where("eventTime.start", ">=", now),
|
|
419
|
+
where("status", "in", ["pending", "confirmed"])
|
|
420
|
+
);
|
|
421
|
+
const snapshot = await getDocs(q);
|
|
422
|
+
return !snapshot.empty;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// --- Resource Instance Blocking Events ---
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Creates a blocking event on a resource instance's calendar.
|
|
429
|
+
* Blocking events prevent the instance from being booked during the specified time.
|
|
430
|
+
*/
|
|
431
|
+
async createResourceBlockingEvent(
|
|
432
|
+
params: CreateResourceBlockingEventParams
|
|
433
|
+
): Promise<ResourceCalendarEvent> {
|
|
434
|
+
createResourceBlockingEventSchema.parse(params);
|
|
435
|
+
|
|
436
|
+
const calendarRef = this.getInstanceCalendarRef(
|
|
437
|
+
params.clinicBranchId,
|
|
438
|
+
params.resourceId,
|
|
439
|
+
params.resourceInstanceId
|
|
440
|
+
);
|
|
441
|
+
const eventRef = doc(calendarRef);
|
|
442
|
+
const now = serverTimestamp();
|
|
443
|
+
|
|
444
|
+
const eventData = {
|
|
445
|
+
id: eventRef.id,
|
|
446
|
+
resourceId: params.resourceId,
|
|
447
|
+
resourceInstanceId: params.resourceInstanceId,
|
|
448
|
+
clinicBranchId: params.clinicBranchId,
|
|
449
|
+
eventType: CalendarEventType.BLOCKING,
|
|
450
|
+
eventName: params.eventName,
|
|
451
|
+
eventTime: params.eventTime,
|
|
452
|
+
status: CalendarEventStatus.CONFIRMED,
|
|
453
|
+
description: params.description || "",
|
|
454
|
+
createdAt: now,
|
|
455
|
+
updatedAt: now,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
await setDoc(eventRef, eventData);
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
...eventData,
|
|
462
|
+
createdAt: Timestamp.now(),
|
|
463
|
+
updatedAt: Timestamp.now(),
|
|
464
|
+
} as ResourceCalendarEvent;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Updates an existing blocking event on a resource instance's calendar.
|
|
469
|
+
* Only provided fields are updated.
|
|
470
|
+
*/
|
|
471
|
+
async updateResourceBlockingEvent(
|
|
472
|
+
params: UpdateResourceBlockingEventParams
|
|
473
|
+
): Promise<ResourceCalendarEvent> {
|
|
474
|
+
updateResourceBlockingEventSchema.parse(params);
|
|
475
|
+
|
|
476
|
+
const calendarRef = this.getInstanceCalendarRef(
|
|
477
|
+
params.clinicBranchId,
|
|
478
|
+
params.resourceId,
|
|
479
|
+
params.resourceInstanceId
|
|
480
|
+
);
|
|
481
|
+
const eventRef = doc(calendarRef, params.eventId);
|
|
482
|
+
|
|
483
|
+
const eventSnap = await getDoc(eventRef);
|
|
484
|
+
if (!eventSnap.exists()) {
|
|
485
|
+
throw new Error(`Blocking event ${params.eventId} not found`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const updateData: Record<string, any> = {
|
|
489
|
+
updatedAt: serverTimestamp(),
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
if (params.eventName !== undefined) {
|
|
493
|
+
updateData.eventName = params.eventName;
|
|
494
|
+
}
|
|
495
|
+
if (params.eventTime !== undefined) {
|
|
496
|
+
updateData.eventTime = params.eventTime;
|
|
497
|
+
}
|
|
498
|
+
if (params.description !== undefined) {
|
|
499
|
+
updateData.description = params.description;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
await updateDoc(eventRef, updateData);
|
|
503
|
+
|
|
504
|
+
const updatedSnap = await getDoc(eventRef);
|
|
505
|
+
return { id: updatedSnap.id, ...updatedSnap.data() } as ResourceCalendarEvent;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Deletes a blocking event from a resource instance's calendar (hard delete).
|
|
510
|
+
*/
|
|
511
|
+
async deleteResourceBlockingEvent(
|
|
512
|
+
clinicBranchId: string,
|
|
513
|
+
resourceId: string,
|
|
514
|
+
instanceId: string,
|
|
515
|
+
eventId: string
|
|
516
|
+
): Promise<void> {
|
|
517
|
+
const calendarRef = this.getInstanceCalendarRef(
|
|
518
|
+
clinicBranchId,
|
|
519
|
+
resourceId,
|
|
520
|
+
instanceId
|
|
521
|
+
);
|
|
522
|
+
const eventRef = doc(calendarRef, eventId);
|
|
523
|
+
|
|
524
|
+
const eventSnap = await getDoc(eventRef);
|
|
525
|
+
if (!eventSnap.exists()) {
|
|
526
|
+
throw new Error(`Blocking event ${eventId} not found`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
await deleteDoc(eventRef);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Gets all blocking events for a resource instance, ordered by start time.
|
|
534
|
+
*/
|
|
535
|
+
async getResourceInstanceBlockingEvents(
|
|
536
|
+
clinicBranchId: string,
|
|
537
|
+
resourceId: string,
|
|
538
|
+
instanceId: string
|
|
539
|
+
): Promise<ResourceCalendarEvent[]> {
|
|
540
|
+
const calendarRef = this.getInstanceCalendarRef(
|
|
541
|
+
clinicBranchId,
|
|
542
|
+
resourceId,
|
|
543
|
+
instanceId
|
|
544
|
+
);
|
|
545
|
+
const q = query(
|
|
546
|
+
calendarRef,
|
|
547
|
+
where("eventType", "==", CalendarEventType.BLOCKING),
|
|
548
|
+
orderBy("eventTime.start")
|
|
549
|
+
);
|
|
550
|
+
const snapshot = await getDocs(q);
|
|
551
|
+
return snapshot.docs.map(
|
|
552
|
+
(d) => ({ id: d.id, ...d.data() } as ResourceCalendarEvent)
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./reviews.service";
|
|
1
|
+
export * from "./reviews.service";
|