@7365admin1/module-hygiene 4.28.1 → 4.28.2-staging.11
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/.changeset/hygiene-checkout-approval.md +5 -0
- package/.changeset/hygiene-checkout-stock-return.md +5 -0
- package/dist/index.d.ts +59 -2
- package/dist/index.js +176 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +171 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/test/hygiene-checkout-decision.util.test.mjs +168 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7365admin1/module-hygiene",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "4.28.
|
|
4
|
+
"version": "4.28.2-staging.11",
|
|
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 --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 --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",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"typescript": "^4.9.4"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@7365admin1/core": "^3.
|
|
30
|
+
"@7365admin1/core": "^3.50.1",
|
|
31
31
|
"@7365admin1/node-server-utils": "^1.6.0",
|
|
32
32
|
"@types/qrcode": "^1.5.6",
|
|
33
33
|
"@types/urllib": "^2.33.0",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CHECKOUT_DECIDABLE_STATUS,
|
|
6
|
+
CHECKOUT_DECISIONS,
|
|
7
|
+
applyCheckOutDecision,
|
|
8
|
+
buildCheckOutDecision,
|
|
9
|
+
isCheckOutDecision,
|
|
10
|
+
} from "./.build/hygiene-checkout-decision.util.mjs";
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
The check out item approval decision, without a database.
|
|
14
|
+
|
|
15
|
+
These assertions pin the two things the repository relies on: the `$set` the
|
|
16
|
+
decision produces, and the status the update must MATCH on. If the second one
|
|
17
|
+
is ever dropped, approving an already-decided item stops being a refusal and
|
|
18
|
+
starts silently overwriting the first decision — which is the failure the
|
|
19
|
+
match filter exists to prevent.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const NOW = "2026-09-04T09:00:00.000Z";
|
|
23
|
+
|
|
24
|
+
test("approving flips the row to completed and records approve, not reject", () => {
|
|
25
|
+
const set = buildCheckOutDecision("approve", null, NOW);
|
|
26
|
+
|
|
27
|
+
assert.equal(set.status, "completed");
|
|
28
|
+
assert.equal(set.approve, true);
|
|
29
|
+
assert.equal(set.reject, false);
|
|
30
|
+
assert.equal(set.updatedAt, NOW);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("disapproving also terminates the row, and keeps the remarks", () => {
|
|
34
|
+
const set = buildCheckOutDecision("disapprove", "wrong item drawn", NOW);
|
|
35
|
+
|
|
36
|
+
assert.equal(set.status, "completed");
|
|
37
|
+
assert.equal(set.approve, false);
|
|
38
|
+
assert.equal(set.reject, true);
|
|
39
|
+
assert.equal(set.remarks, "wrong item drawn");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("remarks are never undefined — an absent remark is an empty string", () => {
|
|
43
|
+
assert.equal(buildCheckOutDecision("approve", undefined, NOW).remarks, "");
|
|
44
|
+
assert.equal(buildCheckOutDecision("approve", null, NOW).remarks, "");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("no third status value enters the vocabulary", () => {
|
|
48
|
+
// `allowedCheckOutItemStatus` is ["pending", "completed"]. A decision must
|
|
49
|
+
// land inside it, whichever way it goes.
|
|
50
|
+
for (const decision of CHECKOUT_DECISIONS) {
|
|
51
|
+
assert.equal(buildCheckOutDecision(decision, "", NOW).status, "completed");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("only a pending row is decidable", () => {
|
|
56
|
+
assert.equal(CHECKOUT_DECIDABLE_STATUS, "pending");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("the decision vocabulary is exactly the two the client calls", () => {
|
|
60
|
+
assert.deepEqual([...CHECKOUT_DECISIONS], ["approve", "disapprove"]);
|
|
61
|
+
assert.ok(isCheckOutDecision("approve"));
|
|
62
|
+
assert.ok(isCheckOutDecision("disapprove"));
|
|
63
|
+
assert.equal(isCheckOutDecision("reject"), false);
|
|
64
|
+
assert.equal(isCheckOutDecision(""), false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
Returning the drawn-down quantity to stock on a disapproval.
|
|
69
|
+
|
|
70
|
+
A fake stands in for the repository update. It behaves the way the real one
|
|
71
|
+
does: the row is decided once, and every later attempt matches nothing —
|
|
72
|
+
which the repository turns into a 404. These tests exist so that a refused
|
|
73
|
+
decision can never reach the stock call, because a second credit or a second
|
|
74
|
+
deduction is money the client cannot see going wrong.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
function fakeRow() {
|
|
78
|
+
let pending = true;
|
|
79
|
+
const credits = [];
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
credits,
|
|
83
|
+
// Mirrors `{ _id, status: "pending" }`: decides once, then matches nothing.
|
|
84
|
+
decide: async () => {
|
|
85
|
+
if (!pending) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Check out item not found or is no longer pending approval.",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
pending = false;
|
|
91
|
+
return 1;
|
|
92
|
+
},
|
|
93
|
+
returnStock: async () => {
|
|
94
|
+
credits.push(1);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
test("a disapproval returns the quantity to stock exactly once", async () => {
|
|
100
|
+
const row = fakeRow();
|
|
101
|
+
|
|
102
|
+
assert.equal(
|
|
103
|
+
await applyCheckOutDecision("disapprove", row.decide, row.returnStock),
|
|
104
|
+
1,
|
|
105
|
+
);
|
|
106
|
+
assert.deepEqual(row.credits, [1]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("an approval moves no stock", async () => {
|
|
110
|
+
const row = fakeRow();
|
|
111
|
+
|
|
112
|
+
await applyCheckOutDecision("approve", row.decide, row.returnStock);
|
|
113
|
+
|
|
114
|
+
assert.deepEqual(row.credits, []);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("disapproving twice does not credit stock twice", async () => {
|
|
118
|
+
const row = fakeRow();
|
|
119
|
+
|
|
120
|
+
await applyCheckOutDecision("disapprove", row.decide, row.returnStock);
|
|
121
|
+
|
|
122
|
+
await assert.rejects(
|
|
123
|
+
() => applyCheckOutDecision("disapprove", row.decide, row.returnStock),
|
|
124
|
+
/no longer pending/,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
assert.deepEqual(row.credits, [1]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("approving after a disapproval is refused, so nothing is deducted again", async () => {
|
|
131
|
+
const row = fakeRow();
|
|
132
|
+
|
|
133
|
+
await applyCheckOutDecision("disapprove", row.decide, row.returnStock);
|
|
134
|
+
|
|
135
|
+
await assert.rejects(
|
|
136
|
+
() => applyCheckOutDecision("approve", row.decide, row.returnStock),
|
|
137
|
+
/no longer pending/,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
assert.deepEqual(row.credits, [1]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("a decision that matched no row never reaches the stock call", async () => {
|
|
144
|
+
const credits = [];
|
|
145
|
+
|
|
146
|
+
const decided = await applyCheckOutDecision(
|
|
147
|
+
"disapprove",
|
|
148
|
+
async () => 0,
|
|
149
|
+
async () => credits.push(1),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
assert.equal(decided, 0);
|
|
153
|
+
assert.deepEqual(credits, []);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("a failed stock return propagates, so the caller can abort the transaction", async () => {
|
|
157
|
+
await assert.rejects(
|
|
158
|
+
() =>
|
|
159
|
+
applyCheckOutDecision(
|
|
160
|
+
"disapprove",
|
|
161
|
+
async () => 1,
|
|
162
|
+
async () => {
|
|
163
|
+
throw new Error("Supply not found.");
|
|
164
|
+
},
|
|
165
|
+
),
|
|
166
|
+
/Supply not found/,
|
|
167
|
+
);
|
|
168
|
+
});
|