@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
|
@@ -1,240 +1,240 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Firestore,
|
|
3
|
-
collection,
|
|
4
|
-
doc,
|
|
5
|
-
getDoc,
|
|
6
|
-
getDocs,
|
|
7
|
-
query,
|
|
8
|
-
where,
|
|
9
|
-
} from 'firebase/firestore';
|
|
10
|
-
import {
|
|
11
|
-
CLINIC_GROUPS_COLLECTION,
|
|
12
|
-
CLINICS_COLLECTION,
|
|
13
|
-
} from '../types/clinic/index';
|
|
14
|
-
import { PRACTITIONERS_COLLECTION } from '../types/practitioner/index';
|
|
15
|
-
import { PROCEDURES_COLLECTION } from '../types/procedure/index';
|
|
16
|
-
import { TIER_CONFIG, resolveEffectiveTier } from '../config/tiers.config';
|
|
17
|
-
|
|
18
|
-
export class TierLimitError extends Error {
|
|
19
|
-
public readonly code = 'TIER_LIMIT_EXCEEDED';
|
|
20
|
-
public readonly currentTier: string;
|
|
21
|
-
public readonly resource: string;
|
|
22
|
-
public readonly currentCount: number;
|
|
23
|
-
public readonly maxAllowed: number;
|
|
24
|
-
|
|
25
|
-
constructor(
|
|
26
|
-
resource: string,
|
|
27
|
-
currentTier: string,
|
|
28
|
-
currentCount: number,
|
|
29
|
-
maxAllowed: number
|
|
30
|
-
) {
|
|
31
|
-
const tierLabel = currentTier.charAt(0).toUpperCase() + currentTier.slice(1);
|
|
32
|
-
const canBuyAddons = currentTier === 'connect' || currentTier === 'pro';
|
|
33
|
-
const actionHint = canBuyAddons
|
|
34
|
-
? 'Please upgrade your plan or purchase an add-on.'
|
|
35
|
-
: 'Please upgrade to Connect or Pro to add more.';
|
|
36
|
-
super(
|
|
37
|
-
`Your ${tierLabel} plan allows a maximum of ${maxAllowed} ${resource}. ` +
|
|
38
|
-
`You currently have ${currentCount}. ${actionHint}`
|
|
39
|
-
);
|
|
40
|
-
this.name = 'TierLimitError';
|
|
41
|
-
this.currentTier = currentTier;
|
|
42
|
-
this.resource = resource;
|
|
43
|
-
this.currentCount = currentCount;
|
|
44
|
-
this.maxAllowed = maxAllowed;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Reads the clinic group doc and returns tier + billing data in one call.
|
|
50
|
-
*/
|
|
51
|
-
async function getClinicGroupTierData(
|
|
52
|
-
db: Firestore,
|
|
53
|
-
clinicGroupId: string
|
|
54
|
-
): Promise<{
|
|
55
|
-
tier: string;
|
|
56
|
-
billing: Record<string, any> | undefined;
|
|
57
|
-
}> {
|
|
58
|
-
const groupRef = doc(db, CLINIC_GROUPS_COLLECTION, clinicGroupId);
|
|
59
|
-
const groupSnap = await getDoc(groupRef);
|
|
60
|
-
if (!groupSnap.exists()) {
|
|
61
|
-
throw new Error(`Clinic group ${clinicGroupId} not found`);
|
|
62
|
-
}
|
|
63
|
-
const data = groupSnap.data();
|
|
64
|
-
const subscriptionModel = data.subscriptionModel || 'no_subscription';
|
|
65
|
-
return {
|
|
66
|
-
tier: resolveEffectiveTier(subscriptionModel),
|
|
67
|
-
billing: data.billing,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Fetches the effective tier for a clinic group.
|
|
73
|
-
*/
|
|
74
|
-
export async function getEffectiveTier(
|
|
75
|
-
db: Firestore,
|
|
76
|
-
clinicGroupId: string
|
|
77
|
-
): Promise<string> {
|
|
78
|
-
const { tier } = await getClinicGroupTierData(db, clinicGroupId);
|
|
79
|
-
return tier;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Counts active providers (practitioners) assigned to a specific clinic branch.
|
|
84
|
-
*/
|
|
85
|
-
async function countProvidersInBranch(
|
|
86
|
-
db: Firestore,
|
|
87
|
-
branchId: string
|
|
88
|
-
): Promise<number> {
|
|
89
|
-
const practitionersQuery = query(
|
|
90
|
-
collection(db, PRACTITIONERS_COLLECTION),
|
|
91
|
-
where('isActive', '==', true)
|
|
92
|
-
);
|
|
93
|
-
const practitionersSnap = await getDocs(practitionersQuery);
|
|
94
|
-
|
|
95
|
-
return practitionersSnap.docs.filter(d => {
|
|
96
|
-
const clinics: string[] = d.data().clinics || [];
|
|
97
|
-
return clinics.includes(branchId);
|
|
98
|
-
}).length;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Counts active procedures for a specific provider in a specific branch.
|
|
103
|
-
* Uses unique technologyId to avoid double-counting same procedure type.
|
|
104
|
-
*/
|
|
105
|
-
async function countProceduresForProvider(
|
|
106
|
-
db: Firestore,
|
|
107
|
-
branchId: string,
|
|
108
|
-
providerId: string
|
|
109
|
-
): Promise<number> {
|
|
110
|
-
const proceduresQuery = query(
|
|
111
|
-
collection(db, PROCEDURES_COLLECTION),
|
|
112
|
-
where('clinicBranchId', '==', branchId),
|
|
113
|
-
where('practitionerId', '==', providerId),
|
|
114
|
-
where('isActive', '==', true)
|
|
115
|
-
);
|
|
116
|
-
const proceduresSnap = await getDocs(proceduresQuery);
|
|
117
|
-
|
|
118
|
-
const uniqueTechnologyIds = new Set<string>();
|
|
119
|
-
proceduresSnap.docs.forEach(d => {
|
|
120
|
-
const technologyId = d.data().technologyId;
|
|
121
|
-
if (technologyId) {
|
|
122
|
-
uniqueTechnologyIds.add(technologyId);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
return uniqueTechnologyIds.size;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Counts active clinic branches in a clinic group.
|
|
131
|
-
*/
|
|
132
|
-
async function countBranchesInGroup(
|
|
133
|
-
db: Firestore,
|
|
134
|
-
clinicGroupId: string
|
|
135
|
-
): Promise<number> {
|
|
136
|
-
const clinicsQuery = query(
|
|
137
|
-
collection(db, CLINICS_COLLECTION),
|
|
138
|
-
where('clinicGroupId', '==', clinicGroupId),
|
|
139
|
-
where('isActive', '==', true)
|
|
140
|
-
);
|
|
141
|
-
const clinicsSnap = await getDocs(clinicsQuery);
|
|
142
|
-
return clinicsSnap.size;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Resolves the clinicGroupId for a given branch.
|
|
147
|
-
*/
|
|
148
|
-
async function resolveClinicGroupId(
|
|
149
|
-
db: Firestore,
|
|
150
|
-
branchId: string
|
|
151
|
-
): Promise<string> {
|
|
152
|
-
const clinicRef = doc(db, CLINICS_COLLECTION, branchId);
|
|
153
|
-
const clinicSnap = await getDoc(clinicRef);
|
|
154
|
-
if (!clinicSnap.exists()) {
|
|
155
|
-
throw new Error(`Clinic branch ${branchId} not found`);
|
|
156
|
-
}
|
|
157
|
-
const clinicGroupId = clinicSnap.data().clinicGroupId;
|
|
158
|
-
if (!clinicGroupId) {
|
|
159
|
-
throw new Error(`Clinic branch ${branchId} has no clinicGroupId`);
|
|
160
|
-
}
|
|
161
|
-
return clinicGroupId;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Enforces tier limit before adding a provider to a specific branch.
|
|
166
|
-
* Add-on aware: effective limit = base + per-branch extraProviders.
|
|
167
|
-
*/
|
|
168
|
-
export async function enforceProviderLimit(
|
|
169
|
-
db: Firestore,
|
|
170
|
-
clinicGroupId: string,
|
|
171
|
-
branchId: string
|
|
172
|
-
): Promise<void> {
|
|
173
|
-
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
174
|
-
const config = TIER_CONFIG[tier];
|
|
175
|
-
if (!config) return;
|
|
176
|
-
|
|
177
|
-
const baseMax = config.limits.maxProvidersPerBranch;
|
|
178
|
-
if (baseMax === -1) return;
|
|
179
|
-
|
|
180
|
-
const addOns = billing?.addOns || {};
|
|
181
|
-
const extraProviders = addOns[branchId]?.extraProviders || 0;
|
|
182
|
-
const effectiveMax = baseMax + extraProviders;
|
|
183
|
-
|
|
184
|
-
const currentCount = await countProvidersInBranch(db, branchId);
|
|
185
|
-
if (currentCount + 1 > effectiveMax) {
|
|
186
|
-
throw new TierLimitError('providers in this branch', tier, currentCount, effectiveMax);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Enforces tier limit before creating a procedure for a provider in a branch.
|
|
192
|
-
* Add-on aware: effective limit = base + (procedureBlocks × 5).
|
|
193
|
-
*/
|
|
194
|
-
export async function enforceProcedureLimit(
|
|
195
|
-
db: Firestore,
|
|
196
|
-
clinicGroupId: string,
|
|
197
|
-
branchId: string,
|
|
198
|
-
providerId: string,
|
|
199
|
-
count: number = 1
|
|
200
|
-
): Promise<void> {
|
|
201
|
-
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
202
|
-
const config = TIER_CONFIG[tier];
|
|
203
|
-
if (!config) return;
|
|
204
|
-
|
|
205
|
-
const baseMax = config.limits.maxProceduresPerProvider;
|
|
206
|
-
if (baseMax === -1) return;
|
|
207
|
-
|
|
208
|
-
const addOns = billing?.addOns || {};
|
|
209
|
-
const procedureBlocks = addOns[branchId]?.procedureBlocks || 0;
|
|
210
|
-
const effectiveMax = baseMax + (procedureBlocks * 5);
|
|
211
|
-
|
|
212
|
-
const currentCount = await countProceduresForProvider(db, branchId, providerId);
|
|
213
|
-
if (currentCount + count > effectiveMax) {
|
|
214
|
-
throw new TierLimitError('procedures for this provider', tier, currentCount, effectiveMax);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Enforces tier limit before creating a clinic branch.
|
|
220
|
-
* Add-on aware: effective limit = base + branchAddonCount.
|
|
221
|
-
*/
|
|
222
|
-
export async function enforceBranchLimit(
|
|
223
|
-
db: Firestore,
|
|
224
|
-
clinicGroupId: string
|
|
225
|
-
): Promise<void> {
|
|
226
|
-
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
227
|
-
const config = TIER_CONFIG[tier];
|
|
228
|
-
if (!config) return;
|
|
229
|
-
|
|
230
|
-
const baseMax = config.limits.maxBranches;
|
|
231
|
-
if (baseMax === -1) return;
|
|
232
|
-
|
|
233
|
-
const branchAddonCount = billing?.branchAddonCount || 0;
|
|
234
|
-
const effectiveMax = baseMax + branchAddonCount;
|
|
235
|
-
|
|
236
|
-
const currentCount = await countBranchesInGroup(db, clinicGroupId);
|
|
237
|
-
if (currentCount + 1 > effectiveMax) {
|
|
238
|
-
throw new TierLimitError('clinic branches', tier, currentCount, effectiveMax);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
Firestore,
|
|
3
|
+
collection,
|
|
4
|
+
doc,
|
|
5
|
+
getDoc,
|
|
6
|
+
getDocs,
|
|
7
|
+
query,
|
|
8
|
+
where,
|
|
9
|
+
} from 'firebase/firestore';
|
|
10
|
+
import {
|
|
11
|
+
CLINIC_GROUPS_COLLECTION,
|
|
12
|
+
CLINICS_COLLECTION,
|
|
13
|
+
} from '../types/clinic/index';
|
|
14
|
+
import { PRACTITIONERS_COLLECTION } from '../types/practitioner/index';
|
|
15
|
+
import { PROCEDURES_COLLECTION } from '../types/procedure/index';
|
|
16
|
+
import { TIER_CONFIG, resolveEffectiveTier } from '../config/tiers.config';
|
|
17
|
+
|
|
18
|
+
export class TierLimitError extends Error {
|
|
19
|
+
public readonly code = 'TIER_LIMIT_EXCEEDED';
|
|
20
|
+
public readonly currentTier: string;
|
|
21
|
+
public readonly resource: string;
|
|
22
|
+
public readonly currentCount: number;
|
|
23
|
+
public readonly maxAllowed: number;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
resource: string,
|
|
27
|
+
currentTier: string,
|
|
28
|
+
currentCount: number,
|
|
29
|
+
maxAllowed: number
|
|
30
|
+
) {
|
|
31
|
+
const tierLabel = currentTier.charAt(0).toUpperCase() + currentTier.slice(1);
|
|
32
|
+
const canBuyAddons = currentTier === 'connect' || currentTier === 'pro';
|
|
33
|
+
const actionHint = canBuyAddons
|
|
34
|
+
? 'Please upgrade your plan or purchase an add-on.'
|
|
35
|
+
: 'Please upgrade to Connect or Pro to add more.';
|
|
36
|
+
super(
|
|
37
|
+
`Your ${tierLabel} plan allows a maximum of ${maxAllowed} ${resource}. ` +
|
|
38
|
+
`You currently have ${currentCount}. ${actionHint}`
|
|
39
|
+
);
|
|
40
|
+
this.name = 'TierLimitError';
|
|
41
|
+
this.currentTier = currentTier;
|
|
42
|
+
this.resource = resource;
|
|
43
|
+
this.currentCount = currentCount;
|
|
44
|
+
this.maxAllowed = maxAllowed;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reads the clinic group doc and returns tier + billing data in one call.
|
|
50
|
+
*/
|
|
51
|
+
async function getClinicGroupTierData(
|
|
52
|
+
db: Firestore,
|
|
53
|
+
clinicGroupId: string
|
|
54
|
+
): Promise<{
|
|
55
|
+
tier: string;
|
|
56
|
+
billing: Record<string, any> | undefined;
|
|
57
|
+
}> {
|
|
58
|
+
const groupRef = doc(db, CLINIC_GROUPS_COLLECTION, clinicGroupId);
|
|
59
|
+
const groupSnap = await getDoc(groupRef);
|
|
60
|
+
if (!groupSnap.exists()) {
|
|
61
|
+
throw new Error(`Clinic group ${clinicGroupId} not found`);
|
|
62
|
+
}
|
|
63
|
+
const data = groupSnap.data();
|
|
64
|
+
const subscriptionModel = data.subscriptionModel || 'no_subscription';
|
|
65
|
+
return {
|
|
66
|
+
tier: resolveEffectiveTier(subscriptionModel),
|
|
67
|
+
billing: data.billing,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Fetches the effective tier for a clinic group.
|
|
73
|
+
*/
|
|
74
|
+
export async function getEffectiveTier(
|
|
75
|
+
db: Firestore,
|
|
76
|
+
clinicGroupId: string
|
|
77
|
+
): Promise<string> {
|
|
78
|
+
const { tier } = await getClinicGroupTierData(db, clinicGroupId);
|
|
79
|
+
return tier;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Counts active providers (practitioners) assigned to a specific clinic branch.
|
|
84
|
+
*/
|
|
85
|
+
async function countProvidersInBranch(
|
|
86
|
+
db: Firestore,
|
|
87
|
+
branchId: string
|
|
88
|
+
): Promise<number> {
|
|
89
|
+
const practitionersQuery = query(
|
|
90
|
+
collection(db, PRACTITIONERS_COLLECTION),
|
|
91
|
+
where('isActive', '==', true)
|
|
92
|
+
);
|
|
93
|
+
const practitionersSnap = await getDocs(practitionersQuery);
|
|
94
|
+
|
|
95
|
+
return practitionersSnap.docs.filter(d => {
|
|
96
|
+
const clinics: string[] = d.data().clinics || [];
|
|
97
|
+
return clinics.includes(branchId);
|
|
98
|
+
}).length;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Counts active procedures for a specific provider in a specific branch.
|
|
103
|
+
* Uses unique technologyId to avoid double-counting same procedure type.
|
|
104
|
+
*/
|
|
105
|
+
async function countProceduresForProvider(
|
|
106
|
+
db: Firestore,
|
|
107
|
+
branchId: string,
|
|
108
|
+
providerId: string
|
|
109
|
+
): Promise<number> {
|
|
110
|
+
const proceduresQuery = query(
|
|
111
|
+
collection(db, PROCEDURES_COLLECTION),
|
|
112
|
+
where('clinicBranchId', '==', branchId),
|
|
113
|
+
where('practitionerId', '==', providerId),
|
|
114
|
+
where('isActive', '==', true)
|
|
115
|
+
);
|
|
116
|
+
const proceduresSnap = await getDocs(proceduresQuery);
|
|
117
|
+
|
|
118
|
+
const uniqueTechnologyIds = new Set<string>();
|
|
119
|
+
proceduresSnap.docs.forEach(d => {
|
|
120
|
+
const technologyId = d.data().technologyId;
|
|
121
|
+
if (technologyId) {
|
|
122
|
+
uniqueTechnologyIds.add(technologyId);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return uniqueTechnologyIds.size;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Counts active clinic branches in a clinic group.
|
|
131
|
+
*/
|
|
132
|
+
async function countBranchesInGroup(
|
|
133
|
+
db: Firestore,
|
|
134
|
+
clinicGroupId: string
|
|
135
|
+
): Promise<number> {
|
|
136
|
+
const clinicsQuery = query(
|
|
137
|
+
collection(db, CLINICS_COLLECTION),
|
|
138
|
+
where('clinicGroupId', '==', clinicGroupId),
|
|
139
|
+
where('isActive', '==', true)
|
|
140
|
+
);
|
|
141
|
+
const clinicsSnap = await getDocs(clinicsQuery);
|
|
142
|
+
return clinicsSnap.size;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolves the clinicGroupId for a given branch.
|
|
147
|
+
*/
|
|
148
|
+
async function resolveClinicGroupId(
|
|
149
|
+
db: Firestore,
|
|
150
|
+
branchId: string
|
|
151
|
+
): Promise<string> {
|
|
152
|
+
const clinicRef = doc(db, CLINICS_COLLECTION, branchId);
|
|
153
|
+
const clinicSnap = await getDoc(clinicRef);
|
|
154
|
+
if (!clinicSnap.exists()) {
|
|
155
|
+
throw new Error(`Clinic branch ${branchId} not found`);
|
|
156
|
+
}
|
|
157
|
+
const clinicGroupId = clinicSnap.data().clinicGroupId;
|
|
158
|
+
if (!clinicGroupId) {
|
|
159
|
+
throw new Error(`Clinic branch ${branchId} has no clinicGroupId`);
|
|
160
|
+
}
|
|
161
|
+
return clinicGroupId;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Enforces tier limit before adding a provider to a specific branch.
|
|
166
|
+
* Add-on aware: effective limit = base + per-branch extraProviders.
|
|
167
|
+
*/
|
|
168
|
+
export async function enforceProviderLimit(
|
|
169
|
+
db: Firestore,
|
|
170
|
+
clinicGroupId: string,
|
|
171
|
+
branchId: string
|
|
172
|
+
): Promise<void> {
|
|
173
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
174
|
+
const config = TIER_CONFIG[tier];
|
|
175
|
+
if (!config) return;
|
|
176
|
+
|
|
177
|
+
const baseMax = config.limits.maxProvidersPerBranch;
|
|
178
|
+
if (baseMax === -1) return;
|
|
179
|
+
|
|
180
|
+
const addOns = billing?.addOns || {};
|
|
181
|
+
const extraProviders = addOns[branchId]?.extraProviders || 0;
|
|
182
|
+
const effectiveMax = baseMax + extraProviders;
|
|
183
|
+
|
|
184
|
+
const currentCount = await countProvidersInBranch(db, branchId);
|
|
185
|
+
if (currentCount + 1 > effectiveMax) {
|
|
186
|
+
throw new TierLimitError('providers in this branch', tier, currentCount, effectiveMax);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Enforces tier limit before creating a procedure for a provider in a branch.
|
|
192
|
+
* Add-on aware: effective limit = base + (procedureBlocks × 5).
|
|
193
|
+
*/
|
|
194
|
+
export async function enforceProcedureLimit(
|
|
195
|
+
db: Firestore,
|
|
196
|
+
clinicGroupId: string,
|
|
197
|
+
branchId: string,
|
|
198
|
+
providerId: string,
|
|
199
|
+
count: number = 1
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
202
|
+
const config = TIER_CONFIG[tier];
|
|
203
|
+
if (!config) return;
|
|
204
|
+
|
|
205
|
+
const baseMax = config.limits.maxProceduresPerProvider;
|
|
206
|
+
if (baseMax === -1) return;
|
|
207
|
+
|
|
208
|
+
const addOns = billing?.addOns || {};
|
|
209
|
+
const procedureBlocks = addOns[branchId]?.procedureBlocks || 0;
|
|
210
|
+
const effectiveMax = baseMax + (procedureBlocks * 5);
|
|
211
|
+
|
|
212
|
+
const currentCount = await countProceduresForProvider(db, branchId, providerId);
|
|
213
|
+
if (currentCount + count > effectiveMax) {
|
|
214
|
+
throw new TierLimitError('procedures for this provider', tier, currentCount, effectiveMax);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Enforces tier limit before creating a clinic branch.
|
|
220
|
+
* Add-on aware: effective limit = base + branchAddonCount.
|
|
221
|
+
*/
|
|
222
|
+
export async function enforceBranchLimit(
|
|
223
|
+
db: Firestore,
|
|
224
|
+
clinicGroupId: string
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
227
|
+
const config = TIER_CONFIG[tier];
|
|
228
|
+
if (!config) return;
|
|
229
|
+
|
|
230
|
+
const baseMax = config.limits.maxBranches;
|
|
231
|
+
if (baseMax === -1) return;
|
|
232
|
+
|
|
233
|
+
const branchAddonCount = billing?.branchAddonCount || 0;
|
|
234
|
+
const effectiveMax = baseMax + branchAddonCount;
|
|
235
|
+
|
|
236
|
+
const currentCount = await countBranchesInGroup(db, clinicGroupId);
|
|
237
|
+
if (currentCount + 1 > effectiveMax) {
|
|
238
|
+
throw new TierLimitError('clinic branches', tier, currentCount, effectiveMax);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./user.service";
|
|
1
|
+
export * from "./user.service";
|