@blackcode_sa/metaestetics-api 1.15.10 → 1.15.14
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 +13 -0
- package/dist/admin/index.d.ts +13 -0
- package/dist/index.d.mts +36 -31
- package/dist/index.d.ts +36 -31
- package/dist/index.js +75 -139
- package/dist/index.mjs +75 -136
- package/package.json +1 -1
- package/src/config/tiers.config.ts +16 -20
- package/src/services/practitioner/practitioner.service.ts +3 -3
- package/src/services/procedure/procedure.service.ts +6 -4
- package/src/services/tier-enforcement.ts +106 -156
- package/src/types/clinic/index.ts +13 -0
- package/src/types/clinic/rbac.types.ts +6 -5
package/dist/index.js
CHANGED
|
@@ -155,12 +155,9 @@ __export(index_exports, {
|
|
|
155
155
|
USER_FORMS_SUBCOLLECTION: () => USER_FORMS_SUBCOLLECTION,
|
|
156
156
|
UserRole: () => UserRole,
|
|
157
157
|
UserService: () => UserService,
|
|
158
|
-
enforceAppointmentLimit: () => enforceAppointmentLimit,
|
|
159
158
|
enforceBranchLimit: () => enforceBranchLimit,
|
|
160
|
-
enforceMessageLimit: () => enforceMessageLimit,
|
|
161
159
|
enforceProcedureLimit: () => enforceProcedureLimit,
|
|
162
160
|
enforceProviderLimit: () => enforceProviderLimit,
|
|
163
|
-
enforceStaffLimit: () => enforceStaffLimit,
|
|
164
161
|
getEffectiveTier: () => getEffectiveTier,
|
|
165
162
|
getFirebaseApp: () => getFirebaseApp,
|
|
166
163
|
getFirebaseAuth: () => getFirebaseAuth,
|
|
@@ -12737,11 +12734,8 @@ var TIER_CONFIG = {
|
|
|
12737
12734
|
tier: "free",
|
|
12738
12735
|
name: "Free",
|
|
12739
12736
|
limits: {
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
maxAppointmentsPerMonth: 3,
|
|
12743
|
-
maxMessagesPerMonth: 10,
|
|
12744
|
-
maxStaff: 1,
|
|
12737
|
+
maxProvidersPerBranch: 1,
|
|
12738
|
+
maxProceduresPerProvider: 3,
|
|
12745
12739
|
maxBranches: 1
|
|
12746
12740
|
}
|
|
12747
12741
|
},
|
|
@@ -12749,11 +12743,8 @@ var TIER_CONFIG = {
|
|
|
12749
12743
|
tier: "connect",
|
|
12750
12744
|
name: "Connect",
|
|
12751
12745
|
limits: {
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
maxAppointmentsPerMonth: 100,
|
|
12755
|
-
maxMessagesPerMonth: -1,
|
|
12756
|
-
maxStaff: 5,
|
|
12746
|
+
maxProvidersPerBranch: 3,
|
|
12747
|
+
maxProceduresPerProvider: 10,
|
|
12757
12748
|
maxBranches: 1
|
|
12758
12749
|
}
|
|
12759
12750
|
},
|
|
@@ -12761,12 +12752,9 @@ var TIER_CONFIG = {
|
|
|
12761
12752
|
tier: "pro",
|
|
12762
12753
|
name: "Pro",
|
|
12763
12754
|
limits: {
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
maxMessagesPerMonth: -1,
|
|
12768
|
-
maxStaff: -1,
|
|
12769
|
-
maxBranches: -1
|
|
12755
|
+
maxProvidersPerBranch: 10,
|
|
12756
|
+
maxProceduresPerProvider: 20,
|
|
12757
|
+
maxBranches: 3
|
|
12770
12758
|
}
|
|
12771
12759
|
}
|
|
12772
12760
|
};
|
|
@@ -12897,8 +12885,10 @@ function resolveEffectiveTier(subscriptionModel) {
|
|
|
12897
12885
|
var TierLimitError = class extends Error {
|
|
12898
12886
|
constructor(resource, currentTier, currentCount, maxAllowed) {
|
|
12899
12887
|
const tierLabel = currentTier.charAt(0).toUpperCase() + currentTier.slice(1);
|
|
12888
|
+
const canBuyAddons = currentTier === "connect" || currentTier === "pro";
|
|
12889
|
+
const actionHint = canBuyAddons ? "Please upgrade your plan or purchase an add-on." : "Please upgrade to Connect or Pro to add more.";
|
|
12900
12890
|
super(
|
|
12901
|
-
`Your ${tierLabel} plan allows a maximum of ${maxAllowed} ${resource}. You currently have ${currentCount}.
|
|
12891
|
+
`Your ${tierLabel} plan allows a maximum of ${maxAllowed} ${resource}. You currently have ${currentCount}. ${actionHint}`
|
|
12902
12892
|
);
|
|
12903
12893
|
this.code = "TIER_LIMIT_EXCEEDED";
|
|
12904
12894
|
this.name = "TierLimitError";
|
|
@@ -12908,58 +12898,50 @@ var TierLimitError = class extends Error {
|
|
|
12908
12898
|
this.maxAllowed = maxAllowed;
|
|
12909
12899
|
}
|
|
12910
12900
|
};
|
|
12911
|
-
async function
|
|
12901
|
+
async function getClinicGroupTierData(db, clinicGroupId) {
|
|
12912
12902
|
const groupRef = (0, import_firestore35.doc)(db, CLINIC_GROUPS_COLLECTION, clinicGroupId);
|
|
12913
12903
|
const groupSnap = await (0, import_firestore35.getDoc)(groupRef);
|
|
12914
12904
|
if (!groupSnap.exists()) {
|
|
12915
12905
|
throw new Error(`Clinic group ${clinicGroupId} not found`);
|
|
12916
12906
|
}
|
|
12917
|
-
const
|
|
12918
|
-
|
|
12907
|
+
const data = groupSnap.data();
|
|
12908
|
+
const subscriptionModel = data.subscriptionModel || "no_subscription";
|
|
12909
|
+
return {
|
|
12910
|
+
tier: resolveEffectiveTier(subscriptionModel),
|
|
12911
|
+
billing: data.billing
|
|
12912
|
+
};
|
|
12919
12913
|
}
|
|
12920
|
-
async function
|
|
12921
|
-
const
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
);
|
|
12926
|
-
const clinicsSnap = await (0, import_firestore35.getDocs)(clinicsQuery);
|
|
12927
|
-
const clinicIds = clinicsSnap.docs.map((d) => d.id);
|
|
12928
|
-
if (clinicIds.length === 0) return 0;
|
|
12914
|
+
async function getEffectiveTier(db, clinicGroupId) {
|
|
12915
|
+
const { tier } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12916
|
+
return tier;
|
|
12917
|
+
}
|
|
12918
|
+
async function countProvidersInBranch(db, branchId) {
|
|
12929
12919
|
const practitionersQuery = (0, import_firestore35.query)(
|
|
12930
12920
|
(0, import_firestore35.collection)(db, PRACTITIONERS_COLLECTION),
|
|
12931
12921
|
(0, import_firestore35.where)("isActive", "==", true)
|
|
12932
12922
|
);
|
|
12933
12923
|
const practitionersSnap = await (0, import_firestore35.getDocs)(practitionersQuery);
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
return clinics.some((c) => clinicIdSet.has(c));
|
|
12939
|
-
});
|
|
12940
|
-
return uniqueProviders.length;
|
|
12924
|
+
return practitionersSnap.docs.filter((d) => {
|
|
12925
|
+
const clinics = d.data().clinics || [];
|
|
12926
|
+
return clinics.includes(branchId);
|
|
12927
|
+
}).length;
|
|
12941
12928
|
}
|
|
12942
|
-
async function
|
|
12943
|
-
const
|
|
12944
|
-
(0, import_firestore35.collection)(db,
|
|
12945
|
-
(0, import_firestore35.where)("
|
|
12929
|
+
async function countProceduresForProvider(db, branchId, providerId) {
|
|
12930
|
+
const proceduresQuery = (0, import_firestore35.query)(
|
|
12931
|
+
(0, import_firestore35.collection)(db, PROCEDURES_COLLECTION),
|
|
12932
|
+
(0, import_firestore35.where)("clinicBranchId", "==", branchId),
|
|
12933
|
+
(0, import_firestore35.where)("practitionerId", "==", providerId),
|
|
12946
12934
|
(0, import_firestore35.where)("isActive", "==", true)
|
|
12947
12935
|
);
|
|
12948
|
-
const
|
|
12949
|
-
const
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
(0, import_firestore35.where)("isActive", "==", true)
|
|
12958
|
-
);
|
|
12959
|
-
const proceduresSnap = await (0, import_firestore35.getDocs)(proceduresQuery);
|
|
12960
|
-
totalProcedures += proceduresSnap.size;
|
|
12961
|
-
}
|
|
12962
|
-
return totalProcedures;
|
|
12936
|
+
const proceduresSnap = await (0, import_firestore35.getDocs)(proceduresQuery);
|
|
12937
|
+
const uniqueTechnologyIds = /* @__PURE__ */ new Set();
|
|
12938
|
+
proceduresSnap.docs.forEach((d) => {
|
|
12939
|
+
const technologyId = d.data().technologyId;
|
|
12940
|
+
if (technologyId) {
|
|
12941
|
+
uniqueTechnologyIds.add(technologyId);
|
|
12942
|
+
}
|
|
12943
|
+
});
|
|
12944
|
+
return uniqueTechnologyIds.size;
|
|
12963
12945
|
}
|
|
12964
12946
|
async function countBranchesInGroup(db, clinicGroupId) {
|
|
12965
12947
|
const clinicsQuery = (0, import_firestore35.query)(
|
|
@@ -12970,92 +12952,47 @@ async function countBranchesInGroup(db, clinicGroupId) {
|
|
|
12970
12952
|
const clinicsSnap = await (0, import_firestore35.getDocs)(clinicsQuery);
|
|
12971
12953
|
return clinicsSnap.size;
|
|
12972
12954
|
}
|
|
12973
|
-
async function enforceProviderLimit(db, clinicGroupId) {
|
|
12974
|
-
|
|
12955
|
+
async function enforceProviderLimit(db, clinicGroupId, branchId) {
|
|
12956
|
+
var _a;
|
|
12957
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12975
12958
|
const config = TIER_CONFIG[tier];
|
|
12976
12959
|
if (!config) return;
|
|
12977
|
-
const
|
|
12978
|
-
if (
|
|
12979
|
-
const
|
|
12980
|
-
|
|
12981
|
-
|
|
12960
|
+
const baseMax = config.limits.maxProvidersPerBranch;
|
|
12961
|
+
if (baseMax === -1) return;
|
|
12962
|
+
const addOns = (billing == null ? void 0 : billing.addOns) || {};
|
|
12963
|
+
const extraProviders = ((_a = addOns[branchId]) == null ? void 0 : _a.extraProviders) || 0;
|
|
12964
|
+
const effectiveMax = baseMax + extraProviders;
|
|
12965
|
+
const currentCount = await countProvidersInBranch(db, branchId);
|
|
12966
|
+
if (currentCount + 1 > effectiveMax) {
|
|
12967
|
+
throw new TierLimitError("providers in this branch", tier, currentCount, effectiveMax);
|
|
12982
12968
|
}
|
|
12983
12969
|
}
|
|
12984
|
-
async function enforceProcedureLimit(db, clinicGroupId, count = 1) {
|
|
12985
|
-
|
|
12970
|
+
async function enforceProcedureLimit(db, clinicGroupId, branchId, providerId, count = 1) {
|
|
12971
|
+
var _a;
|
|
12972
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12986
12973
|
const config = TIER_CONFIG[tier];
|
|
12987
12974
|
if (!config) return;
|
|
12988
|
-
const
|
|
12989
|
-
if (
|
|
12990
|
-
const
|
|
12991
|
-
|
|
12992
|
-
|
|
12975
|
+
const baseMax = config.limits.maxProceduresPerProvider;
|
|
12976
|
+
if (baseMax === -1) return;
|
|
12977
|
+
const addOns = (billing == null ? void 0 : billing.addOns) || {};
|
|
12978
|
+
const procedureBlocks = ((_a = addOns[branchId]) == null ? void 0 : _a.procedureBlocks) || 0;
|
|
12979
|
+
const effectiveMax = baseMax + procedureBlocks * 10;
|
|
12980
|
+
const currentCount = await countProceduresForProvider(db, branchId, providerId);
|
|
12981
|
+
if (currentCount + count > effectiveMax) {
|
|
12982
|
+
throw new TierLimitError("procedures for this provider", tier, currentCount, effectiveMax);
|
|
12993
12983
|
}
|
|
12994
12984
|
}
|
|
12995
12985
|
async function enforceBranchLimit(db, clinicGroupId) {
|
|
12996
|
-
const tier = await
|
|
12986
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12997
12987
|
const config = TIER_CONFIG[tier];
|
|
12998
12988
|
if (!config) return;
|
|
12999
|
-
const
|
|
13000
|
-
if (
|
|
12989
|
+
const baseMax = config.limits.maxBranches;
|
|
12990
|
+
if (baseMax === -1) return;
|
|
12991
|
+
const branchAddonCount = (billing == null ? void 0 : billing.branchAddonCount) || 0;
|
|
12992
|
+
const effectiveMax = baseMax + branchAddonCount;
|
|
13001
12993
|
const currentCount = await countBranchesInGroup(db, clinicGroupId);
|
|
13002
|
-
if (currentCount + 1 >
|
|
13003
|
-
throw new TierLimitError("clinic branches", tier, currentCount,
|
|
13004
|
-
}
|
|
13005
|
-
}
|
|
13006
|
-
async function enforceStaffLimit(db, clinicGroupId) {
|
|
13007
|
-
const tier = await getEffectiveTier(db, clinicGroupId);
|
|
13008
|
-
const config = TIER_CONFIG[tier];
|
|
13009
|
-
if (!config) return;
|
|
13010
|
-
const max = config.limits.maxStaff;
|
|
13011
|
-
if (max === -1) return;
|
|
13012
|
-
const staffQuery = (0, import_firestore35.query)(
|
|
13013
|
-
(0, import_firestore35.collection)(db, "clinic_staff_members"),
|
|
13014
|
-
(0, import_firestore35.where)("clinicGroupId", "==", clinicGroupId),
|
|
13015
|
-
(0, import_firestore35.where)("isActive", "==", true)
|
|
13016
|
-
);
|
|
13017
|
-
const staffSnap = await (0, import_firestore35.getDocs)(staffQuery);
|
|
13018
|
-
const currentCount = staffSnap.size;
|
|
13019
|
-
if (currentCount + 1 > max) {
|
|
13020
|
-
throw new TierLimitError("staff members", tier, currentCount, max);
|
|
13021
|
-
}
|
|
13022
|
-
}
|
|
13023
|
-
async function enforceAppointmentLimit(db, clinicGroupId) {
|
|
13024
|
-
var _a;
|
|
13025
|
-
const tier = await getEffectiveTier(db, clinicGroupId);
|
|
13026
|
-
const config = TIER_CONFIG[tier];
|
|
13027
|
-
if (!config) return;
|
|
13028
|
-
const max = config.limits.maxAppointmentsPerMonth;
|
|
13029
|
-
if (max === -1) return;
|
|
13030
|
-
const now = /* @__PURE__ */ new Date();
|
|
13031
|
-
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
13032
|
-
const counterRef = (0, import_firestore35.doc)(
|
|
13033
|
-
db,
|
|
13034
|
-
`${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}/usage_counters/${yearMonth}`
|
|
13035
|
-
);
|
|
13036
|
-
const counterSnap = await (0, import_firestore35.getDoc)(counterRef);
|
|
13037
|
-
const currentCount = counterSnap.exists() ? ((_a = counterSnap.data()) == null ? void 0 : _a.appointmentsCreated) || 0 : 0;
|
|
13038
|
-
if (currentCount + 1 > max) {
|
|
13039
|
-
throw new TierLimitError("appointments this month", tier, currentCount, max);
|
|
13040
|
-
}
|
|
13041
|
-
}
|
|
13042
|
-
async function enforceMessageLimit(db, clinicGroupId) {
|
|
13043
|
-
var _a;
|
|
13044
|
-
const tier = await getEffectiveTier(db, clinicGroupId);
|
|
13045
|
-
const config = TIER_CONFIG[tier];
|
|
13046
|
-
if (!config) return;
|
|
13047
|
-
const max = config.limits.maxMessagesPerMonth;
|
|
13048
|
-
if (max === -1) return;
|
|
13049
|
-
const now = /* @__PURE__ */ new Date();
|
|
13050
|
-
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
13051
|
-
const counterRef = (0, import_firestore35.doc)(
|
|
13052
|
-
db,
|
|
13053
|
-
`${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}/usage_counters/${yearMonth}`
|
|
13054
|
-
);
|
|
13055
|
-
const counterSnap = await (0, import_firestore35.getDoc)(counterRef);
|
|
13056
|
-
const currentCount = counterSnap.exists() ? ((_a = counterSnap.data()) == null ? void 0 : _a.messagesCount) || 0 : 0;
|
|
13057
|
-
if (currentCount + 1 > max) {
|
|
13058
|
-
throw new TierLimitError("messages this month", tier, currentCount, max);
|
|
12994
|
+
if (currentCount + 1 > effectiveMax) {
|
|
12995
|
+
throw new TierLimitError("clinic branches", tier, currentCount, effectiveMax);
|
|
13059
12996
|
}
|
|
13060
12997
|
}
|
|
13061
12998
|
|
|
@@ -13311,7 +13248,7 @@ var PractitionerService = class extends BaseService {
|
|
|
13311
13248
|
if (clinicSnap.exists()) {
|
|
13312
13249
|
const clinicGroupId = clinicSnap.data().clinicGroupId;
|
|
13313
13250
|
if (clinicGroupId) {
|
|
13314
|
-
await enforceProviderLimit(this.db, clinicGroupId);
|
|
13251
|
+
await enforceProviderLimit(this.db, clinicGroupId, validData.clinics[0]);
|
|
13315
13252
|
}
|
|
13316
13253
|
}
|
|
13317
13254
|
}
|
|
@@ -13391,7 +13328,7 @@ var PractitionerService = class extends BaseService {
|
|
|
13391
13328
|
throw new Error(`Clinic ${clinicId} not found`);
|
|
13392
13329
|
}
|
|
13393
13330
|
if (clinic.clinicGroupId) {
|
|
13394
|
-
await enforceProviderLimit(this.db, clinic.clinicGroupId);
|
|
13331
|
+
await enforceProviderLimit(this.db, clinic.clinicGroupId, clinicId);
|
|
13395
13332
|
}
|
|
13396
13333
|
const clinicsToAdd = /* @__PURE__ */ new Set([clinicId]);
|
|
13397
13334
|
if (data.clinics && data.clinics.length > 0) {
|
|
@@ -23182,7 +23119,7 @@ var ProcedureService = class extends BaseService {
|
|
|
23182
23119
|
throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
|
|
23183
23120
|
}
|
|
23184
23121
|
const clinic = clinicSnapshot.data();
|
|
23185
|
-
await enforceProcedureLimit(this.db, clinic.clinicGroupId);
|
|
23122
|
+
await enforceProcedureLimit(this.db, clinic.clinicGroupId, validatedData.clinicBranchId, validatedData.practitionerId);
|
|
23186
23123
|
const practitionerRef = (0, import_firestore63.doc)(this.db, PRACTITIONERS_COLLECTION, validatedData.practitionerId);
|
|
23187
23124
|
const practitionerSnapshot = await (0, import_firestore63.getDoc)(practitionerRef);
|
|
23188
23125
|
if (!practitionerSnapshot.exists()) {
|
|
@@ -23589,7 +23526,9 @@ var ProcedureService = class extends BaseService {
|
|
|
23589
23526
|
throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
|
|
23590
23527
|
}
|
|
23591
23528
|
const clinic = clinicSnapshot.data();
|
|
23592
|
-
|
|
23529
|
+
for (const practitionerId of practitionerIds) {
|
|
23530
|
+
await enforceProcedureLimit(this.db, clinic.clinicGroupId, validatedData.clinicBranchId, practitionerId);
|
|
23531
|
+
}
|
|
23593
23532
|
let processedPhotos = [];
|
|
23594
23533
|
if (validatedData.photos && validatedData.photos.length > 0) {
|
|
23595
23534
|
const batchId = this.generateId();
|
|
@@ -28067,12 +28006,9 @@ var RequirementType = /* @__PURE__ */ ((RequirementType2) => {
|
|
|
28067
28006
|
USER_FORMS_SUBCOLLECTION,
|
|
28068
28007
|
UserRole,
|
|
28069
28008
|
UserService,
|
|
28070
|
-
enforceAppointmentLimit,
|
|
28071
28009
|
enforceBranchLimit,
|
|
28072
|
-
enforceMessageLimit,
|
|
28073
28010
|
enforceProcedureLimit,
|
|
28074
28011
|
enforceProviderLimit,
|
|
28075
|
-
enforceStaffLimit,
|
|
28076
28012
|
getEffectiveTier,
|
|
28077
28013
|
getFirebaseApp,
|
|
28078
28014
|
getFirebaseAuth,
|
package/dist/index.mjs
CHANGED
|
@@ -12752,11 +12752,8 @@ var TIER_CONFIG = {
|
|
|
12752
12752
|
tier: "free",
|
|
12753
12753
|
name: "Free",
|
|
12754
12754
|
limits: {
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
maxAppointmentsPerMonth: 3,
|
|
12758
|
-
maxMessagesPerMonth: 10,
|
|
12759
|
-
maxStaff: 1,
|
|
12755
|
+
maxProvidersPerBranch: 1,
|
|
12756
|
+
maxProceduresPerProvider: 3,
|
|
12760
12757
|
maxBranches: 1
|
|
12761
12758
|
}
|
|
12762
12759
|
},
|
|
@@ -12764,11 +12761,8 @@ var TIER_CONFIG = {
|
|
|
12764
12761
|
tier: "connect",
|
|
12765
12762
|
name: "Connect",
|
|
12766
12763
|
limits: {
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
maxAppointmentsPerMonth: 100,
|
|
12770
|
-
maxMessagesPerMonth: -1,
|
|
12771
|
-
maxStaff: 5,
|
|
12764
|
+
maxProvidersPerBranch: 3,
|
|
12765
|
+
maxProceduresPerProvider: 10,
|
|
12772
12766
|
maxBranches: 1
|
|
12773
12767
|
}
|
|
12774
12768
|
},
|
|
@@ -12776,12 +12770,9 @@ var TIER_CONFIG = {
|
|
|
12776
12770
|
tier: "pro",
|
|
12777
12771
|
name: "Pro",
|
|
12778
12772
|
limits: {
|
|
12779
|
-
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
maxMessagesPerMonth: -1,
|
|
12783
|
-
maxStaff: -1,
|
|
12784
|
-
maxBranches: -1
|
|
12773
|
+
maxProvidersPerBranch: 10,
|
|
12774
|
+
maxProceduresPerProvider: 20,
|
|
12775
|
+
maxBranches: 3
|
|
12785
12776
|
}
|
|
12786
12777
|
}
|
|
12787
12778
|
};
|
|
@@ -12912,8 +12903,10 @@ function resolveEffectiveTier(subscriptionModel) {
|
|
|
12912
12903
|
var TierLimitError = class extends Error {
|
|
12913
12904
|
constructor(resource, currentTier, currentCount, maxAllowed) {
|
|
12914
12905
|
const tierLabel = currentTier.charAt(0).toUpperCase() + currentTier.slice(1);
|
|
12906
|
+
const canBuyAddons = currentTier === "connect" || currentTier === "pro";
|
|
12907
|
+
const actionHint = canBuyAddons ? "Please upgrade your plan or purchase an add-on." : "Please upgrade to Connect or Pro to add more.";
|
|
12915
12908
|
super(
|
|
12916
|
-
`Your ${tierLabel} plan allows a maximum of ${maxAllowed} ${resource}. You currently have ${currentCount}.
|
|
12909
|
+
`Your ${tierLabel} plan allows a maximum of ${maxAllowed} ${resource}. You currently have ${currentCount}. ${actionHint}`
|
|
12917
12910
|
);
|
|
12918
12911
|
this.code = "TIER_LIMIT_EXCEEDED";
|
|
12919
12912
|
this.name = "TierLimitError";
|
|
@@ -12923,58 +12916,50 @@ var TierLimitError = class extends Error {
|
|
|
12923
12916
|
this.maxAllowed = maxAllowed;
|
|
12924
12917
|
}
|
|
12925
12918
|
};
|
|
12926
|
-
async function
|
|
12919
|
+
async function getClinicGroupTierData(db, clinicGroupId) {
|
|
12927
12920
|
const groupRef = doc24(db, CLINIC_GROUPS_COLLECTION, clinicGroupId);
|
|
12928
12921
|
const groupSnap = await getDoc26(groupRef);
|
|
12929
12922
|
if (!groupSnap.exists()) {
|
|
12930
12923
|
throw new Error(`Clinic group ${clinicGroupId} not found`);
|
|
12931
12924
|
}
|
|
12932
|
-
const
|
|
12933
|
-
|
|
12925
|
+
const data = groupSnap.data();
|
|
12926
|
+
const subscriptionModel = data.subscriptionModel || "no_subscription";
|
|
12927
|
+
return {
|
|
12928
|
+
tier: resolveEffectiveTier(subscriptionModel),
|
|
12929
|
+
billing: data.billing
|
|
12930
|
+
};
|
|
12934
12931
|
}
|
|
12935
|
-
async function
|
|
12936
|
-
const
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
);
|
|
12941
|
-
const clinicsSnap = await getDocs13(clinicsQuery);
|
|
12942
|
-
const clinicIds = clinicsSnap.docs.map((d) => d.id);
|
|
12943
|
-
if (clinicIds.length === 0) return 0;
|
|
12932
|
+
async function getEffectiveTier(db, clinicGroupId) {
|
|
12933
|
+
const { tier } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12934
|
+
return tier;
|
|
12935
|
+
}
|
|
12936
|
+
async function countProvidersInBranch(db, branchId) {
|
|
12944
12937
|
const practitionersQuery = query13(
|
|
12945
12938
|
collection13(db, PRACTITIONERS_COLLECTION),
|
|
12946
12939
|
where13("isActive", "==", true)
|
|
12947
12940
|
);
|
|
12948
12941
|
const practitionersSnap = await getDocs13(practitionersQuery);
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
return clinics.some((c) => clinicIdSet.has(c));
|
|
12954
|
-
});
|
|
12955
|
-
return uniqueProviders.length;
|
|
12942
|
+
return practitionersSnap.docs.filter((d) => {
|
|
12943
|
+
const clinics = d.data().clinics || [];
|
|
12944
|
+
return clinics.includes(branchId);
|
|
12945
|
+
}).length;
|
|
12956
12946
|
}
|
|
12957
|
-
async function
|
|
12958
|
-
const
|
|
12959
|
-
collection13(db,
|
|
12960
|
-
where13("
|
|
12947
|
+
async function countProceduresForProvider(db, branchId, providerId) {
|
|
12948
|
+
const proceduresQuery = query13(
|
|
12949
|
+
collection13(db, PROCEDURES_COLLECTION),
|
|
12950
|
+
where13("clinicBranchId", "==", branchId),
|
|
12951
|
+
where13("practitionerId", "==", providerId),
|
|
12961
12952
|
where13("isActive", "==", true)
|
|
12962
12953
|
);
|
|
12963
|
-
const
|
|
12964
|
-
const
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
where13("isActive", "==", true)
|
|
12973
|
-
);
|
|
12974
|
-
const proceduresSnap = await getDocs13(proceduresQuery);
|
|
12975
|
-
totalProcedures += proceduresSnap.size;
|
|
12976
|
-
}
|
|
12977
|
-
return totalProcedures;
|
|
12954
|
+
const proceduresSnap = await getDocs13(proceduresQuery);
|
|
12955
|
+
const uniqueTechnologyIds = /* @__PURE__ */ new Set();
|
|
12956
|
+
proceduresSnap.docs.forEach((d) => {
|
|
12957
|
+
const technologyId = d.data().technologyId;
|
|
12958
|
+
if (technologyId) {
|
|
12959
|
+
uniqueTechnologyIds.add(technologyId);
|
|
12960
|
+
}
|
|
12961
|
+
});
|
|
12962
|
+
return uniqueTechnologyIds.size;
|
|
12978
12963
|
}
|
|
12979
12964
|
async function countBranchesInGroup(db, clinicGroupId) {
|
|
12980
12965
|
const clinicsQuery = query13(
|
|
@@ -12985,92 +12970,47 @@ async function countBranchesInGroup(db, clinicGroupId) {
|
|
|
12985
12970
|
const clinicsSnap = await getDocs13(clinicsQuery);
|
|
12986
12971
|
return clinicsSnap.size;
|
|
12987
12972
|
}
|
|
12988
|
-
async function enforceProviderLimit(db, clinicGroupId) {
|
|
12989
|
-
|
|
12973
|
+
async function enforceProviderLimit(db, clinicGroupId, branchId) {
|
|
12974
|
+
var _a;
|
|
12975
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12990
12976
|
const config = TIER_CONFIG[tier];
|
|
12991
12977
|
if (!config) return;
|
|
12992
|
-
const
|
|
12993
|
-
if (
|
|
12994
|
-
const
|
|
12995
|
-
|
|
12996
|
-
|
|
12978
|
+
const baseMax = config.limits.maxProvidersPerBranch;
|
|
12979
|
+
if (baseMax === -1) return;
|
|
12980
|
+
const addOns = (billing == null ? void 0 : billing.addOns) || {};
|
|
12981
|
+
const extraProviders = ((_a = addOns[branchId]) == null ? void 0 : _a.extraProviders) || 0;
|
|
12982
|
+
const effectiveMax = baseMax + extraProviders;
|
|
12983
|
+
const currentCount = await countProvidersInBranch(db, branchId);
|
|
12984
|
+
if (currentCount + 1 > effectiveMax) {
|
|
12985
|
+
throw new TierLimitError("providers in this branch", tier, currentCount, effectiveMax);
|
|
12997
12986
|
}
|
|
12998
12987
|
}
|
|
12999
|
-
async function enforceProcedureLimit(db, clinicGroupId, count = 1) {
|
|
13000
|
-
|
|
12988
|
+
async function enforceProcedureLimit(db, clinicGroupId, branchId, providerId, count = 1) {
|
|
12989
|
+
var _a;
|
|
12990
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
13001
12991
|
const config = TIER_CONFIG[tier];
|
|
13002
12992
|
if (!config) return;
|
|
13003
|
-
const
|
|
13004
|
-
if (
|
|
13005
|
-
const
|
|
13006
|
-
|
|
13007
|
-
|
|
12993
|
+
const baseMax = config.limits.maxProceduresPerProvider;
|
|
12994
|
+
if (baseMax === -1) return;
|
|
12995
|
+
const addOns = (billing == null ? void 0 : billing.addOns) || {};
|
|
12996
|
+
const procedureBlocks = ((_a = addOns[branchId]) == null ? void 0 : _a.procedureBlocks) || 0;
|
|
12997
|
+
const effectiveMax = baseMax + procedureBlocks * 10;
|
|
12998
|
+
const currentCount = await countProceduresForProvider(db, branchId, providerId);
|
|
12999
|
+
if (currentCount + count > effectiveMax) {
|
|
13000
|
+
throw new TierLimitError("procedures for this provider", tier, currentCount, effectiveMax);
|
|
13008
13001
|
}
|
|
13009
13002
|
}
|
|
13010
13003
|
async function enforceBranchLimit(db, clinicGroupId) {
|
|
13011
|
-
const tier = await
|
|
13004
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
13012
13005
|
const config = TIER_CONFIG[tier];
|
|
13013
13006
|
if (!config) return;
|
|
13014
|
-
const
|
|
13015
|
-
if (
|
|
13007
|
+
const baseMax = config.limits.maxBranches;
|
|
13008
|
+
if (baseMax === -1) return;
|
|
13009
|
+
const branchAddonCount = (billing == null ? void 0 : billing.branchAddonCount) || 0;
|
|
13010
|
+
const effectiveMax = baseMax + branchAddonCount;
|
|
13016
13011
|
const currentCount = await countBranchesInGroup(db, clinicGroupId);
|
|
13017
|
-
if (currentCount + 1 >
|
|
13018
|
-
throw new TierLimitError("clinic branches", tier, currentCount,
|
|
13019
|
-
}
|
|
13020
|
-
}
|
|
13021
|
-
async function enforceStaffLimit(db, clinicGroupId) {
|
|
13022
|
-
const tier = await getEffectiveTier(db, clinicGroupId);
|
|
13023
|
-
const config = TIER_CONFIG[tier];
|
|
13024
|
-
if (!config) return;
|
|
13025
|
-
const max = config.limits.maxStaff;
|
|
13026
|
-
if (max === -1) return;
|
|
13027
|
-
const staffQuery = query13(
|
|
13028
|
-
collection13(db, "clinic_staff_members"),
|
|
13029
|
-
where13("clinicGroupId", "==", clinicGroupId),
|
|
13030
|
-
where13("isActive", "==", true)
|
|
13031
|
-
);
|
|
13032
|
-
const staffSnap = await getDocs13(staffQuery);
|
|
13033
|
-
const currentCount = staffSnap.size;
|
|
13034
|
-
if (currentCount + 1 > max) {
|
|
13035
|
-
throw new TierLimitError("staff members", tier, currentCount, max);
|
|
13036
|
-
}
|
|
13037
|
-
}
|
|
13038
|
-
async function enforceAppointmentLimit(db, clinicGroupId) {
|
|
13039
|
-
var _a;
|
|
13040
|
-
const tier = await getEffectiveTier(db, clinicGroupId);
|
|
13041
|
-
const config = TIER_CONFIG[tier];
|
|
13042
|
-
if (!config) return;
|
|
13043
|
-
const max = config.limits.maxAppointmentsPerMonth;
|
|
13044
|
-
if (max === -1) return;
|
|
13045
|
-
const now = /* @__PURE__ */ new Date();
|
|
13046
|
-
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
13047
|
-
const counterRef = doc24(
|
|
13048
|
-
db,
|
|
13049
|
-
`${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}/usage_counters/${yearMonth}`
|
|
13050
|
-
);
|
|
13051
|
-
const counterSnap = await getDoc26(counterRef);
|
|
13052
|
-
const currentCount = counterSnap.exists() ? ((_a = counterSnap.data()) == null ? void 0 : _a.appointmentsCreated) || 0 : 0;
|
|
13053
|
-
if (currentCount + 1 > max) {
|
|
13054
|
-
throw new TierLimitError("appointments this month", tier, currentCount, max);
|
|
13055
|
-
}
|
|
13056
|
-
}
|
|
13057
|
-
async function enforceMessageLimit(db, clinicGroupId) {
|
|
13058
|
-
var _a;
|
|
13059
|
-
const tier = await getEffectiveTier(db, clinicGroupId);
|
|
13060
|
-
const config = TIER_CONFIG[tier];
|
|
13061
|
-
if (!config) return;
|
|
13062
|
-
const max = config.limits.maxMessagesPerMonth;
|
|
13063
|
-
if (max === -1) return;
|
|
13064
|
-
const now = /* @__PURE__ */ new Date();
|
|
13065
|
-
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
13066
|
-
const counterRef = doc24(
|
|
13067
|
-
db,
|
|
13068
|
-
`${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}/usage_counters/${yearMonth}`
|
|
13069
|
-
);
|
|
13070
|
-
const counterSnap = await getDoc26(counterRef);
|
|
13071
|
-
const currentCount = counterSnap.exists() ? ((_a = counterSnap.data()) == null ? void 0 : _a.messagesCount) || 0 : 0;
|
|
13072
|
-
if (currentCount + 1 > max) {
|
|
13073
|
-
throw new TierLimitError("messages this month", tier, currentCount, max);
|
|
13012
|
+
if (currentCount + 1 > effectiveMax) {
|
|
13013
|
+
throw new TierLimitError("clinic branches", tier, currentCount, effectiveMax);
|
|
13074
13014
|
}
|
|
13075
13015
|
}
|
|
13076
13016
|
|
|
@@ -13326,7 +13266,7 @@ var PractitionerService = class extends BaseService {
|
|
|
13326
13266
|
if (clinicSnap.exists()) {
|
|
13327
13267
|
const clinicGroupId = clinicSnap.data().clinicGroupId;
|
|
13328
13268
|
if (clinicGroupId) {
|
|
13329
|
-
await enforceProviderLimit(this.db, clinicGroupId);
|
|
13269
|
+
await enforceProviderLimit(this.db, clinicGroupId, validData.clinics[0]);
|
|
13330
13270
|
}
|
|
13331
13271
|
}
|
|
13332
13272
|
}
|
|
@@ -13406,7 +13346,7 @@ var PractitionerService = class extends BaseService {
|
|
|
13406
13346
|
throw new Error(`Clinic ${clinicId} not found`);
|
|
13407
13347
|
}
|
|
13408
13348
|
if (clinic.clinicGroupId) {
|
|
13409
|
-
await enforceProviderLimit(this.db, clinic.clinicGroupId);
|
|
13349
|
+
await enforceProviderLimit(this.db, clinic.clinicGroupId, clinicId);
|
|
13410
13350
|
}
|
|
13411
13351
|
const clinicsToAdd = /* @__PURE__ */ new Set([clinicId]);
|
|
13412
13352
|
if (data.clinics && data.clinics.length > 0) {
|
|
@@ -23411,7 +23351,7 @@ var ProcedureService = class extends BaseService {
|
|
|
23411
23351
|
throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
|
|
23412
23352
|
}
|
|
23413
23353
|
const clinic = clinicSnapshot.data();
|
|
23414
|
-
await enforceProcedureLimit(this.db, clinic.clinicGroupId);
|
|
23354
|
+
await enforceProcedureLimit(this.db, clinic.clinicGroupId, validatedData.clinicBranchId, validatedData.practitionerId);
|
|
23415
23355
|
const practitionerRef = doc44(this.db, PRACTITIONERS_COLLECTION, validatedData.practitionerId);
|
|
23416
23356
|
const practitionerSnapshot = await getDoc45(practitionerRef);
|
|
23417
23357
|
if (!practitionerSnapshot.exists()) {
|
|
@@ -23818,7 +23758,9 @@ var ProcedureService = class extends BaseService {
|
|
|
23818
23758
|
throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
|
|
23819
23759
|
}
|
|
23820
23760
|
const clinic = clinicSnapshot.data();
|
|
23821
|
-
|
|
23761
|
+
for (const practitionerId of practitionerIds) {
|
|
23762
|
+
await enforceProcedureLimit(this.db, clinic.clinicGroupId, validatedData.clinicBranchId, practitionerId);
|
|
23763
|
+
}
|
|
23822
23764
|
let processedPhotos = [];
|
|
23823
23765
|
if (validatedData.photos && validatedData.photos.length > 0) {
|
|
23824
23766
|
const batchId = this.generateId();
|
|
@@ -28383,12 +28325,9 @@ export {
|
|
|
28383
28325
|
USER_FORMS_SUBCOLLECTION,
|
|
28384
28326
|
UserRole,
|
|
28385
28327
|
UserService,
|
|
28386
|
-
enforceAppointmentLimit,
|
|
28387
28328
|
enforceBranchLimit,
|
|
28388
|
-
enforceMessageLimit,
|
|
28389
28329
|
enforceProcedureLimit,
|
|
28390
28330
|
enforceProviderLimit,
|
|
28391
|
-
enforceStaffLimit,
|
|
28392
28331
|
getEffectiveTier,
|
|
28393
28332
|
getFirebaseApp,
|
|
28394
28333
|
getFirebaseAuth,
|
package/package.json
CHANGED