@blackcode_sa/metaestetics-api 1.15.13 → 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 -26
- package/dist/index.d.ts +36 -26
- package/dist/index.js +73 -120
- package/dist/index.mjs +73 -118
- package/package.json +1 -1
- package/src/config/tiers.config.ts +16 -17
- package/src/services/practitioner/practitioner.service.ts +3 -3
- package/src/services/procedure/procedure.service.ts +6 -4
- package/src/services/tier-enforcement.ts +103 -131
- package/src/types/clinic/index.ts +13 -0
- package/src/types/clinic/rbac.types.ts +6 -4
package/dist/index.js
CHANGED
|
@@ -155,9 +155,7 @@ __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
161
|
getEffectiveTier: () => getEffectiveTier,
|
|
@@ -12736,10 +12734,8 @@ var TIER_CONFIG = {
|
|
|
12736
12734
|
tier: "free",
|
|
12737
12735
|
name: "Free",
|
|
12738
12736
|
limits: {
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
maxAppointmentsPerMonth: 3,
|
|
12742
|
-
maxMessagesPerMonth: 10,
|
|
12737
|
+
maxProvidersPerBranch: 1,
|
|
12738
|
+
maxProceduresPerProvider: 3,
|
|
12743
12739
|
maxBranches: 1
|
|
12744
12740
|
}
|
|
12745
12741
|
},
|
|
@@ -12747,10 +12743,8 @@ var TIER_CONFIG = {
|
|
|
12747
12743
|
tier: "connect",
|
|
12748
12744
|
name: "Connect",
|
|
12749
12745
|
limits: {
|
|
12750
|
-
|
|
12751
|
-
|
|
12752
|
-
maxAppointmentsPerMonth: 100,
|
|
12753
|
-
maxMessagesPerMonth: -1,
|
|
12746
|
+
maxProvidersPerBranch: 3,
|
|
12747
|
+
maxProceduresPerProvider: 10,
|
|
12754
12748
|
maxBranches: 1
|
|
12755
12749
|
}
|
|
12756
12750
|
},
|
|
@@ -12758,11 +12752,9 @@ var TIER_CONFIG = {
|
|
|
12758
12752
|
tier: "pro",
|
|
12759
12753
|
name: "Pro",
|
|
12760
12754
|
limits: {
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
maxMessagesPerMonth: -1,
|
|
12765
|
-
maxBranches: -1
|
|
12755
|
+
maxProvidersPerBranch: 10,
|
|
12756
|
+
maxProceduresPerProvider: 20,
|
|
12757
|
+
maxBranches: 3
|
|
12766
12758
|
}
|
|
12767
12759
|
}
|
|
12768
12760
|
};
|
|
@@ -12893,8 +12885,10 @@ function resolveEffectiveTier(subscriptionModel) {
|
|
|
12893
12885
|
var TierLimitError = class extends Error {
|
|
12894
12886
|
constructor(resource, currentTier, currentCount, maxAllowed) {
|
|
12895
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.";
|
|
12896
12890
|
super(
|
|
12897
|
-
`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}`
|
|
12898
12892
|
);
|
|
12899
12893
|
this.code = "TIER_LIMIT_EXCEEDED";
|
|
12900
12894
|
this.name = "TierLimitError";
|
|
@@ -12904,62 +12898,49 @@ var TierLimitError = class extends Error {
|
|
|
12904
12898
|
this.maxAllowed = maxAllowed;
|
|
12905
12899
|
}
|
|
12906
12900
|
};
|
|
12907
|
-
async function
|
|
12901
|
+
async function getClinicGroupTierData(db, clinicGroupId) {
|
|
12908
12902
|
const groupRef = (0, import_firestore35.doc)(db, CLINIC_GROUPS_COLLECTION, clinicGroupId);
|
|
12909
12903
|
const groupSnap = await (0, import_firestore35.getDoc)(groupRef);
|
|
12910
12904
|
if (!groupSnap.exists()) {
|
|
12911
12905
|
throw new Error(`Clinic group ${clinicGroupId} not found`);
|
|
12912
12906
|
}
|
|
12913
|
-
const
|
|
12914
|
-
|
|
12907
|
+
const data = groupSnap.data();
|
|
12908
|
+
const subscriptionModel = data.subscriptionModel || "no_subscription";
|
|
12909
|
+
return {
|
|
12910
|
+
tier: resolveEffectiveTier(subscriptionModel),
|
|
12911
|
+
billing: data.billing
|
|
12912
|
+
};
|
|
12915
12913
|
}
|
|
12916
|
-
async function
|
|
12917
|
-
const
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
);
|
|
12922
|
-
const clinicsSnap = await (0, import_firestore35.getDocs)(clinicsQuery);
|
|
12923
|
-
const clinicIds = clinicsSnap.docs.map((d) => d.id);
|
|
12924
|
-
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) {
|
|
12925
12919
|
const practitionersQuery = (0, import_firestore35.query)(
|
|
12926
12920
|
(0, import_firestore35.collection)(db, PRACTITIONERS_COLLECTION),
|
|
12927
12921
|
(0, import_firestore35.where)("isActive", "==", true)
|
|
12928
12922
|
);
|
|
12929
12923
|
const practitionersSnap = await (0, import_firestore35.getDocs)(practitionersQuery);
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
return clinics.some((c) => clinicIdSet.has(c));
|
|
12935
|
-
});
|
|
12936
|
-
return uniqueProviders.length;
|
|
12924
|
+
return practitionersSnap.docs.filter((d) => {
|
|
12925
|
+
const clinics = d.data().clinics || [];
|
|
12926
|
+
return clinics.includes(branchId);
|
|
12927
|
+
}).length;
|
|
12937
12928
|
}
|
|
12938
|
-
async function
|
|
12939
|
-
const
|
|
12940
|
-
(0, import_firestore35.collection)(db,
|
|
12941
|
-
(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),
|
|
12942
12934
|
(0, import_firestore35.where)("isActive", "==", true)
|
|
12943
12935
|
);
|
|
12944
|
-
const
|
|
12945
|
-
const clinicIds = clinicsSnap.docs.map((d) => d.id);
|
|
12946
|
-
if (clinicIds.length === 0) return 0;
|
|
12936
|
+
const proceduresSnap = await (0, import_firestore35.getDocs)(proceduresQuery);
|
|
12947
12937
|
const uniqueTechnologyIds = /* @__PURE__ */ new Set();
|
|
12948
|
-
|
|
12949
|
-
const
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
);
|
|
12955
|
-
const proceduresSnap = await (0, import_firestore35.getDocs)(proceduresQuery);
|
|
12956
|
-
proceduresSnap.docs.forEach((d) => {
|
|
12957
|
-
const technologyId = d.data().technologyId;
|
|
12958
|
-
if (technologyId) {
|
|
12959
|
-
uniqueTechnologyIds.add(technologyId);
|
|
12960
|
-
}
|
|
12961
|
-
});
|
|
12962
|
-
}
|
|
12938
|
+
proceduresSnap.docs.forEach((d) => {
|
|
12939
|
+
const technologyId = d.data().technologyId;
|
|
12940
|
+
if (technologyId) {
|
|
12941
|
+
uniqueTechnologyIds.add(technologyId);
|
|
12942
|
+
}
|
|
12943
|
+
});
|
|
12963
12944
|
return uniqueTechnologyIds.size;
|
|
12964
12945
|
}
|
|
12965
12946
|
async function countBranchesInGroup(db, clinicGroupId) {
|
|
@@ -12971,75 +12952,47 @@ async function countBranchesInGroup(db, clinicGroupId) {
|
|
|
12971
12952
|
const clinicsSnap = await (0, import_firestore35.getDocs)(clinicsQuery);
|
|
12972
12953
|
return clinicsSnap.size;
|
|
12973
12954
|
}
|
|
12974
|
-
async function enforceProviderLimit(db, clinicGroupId) {
|
|
12975
|
-
|
|
12955
|
+
async function enforceProviderLimit(db, clinicGroupId, branchId) {
|
|
12956
|
+
var _a;
|
|
12957
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12976
12958
|
const config = TIER_CONFIG[tier];
|
|
12977
12959
|
if (!config) return;
|
|
12978
|
-
const
|
|
12979
|
-
if (
|
|
12980
|
-
const
|
|
12981
|
-
|
|
12982
|
-
|
|
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);
|
|
12983
12968
|
}
|
|
12984
12969
|
}
|
|
12985
|
-
async function enforceProcedureLimit(db, clinicGroupId, count = 1) {
|
|
12986
|
-
|
|
12970
|
+
async function enforceProcedureLimit(db, clinicGroupId, branchId, providerId, count = 1) {
|
|
12971
|
+
var _a;
|
|
12972
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12987
12973
|
const config = TIER_CONFIG[tier];
|
|
12988
12974
|
if (!config) return;
|
|
12989
|
-
const
|
|
12990
|
-
if (
|
|
12991
|
-
const
|
|
12992
|
-
|
|
12993
|
-
|
|
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);
|
|
12994
12983
|
}
|
|
12995
12984
|
}
|
|
12996
12985
|
async function enforceBranchLimit(db, clinicGroupId) {
|
|
12997
|
-
const tier = await
|
|
12986
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12998
12987
|
const config = TIER_CONFIG[tier];
|
|
12999
12988
|
if (!config) return;
|
|
13000
|
-
const
|
|
13001
|
-
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;
|
|
13002
12993
|
const currentCount = await countBranchesInGroup(db, clinicGroupId);
|
|
13003
|
-
if (currentCount + 1 >
|
|
13004
|
-
throw new TierLimitError("clinic branches", tier, currentCount,
|
|
13005
|
-
}
|
|
13006
|
-
}
|
|
13007
|
-
async function enforceAppointmentLimit(db, clinicGroupId) {
|
|
13008
|
-
var _a;
|
|
13009
|
-
const tier = await getEffectiveTier(db, clinicGroupId);
|
|
13010
|
-
const config = TIER_CONFIG[tier];
|
|
13011
|
-
if (!config) return;
|
|
13012
|
-
const max = config.limits.maxAppointmentsPerMonth;
|
|
13013
|
-
if (max === -1) return;
|
|
13014
|
-
const now = /* @__PURE__ */ new Date();
|
|
13015
|
-
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
13016
|
-
const counterRef = (0, import_firestore35.doc)(
|
|
13017
|
-
db,
|
|
13018
|
-
`${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}/usage_counters/${yearMonth}`
|
|
13019
|
-
);
|
|
13020
|
-
const counterSnap = await (0, import_firestore35.getDoc)(counterRef);
|
|
13021
|
-
const currentCount = counterSnap.exists() ? ((_a = counterSnap.data()) == null ? void 0 : _a.appointmentsCreated) || 0 : 0;
|
|
13022
|
-
if (currentCount + 1 > max) {
|
|
13023
|
-
throw new TierLimitError("appointments this month", tier, currentCount, max);
|
|
13024
|
-
}
|
|
13025
|
-
}
|
|
13026
|
-
async function enforceMessageLimit(db, clinicGroupId) {
|
|
13027
|
-
var _a;
|
|
13028
|
-
const tier = await getEffectiveTier(db, clinicGroupId);
|
|
13029
|
-
const config = TIER_CONFIG[tier];
|
|
13030
|
-
if (!config) return;
|
|
13031
|
-
const max = config.limits.maxMessagesPerMonth;
|
|
13032
|
-
if (max === -1) return;
|
|
13033
|
-
const now = /* @__PURE__ */ new Date();
|
|
13034
|
-
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
13035
|
-
const counterRef = (0, import_firestore35.doc)(
|
|
13036
|
-
db,
|
|
13037
|
-
`${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}/usage_counters/${yearMonth}`
|
|
13038
|
-
);
|
|
13039
|
-
const counterSnap = await (0, import_firestore35.getDoc)(counterRef);
|
|
13040
|
-
const currentCount = counterSnap.exists() ? ((_a = counterSnap.data()) == null ? void 0 : _a.messagesCount) || 0 : 0;
|
|
13041
|
-
if (currentCount + 1 > max) {
|
|
13042
|
-
throw new TierLimitError("messages this month", tier, currentCount, max);
|
|
12994
|
+
if (currentCount + 1 > effectiveMax) {
|
|
12995
|
+
throw new TierLimitError("clinic branches", tier, currentCount, effectiveMax);
|
|
13043
12996
|
}
|
|
13044
12997
|
}
|
|
13045
12998
|
|
|
@@ -13295,7 +13248,7 @@ var PractitionerService = class extends BaseService {
|
|
|
13295
13248
|
if (clinicSnap.exists()) {
|
|
13296
13249
|
const clinicGroupId = clinicSnap.data().clinicGroupId;
|
|
13297
13250
|
if (clinicGroupId) {
|
|
13298
|
-
await enforceProviderLimit(this.db, clinicGroupId);
|
|
13251
|
+
await enforceProviderLimit(this.db, clinicGroupId, validData.clinics[0]);
|
|
13299
13252
|
}
|
|
13300
13253
|
}
|
|
13301
13254
|
}
|
|
@@ -13375,7 +13328,7 @@ var PractitionerService = class extends BaseService {
|
|
|
13375
13328
|
throw new Error(`Clinic ${clinicId} not found`);
|
|
13376
13329
|
}
|
|
13377
13330
|
if (clinic.clinicGroupId) {
|
|
13378
|
-
await enforceProviderLimit(this.db, clinic.clinicGroupId);
|
|
13331
|
+
await enforceProviderLimit(this.db, clinic.clinicGroupId, clinicId);
|
|
13379
13332
|
}
|
|
13380
13333
|
const clinicsToAdd = /* @__PURE__ */ new Set([clinicId]);
|
|
13381
13334
|
if (data.clinics && data.clinics.length > 0) {
|
|
@@ -23166,7 +23119,7 @@ var ProcedureService = class extends BaseService {
|
|
|
23166
23119
|
throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
|
|
23167
23120
|
}
|
|
23168
23121
|
const clinic = clinicSnapshot.data();
|
|
23169
|
-
await enforceProcedureLimit(this.db, clinic.clinicGroupId);
|
|
23122
|
+
await enforceProcedureLimit(this.db, clinic.clinicGroupId, validatedData.clinicBranchId, validatedData.practitionerId);
|
|
23170
23123
|
const practitionerRef = (0, import_firestore63.doc)(this.db, PRACTITIONERS_COLLECTION, validatedData.practitionerId);
|
|
23171
23124
|
const practitionerSnapshot = await (0, import_firestore63.getDoc)(practitionerRef);
|
|
23172
23125
|
if (!practitionerSnapshot.exists()) {
|
|
@@ -23573,7 +23526,9 @@ var ProcedureService = class extends BaseService {
|
|
|
23573
23526
|
throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
|
|
23574
23527
|
}
|
|
23575
23528
|
const clinic = clinicSnapshot.data();
|
|
23576
|
-
|
|
23529
|
+
for (const practitionerId of practitionerIds) {
|
|
23530
|
+
await enforceProcedureLimit(this.db, clinic.clinicGroupId, validatedData.clinicBranchId, practitionerId);
|
|
23531
|
+
}
|
|
23577
23532
|
let processedPhotos = [];
|
|
23578
23533
|
if (validatedData.photos && validatedData.photos.length > 0) {
|
|
23579
23534
|
const batchId = this.generateId();
|
|
@@ -28051,9 +28006,7 @@ var RequirementType = /* @__PURE__ */ ((RequirementType2) => {
|
|
|
28051
28006
|
USER_FORMS_SUBCOLLECTION,
|
|
28052
28007
|
UserRole,
|
|
28053
28008
|
UserService,
|
|
28054
|
-
enforceAppointmentLimit,
|
|
28055
28009
|
enforceBranchLimit,
|
|
28056
|
-
enforceMessageLimit,
|
|
28057
28010
|
enforceProcedureLimit,
|
|
28058
28011
|
enforceProviderLimit,
|
|
28059
28012
|
getEffectiveTier,
|
package/dist/index.mjs
CHANGED
|
@@ -12752,10 +12752,8 @@ var TIER_CONFIG = {
|
|
|
12752
12752
|
tier: "free",
|
|
12753
12753
|
name: "Free",
|
|
12754
12754
|
limits: {
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
maxAppointmentsPerMonth: 3,
|
|
12758
|
-
maxMessagesPerMonth: 10,
|
|
12755
|
+
maxProvidersPerBranch: 1,
|
|
12756
|
+
maxProceduresPerProvider: 3,
|
|
12759
12757
|
maxBranches: 1
|
|
12760
12758
|
}
|
|
12761
12759
|
},
|
|
@@ -12763,10 +12761,8 @@ var TIER_CONFIG = {
|
|
|
12763
12761
|
tier: "connect",
|
|
12764
12762
|
name: "Connect",
|
|
12765
12763
|
limits: {
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
maxAppointmentsPerMonth: 100,
|
|
12769
|
-
maxMessagesPerMonth: -1,
|
|
12764
|
+
maxProvidersPerBranch: 3,
|
|
12765
|
+
maxProceduresPerProvider: 10,
|
|
12770
12766
|
maxBranches: 1
|
|
12771
12767
|
}
|
|
12772
12768
|
},
|
|
@@ -12774,11 +12770,9 @@ var TIER_CONFIG = {
|
|
|
12774
12770
|
tier: "pro",
|
|
12775
12771
|
name: "Pro",
|
|
12776
12772
|
limits: {
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
12780
|
-
maxMessagesPerMonth: -1,
|
|
12781
|
-
maxBranches: -1
|
|
12773
|
+
maxProvidersPerBranch: 10,
|
|
12774
|
+
maxProceduresPerProvider: 20,
|
|
12775
|
+
maxBranches: 3
|
|
12782
12776
|
}
|
|
12783
12777
|
}
|
|
12784
12778
|
};
|
|
@@ -12909,8 +12903,10 @@ function resolveEffectiveTier(subscriptionModel) {
|
|
|
12909
12903
|
var TierLimitError = class extends Error {
|
|
12910
12904
|
constructor(resource, currentTier, currentCount, maxAllowed) {
|
|
12911
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.";
|
|
12912
12908
|
super(
|
|
12913
|
-
`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}`
|
|
12914
12910
|
);
|
|
12915
12911
|
this.code = "TIER_LIMIT_EXCEEDED";
|
|
12916
12912
|
this.name = "TierLimitError";
|
|
@@ -12920,62 +12916,49 @@ var TierLimitError = class extends Error {
|
|
|
12920
12916
|
this.maxAllowed = maxAllowed;
|
|
12921
12917
|
}
|
|
12922
12918
|
};
|
|
12923
|
-
async function
|
|
12919
|
+
async function getClinicGroupTierData(db, clinicGroupId) {
|
|
12924
12920
|
const groupRef = doc24(db, CLINIC_GROUPS_COLLECTION, clinicGroupId);
|
|
12925
12921
|
const groupSnap = await getDoc26(groupRef);
|
|
12926
12922
|
if (!groupSnap.exists()) {
|
|
12927
12923
|
throw new Error(`Clinic group ${clinicGroupId} not found`);
|
|
12928
12924
|
}
|
|
12929
|
-
const
|
|
12930
|
-
|
|
12925
|
+
const data = groupSnap.data();
|
|
12926
|
+
const subscriptionModel = data.subscriptionModel || "no_subscription";
|
|
12927
|
+
return {
|
|
12928
|
+
tier: resolveEffectiveTier(subscriptionModel),
|
|
12929
|
+
billing: data.billing
|
|
12930
|
+
};
|
|
12931
12931
|
}
|
|
12932
|
-
async function
|
|
12933
|
-
const
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
);
|
|
12938
|
-
const clinicsSnap = await getDocs13(clinicsQuery);
|
|
12939
|
-
const clinicIds = clinicsSnap.docs.map((d) => d.id);
|
|
12940
|
-
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) {
|
|
12941
12937
|
const practitionersQuery = query13(
|
|
12942
12938
|
collection13(db, PRACTITIONERS_COLLECTION),
|
|
12943
12939
|
where13("isActive", "==", true)
|
|
12944
12940
|
);
|
|
12945
12941
|
const practitionersSnap = await getDocs13(practitionersQuery);
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
return clinics.some((c) => clinicIdSet.has(c));
|
|
12951
|
-
});
|
|
12952
|
-
return uniqueProviders.length;
|
|
12942
|
+
return practitionersSnap.docs.filter((d) => {
|
|
12943
|
+
const clinics = d.data().clinics || [];
|
|
12944
|
+
return clinics.includes(branchId);
|
|
12945
|
+
}).length;
|
|
12953
12946
|
}
|
|
12954
|
-
async function
|
|
12955
|
-
const
|
|
12956
|
-
collection13(db,
|
|
12957
|
-
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),
|
|
12958
12952
|
where13("isActive", "==", true)
|
|
12959
12953
|
);
|
|
12960
|
-
const
|
|
12961
|
-
const clinicIds = clinicsSnap.docs.map((d) => d.id);
|
|
12962
|
-
if (clinicIds.length === 0) return 0;
|
|
12954
|
+
const proceduresSnap = await getDocs13(proceduresQuery);
|
|
12963
12955
|
const uniqueTechnologyIds = /* @__PURE__ */ new Set();
|
|
12964
|
-
|
|
12965
|
-
const
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
);
|
|
12971
|
-
const proceduresSnap = await getDocs13(proceduresQuery);
|
|
12972
|
-
proceduresSnap.docs.forEach((d) => {
|
|
12973
|
-
const technologyId = d.data().technologyId;
|
|
12974
|
-
if (technologyId) {
|
|
12975
|
-
uniqueTechnologyIds.add(technologyId);
|
|
12976
|
-
}
|
|
12977
|
-
});
|
|
12978
|
-
}
|
|
12956
|
+
proceduresSnap.docs.forEach((d) => {
|
|
12957
|
+
const technologyId = d.data().technologyId;
|
|
12958
|
+
if (technologyId) {
|
|
12959
|
+
uniqueTechnologyIds.add(technologyId);
|
|
12960
|
+
}
|
|
12961
|
+
});
|
|
12979
12962
|
return uniqueTechnologyIds.size;
|
|
12980
12963
|
}
|
|
12981
12964
|
async function countBranchesInGroup(db, clinicGroupId) {
|
|
@@ -12987,75 +12970,47 @@ async function countBranchesInGroup(db, clinicGroupId) {
|
|
|
12987
12970
|
const clinicsSnap = await getDocs13(clinicsQuery);
|
|
12988
12971
|
return clinicsSnap.size;
|
|
12989
12972
|
}
|
|
12990
|
-
async function enforceProviderLimit(db, clinicGroupId) {
|
|
12991
|
-
|
|
12973
|
+
async function enforceProviderLimit(db, clinicGroupId, branchId) {
|
|
12974
|
+
var _a;
|
|
12975
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
12992
12976
|
const config = TIER_CONFIG[tier];
|
|
12993
12977
|
if (!config) return;
|
|
12994
|
-
const
|
|
12995
|
-
if (
|
|
12996
|
-
const
|
|
12997
|
-
|
|
12998
|
-
|
|
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);
|
|
12999
12986
|
}
|
|
13000
12987
|
}
|
|
13001
|
-
async function enforceProcedureLimit(db, clinicGroupId, count = 1) {
|
|
13002
|
-
|
|
12988
|
+
async function enforceProcedureLimit(db, clinicGroupId, branchId, providerId, count = 1) {
|
|
12989
|
+
var _a;
|
|
12990
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
13003
12991
|
const config = TIER_CONFIG[tier];
|
|
13004
12992
|
if (!config) return;
|
|
13005
|
-
const
|
|
13006
|
-
if (
|
|
13007
|
-
const
|
|
13008
|
-
|
|
13009
|
-
|
|
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);
|
|
13010
13001
|
}
|
|
13011
13002
|
}
|
|
13012
13003
|
async function enforceBranchLimit(db, clinicGroupId) {
|
|
13013
|
-
const tier = await
|
|
13004
|
+
const { tier, billing } = await getClinicGroupTierData(db, clinicGroupId);
|
|
13014
13005
|
const config = TIER_CONFIG[tier];
|
|
13015
13006
|
if (!config) return;
|
|
13016
|
-
const
|
|
13017
|
-
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;
|
|
13018
13011
|
const currentCount = await countBranchesInGroup(db, clinicGroupId);
|
|
13019
|
-
if (currentCount + 1 >
|
|
13020
|
-
throw new TierLimitError("clinic branches", tier, currentCount,
|
|
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 = doc24(
|
|
13033
|
-
db,
|
|
13034
|
-
`${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}/usage_counters/${yearMonth}`
|
|
13035
|
-
);
|
|
13036
|
-
const counterSnap = await getDoc26(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 = doc24(
|
|
13052
|
-
db,
|
|
13053
|
-
`${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}/usage_counters/${yearMonth}`
|
|
13054
|
-
);
|
|
13055
|
-
const counterSnap = await getDoc26(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);
|
|
13012
|
+
if (currentCount + 1 > effectiveMax) {
|
|
13013
|
+
throw new TierLimitError("clinic branches", tier, currentCount, effectiveMax);
|
|
13059
13014
|
}
|
|
13060
13015
|
}
|
|
13061
13016
|
|
|
@@ -13311,7 +13266,7 @@ var PractitionerService = class extends BaseService {
|
|
|
13311
13266
|
if (clinicSnap.exists()) {
|
|
13312
13267
|
const clinicGroupId = clinicSnap.data().clinicGroupId;
|
|
13313
13268
|
if (clinicGroupId) {
|
|
13314
|
-
await enforceProviderLimit(this.db, clinicGroupId);
|
|
13269
|
+
await enforceProviderLimit(this.db, clinicGroupId, validData.clinics[0]);
|
|
13315
13270
|
}
|
|
13316
13271
|
}
|
|
13317
13272
|
}
|
|
@@ -13391,7 +13346,7 @@ var PractitionerService = class extends BaseService {
|
|
|
13391
13346
|
throw new Error(`Clinic ${clinicId} not found`);
|
|
13392
13347
|
}
|
|
13393
13348
|
if (clinic.clinicGroupId) {
|
|
13394
|
-
await enforceProviderLimit(this.db, clinic.clinicGroupId);
|
|
13349
|
+
await enforceProviderLimit(this.db, clinic.clinicGroupId, clinicId);
|
|
13395
13350
|
}
|
|
13396
13351
|
const clinicsToAdd = /* @__PURE__ */ new Set([clinicId]);
|
|
13397
13352
|
if (data.clinics && data.clinics.length > 0) {
|
|
@@ -23396,7 +23351,7 @@ var ProcedureService = class extends BaseService {
|
|
|
23396
23351
|
throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
|
|
23397
23352
|
}
|
|
23398
23353
|
const clinic = clinicSnapshot.data();
|
|
23399
|
-
await enforceProcedureLimit(this.db, clinic.clinicGroupId);
|
|
23354
|
+
await enforceProcedureLimit(this.db, clinic.clinicGroupId, validatedData.clinicBranchId, validatedData.practitionerId);
|
|
23400
23355
|
const practitionerRef = doc44(this.db, PRACTITIONERS_COLLECTION, validatedData.practitionerId);
|
|
23401
23356
|
const practitionerSnapshot = await getDoc45(practitionerRef);
|
|
23402
23357
|
if (!practitionerSnapshot.exists()) {
|
|
@@ -23803,7 +23758,9 @@ var ProcedureService = class extends BaseService {
|
|
|
23803
23758
|
throw new Error(`Clinic with ID ${validatedData.clinicBranchId} not found`);
|
|
23804
23759
|
}
|
|
23805
23760
|
const clinic = clinicSnapshot.data();
|
|
23806
|
-
|
|
23761
|
+
for (const practitionerId of practitionerIds) {
|
|
23762
|
+
await enforceProcedureLimit(this.db, clinic.clinicGroupId, validatedData.clinicBranchId, practitionerId);
|
|
23763
|
+
}
|
|
23807
23764
|
let processedPhotos = [];
|
|
23808
23765
|
if (validatedData.photos && validatedData.photos.length > 0) {
|
|
23809
23766
|
const batchId = this.generateId();
|
|
@@ -28368,9 +28325,7 @@ export {
|
|
|
28368
28325
|
USER_FORMS_SUBCOLLECTION,
|
|
28369
28326
|
UserRole,
|
|
28370
28327
|
UserService,
|
|
28371
|
-
enforceAppointmentLimit,
|
|
28372
28328
|
enforceBranchLimit,
|
|
28373
|
-
enforceMessageLimit,
|
|
28374
28329
|
enforceProcedureLimit,
|
|
28375
28330
|
enforceProviderLimit,
|
|
28376
28331
|
getEffectiveTier,
|
package/package.json
CHANGED