@7365admin1/module-hygiene 4.29.1-staging.12 → 4.29.1-staging.13

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.
@@ -0,0 +1,9 @@
1
+ ---
2
+ "@7365admin1/module-hygiene": patch
3
+ ---
4
+
5
+ Decide which approve finishes a checklist set from approve/reject, the same rule
6
+ the units list uses, instead of the unit `status` field. Units at rest carry
7
+ "open" or "closed", so the old check never saw a set as finished: a set holding a
8
+ rejected unit could not be completed, and the finishing tick was not recognised.
9
+ No change to when a photo is required, and no schema or data change.
@@ -0,0 +1,9 @@
1
+ ---
2
+ "@7365admin1/module-hygiene": patch
3
+ ---
4
+
5
+ Require the completion photo only on the approve that FINISHES a set, not on
6
+ every unit tick. A task in this product is a set: both the web checklist and the
7
+ five field apps tick units one at a time and open the photographs dialog on the
8
+ tick that completes the set. The previous rule demanded a photo on every
9
+ approve, which refused every intermediate tick on both platforms.
package/dist/index.d.ts CHANGED
@@ -388,6 +388,10 @@ declare function useAreaChecklistRepo(): {
388
388
  updateAreaChecklist: (_id: string | ObjectId, value: TAreaChecklistUpdate, session?: ClientSession) => Promise<number>;
389
389
  updateAreaChecklistStatus: (_id: string | ObjectId, status: (typeof allowedStatus)[number], session?: ClientSession) => Promise<number>;
390
390
  updateAreaChecklistUnits: (_id: string | ObjectId, set: number, unitId: string | ObjectId, value: TAreaChecklistUnitsUpdate, session?: ClientSession) => Promise<number>;
391
+ readSetCompletionState: (_id: string | ObjectId, set: number, unitId: string) => Promise<{
392
+ completesSet: boolean;
393
+ hasEvidence: boolean;
394
+ }>;
391
395
  getMaxSetNumberForArea: (areaId: string | ObjectId, session?: ClientSession) => Promise<any>;
392
396
  closeExpiredAreaChecklists: () => Promise<number>;
393
397
  pushScheduleTaskSets: (scheduleId: string | ObjectId, serviceType: TAppServiceType, areaId: string | ObjectId, scheduleTaskId: string | ObjectId, newSets: TAreaChecklist[]) => Promise<number>;
package/dist/index.js CHANGED
@@ -3288,6 +3288,39 @@ var import_core10 = require("@7365admin1/core");
3288
3288
  // src/repositories/hygiene-area-checklist.repository.ts
3289
3289
  var import_mongodb9 = require("mongodb");
3290
3290
  var import_node_server_utils17 = require("@7365admin1/node-server-utils");
3291
+
3292
+ // src/utils/completion-photo.util.ts
3293
+ var COMPLETION_PHOTO_MESSAGE = "A photo is required to complete this task. Take or upload at least one photo before marking it complete.";
3294
+ function readCompletion(units, unitId) {
3295
+ if (units.length === 0)
3296
+ return { completesSet: false, hasEvidence: false };
3297
+ const others = units.filter((unit) => String(unit.unit) !== String(unitId));
3298
+ return {
3299
+ completesSet: others.every(
3300
+ (unit) => unit.approve === true || unit.reject === true
3301
+ ),
3302
+ hasEvidence: others.some(
3303
+ (unit) => Array.isArray(unit.attachment) && unit.attachment.length > 0
3304
+ )
3305
+ };
3306
+ }
3307
+ function requiresCompletionPhoto({
3308
+ decision,
3309
+ attachment,
3310
+ completesSet,
3311
+ hasEvidence
3312
+ }) {
3313
+ if (decision !== "approve")
3314
+ return false;
3315
+ if (!completesSet)
3316
+ return false;
3317
+ if (hasEvidence)
3318
+ return false;
3319
+ const carriesEvidence = Array.isArray(attachment) && attachment.some((id) => typeof id === "string" && id.trim().length > 0);
3320
+ return !carriesEvidence;
3321
+ }
3322
+
3323
+ // src/repositories/hygiene-area-checklist.repository.ts
3291
3324
  function useAreaChecklistRepo() {
3292
3325
  const db = import_node_server_utils17.useAtlas.getDb();
3293
3326
  if (!db) {
@@ -3359,7 +3392,11 @@ function useAreaChecklistRepo() {
3359
3392
  const parentChecklistId = new import_mongodb9.ObjectId(value.schedule);
3360
3393
  const areaId = new import_mongodb9.ObjectId(value.area);
3361
3394
  const existingChecklist = await collection.findOne(
3362
- { schedule: parentChecklistId, area: areaId, serviceType: value.serviceType },
3395
+ {
3396
+ schedule: parentChecklistId,
3397
+ area: areaId,
3398
+ serviceType: value.serviceType
3399
+ },
3363
3400
  { session }
3364
3401
  );
3365
3402
  if (existingChecklist) {
@@ -4115,6 +4152,21 @@ function useAreaChecklistRepo() {
4115
4152
  throw error;
4116
4153
  }
4117
4154
  }
4155
+ async function readSetCompletionState(_id, set, unitId) {
4156
+ try {
4157
+ _id = new import_mongodb9.ObjectId(_id);
4158
+ } catch (error) {
4159
+ throw new import_node_server_utils17.BadRequestError("Invalid area checklist ID format.");
4160
+ }
4161
+ const doc = await collection.findOne(
4162
+ { _id, "checklist.set": set },
4163
+ { projection: { checklist: 1 } }
4164
+ );
4165
+ const group = (doc?.checklist ?? []).find(
4166
+ (entry) => entry.set === set
4167
+ );
4168
+ return readCompletion(group?.units ?? [], unitId);
4169
+ }
4118
4170
  async function updateAreaChecklist(_id, value, session) {
4119
4171
  try {
4120
4172
  _id = new import_mongodb9.ObjectId(_id);
@@ -4399,6 +4451,7 @@ function useAreaChecklistRepo() {
4399
4451
  updateAreaChecklist,
4400
4452
  updateAreaChecklistStatus,
4401
4453
  updateAreaChecklistUnits,
4454
+ readSetCompletionState,
4402
4455
  getMaxSetNumberForArea,
4403
4456
  closeExpiredAreaChecklists,
4404
4457
  pushScheduleTaskSets,
@@ -4870,7 +4923,8 @@ function useAreaChecklistController() {
4870
4923
  getAllAreaChecklist: _getAllAreaChecklist,
4871
4924
  getAreaChecklistHistory: _getAreaChecklistHistory,
4872
4925
  getAreaChecklistHistoryDetails: _getAreaChecklistHistoryDetails,
4873
- getAreaChecklistUnits: _getAreaChecklistUnits
4926
+ getAreaChecklistUnits: _getAreaChecklistUnits,
4927
+ readSetCompletionState: _readSetCompletionState
4874
4928
  } = useAreaChecklistRepo();
4875
4929
  const {
4876
4930
  createAreaChecklist: _createAreaChecklist,
@@ -5075,36 +5129,11 @@ function useAreaChecklistController() {
5075
5129
  reject: import_joi9.default.boolean().optional(),
5076
5130
  remarks: import_joi9.default.string().optional().allow("", null),
5077
5131
  /*
5078
- * A PHOTO IS REQUIRED TO COMPLETE A TASK.
5079
- *
5080
- * Owner requirement, 2026-09-10: *"once task was completed they required
5081
- * to take photo before completing the task ... all the services that has
5082
- * this module must be the same process."*
5083
- *
5084
- * This endpoint is the one place every service and every client meets:
5085
- * the five field apps (M&E, Cleaning, Landscape, Pest, Pool) all PATCH
5086
- * `/api/hygiene-area-checklist/...` with a `serviceType`, and the web
5087
- * checklist (`layer-common ManageChecklistMain`) posts here too. Requiring
5088
- * it here is what makes "the same process" true rather than five client
5089
- * guards that can drift apart — and a client guard alone is a suggestion.
5090
- *
5091
- * ONLY ON APPROVE. Completing a unit is the act the evidence is for.
5092
- * A REJECT is the technician reporting a problem, and demanding a
5093
- * photograph before somebody can report one would stop them reporting it.
5094
- * `decision` carries which is which.
5095
- *
5096
- * At least one entry, and each must be a real id — an empty array or a
5097
- * blank string would satisfy `.optional()` while carrying no evidence at
5098
- * all, which is the shape a client sends when its uploader failed.
5132
+ * Shape only. WHETHER a photo is required depends on the set's current
5133
+ * state in the database, which Joi cannot see — see the check below.
5134
+ * Each entry must still be a real id: a blank string carries no evidence.
5099
5135
  */
5100
- attachment: import_joi9.default.array().items(import_joi9.default.string().trim().min(1)).when("decision", {
5101
- is: "approve",
5102
- then: import_joi9.default.array().min(1).required().messages({
5103
- "array.min": "A photo is required to complete this task. Take or upload at least one photo before marking it complete.",
5104
- "any.required": "A photo is required to complete this task. Take or upload at least one photo before marking it complete."
5105
- }),
5106
- otherwise: import_joi9.default.array().optional()
5107
- }),
5136
+ attachment: import_joi9.default.array().items(import_joi9.default.string().trim().min(1)).optional(),
5108
5137
  completedBy: import_joi9.default.string().hex().required()
5109
5138
  });
5110
5139
  const { error } = validation.validate(payload);
@@ -5115,7 +5144,24 @@ function useAreaChecklistController() {
5115
5144
  }
5116
5145
  try {
5117
5146
  const { id, set, unit, decision: decision2, ...value } = payload;
5118
- await _updateAreaChecklistUnits(id, parseInt(set), unit, value);
5147
+ const setNumber = parseInt(set);
5148
+ if (decision2 === "approve") {
5149
+ const { completesSet, hasEvidence } = await _readSetCompletionState(
5150
+ id,
5151
+ setNumber,
5152
+ unit
5153
+ );
5154
+ if (requiresCompletionPhoto({
5155
+ decision: decision2,
5156
+ attachment: value.attachment,
5157
+ completesSet,
5158
+ hasEvidence
5159
+ })) {
5160
+ next(new import_node_server_utils20.BadRequestError(COMPLETION_PHOTO_MESSAGE));
5161
+ return;
5162
+ }
5163
+ }
5164
+ await _updateAreaChecklistUnits(id, setNumber, unit, value);
5119
5165
  res.json({ message: "Area checklist updated successfully." });
5120
5166
  return;
5121
5167
  } catch (error2) {