@7365admin1/layer-common 4.61.0 → 4.62.0
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,17 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.62.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a9c14d5: Inventory Record Usage: a before photo and an after photo are required (owner decision 2026-09-27). Save is refused with "Add a before photo and an after photo to record usage." until both are added; the API enforces the same rule.
|
|
8
|
+
|
|
9
|
+
## 4.61.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 598f6c1: Inventory store switcher: when a person can see several stores of the same kind, each is named with its organisation ("Property store · Margaret Ville"); more than three stores are chosen from a list instead of a row of identical buttons.
|
|
14
|
+
|
|
3
15
|
## 4.61.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -1667,11 +1679,11 @@
|
|
|
1667
1679
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
1668
1680
|
own rule from two endpoints the console already calls, unprojected:
|
|
1669
1681
|
|
|
1670
|
-
|
|
1671
|
-
|
|
1682
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
1683
|
+
GET /api/roles/id/:role that membership's role document
|
|
1672
1684
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1685
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
1686
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
1675
1687
|
|
|
1676
1688
|
`role.default` is the marker because it is the only property of a platform
|
|
1677
1689
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -2,8 +2,20 @@
|
|
|
2
2
|
<div class="inventory">
|
|
3
3
|
<PageHeader :title="store ? store.name : 'Inventory'" :subtitle="subtitle">
|
|
4
4
|
<template #actions>
|
|
5
|
+
<!-- Many stores (staff of several organisations at one property): a list, not a row of buttons. -->
|
|
6
|
+
<v-select
|
|
7
|
+
v-if="stores.length > 3"
|
|
8
|
+
v-model="storeId"
|
|
9
|
+
:items="stores"
|
|
10
|
+
:item-title="(s: TInventoryStore) => inventoryStoreLabel(s, stores)"
|
|
11
|
+
item-value="_id"
|
|
12
|
+
label="Store"
|
|
13
|
+
density="compact"
|
|
14
|
+
hide-details
|
|
15
|
+
style="min-width: 300px"
|
|
16
|
+
/>
|
|
5
17
|
<v-btn-toggle
|
|
6
|
-
v-if="stores.length > 1"
|
|
18
|
+
v-else-if="stores.length > 1"
|
|
7
19
|
v-model="storeId"
|
|
8
20
|
mandatory
|
|
9
21
|
density="comfortable"
|
|
@@ -11,7 +23,7 @@
|
|
|
11
23
|
divided
|
|
12
24
|
>
|
|
13
25
|
<v-btn v-for="s in stores" :key="s._id" :value="s._id" class="text-none">
|
|
14
|
-
{{ s
|
|
26
|
+
{{ inventoryStoreLabel(s, stores) }}
|
|
15
27
|
</v-btn>
|
|
16
28
|
</v-btn-toggle>
|
|
17
29
|
</template>
|
|
@@ -102,6 +114,7 @@
|
|
|
102
114
|
import useInventory from "../composables/useInventory";
|
|
103
115
|
import useSite from "../composables/useSite";
|
|
104
116
|
import useUtils from "../composables/useUtils";
|
|
117
|
+
import { inventoryStoreLabel } from "../utils/inventory-store-label";
|
|
105
118
|
|
|
106
119
|
const props = defineProps({
|
|
107
120
|
orgId: { type: String, required: true },
|
|
@@ -144,7 +157,7 @@ const storeSites = computed(() =>
|
|
|
144
157
|
const subtitle = computed(() => {
|
|
145
158
|
if (!store.value) return "";
|
|
146
159
|
if (storeSites.value.length > 1) return `Supplies ${storeSites.value.map((s) => s.name).join(", ")}`;
|
|
147
|
-
return store.value.
|
|
160
|
+
return inventoryStoreLabel(store.value, stores.value);
|
|
148
161
|
});
|
|
149
162
|
|
|
150
163
|
const tabs = computed(() => {
|
|
@@ -72,14 +72,15 @@
|
|
|
72
72
|
|
|
73
73
|
<v-row dense>
|
|
74
74
|
<v-col cols="12" sm="6">
|
|
75
|
-
<InputLabel title="Photo before
|
|
75
|
+
<InputLabel title="Photo before" required class="mb-1" />
|
|
76
76
|
<InventoryPhotoInput v-model="photosBefore" :max="3" />
|
|
77
77
|
</v-col>
|
|
78
78
|
<v-col cols="12" sm="6">
|
|
79
|
-
<InputLabel title="Photo after
|
|
79
|
+
<InputLabel title="Photo after" required class="mb-1" />
|
|
80
80
|
<InventoryPhotoInput v-model="photosAfter" :max="3" />
|
|
81
81
|
</v-col>
|
|
82
82
|
</v-row>
|
|
83
|
+
<div v-if="photoError" class="text-error text-body-2 mt-1" role="alert">{{ photoError }}</div>
|
|
83
84
|
|
|
84
85
|
<InputLabel title="Date used" class="mb-1 mt-3" />
|
|
85
86
|
<v-text-field v-model="date" type="date" :max="today" density="comfortable" style="max-width: 200px" />
|
|
@@ -128,6 +129,8 @@ const reportedBy = ref("");
|
|
|
128
129
|
const date = ref(today);
|
|
129
130
|
const photosBefore = ref<string[]>([]);
|
|
130
131
|
const photosAfter = ref<string[]>([]);
|
|
132
|
+
const photoError = ref("");
|
|
133
|
+
const USAGE_PHOTOS_REQUIRED = "Add a before photo and an after photo to record usage.";
|
|
131
134
|
|
|
132
135
|
const siteChoices = computed(() =>
|
|
133
136
|
props.sites.length ? props.sites : props.store.sites.map((_id) => ({ _id, name: _id === props.siteId ? "This property" : "Other property" })),
|
|
@@ -158,7 +161,9 @@ async function loadAreas() {
|
|
|
158
161
|
|
|
159
162
|
async function save() {
|
|
160
163
|
const { valid } = await form.value.validate();
|
|
161
|
-
|
|
164
|
+
// Owner decision 2026-09-27: a before AND an after photo on every usage record (the API refuses without).
|
|
165
|
+
photoError.value = photosBefore.value.length && photosAfter.value.length ? "" : USAGE_PHOTOS_REQUIRED;
|
|
166
|
+
if (!valid || photoError.value || !props.holding) return;
|
|
162
167
|
saving.value = true;
|
|
163
168
|
try {
|
|
164
169
|
await api.recordUsage(props.store._id, {
|
|
@@ -190,8 +195,13 @@ watch(site, () => {
|
|
|
190
195
|
loadAreas();
|
|
191
196
|
});
|
|
192
197
|
|
|
198
|
+
watch([photosBefore, photosAfter], () => {
|
|
199
|
+
if (photosBefore.value.length && photosAfter.value.length) photoError.value = "";
|
|
200
|
+
});
|
|
201
|
+
|
|
193
202
|
watch(show, (open) => {
|
|
194
203
|
if (!open) return;
|
|
204
|
+
photoError.value = "";
|
|
195
205
|
photosBefore.value = [];
|
|
196
206
|
photosAfter.value = [];
|
|
197
207
|
qty.value = 1;
|
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.
|
|
5
|
+
"version": "4.62.0",
|
|
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.",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The store switcher's button text. "Property store" / "Our store" is enough
|
|
3
|
+
* when each kind appears once; when a person can see several stores of the
|
|
4
|
+
* same kind (e.g. staff of several organisations at one property), each one
|
|
5
|
+
* also names its organisation so the buttons can be told apart.
|
|
6
|
+
*/
|
|
7
|
+
type TStoreLike = { ownerType: string; orgName?: string };
|
|
8
|
+
|
|
9
|
+
export function inventoryStoreLabel(store: TStoreLike, all: TStoreLike[]) {
|
|
10
|
+
const kind = store.ownerType === "property" ? "Property store" : "Our store";
|
|
11
|
+
const sameKind = all.filter((s) => s.ownerType === store.ownerType).length;
|
|
12
|
+
return sameKind > 1 && store.orgName ? `${kind} · ${store.orgName}` : kind;
|
|
13
|
+
}
|