@blackcode_sa/metaestetics-api 1.13.20 → 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
@@ -7838,7 +7838,7 @@ var contraindicationSchema = import_zod6.z.object({
7838
7838
  notes: import_zod6.z.string().optional().nullable(),
7839
7839
  isActive: import_zod6.z.boolean()
7840
7840
  });
7841
- var medicationSchema = import_zod6.z.object({
7841
+ var baseMedicationSchema = import_zod6.z.object({
7842
7842
  name: import_zod6.z.string().min(1),
7843
7843
  dosage: import_zod6.z.string().min(1),
7844
7844
  frequency: import_zod6.z.string().min(1),
@@ -7846,6 +7846,24 @@ var medicationSchema = import_zod6.z.object({
7846
7846
  endDate: timestampSchema.optional().nullable(),
7847
7847
  prescribedBy: import_zod6.z.string().optional().nullable()
7848
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
+ );
7849
7867
  var patientMedicalInfoSchema = import_zod6.z.object({
7850
7868
  patientId: import_zod6.z.string(),
7851
7869
  vitalStats: vitalStatsSchema,
@@ -7881,9 +7899,26 @@ var updateContraindicationSchema = contraindicationSchema.partial().extend({
7881
7899
  contraindicationIndex: import_zod6.z.number().min(0)
7882
7900
  });
7883
7901
  var addMedicationSchema = medicationSchema;
7884
- var updateMedicationSchema = medicationSchema.partial().extend({
7902
+ var updateMedicationSchema = baseMedicationSchema.partial().extend({
7885
7903
  medicationIndex: import_zod6.z.number().min(0)
7886
- });
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
+ );
7887
7922
 
7888
7923
  // src/validations/patient.schema.ts
7889
7924
  var locationDataSchema = import_zod7.z.object({
@@ -12315,6 +12350,9 @@ var PractitionerService = class extends BaseService {
12315
12350
  */
12316
12351
  async EnableFreeConsultation(practitionerId, clinicId) {
12317
12352
  try {
12353
+ console.log(
12354
+ `[EnableFreeConsultation] Starting for practitioner ${practitionerId} in clinic ${clinicId}`
12355
+ );
12318
12356
  await this.ensureFreeConsultationInfrastructure();
12319
12357
  const practitioner = await this.getPractitioner(practitionerId);
12320
12358
  if (!practitioner) {
@@ -12330,32 +12368,83 @@ var PractitionerService = class extends BaseService {
12330
12368
  );
12331
12369
  }
12332
12370
  const [activeProcedures, inactiveProcedures] = await Promise.all([
12333
- this.getProcedureService().getProceduresByPractitioner(practitionerId),
12371
+ this.getProcedureService().getProceduresByPractitioner(
12372
+ practitionerId,
12373
+ void 0,
12374
+ // clinicBranchId
12375
+ false
12376
+ // excludeDraftPractitioners - allow draft practitioners
12377
+ ),
12334
12378
  this.getProcedureService().getInactiveProceduresByPractitioner(
12335
12379
  practitionerId
12336
12380
  )
12337
12381
  ]);
12338
12382
  const allProcedures = [...activeProcedures, ...inactiveProcedures];
12339
- const existingConsultation = allProcedures.find(
12383
+ const existingConsultations = allProcedures.filter(
12340
12384
  (procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId
12341
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];
12342
12403
  if (existingConsultation) {
12343
12404
  if (existingConsultation.isActive) {
12344
12405
  console.log(
12345
- `Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
12406
+ `[EnableFreeConsultation] Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
12346
12407
  );
12347
12408
  return;
12348
12409
  } else {
12410
+ console.log(
12411
+ `[EnableFreeConsultation] Reactivating existing consultation ${existingConsultation.id}`
12412
+ );
12349
12413
  await this.getProcedureService().updateProcedure(
12350
12414
  existingConsultation.id,
12351
12415
  { isActive: true }
12352
12416
  );
12353
12417
  console.log(
12354
- `Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
12418
+ `[EnableFreeConsultation] Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
12355
12419
  );
12356
12420
  return;
12357
12421
  }
12358
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
+ }
12359
12448
  const consultationData = {
12360
12449
  name: "Free Consultation",
12361
12450
  nameLower: "free consultation",
@@ -12375,15 +12464,18 @@ var PractitionerService = class extends BaseService {
12375
12464
  photos: []
12376
12465
  // No photos for consultation
12377
12466
  };
12467
+ console.log(
12468
+ `[EnableFreeConsultation] Creating new free consultation procedure`
12469
+ );
12378
12470
  await this.getProcedureService().createConsultationProcedure(
12379
12471
  consultationData
12380
12472
  );
12381
12473
  console.log(
12382
- `Free consultation enabled for practitioner ${practitionerId} in clinic ${clinicId}`
12474
+ `[EnableFreeConsultation] Successfully created free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
12383
12475
  );
12384
12476
  } catch (error) {
12385
12477
  console.error(
12386
- `Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
12478
+ `[EnableFreeConsultation] Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
12387
12479
  error
12388
12480
  );
12389
12481
  throw error;
@@ -12479,17 +12571,40 @@ var PractitionerService = class extends BaseService {
12479
12571
  );
12480
12572
  }
12481
12573
  const existingProcedures = await this.getProcedureService().getProceduresByPractitioner(
12482
- 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}`
12483
12582
  );
12484
12583
  const freeConsultation = existingProcedures.find(
12485
12584
  (procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId && procedure.isActive
12486
12585
  );
12487
12586
  if (!freeConsultation) {
12488
12587
  console.log(
12489
- `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
+ })
12490
12602
  );
12491
12603
  return;
12492
12604
  }
12605
+ console.log(
12606
+ `[DisableFreeConsultation] Found free consultation procedure ${freeConsultation.id}, deactivating...`
12607
+ );
12493
12608
  await this.getProcedureService().deactivateProcedure(freeConsultation.id);
12494
12609
  console.log(
12495
12610
  `Free consultation disabled for practitioner ${practitionerId} in clinic ${clinicId}`
package/dist/index.mjs CHANGED
@@ -7778,7 +7778,7 @@ var contraindicationSchema = z6.object({
7778
7778
  notes: z6.string().optional().nullable(),
7779
7779
  isActive: z6.boolean()
7780
7780
  });
7781
- var medicationSchema = z6.object({
7781
+ var baseMedicationSchema = z6.object({
7782
7782
  name: z6.string().min(1),
7783
7783
  dosage: z6.string().min(1),
7784
7784
  frequency: z6.string().min(1),
@@ -7786,6 +7786,24 @@ var medicationSchema = z6.object({
7786
7786
  endDate: timestampSchema.optional().nullable(),
7787
7787
  prescribedBy: z6.string().optional().nullable()
7788
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
+ );
7789
7807
  var patientMedicalInfoSchema = z6.object({
7790
7808
  patientId: z6.string(),
7791
7809
  vitalStats: vitalStatsSchema,
@@ -7821,9 +7839,26 @@ var updateContraindicationSchema = contraindicationSchema.partial().extend({
7821
7839
  contraindicationIndex: z6.number().min(0)
7822
7840
  });
7823
7841
  var addMedicationSchema = medicationSchema;
7824
- var updateMedicationSchema = medicationSchema.partial().extend({
7842
+ var updateMedicationSchema = baseMedicationSchema.partial().extend({
7825
7843
  medicationIndex: z6.number().min(0)
7826
- });
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
+ );
7827
7862
 
7828
7863
  // src/validations/patient.schema.ts
7829
7864
  var locationDataSchema = z7.object({
@@ -12338,6 +12373,9 @@ var PractitionerService = class extends BaseService {
12338
12373
  */
12339
12374
  async EnableFreeConsultation(practitionerId, clinicId) {
12340
12375
  try {
12376
+ console.log(
12377
+ `[EnableFreeConsultation] Starting for practitioner ${practitionerId} in clinic ${clinicId}`
12378
+ );
12341
12379
  await this.ensureFreeConsultationInfrastructure();
12342
12380
  const practitioner = await this.getPractitioner(practitionerId);
12343
12381
  if (!practitioner) {
@@ -12353,32 +12391,83 @@ var PractitionerService = class extends BaseService {
12353
12391
  );
12354
12392
  }
12355
12393
  const [activeProcedures, inactiveProcedures] = await Promise.all([
12356
- this.getProcedureService().getProceduresByPractitioner(practitionerId),
12394
+ this.getProcedureService().getProceduresByPractitioner(
12395
+ practitionerId,
12396
+ void 0,
12397
+ // clinicBranchId
12398
+ false
12399
+ // excludeDraftPractitioners - allow draft practitioners
12400
+ ),
12357
12401
  this.getProcedureService().getInactiveProceduresByPractitioner(
12358
12402
  practitionerId
12359
12403
  )
12360
12404
  ]);
12361
12405
  const allProcedures = [...activeProcedures, ...inactiveProcedures];
12362
- const existingConsultation = allProcedures.find(
12406
+ const existingConsultations = allProcedures.filter(
12363
12407
  (procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId
12364
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];
12365
12426
  if (existingConsultation) {
12366
12427
  if (existingConsultation.isActive) {
12367
12428
  console.log(
12368
- `Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
12429
+ `[EnableFreeConsultation] Free consultation already active for practitioner ${practitionerId} in clinic ${clinicId}`
12369
12430
  );
12370
12431
  return;
12371
12432
  } else {
12433
+ console.log(
12434
+ `[EnableFreeConsultation] Reactivating existing consultation ${existingConsultation.id}`
12435
+ );
12372
12436
  await this.getProcedureService().updateProcedure(
12373
12437
  existingConsultation.id,
12374
12438
  { isActive: true }
12375
12439
  );
12376
12440
  console.log(
12377
- `Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
12441
+ `[EnableFreeConsultation] Reactivated existing free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
12378
12442
  );
12379
12443
  return;
12380
12444
  }
12381
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
+ }
12382
12471
  const consultationData = {
12383
12472
  name: "Free Consultation",
12384
12473
  nameLower: "free consultation",
@@ -12398,15 +12487,18 @@ var PractitionerService = class extends BaseService {
12398
12487
  photos: []
12399
12488
  // No photos for consultation
12400
12489
  };
12490
+ console.log(
12491
+ `[EnableFreeConsultation] Creating new free consultation procedure`
12492
+ );
12401
12493
  await this.getProcedureService().createConsultationProcedure(
12402
12494
  consultationData
12403
12495
  );
12404
12496
  console.log(
12405
- `Free consultation enabled for practitioner ${practitionerId} in clinic ${clinicId}`
12497
+ `[EnableFreeConsultation] Successfully created free consultation for practitioner ${practitionerId} in clinic ${clinicId}`
12406
12498
  );
12407
12499
  } catch (error) {
12408
12500
  console.error(
12409
- `Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
12501
+ `[EnableFreeConsultation] Error enabling free consultation for practitioner ${practitionerId} in clinic ${clinicId}:`,
12410
12502
  error
12411
12503
  );
12412
12504
  throw error;
@@ -12502,17 +12594,40 @@ var PractitionerService = class extends BaseService {
12502
12594
  );
12503
12595
  }
12504
12596
  const existingProcedures = await this.getProcedureService().getProceduresByPractitioner(
12505
- 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}`
12506
12605
  );
12507
12606
  const freeConsultation = existingProcedures.find(
12508
12607
  (procedure) => procedure.technology.id === "free-consultation-tech" && procedure.clinicBranchId === clinicId && procedure.isActive
12509
12608
  );
12510
12609
  if (!freeConsultation) {
12511
12610
  console.log(
12512
- `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
+ })
12513
12625
  );
12514
12626
  return;
12515
12627
  }
12628
+ console.log(
12629
+ `[DisableFreeConsultation] Found free consultation procedure ${freeConsultation.id}, deactivating...`
12630
+ );
12516
12631
  await this.getProcedureService().deactivateProcedure(freeConsultation.id);
12517
12632
  console.log(
12518
12633
  `Free consultation disabled for practitioner ${practitionerId} in clinic ${clinicId}`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.13.20",
4
+ "version": "1.13.21",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -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
- // Get all procedures for this practitioner (including inactive ones)
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(practitionerId),
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 existingConsultation = allProcedures.find(
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
- `Free consultation enabled for practitioner ${practitionerId} in clinic ${clinicId}`
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
- export const medicationSchema = z.object({
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 = medicationSchema.partial().extend({
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
+ );