@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 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
- GET /api/members/user/:user/app/admin the Seven365 staff membership
2973
- GET /api/roles/id/:role that membership's role document
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
- owner = member.type === "admin" && role.type === "admin" && role.default === true
2976
- staff = member.type === "admin" && role.type === "admin"
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
- preview.value = await previewProviderChange(body());
227
- replacements.value = preview.value?.replacements ?? [];
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.0",
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.",
@@ -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
- const forms = UNIT_FORMS[String(unit ?? "").trim().toLowerCase()];
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)". */