@7365admin1/layer-common 4.99.0 → 4.99.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 +10 -4
- package/components/InventoryItemDetail.vue +2 -2
- package/components/InventoryLinesDialog.vue +2 -2
- package/components/InventoryMyStockTab.vue +3 -3
- package/components/InventoryUsageDialog.vue +2 -2
- package/components/InventoryWriteOffDialog.vue +2 -2
- package/components/SiteProviderChange.vue +16 -3
- package/package.json +1 -1
- package/utils/inventory-ack.ts +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.99.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1e71b4c: Provider change dialog: only the newest preview is shown (typing a date could leave an older "Straight away" preview on screen), and "On a date" without a date asks for the date. Inventory quantity boxes show "1 pc" / "2 pcs" (the suffix follows the number).
|
|
8
|
+
|
|
3
9
|
## 4.99.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -2969,11 +2975,11 @@
|
|
|
2969
2975
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
2970
2976
|
own rule from two endpoints the console already calls, unprojected:
|
|
2971
2977
|
|
|
2972
|
-
|
|
2973
|
-
|
|
2978
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
2979
|
+
GET /api/roles/id/:role that membership's role document
|
|
2974
2980
|
|
|
2975
|
-
|
|
2976
|
-
|
|
2981
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
2982
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
2977
2983
|
|
|
2978
2984
|
`role.default` is the marker because it is the only property of a platform
|
|
2979
2985
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<AppCard class="pa-4">
|
|
41
41
|
<div class="text-subtitle-2 mb-2">How many are on the shelf right now?</div>
|
|
42
42
|
<div class="d-flex flex-wrap ga-2">
|
|
43
|
-
<v-text-field v-model.number="counted" type="number" min="0" label="Counted" :suffix="item.unit" density="comfortable" style="max-width: 160px" :rules="[(v: any) => (Number.isInteger(Number(v)) && Number(v) >= 0) || 'Enter a whole number']" />
|
|
43
|
+
<v-text-field v-model.number="counted" type="number" min="0" label="Counted" :suffix="unitWord(counted, item.unit)" density="comfortable" style="max-width: 160px" :rules="[(v: any) => (Number.isInteger(Number(v)) && Number(v) >= 0) || 'Enter a whole number']" />
|
|
44
44
|
<v-text-field v-model="reason" label="Reason for the difference" density="comfortable" class="flex-grow-1" :rules="[requiredRule]" placeholder="e.g. 2 broken, found extra box" />
|
|
45
45
|
<v-btn class="text-none screen-btn-primary" variant="flat" :loading="saving" @click="saveCount">Save count</v-btn>
|
|
46
46
|
</div>
|
|
@@ -93,7 +93,7 @@ import useUtils from "../composables/useUtils";
|
|
|
93
93
|
import { inventoryItemMeta } from "../utils/inventory-store-label";
|
|
94
94
|
import { inventoryControls } from "../utils/inventory-presets";
|
|
95
95
|
import { receiptRefText } from "../utils/inventory-receiving";
|
|
96
|
-
import { availableText, inventoryStepsLive, lowStockAlertText, qtyUnit, writeOffLabel } from "../utils/inventory-ack";
|
|
96
|
+
import { availableText, inventoryStepsLive, lowStockAlertText, qtyUnit, unitWord, writeOffLabel } from "../utils/inventory-ack";
|
|
97
97
|
|
|
98
98
|
const show = defineModel<boolean>({ default: false });
|
|
99
99
|
const props = defineProps({
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
density="comfortable"
|
|
72
72
|
style="max-width: 110px"
|
|
73
73
|
:rules="[qtyRule(line)]"
|
|
74
|
-
:suffix="unitOf(line.item)"
|
|
74
|
+
:suffix="unitWord(line.qty, unitOf(line.item))"
|
|
75
75
|
/>
|
|
76
76
|
<v-text-field
|
|
77
77
|
v-if="mode === 'receive' && receivingType !== 'CASH'"
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
import useInventory, { pickedDate } from "../composables/useInventory";
|
|
172
172
|
import useUtils from "../composables/useUtils";
|
|
173
173
|
import { normaliseUsedBy, usedByChips, usedByText } from "../utils/inventory-used-by";
|
|
174
|
-
import { availableQty, availableText as availableTextOf, qtyUnit } from "../utils/inventory-ack";
|
|
174
|
+
import { availableQty, availableText as availableTextOf, qtyUnit, unitWord } from "../utils/inventory-ack";
|
|
175
175
|
import { RECEIVING_TYPES, receivingFields, receivingProblem, type TReceivingType } from "../utils/inventory-receiving";
|
|
176
176
|
|
|
177
177
|
type TMode = "receive" | "issue" | "request" | "return";
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
type="number"
|
|
171
171
|
min="1"
|
|
172
172
|
density="comfortable"
|
|
173
|
-
:suffix="give.holding?.unit"
|
|
173
|
+
:suffix="unitWord(give.qty, give.holding?.unit)"
|
|
174
174
|
:rules="[giveQtyRule]"
|
|
175
175
|
hint="Counted in the unit the store issues it in."
|
|
176
176
|
persistent-hint
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
type="number"
|
|
202
202
|
min="1"
|
|
203
203
|
density="comfortable"
|
|
204
|
-
:suffix="ret.holding.unit"
|
|
204
|
+
:suffix="unitWord(ret.qty, ret.holding.unit)"
|
|
205
205
|
style="max-width: 200px"
|
|
206
206
|
:rules="[retQtyRule]"
|
|
207
207
|
/>
|
|
@@ -237,7 +237,7 @@ import useUtils from "../composables/useUtils";
|
|
|
237
237
|
import useLocalAuth from "../composables/useLocalAuth";
|
|
238
238
|
import { inventoryControls } from "../utils/inventory-presets";
|
|
239
239
|
import { colleagueDraftText, draftCardText } from "../utils/inventory-usage";
|
|
240
|
-
import { ackText, holdText, inventoryStepsLive, qtyUnit } from "../utils/inventory-ack";
|
|
240
|
+
import { ackText, holdText, inventoryStepsLive, qtyUnit, unitWord } from "../utils/inventory-ack";
|
|
241
241
|
|
|
242
242
|
const props = defineProps({
|
|
243
243
|
store: { type: Object as PropType<TInventoryStore>, required: true },
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
type="number"
|
|
23
23
|
min="1"
|
|
24
24
|
:max="maxQty"
|
|
25
|
-
:suffix="holding.unit"
|
|
25
|
+
:suffix="unitWord(qty, holding.unit)"
|
|
26
26
|
density="comfortable"
|
|
27
27
|
style="max-width: 200px"
|
|
28
28
|
:rules="[(v: any) => (Number.isInteger(Number(v)) && Number(v) >= 1 && Number(v) <= maxQty) || `1 to ${maxQty}`]"
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
<script setup lang="ts">
|
|
135
135
|
import useInventory, { pickedDate } from "../composables/useInventory";
|
|
136
136
|
import useUtils from "../composables/useUtils";
|
|
137
|
-
import { inventoryStepsLive, qtyUnit } from "../utils/inventory-ack";
|
|
137
|
+
import { inventoryStepsLive, qtyUnit, unitWord } from "../utils/inventory-ack";
|
|
138
138
|
|
|
139
139
|
const show = defineModel<boolean>({ default: false });
|
|
140
140
|
const props = defineProps({
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
min="1"
|
|
24
24
|
:max="max"
|
|
25
25
|
density="comfortable"
|
|
26
|
-
:suffix="unit"
|
|
26
|
+
:suffix="unitWord(qty, unit)"
|
|
27
27
|
style="max-width: 200px"
|
|
28
28
|
:rules="[(v: any) => (Number.isInteger(Number(v)) && Number(v) >= 1) || 'Enter a whole number', (v: any) => Number(v) <= max || `You can write off up to ${max}`]"
|
|
29
29
|
/>
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
<script setup lang="ts">
|
|
50
50
|
import useInventory from "../composables/useInventory";
|
|
51
|
-
import { qtyUnit, WRITE_OFF_REASONS, writeOffDefaultReason, writeOffLabel, writeOffProblem } from "../utils/inventory-ack";
|
|
51
|
+
import { qtyUnit, unitWord, WRITE_OFF_REASONS, writeOffDefaultReason, writeOffLabel, writeOffProblem } from "../utils/inventory-ack";
|
|
52
52
|
|
|
53
53
|
/** Write-off (owner 2026-10-03): from the person's own stock (`holding`) or, for a store keeper, from the shelf (`store`). */
|
|
54
54
|
const show = defineModel<boolean>({ default: false });
|
|
@@ -218,18 +218,31 @@ function body() {
|
|
|
218
218
|
};
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
/** Only the newest preview may land: typing a date fires several, and they can answer out of order. */
|
|
222
|
+
let previewSeq = 0;
|
|
221
223
|
async function refresh() {
|
|
222
224
|
if (!from.value) return;
|
|
225
|
+
const mine = ++previewSeq;
|
|
226
|
+
// "On a date" without a date would preview as "Straight away" - ask for the date instead.
|
|
227
|
+
if (when.value === "date" && !endDate.value) {
|
|
228
|
+
preview.value = null;
|
|
229
|
+
loading.value = false;
|
|
230
|
+
error.value = "Pick the date for this change.";
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
223
233
|
loading.value = true;
|
|
224
234
|
error.value = "";
|
|
225
235
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
const res = await previewProviderChange(body());
|
|
237
|
+
if (mine !== previewSeq) return;
|
|
238
|
+
preview.value = res;
|
|
239
|
+
replacements.value = res?.replacements ?? [];
|
|
228
240
|
} catch (e: any) {
|
|
241
|
+
if (mine !== previewSeq) return;
|
|
229
242
|
preview.value = null;
|
|
230
243
|
error.value = message(e);
|
|
231
244
|
} finally {
|
|
232
|
-
loading.value = false;
|
|
245
|
+
if (mine === previewSeq) loading.value = false;
|
|
233
246
|
}
|
|
234
247
|
}
|
|
235
248
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "4.99.
|
|
5
|
+
"version": "4.99.1",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|
package/utils/inventory-ack.ts
CHANGED
|
@@ -20,11 +20,15 @@ const UNIT_FORMS: Record<string, [string, string]> = {
|
|
|
20
20
|
metre: ["metre", "metres"], metres: ["metre", "metres"],
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
/** The unit word for a quantity: "pc" for 1, "pcs" otherwise; kg and unknown units unchanged. */
|
|
24
|
+
export function unitWord(qty: number | string | null | undefined, unit?: string | null) {
|
|
25
|
+
const forms = UNIT_FORMS[String(unit ?? "").trim().toLowerCase()];
|
|
26
|
+
return forms ? forms[Math.abs(Number(qty)) === 1 ? 0 : 1] : (unit ?? "");
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
/** "1 pc", "2 pcs", "1 box", "3 boxes"; kg and unknown units unchanged. */
|
|
24
30
|
export function qtyUnit(qty: number | string | null | undefined, unit?: string | null) {
|
|
25
|
-
|
|
26
|
-
const word = forms ? forms[Math.abs(Number(qty)) === 1 ? 0 : 1] : (unit ?? "");
|
|
27
|
-
return `${qty ?? ""} ${word}`.trim();
|
|
31
|
+
return `${qty ?? ""} ${unitWord(qty, unit)}`.trim();
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
/** "you hold 2 pcs" or, while some still wait for Received, "you hold 2 pcs (2 waiting for you to tap Received)". */
|