@7365admin1/module-hygiene 4.11.0 → 4.12.0

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
@@ -2529,12 +2529,30 @@ function useParentChecklistRepo() {
2529
2529
  throw error;
2530
2530
  }
2531
2531
  }
2532
+ async function getTodayParentChecklists() {
2533
+ const now = /* @__PURE__ */ new Date();
2534
+ const start = new Date(now);
2535
+ start.setUTCHours(0, 0, 0, 0);
2536
+ const end = new Date(now);
2537
+ end.setUTCHours(23, 59, 59, 999);
2538
+ try {
2539
+ const items = await collection.find(
2540
+ { createdAt: { $gte: start, $lte: end } },
2541
+ { projection: { _id: 1, site: 1 } }
2542
+ ).toArray();
2543
+ return items;
2544
+ } catch (error) {
2545
+ import_node_server_utils14.logger.error("Failed to get today's parent checklists", error);
2546
+ throw error;
2547
+ }
2548
+ }
2532
2549
  return {
2533
2550
  createIndex,
2534
2551
  createParentChecklist,
2535
2552
  getAllParentChecklist,
2536
2553
  updateParentChecklistStatuses,
2537
- closeExpiredParentChecklists
2554
+ closeExpiredParentChecklists,
2555
+ getTodayParentChecklists
2538
2556
  };
2539
2557
  }
2540
2558
 
@@ -2630,7 +2648,9 @@ var areaChecklistSchema = import_joi8.default.object({
2630
2648
  unit: import_joi8.default.string().hex().required(),
2631
2649
  name: import_joi8.default.string().required()
2632
2650
  }).required()
2633
- ).min(1).required()
2651
+ ).min(1).required(),
2652
+ isScheduleTask: import_joi8.default.boolean().optional().default(false),
2653
+ scheduleTaskId: import_joi8.default.string().hex().optional().allow(null)
2634
2654
  }).required()
2635
2655
  ).optional().default([]),
2636
2656
  createdBy: import_joi8.default.string().hex().required()
@@ -2659,6 +2679,8 @@ function MAreaChecklist(value) {
2659
2679
  value.checklist = value.checklist.map((checklistItem) => {
2660
2680
  return {
2661
2681
  set: checklistItem.set,
2682
+ isScheduleTask: checklistItem.isScheduleTask ?? false,
2683
+ scheduleTaskId: checklistItem.scheduleTaskId ?? void 0,
2662
2684
  units: checklistItem.units.map((unit) => ({
2663
2685
  unit: new import_mongodb8.ObjectId(unit.unit),
2664
2686
  name: unit.name,
@@ -2694,6 +2716,7 @@ function MAreaChecklist(value) {
2694
2716
  }
2695
2717
 
2696
2718
  // src/services/hygiene-area-checklist.service.ts
2719
+ var import_mongodb10 = require("mongodb");
2697
2720
  var import_node_server_utils18 = require("@7365admin1/node-server-utils");
2698
2721
  var import_core = require("@7365admin1/core");
2699
2722
 
@@ -2737,26 +2760,26 @@ function useAreaChecklistRepo() {
2737
2760
  );
2738
2761
  }
2739
2762
  }
2763
+ async function createUniqueIndex() {
2764
+ try {
2765
+ await collection.createIndex({ schedule: 1, area: 1 }, { unique: true });
2766
+ } catch (error) {
2767
+ throw new import_node_server_utils17.InternalServerError(
2768
+ "Failed to create unique index on hygiene checklist area."
2769
+ );
2770
+ }
2771
+ }
2740
2772
  async function createAreaChecklist(value, session) {
2741
2773
  try {
2742
2774
  const parentChecklistId = new import_mongodb9.ObjectId(value.schedule);
2743
- const currentDate = /* @__PURE__ */ new Date();
2744
- const startOfDay = new Date(currentDate);
2745
- startOfDay.setUTCHours(0, 0, 0, 0);
2746
- const endOfDay = new Date(currentDate);
2747
- endOfDay.setUTCHours(23, 59, 59, 999);
2748
- const existingChecklist = await collection.findOne({
2749
- type: value.type,
2750
- schedule: parentChecklistId,
2751
- name: value.name,
2752
- createdAt: {
2753
- $gte: startOfDay,
2754
- $lte: endOfDay
2755
- }
2756
- });
2775
+ const areaId = new import_mongodb9.ObjectId(value.area);
2776
+ const existingChecklist = await collection.findOne(
2777
+ { schedule: parentChecklistId, area: areaId },
2778
+ { session }
2779
+ );
2757
2780
  if (existingChecklist) {
2758
2781
  import_node_server_utils17.logger.info(
2759
- `Area checklist already exists for area ${value.name} on ${currentDate.toISOString().split("T")[0]}`
2782
+ `Area checklist already exists for area ${value.name} (schedule: ${parentChecklistId}, area: ${areaId})`
2760
2783
  );
2761
2784
  return existingChecklist._id;
2762
2785
  }
@@ -2772,6 +2795,14 @@ function useAreaChecklistRepo() {
2772
2795
  });
2773
2796
  return result.insertedId;
2774
2797
  } catch (error) {
2798
+ if (error?.code === 11e3) {
2799
+ import_node_server_utils17.logger.info(`Duplicate area checklist skipped for area ${value.name}`);
2800
+ const existing = await collection.findOne({
2801
+ schedule: new import_mongodb9.ObjectId(value.schedule),
2802
+ area: new import_mongodb9.ObjectId(value.area)
2803
+ });
2804
+ return existing._id;
2805
+ }
2775
2806
  throw error;
2776
2807
  }
2777
2808
  }
@@ -3109,6 +3140,9 @@ function useAreaChecklistRepo() {
3109
3140
  $project: {
3110
3141
  _id: 0,
3111
3142
  set: "$checklist.set",
3143
+ isScheduleTask: {
3144
+ $ifNull: ["$checklist.isScheduleTask", false]
3145
+ },
3112
3146
  unit: "$checklist.units.unit",
3113
3147
  name: "$checklist.units.name",
3114
3148
  remarks: "$checklist.units.remarks",
@@ -3139,6 +3173,7 @@ function useAreaChecklistRepo() {
3139
3173
  {
3140
3174
  $group: {
3141
3175
  _id: "$set",
3176
+ isScheduleTask: { $first: "$isScheduleTask" },
3142
3177
  units: {
3143
3178
  $push: {
3144
3179
  unit: "$unit",
@@ -3155,6 +3190,7 @@ function useAreaChecklistRepo() {
3155
3190
  $project: {
3156
3191
  _id: 0,
3157
3192
  set: "$_id",
3193
+ isScheduleTask: 1,
3158
3194
  units: 1
3159
3195
  }
3160
3196
  },
@@ -3271,7 +3307,7 @@ function useAreaChecklistRepo() {
3271
3307
  },
3272
3308
  {
3273
3309
  $sort: {
3274
- set: 1,
3310
+ "checklist.set": 1,
3275
3311
  isActioned: 1,
3276
3312
  "checklist.units.timestamp": 1,
3277
3313
  isCompleted: -1
@@ -3281,6 +3317,12 @@ function useAreaChecklistRepo() {
3281
3317
  $project: {
3282
3318
  _id: 0,
3283
3319
  set: "$checklist.set",
3320
+ isScheduleTask: {
3321
+ $ifNull: ["$checklist.isScheduleTask", false]
3322
+ },
3323
+ scheduleTaskId: {
3324
+ $ifNull: ["$checklist.scheduleTaskId", null]
3325
+ },
3284
3326
  remarks: "$checklist.remarks",
3285
3327
  attachment: "$checklist.attachment",
3286
3328
  unit: "$checklist.units.unit",
@@ -3313,6 +3355,8 @@ function useAreaChecklistRepo() {
3313
3355
  {
3314
3356
  $group: {
3315
3357
  _id: "$set",
3358
+ isScheduleTask: { $first: "$isScheduleTask" },
3359
+ scheduleTaskId: { $first: "$scheduleTaskId" },
3316
3360
  remarks: { $first: "$remarks" },
3317
3361
  attachment: { $first: "$attachment" },
3318
3362
  completedByName: { $first: "$completedByName" },
@@ -3334,6 +3378,8 @@ function useAreaChecklistRepo() {
3334
3378
  $project: {
3335
3379
  _id: 0,
3336
3380
  set: "$_id",
3381
+ isScheduleTask: 1,
3382
+ scheduleTaskId: 1,
3337
3383
  remarks: "$remarks",
3338
3384
  attachment: "$attachment",
3339
3385
  completedByName: 1,
@@ -3358,7 +3404,7 @@ function useAreaChecklistRepo() {
3358
3404
  }
3359
3405
  }
3360
3406
  },
3361
- { $sort: { isFullyActioned: 1 } },
3407
+ { $sort: { isFullyActioned: 1, set: 1 } },
3362
3408
  { $skip: page * limit },
3363
3409
  { $limit: limit }
3364
3410
  ];
@@ -3581,7 +3627,7 @@ function useAreaChecklistRepo() {
3581
3627
  const _id = new import_mongodb9.ObjectId(areaId);
3582
3628
  const result = await collection.aggregate(
3583
3629
  [
3584
- { $match: { area: _id.toString() } },
3630
+ { $match: { area: _id } },
3585
3631
  { $unwind: "$checklist" },
3586
3632
  { $group: { _id: null, maxSet: { $max: "$checklist.set" } } }
3587
3633
  ],
@@ -3629,9 +3675,155 @@ function useAreaChecklistRepo() {
3629
3675
  throw error;
3630
3676
  }
3631
3677
  }
3678
+ async function pushScheduleTaskSets(scheduleId, areaId, scheduleTaskId, newSets) {
3679
+ try {
3680
+ const schedule = new import_mongodb9.ObjectId(scheduleId);
3681
+ const area = new import_mongodb9.ObjectId(areaId);
3682
+ const taskId = new import_mongodb9.ObjectId(scheduleTaskId);
3683
+ const result = await collection.updateOne(
3684
+ {
3685
+ schedule,
3686
+ area,
3687
+ checklist: { $not: { $elemMatch: { scheduleTaskId: taskId } } }
3688
+ },
3689
+ {
3690
+ $push: { checklist: { $each: newSets } },
3691
+ $set: { updatedAt: /* @__PURE__ */ new Date() }
3692
+ }
3693
+ );
3694
+ if (result.modifiedCount > 0) {
3695
+ delNamespace().catch((err) => {
3696
+ import_node_server_utils17.logger.error(
3697
+ `Failed to clear cache for namespace: ${namespace_collection}`,
3698
+ err
3699
+ );
3700
+ });
3701
+ }
3702
+ return result.modifiedCount;
3703
+ } catch (error) {
3704
+ import_node_server_utils17.logger.error("Error in pushScheduleTaskSets:", error);
3705
+ throw error;
3706
+ }
3707
+ }
3708
+ async function insertAutoGenSets(scheduleId, areaId, newSets) {
3709
+ try {
3710
+ const schedule = new import_mongodb9.ObjectId(scheduleId);
3711
+ const area = new import_mongodb9.ObjectId(areaId);
3712
+ const result = await collection.updateOne(
3713
+ {
3714
+ schedule,
3715
+ area,
3716
+ checklist: {
3717
+ $not: { $elemMatch: { isScheduleTask: { $ne: true } } }
3718
+ }
3719
+ },
3720
+ {
3721
+ $push: { checklist: { $each: newSets } },
3722
+ $set: { updatedAt: /* @__PURE__ */ new Date() }
3723
+ }
3724
+ );
3725
+ if (result.modifiedCount > 0) {
3726
+ delNamespace().catch((err) => {
3727
+ import_node_server_utils17.logger.error(
3728
+ `Failed to clear cache for namespace: ${namespace_collection}`,
3729
+ err
3730
+ );
3731
+ });
3732
+ }
3733
+ return result.modifiedCount;
3734
+ } catch (error) {
3735
+ import_node_server_utils17.logger.error("Error in insertAutoGenSets:", error);
3736
+ throw error;
3737
+ }
3738
+ }
3739
+ async function trimOverflowSets() {
3740
+ try {
3741
+ const db2 = import_node_server_utils17.useAtlas.getDb();
3742
+ if (!db2)
3743
+ return 0;
3744
+ const bloated = await collection.aggregate([
3745
+ {
3746
+ $lookup: {
3747
+ from: "site.cleaning.areas",
3748
+ localField: "area",
3749
+ foreignField: "_id",
3750
+ as: "areaDoc"
3751
+ }
3752
+ },
3753
+ {
3754
+ $unwind: { path: "$areaDoc", preserveNullAndEmptyArrays: false }
3755
+ },
3756
+ {
3757
+ $addFields: {
3758
+ configuredSet: { $max: [1, { $ifNull: ["$areaDoc.set", 1] }] },
3759
+ autoGenSets: {
3760
+ $filter: {
3761
+ input: { $ifNull: ["$checklist", []] },
3762
+ as: "item",
3763
+ cond: { $ne: ["$$item.isScheduleTask", true] }
3764
+ }
3765
+ },
3766
+ scheduledSets: {
3767
+ $filter: {
3768
+ input: { $ifNull: ["$checklist", []] },
3769
+ as: "item",
3770
+ cond: { $eq: ["$$item.isScheduleTask", true] }
3771
+ }
3772
+ }
3773
+ }
3774
+ },
3775
+ {
3776
+ $match: {
3777
+ $expr: {
3778
+ $gt: [{ $size: "$autoGenSets" }, "$configuredSet"]
3779
+ }
3780
+ }
3781
+ },
3782
+ {
3783
+ $project: {
3784
+ _id: 1,
3785
+ checklist: 1,
3786
+ configuredSet: 1,
3787
+ autoGenSets: 1,
3788
+ scheduledSets: 1
3789
+ }
3790
+ }
3791
+ ]).toArray();
3792
+ if (bloated.length === 0) {
3793
+ import_node_server_utils17.logger.info(
3794
+ "trimOverflowSets: no bloated area-checklist documents found"
3795
+ );
3796
+ return 0;
3797
+ }
3798
+ let trimmed = 0;
3799
+ for (const doc of bloated) {
3800
+ const limit = Number(doc.configuredSet) || 1;
3801
+ const trimmedAutoGen = [...doc.autoGenSets ?? []].sort((a, b) => (a.set ?? 0) - (b.set ?? 0)).slice(0, limit);
3802
+ const keep = [...trimmedAutoGen, ...doc.scheduledSets ?? []];
3803
+ const res = await collection.updateOne(
3804
+ { _id: doc._id },
3805
+ { $set: { checklist: keep, updatedAt: /* @__PURE__ */ new Date() } }
3806
+ );
3807
+ if (res.modifiedCount > 0) {
3808
+ trimmed++;
3809
+ import_node_server_utils17.logger.info(
3810
+ `trimOverflowSets: trimmed doc ${doc._id} \u2014 kept ${keep.length} sets (${trimmedAutoGen.length} auto-gen, ${(doc.scheduledSets ?? []).length} scheduled), removed ${doc.checklist.length - keep.length} excess auto-gen`
3811
+ );
3812
+ }
3813
+ }
3814
+ delNamespace().catch(() => {
3815
+ });
3816
+ import_node_server_utils17.logger.info(`trimOverflowSets: trimmed ${trimmed} document(s)`);
3817
+ return trimmed;
3818
+ } catch (error) {
3819
+ import_node_server_utils17.logger.error("Error in trimOverflowSets:", error);
3820
+ return 0;
3821
+ }
3822
+ }
3632
3823
  return {
3633
3824
  createIndex,
3634
3825
  createTextIndex,
3826
+ createUniqueIndex,
3635
3827
  createAreaChecklist,
3636
3828
  getAllAreaChecklist,
3637
3829
  getAreaChecklistHistory,
@@ -3644,7 +3836,10 @@ function useAreaChecklistRepo() {
3644
3836
  updateAreaChecklistStatus,
3645
3837
  updateAreaChecklistUnits,
3646
3838
  getMaxSetNumberForArea,
3647
- closeExpiredAreaChecklists
3839
+ closeExpiredAreaChecklists,
3840
+ pushScheduleTaskSets,
3841
+ insertAutoGenSets,
3842
+ trimOverflowSets
3648
3843
  };
3649
3844
  }
3650
3845
 
@@ -3656,19 +3851,18 @@ function useAreaChecklistService() {
3656
3851
  getAllAreaChecklist,
3657
3852
  getAreaChecklistUnits,
3658
3853
  getAreaChecklistById,
3854
+ getAreaChecklistByAreaAndSchedule,
3659
3855
  getAreaChecklistSetOwner,
3660
3856
  updateAreaChecklistUnits: _updateAreaChecklistUnits,
3661
3857
  updateAreaChecklistStatus,
3662
- getMaxSetNumberForArea
3858
+ insertAutoGenSets
3663
3859
  } = useAreaChecklistRepo();
3664
3860
  const { updateParentChecklistStatuses } = useParentChecklistRepo();
3665
3861
  const { getUserById } = (0, import_core.useUserRepo)();
3666
3862
  async function createAreaChecklist(value) {
3667
- const session = import_node_server_utils18.useAtlas.getClient()?.startSession();
3863
+ const results = [];
3864
+ let totalChecklistsCreated = 0;
3668
3865
  try {
3669
- session?.startTransaction();
3670
- const results = [];
3671
- let totalChecklistsCreated = 0;
3672
3866
  const BATCH_SIZE = 10;
3673
3867
  const areasResult = await getAreasForChecklist(value.site);
3674
3868
  const areas = areasResult || [];
@@ -3682,15 +3876,72 @@ function useAreaChecklistService() {
3682
3876
  );
3683
3877
  return null;
3684
3878
  }
3685
- const maxSet = await getMaxSetNumberForArea(area._id, session);
3686
- const setCount = area.set || 1;
3879
+ let existing = null;
3880
+ try {
3881
+ existing = await getAreaChecklistByAreaAndSchedule(
3882
+ value.schedule.toString(),
3883
+ area._id.toString()
3884
+ );
3885
+ } catch (_) {
3886
+ }
3887
+ if (existing) {
3888
+ const hasAutoGenSets = existing.checklist?.some(
3889
+ (c) => c.isScheduleTask !== true
3890
+ );
3891
+ if (hasAutoGenSets) {
3892
+ import_node_server_utils18.logger.info(
3893
+ `Area checklist already exists with auto-gen sets for area ${area.name}, skipping`
3894
+ );
3895
+ return existing._id;
3896
+ }
3897
+ import_node_server_utils18.logger.info(
3898
+ `Area checklist for area ${area.name} exists but has no auto-gen sets. Inserting auto-gen sets.`
3899
+ );
3900
+ const setCount2 = Number(area.set) || 1;
3901
+ const totalExistingSets = existing.checklist?.length ?? 0;
3902
+ if (totalExistingSets >= setCount2) {
3903
+ import_node_server_utils18.logger.info(
3904
+ `Area checklist for area ${area.name} already has ${totalExistingSets} set(s) (configured: ${setCount2}). Skipping auto-gen sets.`
3905
+ );
3906
+ return existing._id;
3907
+ }
3908
+ const maxExistingSet = existing.checklist?.reduce(
3909
+ (max, c) => Math.max(max, c.set ?? 0),
3910
+ 0
3911
+ ) ?? 0;
3912
+ const setsToAdd = setCount2 - totalExistingSets;
3913
+ const autoGenSets = Array.from(
3914
+ { length: setsToAdd },
3915
+ (_, index) => ({
3916
+ set: maxExistingSet + index + 1,
3917
+ isScheduleTask: false,
3918
+ units: area.units.map((unit) => ({
3919
+ unit: new import_mongodb10.ObjectId(unit.unit),
3920
+ name: unit.name,
3921
+ approve: false,
3922
+ reject: false,
3923
+ status: "open",
3924
+ remarks: "",
3925
+ completedBy: "",
3926
+ timestamp: ""
3927
+ }))
3928
+ })
3929
+ );
3930
+ await insertAutoGenSets(
3931
+ value.schedule.toString(),
3932
+ area._id.toString(),
3933
+ autoGenSets
3934
+ );
3935
+ return existing._id;
3936
+ }
3937
+ const setCount = Number(area.set) || 1;
3687
3938
  const checklistData = {
3688
3939
  schedule: value.schedule,
3689
3940
  area: area._id.toString(),
3690
3941
  name: area.name,
3691
3942
  type: area.type,
3692
3943
  checklist: Array.from({ length: setCount }, (_, index) => ({
3693
- set: maxSet + index + 1,
3944
+ set: index + 1,
3694
3945
  units: area.units.map((unit) => ({
3695
3946
  unit: unit.unit.toString(),
3696
3947
  name: unit.name
@@ -3698,10 +3949,7 @@ function useAreaChecklistService() {
3698
3949
  })),
3699
3950
  createdBy: value.createdBy
3700
3951
  };
3701
- const insertedId = await _createAreaChecklist(
3702
- checklistData,
3703
- session
3704
- );
3952
+ const insertedId = await _createAreaChecklist(checklistData);
3705
3953
  totalChecklistsCreated++;
3706
3954
  return insertedId;
3707
3955
  });
@@ -3714,19 +3962,13 @@ function useAreaChecklistService() {
3714
3962
  } else {
3715
3963
  import_node_server_utils18.logger.warn(`No common areas found for site: ${value.site}`);
3716
3964
  }
3717
- await session?.commitTransaction();
3718
3965
  import_node_server_utils18.logger.info(
3719
3966
  `Successfully created ${totalChecklistsCreated} area checklists for site: ${value.site}`
3720
3967
  );
3721
3968
  return results;
3722
3969
  } catch (error) {
3723
3970
  import_node_server_utils18.logger.error(`Error generating area checklists:`, error);
3724
- if (session?.inTransaction()) {
3725
- await session?.abortTransaction();
3726
- }
3727
3971
  throw error;
3728
- } finally {
3729
- session?.endSession();
3730
3972
  }
3731
3973
  }
3732
3974
  async function updateAreaChecklistUnits(_id, set, unitId, value) {
@@ -3830,7 +4072,7 @@ var import_node_server_utils20 = require("@7365admin1/node-server-utils");
3830
4072
  // src/services/hygiene-checklist-pdf.service.ts
3831
4073
  var import_puppeteer = require("puppeteer");
3832
4074
  var import_node_server_utils19 = require("@7365admin1/node-server-utils");
3833
- var import_mongodb10 = require("mongodb");
4075
+ var import_mongodb11 = require("mongodb");
3834
4076
  function escapeHtml(text) {
3835
4077
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
3836
4078
  }
@@ -4004,7 +4246,7 @@ function useChecklistPdfService() {
4004
4246
  const collection = db.collection("site.cleaning.schedule.areas");
4005
4247
  let scheduleObjectId;
4006
4248
  try {
4007
- scheduleObjectId = new import_mongodb10.ObjectId(scheduleId);
4249
+ scheduleObjectId = new import_mongodb11.ObjectId(scheduleId);
4008
4250
  } catch {
4009
4251
  throw new import_node_server_utils19.InternalServerError("Invalid schedule ID format.");
4010
4252
  }
@@ -4313,7 +4555,7 @@ function useAreaChecklistController() {
4313
4555
 
4314
4556
  // src/models/hygiene-supply.model.ts
4315
4557
  var import_joi10 = __toESM(require("joi"));
4316
- var import_mongodb11 = require("mongodb");
4558
+ var import_mongodb12 = require("mongodb");
4317
4559
  var import_node_server_utils21 = require("@7365admin1/node-server-utils");
4318
4560
  var supplySchema = import_joi10.default.object({
4319
4561
  site: import_joi10.default.string().hex().required(),
@@ -4328,7 +4570,7 @@ function MSupply(value) {
4328
4570
  }
4329
4571
  if (value.site) {
4330
4572
  try {
4331
- value.site = new import_mongodb11.ObjectId(value.site);
4573
+ value.site = new import_mongodb12.ObjectId(value.site);
4332
4574
  } catch (error2) {
4333
4575
  throw new import_node_server_utils21.BadRequestError("Invalid site ID format.");
4334
4576
  }
@@ -4346,7 +4588,7 @@ function MSupply(value) {
4346
4588
  }
4347
4589
 
4348
4590
  // src/repositories/hygiene-supply.repository.ts
4349
- var import_mongodb12 = require("mongodb");
4591
+ var import_mongodb13 = require("mongodb");
4350
4592
  var import_node_server_utils22 = require("@7365admin1/node-server-utils");
4351
4593
  function useSupplyRepository() {
4352
4594
  const db = import_node_server_utils22.useAtlas.getDb();
@@ -4422,7 +4664,7 @@ function useSupplyRepository() {
4422
4664
  limit
4423
4665
  };
4424
4666
  try {
4425
- site = new import_mongodb12.ObjectId(site);
4667
+ site = new import_mongodb13.ObjectId(site);
4426
4668
  query.site = site;
4427
4669
  cacheOptions.site = site.toString();
4428
4670
  } catch (error) {
@@ -4466,7 +4708,7 @@ function useSupplyRepository() {
4466
4708
  }
4467
4709
  async function getSupplyById(_id, session) {
4468
4710
  try {
4469
- _id = new import_mongodb12.ObjectId(_id);
4711
+ _id = new import_mongodb13.ObjectId(_id);
4470
4712
  } catch (error) {
4471
4713
  throw new import_node_server_utils22.BadRequestError("Invalid supply ID format.");
4472
4714
  }
@@ -4512,7 +4754,7 @@ function useSupplyRepository() {
4512
4754
  }
4513
4755
  async function updateSupply(_id, value, session) {
4514
4756
  try {
4515
- _id = new import_mongodb12.ObjectId(_id);
4757
+ _id = new import_mongodb13.ObjectId(_id);
4516
4758
  } catch (error) {
4517
4759
  throw new import_node_server_utils22.BadRequestError("Invalid supply ID format.");
4518
4760
  }
@@ -4545,7 +4787,7 @@ function useSupplyRepository() {
4545
4787
  }
4546
4788
  async function deleteSupply(_id, session) {
4547
4789
  try {
4548
- _id = new import_mongodb12.ObjectId(_id);
4790
+ _id = new import_mongodb13.ObjectId(_id);
4549
4791
  } catch (error) {
4550
4792
  throw new import_node_server_utils22.BadRequestError("Invalid supply ID format.");
4551
4793
  }
@@ -4726,7 +4968,7 @@ function useSupplyController() {
4726
4968
 
4727
4969
  // src/models/hygiene-stock.model.ts
4728
4970
  var import_joi12 = __toESM(require("joi"));
4729
- var import_mongodb13 = require("mongodb");
4971
+ var import_mongodb14 = require("mongodb");
4730
4972
  var import_node_server_utils24 = require("@7365admin1/node-server-utils");
4731
4973
  var stockSchema = import_joi12.default.object({
4732
4974
  site: import_joi12.default.string().hex().required(),
@@ -4744,14 +4986,14 @@ function MStock(value) {
4744
4986
  }
4745
4987
  if (value.site) {
4746
4988
  try {
4747
- value.site = new import_mongodb13.ObjectId(value.site);
4989
+ value.site = new import_mongodb14.ObjectId(value.site);
4748
4990
  } catch (error2) {
4749
4991
  throw new import_node_server_utils24.BadRequestError("Invalid site ID format.");
4750
4992
  }
4751
4993
  }
4752
4994
  if (value.supply) {
4753
4995
  try {
4754
- value.supply = new import_mongodb13.ObjectId(value.supply);
4996
+ value.supply = new import_mongodb14.ObjectId(value.supply);
4755
4997
  } catch (error2) {
4756
4998
  throw new import_node_server_utils24.BadRequestError("Invalid supply ID format.");
4757
4999
  }
@@ -4771,7 +5013,7 @@ function MStock(value) {
4771
5013
  }
4772
5014
 
4773
5015
  // src/repositories/hygiene-stock.repository.ts
4774
- var import_mongodb14 = require("mongodb");
5016
+ var import_mongodb15 = require("mongodb");
4775
5017
  var import_node_server_utils25 = require("@7365admin1/node-server-utils");
4776
5018
  function useStockRepository() {
4777
5019
  const db = import_node_server_utils25.useAtlas.getDb();
@@ -4836,14 +5078,14 @@ function useStockRepository() {
4836
5078
  limit
4837
5079
  };
4838
5080
  try {
4839
- site = new import_mongodb14.ObjectId(site);
5081
+ site = new import_mongodb15.ObjectId(site);
4840
5082
  query.site = site;
4841
5083
  cacheOptions.site = site.toString();
4842
5084
  } catch (error) {
4843
5085
  throw new import_node_server_utils25.BadRequestError("Invalid site ID format.");
4844
5086
  }
4845
5087
  try {
4846
- supply = new import_mongodb14.ObjectId(supply);
5088
+ supply = new import_mongodb15.ObjectId(supply);
4847
5089
  query.supply = supply;
4848
5090
  cacheOptions.supply = supply.toString();
4849
5091
  } catch (error) {
@@ -5017,7 +5259,7 @@ function useStockController() {
5017
5259
 
5018
5260
  // src/models/hygiene-checkout-item.model.ts
5019
5261
  var import_joi14 = __toESM(require("joi"));
5020
- var import_mongodb15 = require("mongodb");
5262
+ var import_mongodb16 = require("mongodb");
5021
5263
  var import_node_server_utils28 = require("@7365admin1/node-server-utils");
5022
5264
  var allowedCheckOutItemStatus = ["pending", "completed"];
5023
5265
  var checkOutItemSchema = import_joi14.default.object({
@@ -5037,14 +5279,14 @@ function MCheckOutItem(value) {
5037
5279
  }
5038
5280
  if (value.site) {
5039
5281
  try {
5040
- value.site = new import_mongodb15.ObjectId(value.site);
5282
+ value.site = new import_mongodb16.ObjectId(value.site);
5041
5283
  } catch (error2) {
5042
5284
  throw new import_node_server_utils28.BadRequestError("Invalid site ID format.");
5043
5285
  }
5044
5286
  }
5045
5287
  if (value.supply) {
5046
5288
  try {
5047
- value.supply = new import_mongodb15.ObjectId(value.supply);
5289
+ value.supply = new import_mongodb16.ObjectId(value.supply);
5048
5290
  } catch (error2) {
5049
5291
  throw new import_node_server_utils28.BadRequestError("Invalid supply ID format.");
5050
5292
  }
@@ -5065,7 +5307,7 @@ function MCheckOutItem(value) {
5065
5307
  }
5066
5308
 
5067
5309
  // src/repositories/hygiene-checkout-item.repository.ts
5068
- var import_mongodb16 = require("mongodb");
5310
+ var import_mongodb17 = require("mongodb");
5069
5311
  var import_node_server_utils29 = require("@7365admin1/node-server-utils");
5070
5312
  function useCheckOutItemRepository() {
5071
5313
  const db = import_node_server_utils29.useAtlas.getDb();
@@ -5129,7 +5371,7 @@ function useCheckOutItemRepository() {
5129
5371
  limit
5130
5372
  };
5131
5373
  try {
5132
- site = new import_mongodb16.ObjectId(site);
5374
+ site = new import_mongodb17.ObjectId(site);
5133
5375
  query.site = site;
5134
5376
  cacheOptions.site = site.toString();
5135
5377
  } catch (error) {
@@ -5202,7 +5444,7 @@ function useCheckOutItemRepository() {
5202
5444
  }
5203
5445
  async function getCheckOutItemById(_id, session) {
5204
5446
  try {
5205
- _id = new import_mongodb16.ObjectId(_id);
5447
+ _id = new import_mongodb17.ObjectId(_id);
5206
5448
  } catch (error) {
5207
5449
  throw new import_node_server_utils29.BadRequestError("Invalid check out item ID format.");
5208
5450
  }
@@ -5266,7 +5508,7 @@ function useCheckOutItemRepository() {
5266
5508
  }
5267
5509
  async function completeCheckOutItem(_id, session) {
5268
5510
  try {
5269
- _id = new import_mongodb16.ObjectId(_id);
5511
+ _id = new import_mongodb17.ObjectId(_id);
5270
5512
  } catch (error) {
5271
5513
  throw new import_node_server_utils29.BadRequestError("Invalid check out item ID format.");
5272
5514
  }
@@ -5552,7 +5794,7 @@ function useCheckOutItemController() {
5552
5794
  // src/models/hygiene-schedule-task.model.ts
5553
5795
  var import_node_server_utils32 = require("@7365admin1/node-server-utils");
5554
5796
  var import_joi16 = __toESM(require("joi"));
5555
- var import_mongodb17 = require("mongodb");
5797
+ var import_mongodb18 = require("mongodb");
5556
5798
  var scheduleTaskSchema = import_joi16.default.object({
5557
5799
  site: import_joi16.default.string().hex().required(),
5558
5800
  title: import_joi16.default.string().required(),
@@ -5577,7 +5819,7 @@ function MScheduleTask(value) {
5577
5819
  }
5578
5820
  if (value.site) {
5579
5821
  try {
5580
- value.site = new import_mongodb17.ObjectId(value.site);
5822
+ value.site = new import_mongodb18.ObjectId(value.site);
5581
5823
  } catch (error2) {
5582
5824
  throw new import_node_server_utils32.BadRequestError("Invalid site ID format.");
5583
5825
  }
@@ -5587,7 +5829,7 @@ function MScheduleTask(value) {
5587
5829
  try {
5588
5830
  return {
5589
5831
  name: area.name,
5590
- value: new import_mongodb17.ObjectId(area.value.toString())
5832
+ value: new import_mongodb18.ObjectId(area.value.toString())
5591
5833
  };
5592
5834
  } catch (error2) {
5593
5835
  throw new import_node_server_utils32.BadRequestError(`Invalid area value format: ${area.name}`);
@@ -5596,7 +5838,7 @@ function MScheduleTask(value) {
5596
5838
  }
5597
5839
  if (value.createdBy) {
5598
5840
  try {
5599
- value.createdBy = new import_mongodb17.ObjectId(value.createdBy);
5841
+ value.createdBy = new import_mongodb18.ObjectId(value.createdBy);
5600
5842
  } catch (error2) {
5601
5843
  throw new import_node_server_utils32.BadRequestError("Invalid createdBy ID format.");
5602
5844
  }
@@ -5617,7 +5859,7 @@ function MScheduleTask(value) {
5617
5859
  }
5618
5860
 
5619
5861
  // src/repositories/hygiene-schedule-task.repository.ts
5620
- var import_mongodb18 = require("mongodb");
5862
+ var import_mongodb19 = require("mongodb");
5621
5863
  var import_node_server_utils33 = require("@7365admin1/node-server-utils");
5622
5864
  function useScheduleTaskRepository() {
5623
5865
  const db = import_node_server_utils33.useAtlas.getDb();
@@ -5680,7 +5922,7 @@ function useScheduleTaskRepository() {
5680
5922
  limit
5681
5923
  };
5682
5924
  try {
5683
- site = new import_mongodb18.ObjectId(site);
5925
+ site = new import_mongodb19.ObjectId(site);
5684
5926
  query.site = site;
5685
5927
  cacheOptions.site = site.toString();
5686
5928
  } catch (error) {
@@ -5748,7 +5990,7 @@ function useScheduleTaskRepository() {
5748
5990
  limit
5749
5991
  };
5750
5992
  try {
5751
- site = new import_mongodb18.ObjectId(site);
5993
+ site = new import_mongodb19.ObjectId(site);
5752
5994
  query.site = site;
5753
5995
  cacheOptions.site = site.toString();
5754
5996
  } catch (error) {
@@ -5791,7 +6033,7 @@ function useScheduleTaskRepository() {
5791
6033
  }
5792
6034
  async function getScheduleTaskById(_id, session) {
5793
6035
  try {
5794
- _id = new import_mongodb18.ObjectId(_id);
6036
+ _id = new import_mongodb19.ObjectId(_id);
5795
6037
  } catch (error) {
5796
6038
  throw new import_node_server_utils33.BadRequestError("Invalid schedule task ID format.");
5797
6039
  }
@@ -5841,7 +6083,7 @@ function useScheduleTaskRepository() {
5841
6083
  }
5842
6084
  async function updateScheduleTask(_id, value, session) {
5843
6085
  try {
5844
- _id = new import_mongodb18.ObjectId(_id);
6086
+ _id = new import_mongodb19.ObjectId(_id);
5845
6087
  } catch (error) {
5846
6088
  throw new import_node_server_utils33.BadRequestError("Invalid schedule task ID format.");
5847
6089
  }
@@ -5850,7 +6092,7 @@ function useScheduleTaskRepository() {
5850
6092
  try {
5851
6093
  return {
5852
6094
  name: area.name,
5853
- value: new import_mongodb18.ObjectId(area.value.toString())
6095
+ value: new import_mongodb19.ObjectId(area.value.toString())
5854
6096
  };
5855
6097
  } catch (error) {
5856
6098
  throw new import_node_server_utils33.BadRequestError(`Invalid area value format: ${area.name}`);
@@ -5895,6 +6137,7 @@ function useScheduleTaskRepository() {
5895
6137
  }
5896
6138
 
5897
6139
  // src/services/hygiene-schedule-task.service.ts
6140
+ var import_mongodb20 = require("mongodb");
5898
6141
  var import_node_server_utils34 = require("@7365admin1/node-server-utils");
5899
6142
  function useScheduleTaskService() {
5900
6143
  const { createParentChecklist } = useParentChecklistRepo();
@@ -5902,7 +6145,7 @@ function useScheduleTaskService() {
5902
6145
  const {
5903
6146
  createAreaChecklist,
5904
6147
  getAreaChecklistByAreaAndSchedule,
5905
- updateAreaChecklist
6148
+ pushScheduleTaskSets
5906
6149
  } = useAreaChecklistRepo();
5907
6150
  const { getAreaById } = useAreaRepo();
5908
6151
  function checkScheduleConditions(schedule, currentDate = /* @__PURE__ */ new Date()) {
@@ -5941,6 +6184,35 @@ function useScheduleTaskService() {
5941
6184
  );
5942
6185
  return false;
5943
6186
  }
6187
+ const relevantTimestamp = schedule.updatedAt || schedule.createdAt;
6188
+ if (relevantTimestamp) {
6189
+ const savedAt = new Date(relevantTimestamp);
6190
+ const savedHour = parseInt(
6191
+ savedAt.toLocaleTimeString("en-US", {
6192
+ hour: "2-digit",
6193
+ hour12: false,
6194
+ timeZone: "Asia/Singapore"
6195
+ }),
6196
+ 10
6197
+ );
6198
+ const savedMinute = savedAt.toLocaleString("en-US", {
6199
+ minute: "2-digit",
6200
+ timeZone: "Asia/Singapore"
6201
+ });
6202
+ const savedDateFormatted = savedAt.toLocaleDateString("en-CA", {
6203
+ timeZone: "Asia/Singapore"
6204
+ });
6205
+ const savedMinNum = parseInt(savedMinute, 10);
6206
+ const wasSavedThisMinute = savedDateFormatted === currentDateFormatted && savedHour === currentHour && savedMinNum === currentMinute;
6207
+ if (wasSavedThisMinute) {
6208
+ import_node_server_utils34.logger.info(
6209
+ `Schedule ${schedule._id}: Skipping \u2014 task was saved this minute (${savedHour}:${String(
6210
+ savedMinNum
6211
+ ).padStart(2, "0")}). Will fire on next occurrence.`
6212
+ );
6213
+ return false;
6214
+ }
6215
+ }
5944
6216
  import_node_server_utils34.logger.info(
5945
6217
  `Schedule ${schedule._id}: All conditions matched - Date is within range and time matches`
5946
6218
  );
@@ -6008,8 +6280,14 @@ function useScheduleTaskService() {
6008
6280
  let units = [];
6009
6281
  if (areaDetails.units && areaDetails.units.length > 0) {
6010
6282
  units = areaDetails.units.map((unit) => ({
6011
- unit: unit.unit.toString(),
6012
- name: unit.name
6283
+ unit: new import_mongodb20.ObjectId(unit.unit),
6284
+ name: unit.name,
6285
+ approve: false,
6286
+ reject: false,
6287
+ status: "open",
6288
+ remarks: "",
6289
+ completedBy: "",
6290
+ timestamp: ""
6013
6291
  }));
6014
6292
  import_node_server_utils34.logger.info(
6015
6293
  `Area ${area.name} (${areaId}): Using units from area details: ${JSON.stringify(
@@ -6046,79 +6324,62 @@ function useScheduleTaskService() {
6046
6324
  );
6047
6325
  }
6048
6326
  if (existingAreaChecklist) {
6049
- const currentSetNumber = existingAreaChecklist.checklist?.length || 0;
6050
- const newSet = {
6051
- set: currentSetNumber + 1,
6052
- units
6053
- };
6054
- const updatedChecklist = [
6055
- ...existingAreaChecklist.checklist || [],
6056
- newSet
6057
- ];
6058
- import_node_server_utils34.logger.info(
6059
- `Area ${area.name} (${areaId}): Appending new set ${newSet.set} to checklist. Updated checklist: ${JSON.stringify(
6060
- updatedChecklist
6061
- )}`
6327
+ const areaSetConfig = Number(areaDetails.set) || 1;
6328
+ const maxSetInChecklist = existingAreaChecklist.checklist?.length ? Math.max(
6329
+ ...existingAreaChecklist.checklist.map(
6330
+ (c) => c.set
6331
+ )
6332
+ ) : 0;
6333
+ const startSetNumber = maxSetInChecklist + 1;
6334
+ const newSets = Array.from(
6335
+ { length: areaSetConfig },
6336
+ (_, index) => ({
6337
+ set: startSetNumber + index,
6338
+ units,
6339
+ isScheduleTask: true,
6340
+ scheduleTaskId: scheduleTask._id
6341
+ })
6062
6342
  );
6063
- await updateAreaChecklist(existingAreaChecklist._id, {
6064
- checklist: updatedChecklist
6065
- });
6066
- import_node_server_utils34.logger.info(
6067
- `Appended set ${newSet.set} to area checklist for area ${area.name}`
6343
+ const modified = await pushScheduleTaskSets(
6344
+ parentChecklistId.toString(),
6345
+ areaId,
6346
+ scheduleTask._id.toString(),
6347
+ newSets
6068
6348
  );
6069
- try {
6070
- const verifyChecklist = await getAreaChecklistByAreaAndSchedule(
6071
- parentChecklistId.toString(),
6072
- areaId
6073
- );
6349
+ if (modified > 0) {
6074
6350
  import_node_server_utils34.logger.info(
6075
- `Area ${area.name} (${areaId}): Checklist after update: ${JSON.stringify(
6076
- verifyChecklist.checklist
6077
- )}`
6351
+ `Appended ${areaSetConfig} set(s) starting from ${startSetNumber} to area checklist for area ${area.name}`
6078
6352
  );
6079
- } catch (verifyError) {
6080
- import_node_server_utils34.logger.warn(
6081
- `Area ${area.name} (${areaId}): Error verifying checklist after update:`,
6082
- verifyError
6353
+ } else {
6354
+ import_node_server_utils34.logger.info(
6355
+ `Area ${area.name} (${areaId}): Schedule task ${scheduleTask._id} already processed (atomic check), skipping.`
6083
6356
  );
6084
6357
  }
6085
6358
  } else {
6359
+ const areaSetConfig = Number(areaDetails.set) || 1;
6360
+ const schedTaskId = scheduleTask._id;
6361
+ const schedTaskSets = Array.from(
6362
+ { length: areaSetConfig },
6363
+ (_, index) => ({
6364
+ set: index + 1,
6365
+ units,
6366
+ isScheduleTask: true,
6367
+ scheduleTaskId: schedTaskId
6368
+ })
6369
+ );
6086
6370
  const checklistData = {
6087
6371
  schedule: parentChecklistId.toString(),
6088
6372
  area: areaId,
6089
6373
  name: area.name,
6090
6374
  type: areaDetails.type || "common",
6091
- checklist: [
6092
- {
6093
- set: 1,
6094
- units
6095
- }
6096
- ],
6375
+ checklist: schedTaskSets,
6097
6376
  createdBy: scheduleTask.createdBy
6098
6377
  };
6099
6378
  import_node_server_utils34.logger.info(
6100
- `Area ${area.name} (${areaId}): Creating new area checklist with data: ${JSON.stringify(
6101
- checklistData
6102
- )}`
6379
+ `Area ${area.name} (${areaId}): Creating new area checklist with schedule-task sets only`
6103
6380
  );
6104
6381
  await createAreaChecklist(checklistData);
6105
6382
  import_node_server_utils34.logger.info(`Created new area checklist for area ${area.name}`);
6106
- try {
6107
- const verifyChecklist = await getAreaChecklistByAreaAndSchedule(
6108
- parentChecklistId.toString(),
6109
- areaId
6110
- );
6111
- import_node_server_utils34.logger.info(
6112
- `Area ${area.name} (${areaId}): Checklist after creation: ${JSON.stringify(
6113
- verifyChecklist.checklist
6114
- )}`
6115
- );
6116
- } catch (verifyError) {
6117
- import_node_server_utils34.logger.warn(
6118
- `Area ${area.name} (${areaId}): Error verifying checklist after creation:`,
6119
- verifyError
6120
- );
6121
- }
6122
6383
  }
6123
6384
  } catch (error) {
6124
6385
  import_node_server_utils34.logger.error(`Error processing area ${area.name}:`, error);