@blackcode_sa/metaestetics-api 1.13.19 → 1.13.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -4606,7 +4606,7 @@ async function updateAppointmentUtil(db, appointmentId, data) {
|
|
|
4606
4606
|
updateData.confirmationTime = import_firestore6.Timestamp.now();
|
|
4607
4607
|
}
|
|
4608
4608
|
if (currentAppointment.calendarEventId) {
|
|
4609
|
-
await updateCalendarEventStatus(db, currentAppointment
|
|
4609
|
+
await updateCalendarEventStatus(db, currentAppointment, data.status);
|
|
4610
4610
|
}
|
|
4611
4611
|
}
|
|
4612
4612
|
await (0, import_firestore6.updateDoc)(appointmentRef, updateData);
|
|
@@ -4620,12 +4620,11 @@ async function updateAppointmentUtil(db, appointmentId, data) {
|
|
|
4620
4620
|
throw error;
|
|
4621
4621
|
}
|
|
4622
4622
|
}
|
|
4623
|
-
async function updateCalendarEventStatus(db,
|
|
4623
|
+
async function updateCalendarEventStatus(db, appointment, appointmentStatus) {
|
|
4624
4624
|
try {
|
|
4625
|
-
const
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
console.warn(`Calendar event with ID ${calendarEventId} not found`);
|
|
4625
|
+
const calendarEventId = appointment.calendarEventId;
|
|
4626
|
+
if (!calendarEventId) {
|
|
4627
|
+
console.warn(`Appointment ${appointment.id} has no calendarEventId, skipping calendar event update`);
|
|
4629
4628
|
return;
|
|
4630
4629
|
}
|
|
4631
4630
|
let calendarStatus;
|
|
@@ -4660,12 +4659,48 @@ async function updateCalendarEventStatus(db, calendarEventId, appointmentStatus)
|
|
|
4660
4659
|
console.warn(`Unknown appointment status: ${appointmentStatus}, not updating calendar event`);
|
|
4661
4660
|
return;
|
|
4662
4661
|
}
|
|
4663
|
-
|
|
4662
|
+
const updateData = {
|
|
4664
4663
|
status: calendarStatus,
|
|
4665
4664
|
updatedAt: (0, import_firestore6.serverTimestamp)()
|
|
4666
|
-
}
|
|
4665
|
+
};
|
|
4666
|
+
const updatePromises = [];
|
|
4667
|
+
if (appointment.practitionerId) {
|
|
4668
|
+
const practitionerEventRef = (0, import_firestore6.doc)(
|
|
4669
|
+
db,
|
|
4670
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
4671
|
+
);
|
|
4672
|
+
updatePromises.push(
|
|
4673
|
+
(0, import_firestore6.updateDoc)(practitionerEventRef, updateData).catch((error) => {
|
|
4674
|
+
console.error(`Error updating practitioner calendar event ${calendarEventId}:`, error);
|
|
4675
|
+
})
|
|
4676
|
+
);
|
|
4677
|
+
}
|
|
4678
|
+
if (appointment.patientId) {
|
|
4679
|
+
const patientEventRef = (0, import_firestore6.doc)(
|
|
4680
|
+
db,
|
|
4681
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
4682
|
+
);
|
|
4683
|
+
updatePromises.push(
|
|
4684
|
+
(0, import_firestore6.updateDoc)(patientEventRef, updateData).catch((error) => {
|
|
4685
|
+
console.error(`Error updating patient calendar event ${calendarEventId}:`, error);
|
|
4686
|
+
})
|
|
4687
|
+
);
|
|
4688
|
+
}
|
|
4689
|
+
if (appointment.clinicBranchId) {
|
|
4690
|
+
const clinicEventRef = (0, import_firestore6.doc)(
|
|
4691
|
+
db,
|
|
4692
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
4693
|
+
);
|
|
4694
|
+
updatePromises.push(
|
|
4695
|
+
(0, import_firestore6.updateDoc)(clinicEventRef, updateData).catch((error) => {
|
|
4696
|
+
console.error(`Error updating clinic calendar event ${calendarEventId}:`, error);
|
|
4697
|
+
})
|
|
4698
|
+
);
|
|
4699
|
+
}
|
|
4700
|
+
await Promise.all(updatePromises);
|
|
4701
|
+
console.log(`Successfully updated calendar event ${calendarEventId} status to ${calendarStatus} across all collections`);
|
|
4667
4702
|
} catch (error) {
|
|
4668
|
-
console.error(`Error updating calendar
|
|
4703
|
+
console.error(`Error updating calendar events for appointment ${appointment.id}:`, error);
|
|
4669
4704
|
}
|
|
4670
4705
|
}
|
|
4671
4706
|
async function getAppointmentByIdUtil(db, appointmentId) {
|
|
@@ -7803,7 +7838,7 @@ var contraindicationSchema = import_zod6.z.object({
|
|
|
7803
7838
|
notes: import_zod6.z.string().optional().nullable(),
|
|
7804
7839
|
isActive: import_zod6.z.boolean()
|
|
7805
7840
|
});
|
|
7806
|
-
var
|
|
7841
|
+
var baseMedicationSchema = import_zod6.z.object({
|
|
7807
7842
|
name: import_zod6.z.string().min(1),
|
|
7808
7843
|
dosage: import_zod6.z.string().min(1),
|
|
7809
7844
|
frequency: import_zod6.z.string().min(1),
|
|
@@ -7811,6 +7846,24 @@ var medicationSchema = import_zod6.z.object({
|
|
|
7811
7846
|
endDate: timestampSchema.optional().nullable(),
|
|
7812
7847
|
prescribedBy: import_zod6.z.string().optional().nullable()
|
|
7813
7848
|
});
|
|
7849
|
+
var medicationSchema = baseMedicationSchema.refine(
|
|
7850
|
+
(data) => {
|
|
7851
|
+
if (!data.endDate) {
|
|
7852
|
+
return true;
|
|
7853
|
+
}
|
|
7854
|
+
if (!data.startDate) {
|
|
7855
|
+
return false;
|
|
7856
|
+
}
|
|
7857
|
+
const startDate = data.startDate.toDate();
|
|
7858
|
+
const endDate = data.endDate.toDate();
|
|
7859
|
+
return endDate >= startDate;
|
|
7860
|
+
},
|
|
7861
|
+
{
|
|
7862
|
+
message: "End date requires a start date and must be equal to or after start date",
|
|
7863
|
+
path: ["endDate"]
|
|
7864
|
+
// This will attach the error to the endDate field
|
|
7865
|
+
}
|
|
7866
|
+
);
|
|
7814
7867
|
var patientMedicalInfoSchema = import_zod6.z.object({
|
|
7815
7868
|
patientId: import_zod6.z.string(),
|
|
7816
7869
|
vitalStats: vitalStatsSchema,
|
|
@@ -7846,9 +7899,26 @@ var updateContraindicationSchema = contraindicationSchema.partial().extend({
|
|
|
7846
7899
|
contraindicationIndex: import_zod6.z.number().min(0)
|
|
7847
7900
|
});
|
|
7848
7901
|
var addMedicationSchema = medicationSchema;
|
|
7849
|
-
var updateMedicationSchema =
|
|
7902
|
+
var updateMedicationSchema = baseMedicationSchema.partial().extend({
|
|
7850
7903
|
medicationIndex: import_zod6.z.number().min(0)
|
|
7851
|
-
})
|
|
7904
|
+
}).refine(
|
|
7905
|
+
(data) => {
|
|
7906
|
+
if (!data.endDate) {
|
|
7907
|
+
return true;
|
|
7908
|
+
}
|
|
7909
|
+
if (!data.startDate) {
|
|
7910
|
+
return false;
|
|
7911
|
+
}
|
|
7912
|
+
const startDate = data.startDate.toDate();
|
|
7913
|
+
const endDate = data.endDate.toDate();
|
|
7914
|
+
return endDate >= startDate;
|
|
7915
|
+
},
|
|
7916
|
+
{
|
|
7917
|
+
message: "End date requires a start date and must be equal to or after start date",
|
|
7918
|
+
path: ["endDate"]
|
|
7919
|
+
// This will attach the error to the endDate field
|
|
7920
|
+
}
|
|
7921
|
+
);
|
|
7852
7922
|
|
|
7853
7923
|
// src/validations/patient.schema.ts
|
|
7854
7924
|
var locationDataSchema = import_zod7.z.object({
|
|
@@ -12280,6 +12350,9 @@ var PractitionerService = class extends BaseService {
|
|
|
12280
12350
|
*/
|
|
12281
12351
|
async EnableFreeConsultation(practitionerId, clinicId) {
|
|
12282
12352
|
try {
|
|
12353
|
+
console.log(
|
|
12354
|
+
`[EnableFreeConsultation] Starting for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12355
|
+
);
|
|
12283
12356
|
await this.ensureFreeConsultationInfrastructure();
|
|
12284
12357
|
const practitioner = await this.getPractitioner(practitionerId);
|
|
12285
12358
|
if (!practitioner) {
|
|
@@ -12295,32 +12368,83 @@ var PractitionerService = class extends BaseService {
|
|
|
12295
12368
|
);
|
|
12296
12369
|
}
|
|
12297
12370
|
const [activeProcedures, inactiveProcedures] = await Promise.all([
|
|
12298
|
-
this.getProcedureService().getProceduresByPractitioner(
|
|
12371
|
+
this.getProcedureService().getProceduresByPractitioner(
|
|
12372
|
+
practitionerId,
|
|
12373
|
+
void 0,
|
|
12374
|
+
// clinicBranchId
|
|
12375
|
+
false
|
|
12376
|
+
// excludeDraftPractitioners - allow draft practitioners
|
|
12377
|
+
),
|
|
12299
12378
|
this.getProcedureService().getInactiveProceduresByPractitioner(
|
|
12300
12379
|
practitionerId
|
|
12301
12380
|
)
|
|
12302
12381
|
]);
|
|
12303
12382
|
const allProcedures = [...activeProcedures, ...inactiveProcedures];
|
|
12304
|
-
const
|
|
12383
|
+
const existingConsultations = allProcedures.filter(
|
|
12305
12384
|
(procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId
|
|
12306
12385
|
);
|
|
12386
|
+
console.log(
|
|
12387
|
+
`[EnableFreeConsultation] Found ${existingConsultations.length} existing free consultation(s)`
|
|
12388
|
+
);
|
|
12389
|
+
if (existingConsultations.length > 1) {
|
|
12390
|
+
console.warn(
|
|
12391
|
+
`[EnableFreeConsultation] WARNING: Found ${existingConsultations.length} duplicate free consultations for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12392
|
+
);
|
|
12393
|
+
for (let i = 1; i < existingConsultations.length; i++) {
|
|
12394
|
+
console.log(
|
|
12395
|
+
`[EnableFreeConsultation] Deactivating duplicate consultation ${existingConsultations[i].id}`
|
|
12396
|
+
);
|
|
12397
|
+
await this.getProcedureService().deactivateProcedure(
|
|
12398
|
+
existingConsultations[i].id
|
|
12399
|
+
);
|
|
12400
|
+
}
|
|
12401
|
+
}
|
|
12402
|
+
const existingConsultation = existingConsultations[0];
|
|
12307
12403
|
if (existingConsultation) {
|
|
12308
12404
|
if (existingConsultation.isActive) {
|
|
12309
12405
|
console.log(
|
|
12310
|
-
`Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12406
|
+
`[EnableFreeConsultation] Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12311
12407
|
);
|
|
12312
12408
|
return;
|
|
12313
12409
|
} else {
|
|
12410
|
+
console.log(
|
|
12411
|
+
`[EnableFreeConsultation] Reactivating existing consultation ${existingConsultation.id}`
|
|
12412
|
+
);
|
|
12314
12413
|
await this.getProcedureService().updateProcedure(
|
|
12315
12414
|
existingConsultation.id,
|
|
12316
12415
|
{ isActive: true }
|
|
12317
12416
|
);
|
|
12318
12417
|
console.log(
|
|
12319
|
-
`Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12418
|
+
`[EnableFreeConsultation] Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12320
12419
|
);
|
|
12321
12420
|
return;
|
|
12322
12421
|
}
|
|
12323
12422
|
}
|
|
12423
|
+
console.log(
|
|
12424
|
+
`[EnableFreeConsultation] Final race condition check before creating new procedure`
|
|
12425
|
+
);
|
|
12426
|
+
const finalCheckProcedures = await this.getProcedureService().getProceduresByPractitioner(
|
|
12427
|
+
practitionerId,
|
|
12428
|
+
void 0,
|
|
12429
|
+
// clinicBranchId
|
|
12430
|
+
false
|
|
12431
|
+
// excludeDraftPractitioners - allow draft practitioners
|
|
12432
|
+
);
|
|
12433
|
+
const raceConditionCheck = finalCheckProcedures.find(
|
|
12434
|
+
(procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId
|
|
12435
|
+
);
|
|
12436
|
+
if (raceConditionCheck) {
|
|
12437
|
+
console.log(
|
|
12438
|
+
`[EnableFreeConsultation] Race condition detected! Procedure was created by another request. Using existing procedure ${raceConditionCheck.id}`
|
|
12439
|
+
);
|
|
12440
|
+
if (!raceConditionCheck.isActive) {
|
|
12441
|
+
await this.getProcedureService().updateProcedure(
|
|
12442
|
+
raceConditionCheck.id,
|
|
12443
|
+
{ isActive: true }
|
|
12444
|
+
);
|
|
12445
|
+
}
|
|
12446
|
+
return;
|
|
12447
|
+
}
|
|
12324
12448
|
const consultationData = {
|
|
12325
12449
|
name: "Free Consultation",
|
|
12326
12450
|
nameLower: "free consultation",
|
|
@@ -12340,15 +12464,18 @@ var PractitionerService = class extends BaseService {
|
|
|
12340
12464
|
photos: []
|
|
12341
12465
|
// No photos for consultation
|
|
12342
12466
|
};
|
|
12467
|
+
console.log(
|
|
12468
|
+
`[EnableFreeConsultation] Creating new free consultation procedure`
|
|
12469
|
+
);
|
|
12343
12470
|
await this.getProcedureService().createConsultationProcedure(
|
|
12344
12471
|
consultationData
|
|
12345
12472
|
);
|
|
12346
12473
|
console.log(
|
|
12347
|
-
`
|
|
12474
|
+
`[EnableFreeConsultation] Successfully created free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12348
12475
|
);
|
|
12349
12476
|
} catch (error) {
|
|
12350
12477
|
console.error(
|
|
12351
|
-
`Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
|
|
12478
|
+
`[EnableFreeConsultation] Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
|
|
12352
12479
|
error
|
|
12353
12480
|
);
|
|
12354
12481
|
throw error;
|
|
@@ -12444,17 +12571,40 @@ var PractitionerService = class extends BaseService {
|
|
|
12444
12571
|
);
|
|
12445
12572
|
}
|
|
12446
12573
|
const existingProcedures = await this.getProcedureService().getProceduresByPractitioner(
|
|
12447
|
-
practitionerId
|
|
12574
|
+
practitionerId,
|
|
12575
|
+
void 0,
|
|
12576
|
+
// clinicBranchId (optional)
|
|
12577
|
+
false
|
|
12578
|
+
// excludeDraftPractitioners - must be false to find procedures for draft practitioners
|
|
12579
|
+
);
|
|
12580
|
+
console.log(
|
|
12581
|
+
`[DisableFreeConsultation] Found ${existingProcedures.length} procedures for practitioner ${practitionerId}`
|
|
12448
12582
|
);
|
|
12449
12583
|
const freeConsultation = existingProcedures.find(
|
|
12450
12584
|
(procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId && procedure.isActive
|
|
12451
12585
|
);
|
|
12452
12586
|
if (!freeConsultation) {
|
|
12453
12587
|
console.log(
|
|
12454
|
-
`No active free consultation found for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12588
|
+
`[DisableFreeConsultation] No active free consultation found for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12589
|
+
);
|
|
12590
|
+
console.log(
|
|
12591
|
+
`[DisableFreeConsultation] Existing procedures:`,
|
|
12592
|
+
existingProcedures.map((p) => {
|
|
12593
|
+
var _a;
|
|
12594
|
+
return {
|
|
12595
|
+
id: p.id,
|
|
12596
|
+
name: p.name,
|
|
12597
|
+
technologyId: (_a = p.technology) == null ? void 0 : _a.id,
|
|
12598
|
+
clinicBranchId: p.clinicBranchId,
|
|
12599
|
+
isActive: p.isActive
|
|
12600
|
+
};
|
|
12601
|
+
})
|
|
12455
12602
|
);
|
|
12456
12603
|
return;
|
|
12457
12604
|
}
|
|
12605
|
+
console.log(
|
|
12606
|
+
`[DisableFreeConsultation] Found free consultation procedure ${freeConsultation.id}, deactivating...`
|
|
12607
|
+
);
|
|
12458
12608
|
await this.getProcedureService().deactivateProcedure(freeConsultation.id);
|
|
12459
12609
|
console.log(
|
|
12460
12610
|
`Free consultation disabled for practitioner ${practitionerId} in clinic ${clinicId}`
|
package/dist/index.mjs
CHANGED
|
@@ -4493,7 +4493,7 @@ async function updateAppointmentUtil(db, appointmentId, data) {
|
|
|
4493
4493
|
updateData.confirmationTime = Timestamp5.now();
|
|
4494
4494
|
}
|
|
4495
4495
|
if (currentAppointment.calendarEventId) {
|
|
4496
|
-
await updateCalendarEventStatus(db, currentAppointment
|
|
4496
|
+
await updateCalendarEventStatus(db, currentAppointment, data.status);
|
|
4497
4497
|
}
|
|
4498
4498
|
}
|
|
4499
4499
|
await updateDoc2(appointmentRef, updateData);
|
|
@@ -4507,12 +4507,11 @@ async function updateAppointmentUtil(db, appointmentId, data) {
|
|
|
4507
4507
|
throw error;
|
|
4508
4508
|
}
|
|
4509
4509
|
}
|
|
4510
|
-
async function updateCalendarEventStatus(db,
|
|
4510
|
+
async function updateCalendarEventStatus(db, appointment, appointmentStatus) {
|
|
4511
4511
|
try {
|
|
4512
|
-
const
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
console.warn(`Calendar event with ID ${calendarEventId} not found`);
|
|
4512
|
+
const calendarEventId = appointment.calendarEventId;
|
|
4513
|
+
if (!calendarEventId) {
|
|
4514
|
+
console.warn(`Appointment ${appointment.id} has no calendarEventId, skipping calendar event update`);
|
|
4516
4515
|
return;
|
|
4517
4516
|
}
|
|
4518
4517
|
let calendarStatus;
|
|
@@ -4547,12 +4546,48 @@ async function updateCalendarEventStatus(db, calendarEventId, appointmentStatus)
|
|
|
4547
4546
|
console.warn(`Unknown appointment status: ${appointmentStatus}, not updating calendar event`);
|
|
4548
4547
|
return;
|
|
4549
4548
|
}
|
|
4550
|
-
|
|
4549
|
+
const updateData = {
|
|
4551
4550
|
status: calendarStatus,
|
|
4552
4551
|
updatedAt: serverTimestamp()
|
|
4553
|
-
}
|
|
4552
|
+
};
|
|
4553
|
+
const updatePromises = [];
|
|
4554
|
+
if (appointment.practitionerId) {
|
|
4555
|
+
const practitionerEventRef = doc4(
|
|
4556
|
+
db,
|
|
4557
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
4558
|
+
);
|
|
4559
|
+
updatePromises.push(
|
|
4560
|
+
updateDoc2(practitionerEventRef, updateData).catch((error) => {
|
|
4561
|
+
console.error(`Error updating practitioner calendar event ${calendarEventId}:`, error);
|
|
4562
|
+
})
|
|
4563
|
+
);
|
|
4564
|
+
}
|
|
4565
|
+
if (appointment.patientId) {
|
|
4566
|
+
const patientEventRef = doc4(
|
|
4567
|
+
db,
|
|
4568
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
4569
|
+
);
|
|
4570
|
+
updatePromises.push(
|
|
4571
|
+
updateDoc2(patientEventRef, updateData).catch((error) => {
|
|
4572
|
+
console.error(`Error updating patient calendar event ${calendarEventId}:`, error);
|
|
4573
|
+
})
|
|
4574
|
+
);
|
|
4575
|
+
}
|
|
4576
|
+
if (appointment.clinicBranchId) {
|
|
4577
|
+
const clinicEventRef = doc4(
|
|
4578
|
+
db,
|
|
4579
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
4580
|
+
);
|
|
4581
|
+
updatePromises.push(
|
|
4582
|
+
updateDoc2(clinicEventRef, updateData).catch((error) => {
|
|
4583
|
+
console.error(`Error updating clinic calendar event ${calendarEventId}:`, error);
|
|
4584
|
+
})
|
|
4585
|
+
);
|
|
4586
|
+
}
|
|
4587
|
+
await Promise.all(updatePromises);
|
|
4588
|
+
console.log(`Successfully updated calendar event ${calendarEventId} status to ${calendarStatus} across all collections`);
|
|
4554
4589
|
} catch (error) {
|
|
4555
|
-
console.error(`Error updating calendar
|
|
4590
|
+
console.error(`Error updating calendar events for appointment ${appointment.id}:`, error);
|
|
4556
4591
|
}
|
|
4557
4592
|
}
|
|
4558
4593
|
async function getAppointmentByIdUtil(db, appointmentId) {
|
|
@@ -7743,7 +7778,7 @@ var contraindicationSchema = z6.object({
|
|
|
7743
7778
|
notes: z6.string().optional().nullable(),
|
|
7744
7779
|
isActive: z6.boolean()
|
|
7745
7780
|
});
|
|
7746
|
-
var
|
|
7781
|
+
var baseMedicationSchema = z6.object({
|
|
7747
7782
|
name: z6.string().min(1),
|
|
7748
7783
|
dosage: z6.string().min(1),
|
|
7749
7784
|
frequency: z6.string().min(1),
|
|
@@ -7751,6 +7786,24 @@ var medicationSchema = z6.object({
|
|
|
7751
7786
|
endDate: timestampSchema.optional().nullable(),
|
|
7752
7787
|
prescribedBy: z6.string().optional().nullable()
|
|
7753
7788
|
});
|
|
7789
|
+
var medicationSchema = baseMedicationSchema.refine(
|
|
7790
|
+
(data) => {
|
|
7791
|
+
if (!data.endDate) {
|
|
7792
|
+
return true;
|
|
7793
|
+
}
|
|
7794
|
+
if (!data.startDate) {
|
|
7795
|
+
return false;
|
|
7796
|
+
}
|
|
7797
|
+
const startDate = data.startDate.toDate();
|
|
7798
|
+
const endDate = data.endDate.toDate();
|
|
7799
|
+
return endDate >= startDate;
|
|
7800
|
+
},
|
|
7801
|
+
{
|
|
7802
|
+
message: "End date requires a start date and must be equal to or after start date",
|
|
7803
|
+
path: ["endDate"]
|
|
7804
|
+
// This will attach the error to the endDate field
|
|
7805
|
+
}
|
|
7806
|
+
);
|
|
7754
7807
|
var patientMedicalInfoSchema = z6.object({
|
|
7755
7808
|
patientId: z6.string(),
|
|
7756
7809
|
vitalStats: vitalStatsSchema,
|
|
@@ -7786,9 +7839,26 @@ var updateContraindicationSchema = contraindicationSchema.partial().extend({
|
|
|
7786
7839
|
contraindicationIndex: z6.number().min(0)
|
|
7787
7840
|
});
|
|
7788
7841
|
var addMedicationSchema = medicationSchema;
|
|
7789
|
-
var updateMedicationSchema =
|
|
7842
|
+
var updateMedicationSchema = baseMedicationSchema.partial().extend({
|
|
7790
7843
|
medicationIndex: z6.number().min(0)
|
|
7791
|
-
})
|
|
7844
|
+
}).refine(
|
|
7845
|
+
(data) => {
|
|
7846
|
+
if (!data.endDate) {
|
|
7847
|
+
return true;
|
|
7848
|
+
}
|
|
7849
|
+
if (!data.startDate) {
|
|
7850
|
+
return false;
|
|
7851
|
+
}
|
|
7852
|
+
const startDate = data.startDate.toDate();
|
|
7853
|
+
const endDate = data.endDate.toDate();
|
|
7854
|
+
return endDate >= startDate;
|
|
7855
|
+
},
|
|
7856
|
+
{
|
|
7857
|
+
message: "End date requires a start date and must be equal to or after start date",
|
|
7858
|
+
path: ["endDate"]
|
|
7859
|
+
// This will attach the error to the endDate field
|
|
7860
|
+
}
|
|
7861
|
+
);
|
|
7792
7862
|
|
|
7793
7863
|
// src/validations/patient.schema.ts
|
|
7794
7864
|
var locationDataSchema = z7.object({
|
|
@@ -12303,6 +12373,9 @@ var PractitionerService = class extends BaseService {
|
|
|
12303
12373
|
*/
|
|
12304
12374
|
async EnableFreeConsultation(practitionerId, clinicId) {
|
|
12305
12375
|
try {
|
|
12376
|
+
console.log(
|
|
12377
|
+
`[EnableFreeConsultation] Starting for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12378
|
+
);
|
|
12306
12379
|
await this.ensureFreeConsultationInfrastructure();
|
|
12307
12380
|
const practitioner = await this.getPractitioner(practitionerId);
|
|
12308
12381
|
if (!practitioner) {
|
|
@@ -12318,32 +12391,83 @@ var PractitionerService = class extends BaseService {
|
|
|
12318
12391
|
);
|
|
12319
12392
|
}
|
|
12320
12393
|
const [activeProcedures, inactiveProcedures] = await Promise.all([
|
|
12321
|
-
this.getProcedureService().getProceduresByPractitioner(
|
|
12394
|
+
this.getProcedureService().getProceduresByPractitioner(
|
|
12395
|
+
practitionerId,
|
|
12396
|
+
void 0,
|
|
12397
|
+
// clinicBranchId
|
|
12398
|
+
false
|
|
12399
|
+
// excludeDraftPractitioners - allow draft practitioners
|
|
12400
|
+
),
|
|
12322
12401
|
this.getProcedureService().getInactiveProceduresByPractitioner(
|
|
12323
12402
|
practitionerId
|
|
12324
12403
|
)
|
|
12325
12404
|
]);
|
|
12326
12405
|
const allProcedures = [...activeProcedures, ...inactiveProcedures];
|
|
12327
|
-
const
|
|
12406
|
+
const existingConsultations = allProcedures.filter(
|
|
12328
12407
|
(procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId
|
|
12329
12408
|
);
|
|
12409
|
+
console.log(
|
|
12410
|
+
`[EnableFreeConsultation] Found ${existingConsultations.length} existing free consultation(s)`
|
|
12411
|
+
);
|
|
12412
|
+
if (existingConsultations.length > 1) {
|
|
12413
|
+
console.warn(
|
|
12414
|
+
`[EnableFreeConsultation] WARNING: Found ${existingConsultations.length} duplicate free consultations for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12415
|
+
);
|
|
12416
|
+
for (let i = 1; i < existingConsultations.length; i++) {
|
|
12417
|
+
console.log(
|
|
12418
|
+
`[EnableFreeConsultation] Deactivating duplicate consultation ${existingConsultations[i].id}`
|
|
12419
|
+
);
|
|
12420
|
+
await this.getProcedureService().deactivateProcedure(
|
|
12421
|
+
existingConsultations[i].id
|
|
12422
|
+
);
|
|
12423
|
+
}
|
|
12424
|
+
}
|
|
12425
|
+
const existingConsultation = existingConsultations[0];
|
|
12330
12426
|
if (existingConsultation) {
|
|
12331
12427
|
if (existingConsultation.isActive) {
|
|
12332
12428
|
console.log(
|
|
12333
|
-
`Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12429
|
+
`[EnableFreeConsultation] Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12334
12430
|
);
|
|
12335
12431
|
return;
|
|
12336
12432
|
} else {
|
|
12433
|
+
console.log(
|
|
12434
|
+
`[EnableFreeConsultation] Reactivating existing consultation ${existingConsultation.id}`
|
|
12435
|
+
);
|
|
12337
12436
|
await this.getProcedureService().updateProcedure(
|
|
12338
12437
|
existingConsultation.id,
|
|
12339
12438
|
{ isActive: true }
|
|
12340
12439
|
);
|
|
12341
12440
|
console.log(
|
|
12342
|
-
`Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12441
|
+
`[EnableFreeConsultation] Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12343
12442
|
);
|
|
12344
12443
|
return;
|
|
12345
12444
|
}
|
|
12346
12445
|
}
|
|
12446
|
+
console.log(
|
|
12447
|
+
`[EnableFreeConsultation] Final race condition check before creating new procedure`
|
|
12448
|
+
);
|
|
12449
|
+
const finalCheckProcedures = await this.getProcedureService().getProceduresByPractitioner(
|
|
12450
|
+
practitionerId,
|
|
12451
|
+
void 0,
|
|
12452
|
+
// clinicBranchId
|
|
12453
|
+
false
|
|
12454
|
+
// excludeDraftPractitioners - allow draft practitioners
|
|
12455
|
+
);
|
|
12456
|
+
const raceConditionCheck = finalCheckProcedures.find(
|
|
12457
|
+
(procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId
|
|
12458
|
+
);
|
|
12459
|
+
if (raceConditionCheck) {
|
|
12460
|
+
console.log(
|
|
12461
|
+
`[EnableFreeConsultation] Race condition detected! Procedure was created by another request. Using existing procedure ${raceConditionCheck.id}`
|
|
12462
|
+
);
|
|
12463
|
+
if (!raceConditionCheck.isActive) {
|
|
12464
|
+
await this.getProcedureService().updateProcedure(
|
|
12465
|
+
raceConditionCheck.id,
|
|
12466
|
+
{ isActive: true }
|
|
12467
|
+
);
|
|
12468
|
+
}
|
|
12469
|
+
return;
|
|
12470
|
+
}
|
|
12347
12471
|
const consultationData = {
|
|
12348
12472
|
name: "Free Consultation",
|
|
12349
12473
|
nameLower: "free consultation",
|
|
@@ -12363,15 +12487,18 @@ var PractitionerService = class extends BaseService {
|
|
|
12363
12487
|
photos: []
|
|
12364
12488
|
// No photos for consultation
|
|
12365
12489
|
};
|
|
12490
|
+
console.log(
|
|
12491
|
+
`[EnableFreeConsultation] Creating new free consultation procedure`
|
|
12492
|
+
);
|
|
12366
12493
|
await this.getProcedureService().createConsultationProcedure(
|
|
12367
12494
|
consultationData
|
|
12368
12495
|
);
|
|
12369
12496
|
console.log(
|
|
12370
|
-
`
|
|
12497
|
+
`[EnableFreeConsultation] Successfully created free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12371
12498
|
);
|
|
12372
12499
|
} catch (error) {
|
|
12373
12500
|
console.error(
|
|
12374
|
-
`Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
|
|
12501
|
+
`[EnableFreeConsultation] Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
|
|
12375
12502
|
error
|
|
12376
12503
|
);
|
|
12377
12504
|
throw error;
|
|
@@ -12467,17 +12594,40 @@ var PractitionerService = class extends BaseService {
|
|
|
12467
12594
|
);
|
|
12468
12595
|
}
|
|
12469
12596
|
const existingProcedures = await this.getProcedureService().getProceduresByPractitioner(
|
|
12470
|
-
practitionerId
|
|
12597
|
+
practitionerId,
|
|
12598
|
+
void 0,
|
|
12599
|
+
// clinicBranchId (optional)
|
|
12600
|
+
false
|
|
12601
|
+
// excludeDraftPractitioners - must be false to find procedures for draft practitioners
|
|
12602
|
+
);
|
|
12603
|
+
console.log(
|
|
12604
|
+
`[DisableFreeConsultation] Found ${existingProcedures.length} procedures for practitioner ${practitionerId}`
|
|
12471
12605
|
);
|
|
12472
12606
|
const freeConsultation = existingProcedures.find(
|
|
12473
12607
|
(procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId && procedure.isActive
|
|
12474
12608
|
);
|
|
12475
12609
|
if (!freeConsultation) {
|
|
12476
12610
|
console.log(
|
|
12477
|
-
`No active free consultation found for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12611
|
+
`[DisableFreeConsultation] No active free consultation found for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
12612
|
+
);
|
|
12613
|
+
console.log(
|
|
12614
|
+
`[DisableFreeConsultation] Existing procedures:`,
|
|
12615
|
+
existingProcedures.map((p) => {
|
|
12616
|
+
var _a;
|
|
12617
|
+
return {
|
|
12618
|
+
id: p.id,
|
|
12619
|
+
name: p.name,
|
|
12620
|
+
technologyId: (_a = p.technology) == null ? void 0 : _a.id,
|
|
12621
|
+
clinicBranchId: p.clinicBranchId,
|
|
12622
|
+
isActive: p.isActive
|
|
12623
|
+
};
|
|
12624
|
+
})
|
|
12478
12625
|
);
|
|
12479
12626
|
return;
|
|
12480
12627
|
}
|
|
12628
|
+
console.log(
|
|
12629
|
+
`[DisableFreeConsultation] Found free consultation procedure ${freeConsultation.id}, deactivating...`
|
|
12630
|
+
);
|
|
12481
12631
|
await this.getProcedureService().deactivateProcedure(freeConsultation.id);
|
|
12482
12632
|
console.log(
|
|
12483
12633
|
`Free consultation disabled for practitioner ${practitionerId} in clinic ${clinicId}`
|
package/package.json
CHANGED
|
@@ -28,6 +28,9 @@ import {
|
|
|
28
28
|
import { CalendarEvent, CALENDAR_COLLECTION } from '../../../types/calendar';
|
|
29
29
|
import { ProcedureSummaryInfo } from '../../../types/procedure';
|
|
30
30
|
import { ClinicInfo, PatientProfileInfo, PractitionerProfileInfo } from '../../../types/profile';
|
|
31
|
+
import { PRACTITIONERS_COLLECTION } from '../../../types/practitioner';
|
|
32
|
+
import { PATIENTS_COLLECTION } from '../../../types/patient';
|
|
33
|
+
import { CLINICS_COLLECTION } from '../../../types/clinic';
|
|
31
34
|
import { BlockingCondition } from '../../../backoffice/types/static/blocking-condition.types';
|
|
32
35
|
import { Requirement } from '../../../backoffice/types/requirement.types';
|
|
33
36
|
import { PRACTITIONERS_COLLECTION } from '../../../types/practitioner';
|
|
@@ -379,7 +382,7 @@ export async function updateAppointmentUtil(
|
|
|
379
382
|
|
|
380
383
|
// Update the related calendar event status if needed
|
|
381
384
|
if (currentAppointment.calendarEventId) {
|
|
382
|
-
await updateCalendarEventStatus(db, currentAppointment
|
|
385
|
+
await updateCalendarEventStatus(db, currentAppointment, data.status);
|
|
383
386
|
}
|
|
384
387
|
}
|
|
385
388
|
|
|
@@ -400,28 +403,27 @@ export async function updateAppointmentUtil(
|
|
|
400
403
|
}
|
|
401
404
|
|
|
402
405
|
/**
|
|
403
|
-
* Updates the status of
|
|
406
|
+
* Updates the status of calendar events across all collections (practitioner, patient, clinic)
|
|
407
|
+
* based on appointment status changes.
|
|
404
408
|
*
|
|
405
409
|
* @param db Firestore instance
|
|
406
|
-
* @param
|
|
410
|
+
* @param appointment The appointment object containing calendar event references
|
|
407
411
|
* @param appointmentStatus New appointment status
|
|
408
412
|
*/
|
|
409
413
|
async function updateCalendarEventStatus(
|
|
410
414
|
db: Firestore,
|
|
411
|
-
|
|
415
|
+
appointment: Appointment,
|
|
412
416
|
appointmentStatus: AppointmentStatus,
|
|
413
417
|
): Promise<void> {
|
|
414
418
|
try {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (!calendarEventDoc.exists()) {
|
|
419
|
-
console.warn(`Calendar event with ID ${calendarEventId} not found`);
|
|
419
|
+
const calendarEventId = appointment.calendarEventId;
|
|
420
|
+
if (!calendarEventId) {
|
|
421
|
+
console.warn(`Appointment ${appointment.id} has no calendarEventId, skipping calendar event update`);
|
|
420
422
|
return;
|
|
421
423
|
}
|
|
422
424
|
|
|
423
425
|
// Map appointment status to calendar event status
|
|
424
|
-
let calendarStatus;
|
|
426
|
+
let calendarStatus: string;
|
|
425
427
|
switch (appointmentStatus) {
|
|
426
428
|
case AppointmentStatus.PENDING:
|
|
427
429
|
calendarStatus = 'pending';
|
|
@@ -455,12 +457,58 @@ async function updateCalendarEventStatus(
|
|
|
455
457
|
return;
|
|
456
458
|
}
|
|
457
459
|
|
|
458
|
-
|
|
460
|
+
const updateData = {
|
|
459
461
|
status: calendarStatus,
|
|
460
462
|
updatedAt: serverTimestamp(),
|
|
461
|
-
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Update all three calendar event collections in parallel
|
|
466
|
+
const updatePromises: Promise<void>[] = [];
|
|
467
|
+
|
|
468
|
+
// Update practitioner calendar event
|
|
469
|
+
if (appointment.practitionerId) {
|
|
470
|
+
const practitionerEventRef = doc(
|
|
471
|
+
db,
|
|
472
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
473
|
+
);
|
|
474
|
+
updatePromises.push(
|
|
475
|
+
updateDoc(practitionerEventRef, updateData).catch(error => {
|
|
476
|
+
console.error(`Error updating practitioner calendar event ${calendarEventId}:`, error);
|
|
477
|
+
})
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Update patient calendar event
|
|
482
|
+
if (appointment.patientId) {
|
|
483
|
+
const patientEventRef = doc(
|
|
484
|
+
db,
|
|
485
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
486
|
+
);
|
|
487
|
+
updatePromises.push(
|
|
488
|
+
updateDoc(patientEventRef, updateData).catch(error => {
|
|
489
|
+
console.error(`Error updating patient calendar event ${calendarEventId}:`, error);
|
|
490
|
+
})
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Update clinic calendar event
|
|
495
|
+
if (appointment.clinicBranchId) {
|
|
496
|
+
const clinicEventRef = doc(
|
|
497
|
+
db,
|
|
498
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
499
|
+
);
|
|
500
|
+
updatePromises.push(
|
|
501
|
+
updateDoc(clinicEventRef, updateData).catch(error => {
|
|
502
|
+
console.error(`Error updating clinic calendar event ${calendarEventId}:`, error);
|
|
503
|
+
})
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Wait for all updates to complete
|
|
508
|
+
await Promise.all(updatePromises);
|
|
509
|
+
console.log(`Successfully updated calendar event ${calendarEventId} status to ${calendarStatus} across all collections`);
|
|
462
510
|
} catch (error) {
|
|
463
|
-
console.error(`Error updating calendar
|
|
511
|
+
console.error(`Error updating calendar events for appointment ${appointment.id}:`, error);
|
|
464
512
|
// Don't throw error to avoid failing the appointment update
|
|
465
513
|
}
|
|
466
514
|
}
|
|
@@ -1546,6 +1546,10 @@ export class PractitionerService extends BaseService {
|
|
|
1546
1546
|
clinicId: string
|
|
1547
1547
|
): Promise<void> {
|
|
1548
1548
|
try {
|
|
1549
|
+
console.log(
|
|
1550
|
+
`[EnableFreeConsultation] Starting for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1551
|
+
);
|
|
1552
|
+
|
|
1549
1553
|
// First, ensure the free consultation infrastructure exists
|
|
1550
1554
|
await this.ensureFreeConsultationInfrastructure();
|
|
1551
1555
|
|
|
@@ -1573,9 +1577,15 @@ export class PractitionerService extends BaseService {
|
|
|
1573
1577
|
);
|
|
1574
1578
|
}
|
|
1575
1579
|
|
|
1576
|
-
//
|
|
1580
|
+
// CRITICAL: Double-check for existing procedures to prevent race conditions
|
|
1581
|
+
// Fetch procedures again right before creation/update
|
|
1582
|
+
// IMPORTANT: Pass false for excludeDraftPractitioners to work with draft practitioners
|
|
1577
1583
|
const [activeProcedures, inactiveProcedures] = await Promise.all([
|
|
1578
|
-
this.getProcedureService().getProceduresByPractitioner(
|
|
1584
|
+
this.getProcedureService().getProceduresByPractitioner(
|
|
1585
|
+
practitionerId,
|
|
1586
|
+
undefined, // clinicBranchId
|
|
1587
|
+
false // excludeDraftPractitioners - allow draft practitioners
|
|
1588
|
+
),
|
|
1579
1589
|
this.getProcedureService().getInactiveProceduresByPractitioner(
|
|
1580
1590
|
practitionerId
|
|
1581
1591
|
),
|
|
@@ -1585,31 +1595,86 @@ export class PractitionerService extends BaseService {
|
|
|
1585
1595
|
const allProcedures = [...activeProcedures, ...inactiveProcedures];
|
|
1586
1596
|
|
|
1587
1597
|
// Check if free consultation already exists (active or inactive)
|
|
1588
|
-
const
|
|
1598
|
+
const existingConsultations = allProcedures.filter(
|
|
1589
1599
|
(procedure) =>
|
|
1590
1600
|
procedure.technology.id === "free-consultation-tech" &&
|
|
1591
1601
|
procedure.clinicBranchId === clinicId
|
|
1592
1602
|
);
|
|
1593
1603
|
|
|
1604
|
+
console.log(
|
|
1605
|
+
`[EnableFreeConsultation] Found ${existingConsultations.length} existing free consultation(s)`
|
|
1606
|
+
);
|
|
1607
|
+
|
|
1608
|
+
// If multiple consultations exist, log a warning and clean up duplicates
|
|
1609
|
+
if (existingConsultations.length > 1) {
|
|
1610
|
+
console.warn(
|
|
1611
|
+
`[EnableFreeConsultation] WARNING: Found ${existingConsultations.length} duplicate free consultations for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1612
|
+
);
|
|
1613
|
+
// Keep the first one, deactivate the rest
|
|
1614
|
+
for (let i = 1; i < existingConsultations.length; i++) {
|
|
1615
|
+
console.log(
|
|
1616
|
+
`[EnableFreeConsultation] Deactivating duplicate consultation ${existingConsultations[i].id}`
|
|
1617
|
+
);
|
|
1618
|
+
await this.getProcedureService().deactivateProcedure(
|
|
1619
|
+
existingConsultations[i].id
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
const existingConsultation = existingConsultations[0];
|
|
1625
|
+
|
|
1594
1626
|
if (existingConsultation) {
|
|
1595
1627
|
if (existingConsultation.isActive) {
|
|
1596
1628
|
console.log(
|
|
1597
|
-
`Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1629
|
+
`[EnableFreeConsultation] Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1598
1630
|
);
|
|
1599
1631
|
return;
|
|
1600
1632
|
} else {
|
|
1601
1633
|
// Reactivate the existing disabled consultation
|
|
1634
|
+
console.log(
|
|
1635
|
+
`[EnableFreeConsultation] Reactivating existing consultation ${existingConsultation.id}`
|
|
1636
|
+
);
|
|
1602
1637
|
await this.getProcedureService().updateProcedure(
|
|
1603
1638
|
existingConsultation.id,
|
|
1604
1639
|
{ isActive: true }
|
|
1605
1640
|
);
|
|
1606
1641
|
console.log(
|
|
1607
|
-
`Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1642
|
+
`[EnableFreeConsultation] Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1608
1643
|
);
|
|
1609
1644
|
return;
|
|
1610
1645
|
}
|
|
1611
1646
|
}
|
|
1612
1647
|
|
|
1648
|
+
// Final check before creating - race condition guard
|
|
1649
|
+
// Fetch one more time to ensure no procedure was created in parallel
|
|
1650
|
+
console.log(
|
|
1651
|
+
`[EnableFreeConsultation] Final race condition check before creating new procedure`
|
|
1652
|
+
);
|
|
1653
|
+
const finalCheckProcedures =
|
|
1654
|
+
await this.getProcedureService().getProceduresByPractitioner(
|
|
1655
|
+
practitionerId,
|
|
1656
|
+
undefined, // clinicBranchId
|
|
1657
|
+
false // excludeDraftPractitioners - allow draft practitioners
|
|
1658
|
+
);
|
|
1659
|
+
const raceConditionCheck = finalCheckProcedures.find(
|
|
1660
|
+
(procedure) =>
|
|
1661
|
+
procedure.technology.id === "free-consultation-tech" &&
|
|
1662
|
+
procedure.clinicBranchId === clinicId
|
|
1663
|
+
);
|
|
1664
|
+
|
|
1665
|
+
if (raceConditionCheck) {
|
|
1666
|
+
console.log(
|
|
1667
|
+
`[EnableFreeConsultation] Race condition detected! Procedure was created by another request. Using existing procedure ${raceConditionCheck.id}`
|
|
1668
|
+
);
|
|
1669
|
+
if (!raceConditionCheck.isActive) {
|
|
1670
|
+
await this.getProcedureService().updateProcedure(
|
|
1671
|
+
raceConditionCheck.id,
|
|
1672
|
+
{ isActive: true }
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1613
1678
|
// Create procedure data for free consultation (without productId or productsMetadata)
|
|
1614
1679
|
const consultationData: Omit<CreateProcedureData, "productId"> = {
|
|
1615
1680
|
name: "Free Consultation",
|
|
@@ -1631,16 +1696,19 @@ export class PractitionerService extends BaseService {
|
|
|
1631
1696
|
};
|
|
1632
1697
|
|
|
1633
1698
|
// Create the consultation procedure using the special method
|
|
1699
|
+
console.log(
|
|
1700
|
+
`[EnableFreeConsultation] Creating new free consultation procedure`
|
|
1701
|
+
);
|
|
1634
1702
|
await this.getProcedureService().createConsultationProcedure(
|
|
1635
1703
|
consultationData
|
|
1636
1704
|
);
|
|
1637
1705
|
|
|
1638
1706
|
console.log(
|
|
1639
|
-
`
|
|
1707
|
+
`[EnableFreeConsultation] Successfully created free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1640
1708
|
);
|
|
1641
1709
|
} catch (error) {
|
|
1642
1710
|
console.error(
|
|
1643
|
-
`Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
|
|
1711
|
+
`[EnableFreeConsultation] Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
|
|
1644
1712
|
error
|
|
1645
1713
|
);
|
|
1646
1714
|
throw error;
|
|
@@ -1764,10 +1832,18 @@ export class PractitionerService extends BaseService {
|
|
|
1764
1832
|
|
|
1765
1833
|
// Find the free consultation procedure for this practitioner in this clinic
|
|
1766
1834
|
// Use the more specific search by technology ID instead of name
|
|
1835
|
+
// IMPORTANT: Pass false for excludeDraftPractitioners to allow disabling for draft practitioners
|
|
1767
1836
|
const existingProcedures =
|
|
1768
1837
|
await this.getProcedureService().getProceduresByPractitioner(
|
|
1769
|
-
practitionerId
|
|
1838
|
+
practitionerId,
|
|
1839
|
+
undefined, // clinicBranchId (optional)
|
|
1840
|
+
false // excludeDraftPractitioners - must be false to find procedures for draft practitioners
|
|
1770
1841
|
);
|
|
1842
|
+
|
|
1843
|
+
console.log(
|
|
1844
|
+
`[DisableFreeConsultation] Found ${existingProcedures.length} procedures for practitioner ${practitionerId}`
|
|
1845
|
+
);
|
|
1846
|
+
|
|
1771
1847
|
const freeConsultation = existingProcedures.find(
|
|
1772
1848
|
(procedure) =>
|
|
1773
1849
|
procedure.technology.id === "free-consultation-tech" &&
|
|
@@ -1777,10 +1853,24 @@ export class PractitionerService extends BaseService {
|
|
|
1777
1853
|
|
|
1778
1854
|
if (!freeConsultation) {
|
|
1779
1855
|
console.log(
|
|
1780
|
-
`No active free consultation found for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1856
|
+
`[DisableFreeConsultation] No active free consultation found for practitioner ${practitionerId} in clinic ${clinicId}`
|
|
1857
|
+
);
|
|
1858
|
+
console.log(
|
|
1859
|
+
`[DisableFreeConsultation] Existing procedures:`,
|
|
1860
|
+
existingProcedures.map(p => ({
|
|
1861
|
+
id: p.id,
|
|
1862
|
+
name: p.name,
|
|
1863
|
+
technologyId: p.technology?.id,
|
|
1864
|
+
clinicBranchId: p.clinicBranchId,
|
|
1865
|
+
isActive: p.isActive
|
|
1866
|
+
}))
|
|
1781
1867
|
);
|
|
1782
1868
|
return;
|
|
1783
1869
|
}
|
|
1870
|
+
|
|
1871
|
+
console.log(
|
|
1872
|
+
`[DisableFreeConsultation] Found free consultation procedure ${freeConsultation.id}, deactivating...`
|
|
1873
|
+
);
|
|
1784
1874
|
|
|
1785
1875
|
// Deactivate the consultation procedure
|
|
1786
1876
|
await this.getProcedureService().deactivateProcedure(freeConsultation.id);
|
|
@@ -63,7 +63,8 @@ export const contraindicationSchema = z.object({
|
|
|
63
63
|
isActive: z.boolean(),
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
// Base medication schema without refinement (for update operations that need .partial())
|
|
67
|
+
const baseMedicationSchema = z.object({
|
|
67
68
|
name: z.string().min(1),
|
|
68
69
|
dosage: z.string().min(1),
|
|
69
70
|
frequency: z.string().min(1),
|
|
@@ -72,6 +73,33 @@ export const medicationSchema = z.object({
|
|
|
72
73
|
prescribedBy: z.string().optional().nullable(),
|
|
73
74
|
});
|
|
74
75
|
|
|
76
|
+
// Medication schema with date validation refinement
|
|
77
|
+
export const medicationSchema = baseMedicationSchema.refine(
|
|
78
|
+
(data) => {
|
|
79
|
+
// If either date is not provided, skip validation (both are optional)
|
|
80
|
+
// However, if endDate is provided, startDate must also be provided
|
|
81
|
+
if (!data.endDate) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If endDate exists but startDate doesn't, this is invalid
|
|
86
|
+
if (!data.startDate) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Both dates must be Timestamp objects with toDate method
|
|
91
|
+
const startDate = data.startDate.toDate();
|
|
92
|
+
const endDate = data.endDate.toDate();
|
|
93
|
+
|
|
94
|
+
// End date must be >= start date
|
|
95
|
+
return endDate >= startDate;
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
message: "End date requires a start date and must be equal to or after start date",
|
|
99
|
+
path: ["endDate"], // This will attach the error to the endDate field
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
75
103
|
export const patientMedicalInfoSchema = z.object({
|
|
76
104
|
patientId: z.string(),
|
|
77
105
|
vitalStats: vitalStatsSchema,
|
|
@@ -120,6 +148,30 @@ export const updateContraindicationSchema = contraindicationSchema
|
|
|
120
148
|
});
|
|
121
149
|
|
|
122
150
|
export const addMedicationSchema = medicationSchema;
|
|
123
|
-
export const updateMedicationSchema =
|
|
151
|
+
export const updateMedicationSchema = baseMedicationSchema.partial().extend({
|
|
124
152
|
medicationIndex: z.number().min(0),
|
|
125
|
-
})
|
|
153
|
+
}).refine(
|
|
154
|
+
(data) => {
|
|
155
|
+
// If either date is not provided, skip validation (both are optional)
|
|
156
|
+
// However, if endDate is provided, startDate must also be provided
|
|
157
|
+
if (!data.endDate) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If endDate exists but startDate doesn't, this is invalid
|
|
162
|
+
if (!data.startDate) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Both dates must be Timestamp objects with toDate method
|
|
167
|
+
const startDate = data.startDate.toDate();
|
|
168
|
+
const endDate = data.endDate.toDate();
|
|
169
|
+
|
|
170
|
+
// End date must be >= start date
|
|
171
|
+
return endDate >= startDate;
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
message: "End date requires a start date and must be equal to or after start date",
|
|
175
|
+
path: ["endDate"], // This will attach the error to the endDate field
|
|
176
|
+
}
|
|
177
|
+
);
|