@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.mjs CHANGED
@@ -2486,12 +2486,30 @@ function useParentChecklistRepo() {
2486
2486
  throw error;
2487
2487
  }
2488
2488
  }
2489
+ async function getTodayParentChecklists() {
2490
+ const now = /* @__PURE__ */ new Date();
2491
+ const start = new Date(now);
2492
+ start.setUTCHours(0, 0, 0, 0);
2493
+ const end = new Date(now);
2494
+ end.setUTCHours(23, 59, 59, 999);
2495
+ try {
2496
+ const items = await collection.find(
2497
+ { createdAt: { $gte: start, $lte: end } },
2498
+ { projection: { _id: 1, site: 1 } }
2499
+ ).toArray();
2500
+ return items;
2501
+ } catch (error) {
2502
+ logger14.error("Failed to get today's parent checklists", error);
2503
+ throw error;
2504
+ }
2505
+ }
2489
2506
  return {
2490
2507
  createIndex,
2491
2508
  createParentChecklist,
2492
2509
  getAllParentChecklist,
2493
2510
  updateParentChecklistStatuses,
2494
- closeExpiredParentChecklists
2511
+ closeExpiredParentChecklists,
2512
+ getTodayParentChecklists
2495
2513
  };
2496
2514
  }
2497
2515
 
@@ -2587,7 +2605,9 @@ var areaChecklistSchema = Joi8.object({
2587
2605
  unit: Joi8.string().hex().required(),
2588
2606
  name: Joi8.string().required()
2589
2607
  }).required()
2590
- ).min(1).required()
2608
+ ).min(1).required(),
2609
+ isScheduleTask: Joi8.boolean().optional().default(false),
2610
+ scheduleTaskId: Joi8.string().hex().optional().allow(null)
2591
2611
  }).required()
2592
2612
  ).optional().default([]),
2593
2613
  createdBy: Joi8.string().hex().required()
@@ -2616,6 +2636,8 @@ function MAreaChecklist(value) {
2616
2636
  value.checklist = value.checklist.map((checklistItem) => {
2617
2637
  return {
2618
2638
  set: checklistItem.set,
2639
+ isScheduleTask: checklistItem.isScheduleTask ?? false,
2640
+ scheduleTaskId: checklistItem.scheduleTaskId ?? void 0,
2619
2641
  units: checklistItem.units.map((unit) => ({
2620
2642
  unit: new ObjectId8(unit.unit),
2621
2643
  name: unit.name,
@@ -2651,6 +2673,7 @@ function MAreaChecklist(value) {
2651
2673
  }
2652
2674
 
2653
2675
  // src/services/hygiene-area-checklist.service.ts
2676
+ import { ObjectId as ObjectId10 } from "mongodb";
2654
2677
  import {
2655
2678
  logger as logger18,
2656
2679
  UnauthorizedError,
@@ -2706,26 +2729,26 @@ function useAreaChecklistRepo() {
2706
2729
  );
2707
2730
  }
2708
2731
  }
2732
+ async function createUniqueIndex() {
2733
+ try {
2734
+ await collection.createIndex({ schedule: 1, area: 1 }, { unique: true });
2735
+ } catch (error) {
2736
+ throw new InternalServerError5(
2737
+ "Failed to create unique index on hygiene checklist area."
2738
+ );
2739
+ }
2740
+ }
2709
2741
  async function createAreaChecklist(value, session) {
2710
2742
  try {
2711
2743
  const parentChecklistId = new ObjectId9(value.schedule);
2712
- const currentDate = /* @__PURE__ */ new Date();
2713
- const startOfDay = new Date(currentDate);
2714
- startOfDay.setUTCHours(0, 0, 0, 0);
2715
- const endOfDay = new Date(currentDate);
2716
- endOfDay.setUTCHours(23, 59, 59, 999);
2717
- const existingChecklist = await collection.findOne({
2718
- type: value.type,
2719
- schedule: parentChecklistId,
2720
- name: value.name,
2721
- createdAt: {
2722
- $gte: startOfDay,
2723
- $lte: endOfDay
2724
- }
2725
- });
2744
+ const areaId = new ObjectId9(value.area);
2745
+ const existingChecklist = await collection.findOne(
2746
+ { schedule: parentChecklistId, area: areaId },
2747
+ { session }
2748
+ );
2726
2749
  if (existingChecklist) {
2727
2750
  logger17.info(
2728
- `Area checklist already exists for area ${value.name} on ${currentDate.toISOString().split("T")[0]}`
2751
+ `Area checklist already exists for area ${value.name} (schedule: ${parentChecklistId}, area: ${areaId})`
2729
2752
  );
2730
2753
  return existingChecklist._id;
2731
2754
  }
@@ -2741,6 +2764,14 @@ function useAreaChecklistRepo() {
2741
2764
  });
2742
2765
  return result.insertedId;
2743
2766
  } catch (error) {
2767
+ if (error?.code === 11e3) {
2768
+ logger17.info(`Duplicate area checklist skipped for area ${value.name}`);
2769
+ const existing = await collection.findOne({
2770
+ schedule: new ObjectId9(value.schedule),
2771
+ area: new ObjectId9(value.area)
2772
+ });
2773
+ return existing._id;
2774
+ }
2744
2775
  throw error;
2745
2776
  }
2746
2777
  }
@@ -3078,6 +3109,9 @@ function useAreaChecklistRepo() {
3078
3109
  $project: {
3079
3110
  _id: 0,
3080
3111
  set: "$checklist.set",
3112
+ isScheduleTask: {
3113
+ $ifNull: ["$checklist.isScheduleTask", false]
3114
+ },
3081
3115
  unit: "$checklist.units.unit",
3082
3116
  name: "$checklist.units.name",
3083
3117
  remarks: "$checklist.units.remarks",
@@ -3108,6 +3142,7 @@ function useAreaChecklistRepo() {
3108
3142
  {
3109
3143
  $group: {
3110
3144
  _id: "$set",
3145
+ isScheduleTask: { $first: "$isScheduleTask" },
3111
3146
  units: {
3112
3147
  $push: {
3113
3148
  unit: "$unit",
@@ -3124,6 +3159,7 @@ function useAreaChecklistRepo() {
3124
3159
  $project: {
3125
3160
  _id: 0,
3126
3161
  set: "$_id",
3162
+ isScheduleTask: 1,
3127
3163
  units: 1
3128
3164
  }
3129
3165
  },
@@ -3240,7 +3276,7 @@ function useAreaChecklistRepo() {
3240
3276
  },
3241
3277
  {
3242
3278
  $sort: {
3243
- set: 1,
3279
+ "checklist.set": 1,
3244
3280
  isActioned: 1,
3245
3281
  "checklist.units.timestamp": 1,
3246
3282
  isCompleted: -1
@@ -3250,6 +3286,12 @@ function useAreaChecklistRepo() {
3250
3286
  $project: {
3251
3287
  _id: 0,
3252
3288
  set: "$checklist.set",
3289
+ isScheduleTask: {
3290
+ $ifNull: ["$checklist.isScheduleTask", false]
3291
+ },
3292
+ scheduleTaskId: {
3293
+ $ifNull: ["$checklist.scheduleTaskId", null]
3294
+ },
3253
3295
  remarks: "$checklist.remarks",
3254
3296
  attachment: "$checklist.attachment",
3255
3297
  unit: "$checklist.units.unit",
@@ -3282,6 +3324,8 @@ function useAreaChecklistRepo() {
3282
3324
  {
3283
3325
  $group: {
3284
3326
  _id: "$set",
3327
+ isScheduleTask: { $first: "$isScheduleTask" },
3328
+ scheduleTaskId: { $first: "$scheduleTaskId" },
3285
3329
  remarks: { $first: "$remarks" },
3286
3330
  attachment: { $first: "$attachment" },
3287
3331
  completedByName: { $first: "$completedByName" },
@@ -3303,6 +3347,8 @@ function useAreaChecklistRepo() {
3303
3347
  $project: {
3304
3348
  _id: 0,
3305
3349
  set: "$_id",
3350
+ isScheduleTask: 1,
3351
+ scheduleTaskId: 1,
3306
3352
  remarks: "$remarks",
3307
3353
  attachment: "$attachment",
3308
3354
  completedByName: 1,
@@ -3327,7 +3373,7 @@ function useAreaChecklistRepo() {
3327
3373
  }
3328
3374
  }
3329
3375
  },
3330
- { $sort: { isFullyActioned: 1 } },
3376
+ { $sort: { isFullyActioned: 1, set: 1 } },
3331
3377
  { $skip: page * limit },
3332
3378
  { $limit: limit }
3333
3379
  ];
@@ -3550,7 +3596,7 @@ function useAreaChecklistRepo() {
3550
3596
  const _id = new ObjectId9(areaId);
3551
3597
  const result = await collection.aggregate(
3552
3598
  [
3553
- { $match: { area: _id.toString() } },
3599
+ { $match: { area: _id } },
3554
3600
  { $unwind: "$checklist" },
3555
3601
  { $group: { _id: null, maxSet: { $max: "$checklist.set" } } }
3556
3602
  ],
@@ -3598,9 +3644,155 @@ function useAreaChecklistRepo() {
3598
3644
  throw error;
3599
3645
  }
3600
3646
  }
3647
+ async function pushScheduleTaskSets(scheduleId, areaId, scheduleTaskId, newSets) {
3648
+ try {
3649
+ const schedule = new ObjectId9(scheduleId);
3650
+ const area = new ObjectId9(areaId);
3651
+ const taskId = new ObjectId9(scheduleTaskId);
3652
+ const result = await collection.updateOne(
3653
+ {
3654
+ schedule,
3655
+ area,
3656
+ checklist: { $not: { $elemMatch: { scheduleTaskId: taskId } } }
3657
+ },
3658
+ {
3659
+ $push: { checklist: { $each: newSets } },
3660
+ $set: { updatedAt: /* @__PURE__ */ new Date() }
3661
+ }
3662
+ );
3663
+ if (result.modifiedCount > 0) {
3664
+ delNamespace().catch((err) => {
3665
+ logger17.error(
3666
+ `Failed to clear cache for namespace: ${namespace_collection}`,
3667
+ err
3668
+ );
3669
+ });
3670
+ }
3671
+ return result.modifiedCount;
3672
+ } catch (error) {
3673
+ logger17.error("Error in pushScheduleTaskSets:", error);
3674
+ throw error;
3675
+ }
3676
+ }
3677
+ async function insertAutoGenSets(scheduleId, areaId, newSets) {
3678
+ try {
3679
+ const schedule = new ObjectId9(scheduleId);
3680
+ const area = new ObjectId9(areaId);
3681
+ const result = await collection.updateOne(
3682
+ {
3683
+ schedule,
3684
+ area,
3685
+ checklist: {
3686
+ $not: { $elemMatch: { isScheduleTask: { $ne: true } } }
3687
+ }
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
+ logger17.error(
3697
+ `Failed to clear cache for namespace: ${namespace_collection}`,
3698
+ err
3699
+ );
3700
+ });
3701
+ }
3702
+ return result.modifiedCount;
3703
+ } catch (error) {
3704
+ logger17.error("Error in insertAutoGenSets:", error);
3705
+ throw error;
3706
+ }
3707
+ }
3708
+ async function trimOverflowSets() {
3709
+ try {
3710
+ const db2 = useAtlas6.getDb();
3711
+ if (!db2)
3712
+ return 0;
3713
+ const bloated = await collection.aggregate([
3714
+ {
3715
+ $lookup: {
3716
+ from: "site.cleaning.areas",
3717
+ localField: "area",
3718
+ foreignField: "_id",
3719
+ as: "areaDoc"
3720
+ }
3721
+ },
3722
+ {
3723
+ $unwind: { path: "$areaDoc", preserveNullAndEmptyArrays: false }
3724
+ },
3725
+ {
3726
+ $addFields: {
3727
+ configuredSet: { $max: [1, { $ifNull: ["$areaDoc.set", 1] }] },
3728
+ autoGenSets: {
3729
+ $filter: {
3730
+ input: { $ifNull: ["$checklist", []] },
3731
+ as: "item",
3732
+ cond: { $ne: ["$$item.isScheduleTask", true] }
3733
+ }
3734
+ },
3735
+ scheduledSets: {
3736
+ $filter: {
3737
+ input: { $ifNull: ["$checklist", []] },
3738
+ as: "item",
3739
+ cond: { $eq: ["$$item.isScheduleTask", true] }
3740
+ }
3741
+ }
3742
+ }
3743
+ },
3744
+ {
3745
+ $match: {
3746
+ $expr: {
3747
+ $gt: [{ $size: "$autoGenSets" }, "$configuredSet"]
3748
+ }
3749
+ }
3750
+ },
3751
+ {
3752
+ $project: {
3753
+ _id: 1,
3754
+ checklist: 1,
3755
+ configuredSet: 1,
3756
+ autoGenSets: 1,
3757
+ scheduledSets: 1
3758
+ }
3759
+ }
3760
+ ]).toArray();
3761
+ if (bloated.length === 0) {
3762
+ logger17.info(
3763
+ "trimOverflowSets: no bloated area-checklist documents found"
3764
+ );
3765
+ return 0;
3766
+ }
3767
+ let trimmed = 0;
3768
+ for (const doc of bloated) {
3769
+ const limit = Number(doc.configuredSet) || 1;
3770
+ const trimmedAutoGen = [...doc.autoGenSets ?? []].sort((a, b) => (a.set ?? 0) - (b.set ?? 0)).slice(0, limit);
3771
+ const keep = [...trimmedAutoGen, ...doc.scheduledSets ?? []];
3772
+ const res = await collection.updateOne(
3773
+ { _id: doc._id },
3774
+ { $set: { checklist: keep, updatedAt: /* @__PURE__ */ new Date() } }
3775
+ );
3776
+ if (res.modifiedCount > 0) {
3777
+ trimmed++;
3778
+ logger17.info(
3779
+ `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`
3780
+ );
3781
+ }
3782
+ }
3783
+ delNamespace().catch(() => {
3784
+ });
3785
+ logger17.info(`trimOverflowSets: trimmed ${trimmed} document(s)`);
3786
+ return trimmed;
3787
+ } catch (error) {
3788
+ logger17.error("Error in trimOverflowSets:", error);
3789
+ return 0;
3790
+ }
3791
+ }
3601
3792
  return {
3602
3793
  createIndex,
3603
3794
  createTextIndex,
3795
+ createUniqueIndex,
3604
3796
  createAreaChecklist,
3605
3797
  getAllAreaChecklist,
3606
3798
  getAreaChecklistHistory,
@@ -3613,7 +3805,10 @@ function useAreaChecklistRepo() {
3613
3805
  updateAreaChecklistStatus,
3614
3806
  updateAreaChecklistUnits,
3615
3807
  getMaxSetNumberForArea,
3616
- closeExpiredAreaChecklists
3808
+ closeExpiredAreaChecklists,
3809
+ pushScheduleTaskSets,
3810
+ insertAutoGenSets,
3811
+ trimOverflowSets
3617
3812
  };
3618
3813
  }
3619
3814
 
@@ -3625,19 +3820,18 @@ function useAreaChecklistService() {
3625
3820
  getAllAreaChecklist,
3626
3821
  getAreaChecklistUnits,
3627
3822
  getAreaChecklistById,
3823
+ getAreaChecklistByAreaAndSchedule,
3628
3824
  getAreaChecklistSetOwner,
3629
3825
  updateAreaChecklistUnits: _updateAreaChecklistUnits,
3630
3826
  updateAreaChecklistStatus,
3631
- getMaxSetNumberForArea
3827
+ insertAutoGenSets
3632
3828
  } = useAreaChecklistRepo();
3633
3829
  const { updateParentChecklistStatuses } = useParentChecklistRepo();
3634
3830
  const { getUserById } = useUserRepo();
3635
3831
  async function createAreaChecklist(value) {
3636
- const session = useAtlas7.getClient()?.startSession();
3832
+ const results = [];
3833
+ let totalChecklistsCreated = 0;
3637
3834
  try {
3638
- session?.startTransaction();
3639
- const results = [];
3640
- let totalChecklistsCreated = 0;
3641
3835
  const BATCH_SIZE = 10;
3642
3836
  const areasResult = await getAreasForChecklist(value.site);
3643
3837
  const areas = areasResult || [];
@@ -3651,15 +3845,72 @@ function useAreaChecklistService() {
3651
3845
  );
3652
3846
  return null;
3653
3847
  }
3654
- const maxSet = await getMaxSetNumberForArea(area._id, session);
3655
- const setCount = area.set || 1;
3848
+ let existing = null;
3849
+ try {
3850
+ existing = await getAreaChecklistByAreaAndSchedule(
3851
+ value.schedule.toString(),
3852
+ area._id.toString()
3853
+ );
3854
+ } catch (_) {
3855
+ }
3856
+ if (existing) {
3857
+ const hasAutoGenSets = existing.checklist?.some(
3858
+ (c) => c.isScheduleTask !== true
3859
+ );
3860
+ if (hasAutoGenSets) {
3861
+ logger18.info(
3862
+ `Area checklist already exists with auto-gen sets for area ${area.name}, skipping`
3863
+ );
3864
+ return existing._id;
3865
+ }
3866
+ logger18.info(
3867
+ `Area checklist for area ${area.name} exists but has no auto-gen sets. Inserting auto-gen sets.`
3868
+ );
3869
+ const setCount2 = Number(area.set) || 1;
3870
+ const totalExistingSets = existing.checklist?.length ?? 0;
3871
+ if (totalExistingSets >= setCount2) {
3872
+ logger18.info(
3873
+ `Area checklist for area ${area.name} already has ${totalExistingSets} set(s) (configured: ${setCount2}). Skipping auto-gen sets.`
3874
+ );
3875
+ return existing._id;
3876
+ }
3877
+ const maxExistingSet = existing.checklist?.reduce(
3878
+ (max, c) => Math.max(max, c.set ?? 0),
3879
+ 0
3880
+ ) ?? 0;
3881
+ const setsToAdd = setCount2 - totalExistingSets;
3882
+ const autoGenSets = Array.from(
3883
+ { length: setsToAdd },
3884
+ (_, index) => ({
3885
+ set: maxExistingSet + index + 1,
3886
+ isScheduleTask: false,
3887
+ units: area.units.map((unit) => ({
3888
+ unit: new ObjectId10(unit.unit),
3889
+ name: unit.name,
3890
+ approve: false,
3891
+ reject: false,
3892
+ status: "open",
3893
+ remarks: "",
3894
+ completedBy: "",
3895
+ timestamp: ""
3896
+ }))
3897
+ })
3898
+ );
3899
+ await insertAutoGenSets(
3900
+ value.schedule.toString(),
3901
+ area._id.toString(),
3902
+ autoGenSets
3903
+ );
3904
+ return existing._id;
3905
+ }
3906
+ const setCount = Number(area.set) || 1;
3656
3907
  const checklistData = {
3657
3908
  schedule: value.schedule,
3658
3909
  area: area._id.toString(),
3659
3910
  name: area.name,
3660
3911
  type: area.type,
3661
3912
  checklist: Array.from({ length: setCount }, (_, index) => ({
3662
- set: maxSet + index + 1,
3913
+ set: index + 1,
3663
3914
  units: area.units.map((unit) => ({
3664
3915
  unit: unit.unit.toString(),
3665
3916
  name: unit.name
@@ -3667,10 +3918,7 @@ function useAreaChecklistService() {
3667
3918
  })),
3668
3919
  createdBy: value.createdBy
3669
3920
  };
3670
- const insertedId = await _createAreaChecklist(
3671
- checklistData,
3672
- session
3673
- );
3921
+ const insertedId = await _createAreaChecklist(checklistData);
3674
3922
  totalChecklistsCreated++;
3675
3923
  return insertedId;
3676
3924
  });
@@ -3683,19 +3931,13 @@ function useAreaChecklistService() {
3683
3931
  } else {
3684
3932
  logger18.warn(`No common areas found for site: ${value.site}`);
3685
3933
  }
3686
- await session?.commitTransaction();
3687
3934
  logger18.info(
3688
3935
  `Successfully created ${totalChecklistsCreated} area checklists for site: ${value.site}`
3689
3936
  );
3690
3937
  return results;
3691
3938
  } catch (error) {
3692
3939
  logger18.error(`Error generating area checklists:`, error);
3693
- if (session?.inTransaction()) {
3694
- await session?.abortTransaction();
3695
- }
3696
3940
  throw error;
3697
- } finally {
3698
- session?.endSession();
3699
3941
  }
3700
3942
  }
3701
3943
  async function updateAreaChecklistUnits(_id, set, unitId, value) {
@@ -3799,7 +4041,7 @@ import { BadRequestError as BadRequestError16, InternalServerError as InternalSe
3799
4041
  // src/services/hygiene-checklist-pdf.service.ts
3800
4042
  import { launch } from "puppeteer";
3801
4043
  import { InternalServerError as InternalServerError6, useAtlas as useAtlas8 } from "@7365admin1/node-server-utils";
3802
- import { ObjectId as ObjectId10 } from "mongodb";
4044
+ import { ObjectId as ObjectId11 } from "mongodb";
3803
4045
  function escapeHtml(text) {
3804
4046
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
3805
4047
  }
@@ -3973,7 +4215,7 @@ function useChecklistPdfService() {
3973
4215
  const collection = db.collection("site.cleaning.schedule.areas");
3974
4216
  let scheduleObjectId;
3975
4217
  try {
3976
- scheduleObjectId = new ObjectId10(scheduleId);
4218
+ scheduleObjectId = new ObjectId11(scheduleId);
3977
4219
  } catch {
3978
4220
  throw new InternalServerError6("Invalid schedule ID format.");
3979
4221
  }
@@ -4282,7 +4524,7 @@ function useAreaChecklistController() {
4282
4524
 
4283
4525
  // src/models/hygiene-supply.model.ts
4284
4526
  import Joi10 from "joi";
4285
- import { ObjectId as ObjectId11 } from "mongodb";
4527
+ import { ObjectId as ObjectId12 } from "mongodb";
4286
4528
  import { BadRequestError as BadRequestError17, logger as logger21 } from "@7365admin1/node-server-utils";
4287
4529
  var supplySchema = Joi10.object({
4288
4530
  site: Joi10.string().hex().required(),
@@ -4297,7 +4539,7 @@ function MSupply(value) {
4297
4539
  }
4298
4540
  if (value.site) {
4299
4541
  try {
4300
- value.site = new ObjectId11(value.site);
4542
+ value.site = new ObjectId12(value.site);
4301
4543
  } catch (error2) {
4302
4544
  throw new BadRequestError17("Invalid site ID format.");
4303
4545
  }
@@ -4315,7 +4557,7 @@ function MSupply(value) {
4315
4557
  }
4316
4558
 
4317
4559
  // src/repositories/hygiene-supply.repository.ts
4318
- import { ObjectId as ObjectId12 } from "mongodb";
4560
+ import { ObjectId as ObjectId13 } from "mongodb";
4319
4561
  import {
4320
4562
  useAtlas as useAtlas9,
4321
4563
  InternalServerError as InternalServerError8,
@@ -4400,7 +4642,7 @@ function useSupplyRepository() {
4400
4642
  limit
4401
4643
  };
4402
4644
  try {
4403
- site = new ObjectId12(site);
4645
+ site = new ObjectId13(site);
4404
4646
  query.site = site;
4405
4647
  cacheOptions.site = site.toString();
4406
4648
  } catch (error) {
@@ -4444,7 +4686,7 @@ function useSupplyRepository() {
4444
4686
  }
4445
4687
  async function getSupplyById(_id, session) {
4446
4688
  try {
4447
- _id = new ObjectId12(_id);
4689
+ _id = new ObjectId13(_id);
4448
4690
  } catch (error) {
4449
4691
  throw new BadRequestError18("Invalid supply ID format.");
4450
4692
  }
@@ -4490,7 +4732,7 @@ function useSupplyRepository() {
4490
4732
  }
4491
4733
  async function updateSupply(_id, value, session) {
4492
4734
  try {
4493
- _id = new ObjectId12(_id);
4735
+ _id = new ObjectId13(_id);
4494
4736
  } catch (error) {
4495
4737
  throw new BadRequestError18("Invalid supply ID format.");
4496
4738
  }
@@ -4523,7 +4765,7 @@ function useSupplyRepository() {
4523
4765
  }
4524
4766
  async function deleteSupply(_id, session) {
4525
4767
  try {
4526
- _id = new ObjectId12(_id);
4768
+ _id = new ObjectId13(_id);
4527
4769
  } catch (error) {
4528
4770
  throw new BadRequestError18("Invalid supply ID format.");
4529
4771
  }
@@ -4704,7 +4946,7 @@ function useSupplyController() {
4704
4946
 
4705
4947
  // src/models/hygiene-stock.model.ts
4706
4948
  import Joi12 from "joi";
4707
- import { ObjectId as ObjectId13 } from "mongodb";
4949
+ import { ObjectId as ObjectId14 } from "mongodb";
4708
4950
  import { BadRequestError as BadRequestError20, logger as logger24 } from "@7365admin1/node-server-utils";
4709
4951
  var stockSchema = Joi12.object({
4710
4952
  site: Joi12.string().hex().required(),
@@ -4722,14 +4964,14 @@ function MStock(value) {
4722
4964
  }
4723
4965
  if (value.site) {
4724
4966
  try {
4725
- value.site = new ObjectId13(value.site);
4967
+ value.site = new ObjectId14(value.site);
4726
4968
  } catch (error2) {
4727
4969
  throw new BadRequestError20("Invalid site ID format.");
4728
4970
  }
4729
4971
  }
4730
4972
  if (value.supply) {
4731
4973
  try {
4732
- value.supply = new ObjectId13(value.supply);
4974
+ value.supply = new ObjectId14(value.supply);
4733
4975
  } catch (error2) {
4734
4976
  throw new BadRequestError20("Invalid supply ID format.");
4735
4977
  }
@@ -4749,7 +4991,7 @@ function MStock(value) {
4749
4991
  }
4750
4992
 
4751
4993
  // src/repositories/hygiene-stock.repository.ts
4752
- import { ObjectId as ObjectId14 } from "mongodb";
4994
+ import { ObjectId as ObjectId15 } from "mongodb";
4753
4995
  import {
4754
4996
  useAtlas as useAtlas10,
4755
4997
  InternalServerError as InternalServerError9,
@@ -4822,14 +5064,14 @@ function useStockRepository() {
4822
5064
  limit
4823
5065
  };
4824
5066
  try {
4825
- site = new ObjectId14(site);
5067
+ site = new ObjectId15(site);
4826
5068
  query.site = site;
4827
5069
  cacheOptions.site = site.toString();
4828
5070
  } catch (error) {
4829
5071
  throw new BadRequestError21("Invalid site ID format.");
4830
5072
  }
4831
5073
  try {
4832
- supply = new ObjectId14(supply);
5074
+ supply = new ObjectId15(supply);
4833
5075
  query.supply = supply;
4834
5076
  cacheOptions.supply = supply.toString();
4835
5077
  } catch (error) {
@@ -5007,7 +5249,7 @@ function useStockController() {
5007
5249
 
5008
5250
  // src/models/hygiene-checkout-item.model.ts
5009
5251
  import Joi14 from "joi";
5010
- import { ObjectId as ObjectId15 } from "mongodb";
5252
+ import { ObjectId as ObjectId16 } from "mongodb";
5011
5253
  import { BadRequestError as BadRequestError24, logger as logger27 } from "@7365admin1/node-server-utils";
5012
5254
  var allowedCheckOutItemStatus = ["pending", "completed"];
5013
5255
  var checkOutItemSchema = Joi14.object({
@@ -5027,14 +5269,14 @@ function MCheckOutItem(value) {
5027
5269
  }
5028
5270
  if (value.site) {
5029
5271
  try {
5030
- value.site = new ObjectId15(value.site);
5272
+ value.site = new ObjectId16(value.site);
5031
5273
  } catch (error2) {
5032
5274
  throw new BadRequestError24("Invalid site ID format.");
5033
5275
  }
5034
5276
  }
5035
5277
  if (value.supply) {
5036
5278
  try {
5037
- value.supply = new ObjectId15(value.supply);
5279
+ value.supply = new ObjectId16(value.supply);
5038
5280
  } catch (error2) {
5039
5281
  throw new BadRequestError24("Invalid supply ID format.");
5040
5282
  }
@@ -5055,7 +5297,7 @@ function MCheckOutItem(value) {
5055
5297
  }
5056
5298
 
5057
5299
  // src/repositories/hygiene-checkout-item.repository.ts
5058
- import { ObjectId as ObjectId16 } from "mongodb";
5300
+ import { ObjectId as ObjectId17 } from "mongodb";
5059
5301
  import {
5060
5302
  useAtlas as useAtlas12,
5061
5303
  InternalServerError as InternalServerError10,
@@ -5128,7 +5370,7 @@ function useCheckOutItemRepository() {
5128
5370
  limit
5129
5371
  };
5130
5372
  try {
5131
- site = new ObjectId16(site);
5373
+ site = new ObjectId17(site);
5132
5374
  query.site = site;
5133
5375
  cacheOptions.site = site.toString();
5134
5376
  } catch (error) {
@@ -5201,7 +5443,7 @@ function useCheckOutItemRepository() {
5201
5443
  }
5202
5444
  async function getCheckOutItemById(_id, session) {
5203
5445
  try {
5204
- _id = new ObjectId16(_id);
5446
+ _id = new ObjectId17(_id);
5205
5447
  } catch (error) {
5206
5448
  throw new BadRequestError25("Invalid check out item ID format.");
5207
5449
  }
@@ -5265,7 +5507,7 @@ function useCheckOutItemRepository() {
5265
5507
  }
5266
5508
  async function completeCheckOutItem(_id, session) {
5267
5509
  try {
5268
- _id = new ObjectId16(_id);
5510
+ _id = new ObjectId17(_id);
5269
5511
  } catch (error) {
5270
5512
  throw new BadRequestError25("Invalid check out item ID format.");
5271
5513
  }
@@ -5551,7 +5793,7 @@ function useCheckOutItemController() {
5551
5793
  // src/models/hygiene-schedule-task.model.ts
5552
5794
  import { BadRequestError as BadRequestError28, logger as logger30 } from "@7365admin1/node-server-utils";
5553
5795
  import Joi16 from "joi";
5554
- import { ObjectId as ObjectId17 } from "mongodb";
5796
+ import { ObjectId as ObjectId18 } from "mongodb";
5555
5797
  var scheduleTaskSchema = Joi16.object({
5556
5798
  site: Joi16.string().hex().required(),
5557
5799
  title: Joi16.string().required(),
@@ -5576,7 +5818,7 @@ function MScheduleTask(value) {
5576
5818
  }
5577
5819
  if (value.site) {
5578
5820
  try {
5579
- value.site = new ObjectId17(value.site);
5821
+ value.site = new ObjectId18(value.site);
5580
5822
  } catch (error2) {
5581
5823
  throw new BadRequestError28("Invalid site ID format.");
5582
5824
  }
@@ -5586,7 +5828,7 @@ function MScheduleTask(value) {
5586
5828
  try {
5587
5829
  return {
5588
5830
  name: area.name,
5589
- value: new ObjectId17(area.value.toString())
5831
+ value: new ObjectId18(area.value.toString())
5590
5832
  };
5591
5833
  } catch (error2) {
5592
5834
  throw new BadRequestError28(`Invalid area value format: ${area.name}`);
@@ -5595,7 +5837,7 @@ function MScheduleTask(value) {
5595
5837
  }
5596
5838
  if (value.createdBy) {
5597
5839
  try {
5598
- value.createdBy = new ObjectId17(value.createdBy);
5840
+ value.createdBy = new ObjectId18(value.createdBy);
5599
5841
  } catch (error2) {
5600
5842
  throw new BadRequestError28("Invalid createdBy ID format.");
5601
5843
  }
@@ -5616,7 +5858,7 @@ function MScheduleTask(value) {
5616
5858
  }
5617
5859
 
5618
5860
  // src/repositories/hygiene-schedule-task.repository.ts
5619
- import { ObjectId as ObjectId18 } from "mongodb";
5861
+ import { ObjectId as ObjectId19 } from "mongodb";
5620
5862
  import {
5621
5863
  useAtlas as useAtlas14,
5622
5864
  InternalServerError as InternalServerError11,
@@ -5688,7 +5930,7 @@ function useScheduleTaskRepository() {
5688
5930
  limit
5689
5931
  };
5690
5932
  try {
5691
- site = new ObjectId18(site);
5933
+ site = new ObjectId19(site);
5692
5934
  query.site = site;
5693
5935
  cacheOptions.site = site.toString();
5694
5936
  } catch (error) {
@@ -5756,7 +5998,7 @@ function useScheduleTaskRepository() {
5756
5998
  limit
5757
5999
  };
5758
6000
  try {
5759
- site = new ObjectId18(site);
6001
+ site = new ObjectId19(site);
5760
6002
  query.site = site;
5761
6003
  cacheOptions.site = site.toString();
5762
6004
  } catch (error) {
@@ -5799,7 +6041,7 @@ function useScheduleTaskRepository() {
5799
6041
  }
5800
6042
  async function getScheduleTaskById(_id, session) {
5801
6043
  try {
5802
- _id = new ObjectId18(_id);
6044
+ _id = new ObjectId19(_id);
5803
6045
  } catch (error) {
5804
6046
  throw new BadRequestError29("Invalid schedule task ID format.");
5805
6047
  }
@@ -5849,7 +6091,7 @@ function useScheduleTaskRepository() {
5849
6091
  }
5850
6092
  async function updateScheduleTask(_id, value, session) {
5851
6093
  try {
5852
- _id = new ObjectId18(_id);
6094
+ _id = new ObjectId19(_id);
5853
6095
  } catch (error) {
5854
6096
  throw new BadRequestError29("Invalid schedule task ID format.");
5855
6097
  }
@@ -5858,7 +6100,7 @@ function useScheduleTaskRepository() {
5858
6100
  try {
5859
6101
  return {
5860
6102
  name: area.name,
5861
- value: new ObjectId18(area.value.toString())
6103
+ value: new ObjectId19(area.value.toString())
5862
6104
  };
5863
6105
  } catch (error) {
5864
6106
  throw new BadRequestError29(`Invalid area value format: ${area.name}`);
@@ -5903,6 +6145,7 @@ function useScheduleTaskRepository() {
5903
6145
  }
5904
6146
 
5905
6147
  // src/services/hygiene-schedule-task.service.ts
6148
+ import { ObjectId as ObjectId20 } from "mongodb";
5906
6149
  import { logger as logger32 } from "@7365admin1/node-server-utils";
5907
6150
  function useScheduleTaskService() {
5908
6151
  const { createParentChecklist } = useParentChecklistRepo();
@@ -5910,7 +6153,7 @@ function useScheduleTaskService() {
5910
6153
  const {
5911
6154
  createAreaChecklist,
5912
6155
  getAreaChecklistByAreaAndSchedule,
5913
- updateAreaChecklist
6156
+ pushScheduleTaskSets
5914
6157
  } = useAreaChecklistRepo();
5915
6158
  const { getAreaById } = useAreaRepo();
5916
6159
  function checkScheduleConditions(schedule, currentDate = /* @__PURE__ */ new Date()) {
@@ -5949,6 +6192,35 @@ function useScheduleTaskService() {
5949
6192
  );
5950
6193
  return false;
5951
6194
  }
6195
+ const relevantTimestamp = schedule.updatedAt || schedule.createdAt;
6196
+ if (relevantTimestamp) {
6197
+ const savedAt = new Date(relevantTimestamp);
6198
+ const savedHour = parseInt(
6199
+ savedAt.toLocaleTimeString("en-US", {
6200
+ hour: "2-digit",
6201
+ hour12: false,
6202
+ timeZone: "Asia/Singapore"
6203
+ }),
6204
+ 10
6205
+ );
6206
+ const savedMinute = savedAt.toLocaleString("en-US", {
6207
+ minute: "2-digit",
6208
+ timeZone: "Asia/Singapore"
6209
+ });
6210
+ const savedDateFormatted = savedAt.toLocaleDateString("en-CA", {
6211
+ timeZone: "Asia/Singapore"
6212
+ });
6213
+ const savedMinNum = parseInt(savedMinute, 10);
6214
+ const wasSavedThisMinute = savedDateFormatted === currentDateFormatted && savedHour === currentHour && savedMinNum === currentMinute;
6215
+ if (wasSavedThisMinute) {
6216
+ logger32.info(
6217
+ `Schedule ${schedule._id}: Skipping \u2014 task was saved this minute (${savedHour}:${String(
6218
+ savedMinNum
6219
+ ).padStart(2, "0")}). Will fire on next occurrence.`
6220
+ );
6221
+ return false;
6222
+ }
6223
+ }
5952
6224
  logger32.info(
5953
6225
  `Schedule ${schedule._id}: All conditions matched - Date is within range and time matches`
5954
6226
  );
@@ -6016,8 +6288,14 @@ function useScheduleTaskService() {
6016
6288
  let units = [];
6017
6289
  if (areaDetails.units && areaDetails.units.length > 0) {
6018
6290
  units = areaDetails.units.map((unit) => ({
6019
- unit: unit.unit.toString(),
6020
- name: unit.name
6291
+ unit: new ObjectId20(unit.unit),
6292
+ name: unit.name,
6293
+ approve: false,
6294
+ reject: false,
6295
+ status: "open",
6296
+ remarks: "",
6297
+ completedBy: "",
6298
+ timestamp: ""
6021
6299
  }));
6022
6300
  logger32.info(
6023
6301
  `Area ${area.name} (${areaId}): Using units from area details: ${JSON.stringify(
@@ -6054,79 +6332,62 @@ function useScheduleTaskService() {
6054
6332
  );
6055
6333
  }
6056
6334
  if (existingAreaChecklist) {
6057
- const currentSetNumber = existingAreaChecklist.checklist?.length || 0;
6058
- const newSet = {
6059
- set: currentSetNumber + 1,
6060
- units
6061
- };
6062
- const updatedChecklist = [
6063
- ...existingAreaChecklist.checklist || [],
6064
- newSet
6065
- ];
6066
- logger32.info(
6067
- `Area ${area.name} (${areaId}): Appending new set ${newSet.set} to checklist. Updated checklist: ${JSON.stringify(
6068
- updatedChecklist
6069
- )}`
6335
+ const areaSetConfig = Number(areaDetails.set) || 1;
6336
+ const maxSetInChecklist = existingAreaChecklist.checklist?.length ? Math.max(
6337
+ ...existingAreaChecklist.checklist.map(
6338
+ (c) => c.set
6339
+ )
6340
+ ) : 0;
6341
+ const startSetNumber = maxSetInChecklist + 1;
6342
+ const newSets = Array.from(
6343
+ { length: areaSetConfig },
6344
+ (_, index) => ({
6345
+ set: startSetNumber + index,
6346
+ units,
6347
+ isScheduleTask: true,
6348
+ scheduleTaskId: scheduleTask._id
6349
+ })
6070
6350
  );
6071
- await updateAreaChecklist(existingAreaChecklist._id, {
6072
- checklist: updatedChecklist
6073
- });
6074
- logger32.info(
6075
- `Appended set ${newSet.set} to area checklist for area ${area.name}`
6351
+ const modified = await pushScheduleTaskSets(
6352
+ parentChecklistId.toString(),
6353
+ areaId,
6354
+ scheduleTask._id.toString(),
6355
+ newSets
6076
6356
  );
6077
- try {
6078
- const verifyChecklist = await getAreaChecklistByAreaAndSchedule(
6079
- parentChecklistId.toString(),
6080
- areaId
6081
- );
6357
+ if (modified > 0) {
6082
6358
  logger32.info(
6083
- `Area ${area.name} (${areaId}): Checklist after update: ${JSON.stringify(
6084
- verifyChecklist.checklist
6085
- )}`
6359
+ `Appended ${areaSetConfig} set(s) starting from ${startSetNumber} to area checklist for area ${area.name}`
6086
6360
  );
6087
- } catch (verifyError) {
6088
- logger32.warn(
6089
- `Area ${area.name} (${areaId}): Error verifying checklist after update:`,
6090
- verifyError
6361
+ } else {
6362
+ logger32.info(
6363
+ `Area ${area.name} (${areaId}): Schedule task ${scheduleTask._id} already processed (atomic check), skipping.`
6091
6364
  );
6092
6365
  }
6093
6366
  } else {
6367
+ const areaSetConfig = Number(areaDetails.set) || 1;
6368
+ const schedTaskId = scheduleTask._id;
6369
+ const schedTaskSets = Array.from(
6370
+ { length: areaSetConfig },
6371
+ (_, index) => ({
6372
+ set: index + 1,
6373
+ units,
6374
+ isScheduleTask: true,
6375
+ scheduleTaskId: schedTaskId
6376
+ })
6377
+ );
6094
6378
  const checklistData = {
6095
6379
  schedule: parentChecklistId.toString(),
6096
6380
  area: areaId,
6097
6381
  name: area.name,
6098
6382
  type: areaDetails.type || "common",
6099
- checklist: [
6100
- {
6101
- set: 1,
6102
- units
6103
- }
6104
- ],
6383
+ checklist: schedTaskSets,
6105
6384
  createdBy: scheduleTask.createdBy
6106
6385
  };
6107
6386
  logger32.info(
6108
- `Area ${area.name} (${areaId}): Creating new area checklist with data: ${JSON.stringify(
6109
- checklistData
6110
- )}`
6387
+ `Area ${area.name} (${areaId}): Creating new area checklist with schedule-task sets only`
6111
6388
  );
6112
6389
  await createAreaChecklist(checklistData);
6113
6390
  logger32.info(`Created new area checklist for area ${area.name}`);
6114
- try {
6115
- const verifyChecklist = await getAreaChecklistByAreaAndSchedule(
6116
- parentChecklistId.toString(),
6117
- areaId
6118
- );
6119
- logger32.info(
6120
- `Area ${area.name} (${areaId}): Checklist after creation: ${JSON.stringify(
6121
- verifyChecklist.checklist
6122
- )}`
6123
- );
6124
- } catch (verifyError) {
6125
- logger32.warn(
6126
- `Area ${area.name} (${areaId}): Error verifying checklist after creation:`,
6127
- verifyError
6128
- );
6129
- }
6130
6391
  }
6131
6392
  } catch (error) {
6132
6393
  logger32.error(`Error processing area ${area.name}:`, error);