@7365admin1/module-hygiene 4.29.1-staging.12 → 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 +24 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +74 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +74 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/require-photo-on-completion.test.mjs +101 -120
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7365admin1/module-hygiene",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.30.1",
|
|
5
5
|
"author": "7365admin1",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
14
14
|
"release": "yarn run build && changeset publish",
|
|
15
15
|
"lint": "tsc",
|
|
16
|
-
"test": "tsup src/utils/hygiene-dashboard-metrics.util.ts src/utils/hygiene-checkout-decision.util.ts --format esm --out-dir test/.build --no-dts --silent && node --test \"test/*.test.mjs\""
|
|
16
|
+
"test": "tsup src/utils/hygiene-dashboard-metrics.util.ts src/utils/hygiene-checkout-decision.util.ts src/utils/completion-photo.util.ts --format esm --out-dir test/.build --no-dts --silent && node --test \"test/*.test.mjs\""
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@changesets/cli": "^2.26.0",
|
|
@@ -1,160 +1,141 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* A PHOTO IS REQUIRED TO COMPLETE A TASK.
|
|
2
|
+
* A PHOTO IS REQUIRED TO COMPLETE A TASK — and only to COMPLETE one.
|
|
3
3
|
*
|
|
4
4
|
* Owner requirement, 2026-09-10: "once task was completed they required to take
|
|
5
5
|
* photo before completing the task ... all the services that has this module
|
|
6
6
|
* must be the same process."
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* The first version of this rule sat in the Joi schema and demanded a photo on
|
|
9
|
+
* every approve. A task in this product is a SET, not a unit: both clients tick
|
|
10
|
+
* units one at a time and open the photographs dialog on the tick that finishes
|
|
11
|
+
* the set. So that version broke every intermediate tick on both platforms —
|
|
12
|
+
* the owner hit it on the web M&E checklist, and `meareaunits.tsx` makes the
|
|
13
|
+
* same call with `attachment: []`.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* this file.
|
|
15
|
+
* This exercises the SHIPPED decision function, compiled from source by the
|
|
16
|
+
* test script — not a re-typed copy.
|
|
17
17
|
*/
|
|
18
18
|
import test from "node:test";
|
|
19
19
|
import assert from "node:assert/strict";
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const fn = SRC.slice(SRC.indexOf("async function updateAreaChecklistUnits("));
|
|
31
|
-
const start = fn.indexOf("const validation = Joi.object({");
|
|
32
|
-
assert.notEqual(start, -1, "the validation block is gone");
|
|
33
|
-
|
|
34
|
-
// Walk braces from the opening `{` of Joi.object( ... ) to its match.
|
|
35
|
-
const open = fn.indexOf("{", start + "const validation = Joi.object(".length);
|
|
36
|
-
let depth = 0;
|
|
37
|
-
let end = -1;
|
|
38
|
-
for (let i = open; i < fn.length; i++) {
|
|
39
|
-
if (fn[i] === "{") depth++;
|
|
40
|
-
else if (fn[i] === "}") {
|
|
41
|
-
depth--;
|
|
42
|
-
if (depth === 0) {
|
|
43
|
-
end = i;
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
assert.notEqual(end, -1, "could not read the validation block");
|
|
49
|
-
|
|
50
|
-
const body = fn
|
|
51
|
-
.slice(open, end + 1)
|
|
52
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
53
|
-
.replace(/\/\/[^\n]*/g, "");
|
|
54
|
-
return new Function("Joi", `return Joi.object(${body});`)(Joi);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const base = {
|
|
58
|
-
id: "6923d6664150ca6a69b9f2b2",
|
|
59
|
-
set: 1,
|
|
60
|
-
unit: "69251faf8c8936c9e6ebf095",
|
|
61
|
-
completedBy: "6925a31630abdbb211ecd9a8",
|
|
62
|
-
remarks: "done",
|
|
20
|
+
import {
|
|
21
|
+
COMPLETION_PHOTO_MESSAGE,
|
|
22
|
+
requiresCompletionPhoto,
|
|
23
|
+
} from "./.build/completion-photo.util.mjs";
|
|
24
|
+
|
|
25
|
+
/** Approving a unit that still has un-actioned siblings — the common tick. */
|
|
26
|
+
const midSet = {
|
|
27
|
+
decision: "approve",
|
|
28
|
+
completesSet: false,
|
|
29
|
+
hasEvidence: false,
|
|
63
30
|
};
|
|
64
31
|
|
|
65
|
-
|
|
32
|
+
/** Approving the unit that finishes the set — the act evidence is for. */
|
|
33
|
+
const completing = {
|
|
34
|
+
decision: "approve",
|
|
35
|
+
completesSet: true,
|
|
36
|
+
hasEvidence: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/* ── the regression the owner reported ─────────────────────────────────── */
|
|
40
|
+
|
|
41
|
+
test("ticking a unit mid-set needs NO photo", () => {
|
|
42
|
+
// This is what broke: the web posts it with nothing attached, and
|
|
43
|
+
// `meareaunits.tsx` posts it with `attachment: []`.
|
|
44
|
+
assert.equal(requiresCompletionPhoto(midSet), false);
|
|
45
|
+
assert.equal(requiresCompletionPhoto({ ...midSet, attachment: [] }), false);
|
|
46
|
+
assert.equal(
|
|
47
|
+
requiresCompletionPhoto({ ...midSet, attachment: undefined }),
|
|
48
|
+
false
|
|
49
|
+
);
|
|
50
|
+
});
|
|
66
51
|
|
|
67
|
-
/* ── completing requires evidence
|
|
52
|
+
/* ── completing still requires evidence ────────────────────────────────── */
|
|
68
53
|
|
|
69
|
-
test("
|
|
70
|
-
|
|
71
|
-
assert.ok(error, "a task was completed with no photo");
|
|
72
|
-
assert.match(error.message, /photo is required/i);
|
|
54
|
+
test("completing the set WITHOUT a photo is refused", () => {
|
|
55
|
+
assert.equal(requiresCompletionPhoto(completing), true);
|
|
73
56
|
});
|
|
74
57
|
|
|
75
|
-
test("an EMPTY attachment array is
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
approve: true,
|
|
82
|
-
attachment: [],
|
|
83
|
-
});
|
|
84
|
-
assert.ok(error, "an empty attachment list was accepted as evidence");
|
|
85
|
-
assert.match(error.message, /photo is required/i);
|
|
58
|
+
test("an EMPTY attachment array is not evidence", () => {
|
|
59
|
+
// The shape a client sends when its uploader failed.
|
|
60
|
+
assert.equal(
|
|
61
|
+
requiresCompletionPhoto({ ...completing, attachment: [] }),
|
|
62
|
+
true
|
|
63
|
+
);
|
|
86
64
|
});
|
|
87
65
|
|
|
88
66
|
test("a BLANK id is not a photo", () => {
|
|
89
|
-
|
|
90
|
-
...
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
67
|
+
assert.equal(
|
|
68
|
+
requiresCompletionPhoto({ ...completing, attachment: ["", " "] }),
|
|
69
|
+
true
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("completing WITH a photo is allowed", () => {
|
|
74
|
+
assert.equal(
|
|
75
|
+
requiresCompletionPhoto({
|
|
76
|
+
...completing,
|
|
77
|
+
attachment: ["68f05fbe0c11062938dadad9"],
|
|
78
|
+
}),
|
|
79
|
+
false
|
|
80
|
+
);
|
|
96
81
|
});
|
|
97
82
|
|
|
98
|
-
test("
|
|
99
|
-
|
|
100
|
-
...
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
attachment: ["68f05fbe0c11062938dadad9"],
|
|
104
|
-
});
|
|
105
|
-
assert.equal(error, undefined, error?.message);
|
|
83
|
+
test("MULTIPLE photos are allowed — that is the point", () => {
|
|
84
|
+
assert.equal(
|
|
85
|
+
requiresCompletionPhoto({ ...completing, attachment: ["a1", "b2", "c3"] }),
|
|
86
|
+
false
|
|
87
|
+
);
|
|
106
88
|
});
|
|
107
89
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
90
|
+
/* ── the web posts a set's approves in a loop ──────────────────────────── */
|
|
91
|
+
|
|
92
|
+
test("a set already carrying a photo on another unit is not blocked", () => {
|
|
93
|
+
/*
|
|
94
|
+
* `ManageChecklistMain` loops over the set's approved items and puts the ids
|
|
95
|
+
* on only ONE of them, so the approve that completes the set is not reliably
|
|
96
|
+
* the one holding the evidence. Asking only "does THIS request have a photo"
|
|
97
|
+
* would reject a set that is in fact fully documented.
|
|
98
|
+
*/
|
|
99
|
+
assert.equal(
|
|
100
|
+
requiresCompletionPhoto({ ...completing, hasEvidence: true }),
|
|
101
|
+
false
|
|
102
|
+
);
|
|
103
|
+
assert.equal(
|
|
104
|
+
requiresCompletionPhoto({
|
|
105
|
+
...completing,
|
|
106
|
+
hasEvidence: true,
|
|
107
|
+
attachment: [],
|
|
108
|
+
}),
|
|
109
|
+
false
|
|
110
|
+
);
|
|
116
111
|
});
|
|
117
112
|
|
|
118
113
|
/* ── rejecting must NOT be blocked ─────────────────────────────────────── */
|
|
119
114
|
|
|
120
|
-
test("REJECTING
|
|
115
|
+
test("REJECTING is never blocked, even completing the set", () => {
|
|
121
116
|
/*
|
|
122
117
|
* A reject is the technician reporting a problem. Demanding a photograph
|
|
123
118
|
* before somebody can report one would stop them reporting it.
|
|
124
119
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
reject: true,
|
|
134
|
-
attachment: ["a1", "b2"],
|
|
135
|
-
});
|
|
136
|
-
assert.equal(error, undefined, error?.message);
|
|
120
|
+
assert.equal(
|
|
121
|
+
requiresCompletionPhoto({
|
|
122
|
+
decision: "reject",
|
|
123
|
+
completesSet: true,
|
|
124
|
+
hasEvidence: false,
|
|
125
|
+
}),
|
|
126
|
+
false
|
|
127
|
+
);
|
|
137
128
|
});
|
|
138
129
|
|
|
139
130
|
/* ── controls ──────────────────────────────────────────────────────────── */
|
|
140
131
|
|
|
141
|
-
test("CONTROL: the
|
|
142
|
-
// Without this,
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
const payload = {
|
|
146
|
-
...base,
|
|
147
|
-
decision: "approve",
|
|
148
|
-
approve: true,
|
|
149
|
-
attachment: ["a1"],
|
|
150
|
-
};
|
|
151
|
-
delete payload[missing];
|
|
152
|
-
assert.ok(validate(payload).error, `${missing} is no longer required`);
|
|
153
|
-
}
|
|
132
|
+
test("CONTROL: the rule can actually say yes", () => {
|
|
133
|
+
// Without this, every assertion above could be passing against a function
|
|
134
|
+
// that returns false unconditionally.
|
|
135
|
+
assert.equal(requiresCompletionPhoto(completing), true);
|
|
154
136
|
});
|
|
155
137
|
|
|
156
138
|
test("CONTROL: the message names the action a person must take", () => {
|
|
157
139
|
// "Invalid payload" tells a technician standing in a plant room nothing.
|
|
158
|
-
|
|
159
|
-
assert.match(error.message, /take or upload at least one photo/i);
|
|
140
|
+
assert.match(COMPLETION_PHOTO_MESSAGE, /take or upload at least one photo/i);
|
|
160
141
|
});
|