@7365admin1/module-hygiene 4.30.0 → 4.30.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @7365admin1/module-hygiene
2
2
 
3
+ ## 4.30.1
4
+
5
+ ### Patch Changes
6
+
7
+ - b4ed69f: Require the completion photo only on the approve that FINISHES a set, not on
8
+ every unit tick. A task in this product is a set: both the web checklist and the
9
+ five field apps tick units one at a time and open the photographs dialog on the
10
+ tick that completes the set. The previous rule demanded a photo on every
11
+ approve, which refused every intermediate tick on both platforms.
12
+
3
13
  ## 4.30.0
4
14
 
5
15
  ### Minor Changes
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
@@ -3359,7 +3359,11 @@ function useAreaChecklistRepo() {
3359
3359
  const parentChecklistId = new import_mongodb9.ObjectId(value.schedule);
3360
3360
  const areaId = new import_mongodb9.ObjectId(value.area);
3361
3361
  const existingChecklist = await collection.findOne(
3362
- { schedule: parentChecklistId, area: areaId, serviceType: value.serviceType },
3362
+ {
3363
+ schedule: parentChecklistId,
3364
+ area: areaId,
3365
+ serviceType: value.serviceType
3366
+ },
3363
3367
  { session }
3364
3368
  );
3365
3369
  if (existingChecklist) {
@@ -4115,6 +4119,32 @@ function useAreaChecklistRepo() {
4115
4119
  throw error;
4116
4120
  }
4117
4121
  }
4122
+ async function readSetCompletionState(_id, set, unitId) {
4123
+ try {
4124
+ _id = new import_mongodb9.ObjectId(_id);
4125
+ } catch (error) {
4126
+ throw new import_node_server_utils17.BadRequestError("Invalid area checklist ID format.");
4127
+ }
4128
+ const doc = await collection.findOne(
4129
+ { _id, "checklist.set": set },
4130
+ { projection: { checklist: 1 } }
4131
+ );
4132
+ const group = (doc?.checklist ?? []).find(
4133
+ (entry) => entry.set === set
4134
+ );
4135
+ const units = group?.units ?? [];
4136
+ if (units.length === 0)
4137
+ return { completesSet: false, hasEvidence: false };
4138
+ const others = units.filter(
4139
+ (unit) => String(unit.unit) !== String(unitId)
4140
+ );
4141
+ return {
4142
+ completesSet: others.every((unit) => unit.status === "completed"),
4143
+ hasEvidence: others.some(
4144
+ (unit) => Array.isArray(unit.attachment) && unit.attachment.length > 0
4145
+ )
4146
+ };
4147
+ }
4118
4148
  async function updateAreaChecklist(_id, value, session) {
4119
4149
  try {
4120
4150
  _id = new import_mongodb9.ObjectId(_id);
@@ -4399,6 +4429,7 @@ function useAreaChecklistRepo() {
4399
4429
  updateAreaChecklist,
4400
4430
  updateAreaChecklistStatus,
4401
4431
  updateAreaChecklistUnits,
4432
+ readSetCompletionState,
4402
4433
  getMaxSetNumberForArea,
4403
4434
  closeExpiredAreaChecklists,
4404
4435
  pushScheduleTaskSets,
@@ -4650,6 +4681,24 @@ var import_joi9 = __toESM(require("joi"));
4650
4681
  var import_node_server_utils20 = require("@7365admin1/node-server-utils");
4651
4682
  var import_core11 = require("@7365admin1/core");
4652
4683
 
4684
+ // src/utils/completion-photo.util.ts
4685
+ var COMPLETION_PHOTO_MESSAGE = "A photo is required to complete this task. Take or upload at least one photo before marking it complete.";
4686
+ function requiresCompletionPhoto({
4687
+ decision,
4688
+ attachment,
4689
+ completesSet,
4690
+ hasEvidence
4691
+ }) {
4692
+ if (decision !== "approve")
4693
+ return false;
4694
+ if (!completesSet)
4695
+ return false;
4696
+ if (hasEvidence)
4697
+ return false;
4698
+ const carriesEvidence = Array.isArray(attachment) && attachment.some((id) => typeof id === "string" && id.trim().length > 0);
4699
+ return !carriesEvidence;
4700
+ }
4701
+
4653
4702
  // src/services/hygiene-checklist-pdf.service.ts
4654
4703
  var import_puppeteer = require("puppeteer");
4655
4704
  var import_node_server_utils19 = require("@7365admin1/node-server-utils");
@@ -4870,7 +4919,8 @@ function useAreaChecklistController() {
4870
4919
  getAllAreaChecklist: _getAllAreaChecklist,
4871
4920
  getAreaChecklistHistory: _getAreaChecklistHistory,
4872
4921
  getAreaChecklistHistoryDetails: _getAreaChecklistHistoryDetails,
4873
- getAreaChecklistUnits: _getAreaChecklistUnits
4922
+ getAreaChecklistUnits: _getAreaChecklistUnits,
4923
+ readSetCompletionState: _readSetCompletionState
4874
4924
  } = useAreaChecklistRepo();
4875
4925
  const {
4876
4926
  createAreaChecklist: _createAreaChecklist,
@@ -5075,36 +5125,11 @@ function useAreaChecklistController() {
5075
5125
  reject: import_joi9.default.boolean().optional(),
5076
5126
  remarks: import_joi9.default.string().optional().allow("", null),
5077
5127
  /*
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.
5128
+ * Shape only. WHETHER a photo is required depends on the set's current
5129
+ * state in the database, which Joi cannot see — see the check below.
5130
+ * Each entry must still be a real id: a blank string carries no evidence.
5099
5131
  */
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
- }),
5132
+ attachment: import_joi9.default.array().items(import_joi9.default.string().trim().min(1)).optional(),
5108
5133
  completedBy: import_joi9.default.string().hex().required()
5109
5134
  });
5110
5135
  const { error } = validation.validate(payload);
@@ -5115,7 +5140,24 @@ function useAreaChecklistController() {
5115
5140
  }
5116
5141
  try {
5117
5142
  const { id, set, unit, decision: decision2, ...value } = payload;
5118
- await _updateAreaChecklistUnits(id, parseInt(set), unit, value);
5143
+ const setNumber = parseInt(set);
5144
+ if (decision2 === "approve") {
5145
+ const { completesSet, hasEvidence } = await _readSetCompletionState(
5146
+ id,
5147
+ setNumber,
5148
+ unit
5149
+ );
5150
+ if (requiresCompletionPhoto({
5151
+ decision: decision2,
5152
+ attachment: value.attachment,
5153
+ completesSet,
5154
+ hasEvidence
5155
+ })) {
5156
+ next(new import_node_server_utils20.BadRequestError(COMPLETION_PHOTO_MESSAGE));
5157
+ return;
5158
+ }
5159
+ }
5160
+ await _updateAreaChecklistUnits(id, setNumber, unit, value);
5119
5161
  res.json({ message: "Area checklist updated successfully." });
5120
5162
  return;
5121
5163
  } catch (error2) {