@7365admin1/layer-common 4.63.0 → 4.64.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 +16 -4
- package/components/InventoryMain.vue +12 -3
- package/components/InventoryReportsTab.vue +21 -5
- package/components/InventoryRequestsTab.vue +2 -0
- package/composables/useWorkerTerm.ts +9 -2
- package/package.json +1 -1
- package/types/inventory.d.ts +3 -0
- package/utils/inventory-store-label.ts +7 -0
- package/utils/worker-terms.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.64.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6553a23: Inventory: a store that supplies several properties names them all ("Supplies Winsland House I and Winsland House II") for every viewer, including service providers; requests show which property they are for; the Stock Movement report can be filtered to one property.
|
|
8
|
+
|
|
9
|
+
## 4.63.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- c3912eb: Worker nouns: fall back to the deployment's `APP` code when the app sets no `appSlug` (hygiene, M&E, pest, pool, landscape), so hygiene shows Cleaners instead of the neutral Staff.
|
|
14
|
+
|
|
3
15
|
## 4.63.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -1685,11 +1697,11 @@
|
|
|
1685
1697
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
1686
1698
|
own rule from two endpoints the console already calls, unprojected:
|
|
1687
1699
|
|
|
1688
|
-
|
|
1689
|
-
|
|
1700
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
1701
|
+
GET /api/roles/id/:role that membership's role document
|
|
1690
1702
|
|
|
1691
|
-
|
|
1692
|
-
|
|
1703
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
1704
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
1693
1705
|
|
|
1694
1706
|
`role.default` is the marker because it is the only property of a platform
|
|
1695
1707
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -115,7 +115,7 @@ import useInventory from "../composables/useInventory";
|
|
|
115
115
|
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
116
116
|
import useSite from "../composables/useSite";
|
|
117
117
|
import useUtils from "../composables/useUtils";
|
|
118
|
-
import { inventoryStoreLabel } from "../utils/inventory-store-label";
|
|
118
|
+
import { inventoryStoreLabel, inventorySuppliesText } from "../utils/inventory-store-label";
|
|
119
119
|
|
|
120
120
|
const props = defineProps({
|
|
121
121
|
orgId: { type: String, required: true },
|
|
@@ -152,12 +152,21 @@ const isStaff = computed(() => {
|
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
const storeSites = computed(() =>
|
|
155
|
-
(store.value?.sites ?? []).map((id) => ({
|
|
155
|
+
(store.value?.sites ?? []).map((id) => ({
|
|
156
|
+
_id: id,
|
|
157
|
+
name:
|
|
158
|
+
store.value?.siteNames?.find((s) => s._id === id)?.name ||
|
|
159
|
+
orgSites.value.find((s) => s._id === id)?.name ||
|
|
160
|
+
(id === props.siteId ? "This property" : "Another property"),
|
|
161
|
+
})),
|
|
156
162
|
);
|
|
157
163
|
|
|
158
164
|
const subtitle = computed(() => {
|
|
159
165
|
if (!store.value) return "";
|
|
160
|
-
if (storeSites.value.length > 1)
|
|
166
|
+
if (storeSites.value.length > 1) {
|
|
167
|
+
const supplies = inventorySuppliesText(storeSites.value.map((s) => s.name));
|
|
168
|
+
return stores.value.length > 1 ? `${inventoryStoreLabel(store.value, stores.value)} · ${supplies}` : supplies;
|
|
169
|
+
}
|
|
161
170
|
return inventoryStoreLabel(store.value, stores.value);
|
|
162
171
|
});
|
|
163
172
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
/>
|
|
21
21
|
<v-text-field v-model="month" type="month" :label="report === 'purchases' && span === 'year' ? 'Up to month' : 'Month'" density="compact" hide-details style="max-width: 180px" />
|
|
22
22
|
<v-select
|
|
23
|
-
v-if="report === 'usage' && sites.length > 1"
|
|
23
|
+
v-if="(report === 'usage' || report === 'movement') && sites.length > 1"
|
|
24
24
|
v-model="site"
|
|
25
25
|
:items="[{ _id: '', name: 'All properties' }, ...sites]"
|
|
26
26
|
item-title="name"
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
51
|
<p class="text-caption text-medium-emphasis mb-2">{{ help[report] }}</p>
|
|
52
|
+
<p v-if="report === 'movement' && siteName" class="text-caption text-medium-emphasis mb-2">
|
|
53
|
+
Start, received and end are the whole store's (shared by {{ sites.map((s) => s.name).join(" and ") }}). The last columns show what was issued for {{ siteName }} and used there.
|
|
54
|
+
</p>
|
|
52
55
|
|
|
53
56
|
<div v-if="report === 'usage' && rows.length" class="d-flex flex-wrap align-center ga-3 mb-2">
|
|
54
57
|
<div class="text-body-2 font-weight-medium">
|
|
@@ -95,7 +98,7 @@
|
|
|
95
98
|
|
|
96
99
|
<v-data-table
|
|
97
100
|
v-model="selected"
|
|
98
|
-
:headers="
|
|
101
|
+
:headers="tableHeaders"
|
|
99
102
|
:items="rows"
|
|
100
103
|
:loading="loading"
|
|
101
104
|
:show-select="report === 'usage' && canCheck"
|
|
@@ -111,6 +114,7 @@
|
|
|
111
114
|
<template #item.totalCost="{ value }">{{ money(value) }}</template>
|
|
112
115
|
<template #item.receivedCost="{ value }">{{ money(value) }}</template>
|
|
113
116
|
<template #item.usedCost="{ value }">{{ money(value) }}</template>
|
|
117
|
+
<template #item.siteUsedCost="{ value }">{{ money(value) }}</template>
|
|
114
118
|
<!-- The supervisor checks usage against the technician's before/after photos. -->
|
|
115
119
|
<template #item.photos="{ item }">
|
|
116
120
|
<div v-if="usagePhotos(item).length" class="d-flex ga-1 py-1">
|
|
@@ -133,7 +137,7 @@
|
|
|
133
137
|
|
|
134
138
|
<script setup lang="ts">
|
|
135
139
|
import useInventory, { saveBlob } from "../composables/useInventory";
|
|
136
|
-
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
140
|
+
import useWorkerTerm, { workerAppKey } from "../composables/useWorkerTerm";
|
|
137
141
|
import useUtils from "../composables/useUtils";
|
|
138
142
|
import useFile from "../composables/useFile";
|
|
139
143
|
|
|
@@ -152,7 +156,7 @@ const usagePhotos = (row: any) => [
|
|
|
152
156
|
const notify = inject<(text: string, color?: string) => void>("inventoryNotify", () => {});
|
|
153
157
|
|
|
154
158
|
const worker = useWorkerTerm({ providers: true });
|
|
155
|
-
const appSlug =
|
|
159
|
+
const appSlug = workerAppKey();
|
|
156
160
|
|
|
157
161
|
const now = new Date();
|
|
158
162
|
const month = ref(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`);
|
|
@@ -238,6 +242,18 @@ const headers: Record<string, any[]> = {
|
|
|
238
242
|
],
|
|
239
243
|
};
|
|
240
244
|
|
|
245
|
+
const siteName = computed(() => (site.value ? props.sites.find((s) => s._id === site.value)?.name ?? "" : ""));
|
|
246
|
+
const tableHeaders = computed(() =>
|
|
247
|
+
report.value === "movement" && siteName.value
|
|
248
|
+
? [
|
|
249
|
+
...headers.movement,
|
|
250
|
+
{ title: `Issued for ${siteName.value}`, key: "siteIssued" },
|
|
251
|
+
{ title: `Used at ${siteName.value}`, key: "siteUsed" },
|
|
252
|
+
{ title: `Usage cost at ${siteName.value}`, key: "siteUsedCost" },
|
|
253
|
+
]
|
|
254
|
+
: headers[report.value],
|
|
255
|
+
);
|
|
256
|
+
|
|
241
257
|
function period(): TInventoryPeriod & { unchecked?: boolean } {
|
|
242
258
|
const [y, m] = month.value.split("-").map(Number);
|
|
243
259
|
const to = new Date(y, m, 0, 23, 59, 59, 999);
|
|
@@ -245,7 +261,7 @@ function period(): TInventoryPeriod & { unchecked?: boolean } {
|
|
|
245
261
|
return {
|
|
246
262
|
from: from.toISOString(),
|
|
247
263
|
to: to.toISOString(),
|
|
248
|
-
...(report.value === "usage" && site.value ? { site: site.value } : {}),
|
|
264
|
+
...((report.value === "usage" || report.value === "movement") && site.value ? { site: site.value } : {}),
|
|
249
265
|
...(report.value === "usage" && locationType.value ? { locationType: locationType.value } : {}),
|
|
250
266
|
...(report.value === "usage" && uncheckedOnly.value ? { unchecked: true } : {}),
|
|
251
267
|
};
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
<div class="font-weight-medium">{{ r.requestedByName || "Someone" }}</div>
|
|
24
24
|
<div class="text-caption text-medium-emphasis">{{ standardFormatDateTime(r.createdAt) }}</div>
|
|
25
25
|
<v-chip size="small" :color="chip[r.status].color" variant="tonal">{{ chip[r.status].label }}</v-chip>
|
|
26
|
+
<!-- One store can supply several properties: say which one this request is for. -->
|
|
27
|
+
<v-chip v-if="store.sites.length > 1 && r.siteName" size="small" variant="outlined" prepend-icon="mdi-office-building-outline">For {{ r.siteName }}</v-chip>
|
|
26
28
|
<v-spacer />
|
|
27
29
|
<template v-if="r.status === 'pending'">
|
|
28
30
|
<v-btn v-if="store.access.canApprove" class="text-none screen-btn-primary" variant="flat" @click="openDecision(r)">Review</v-btn>
|
|
@@ -3,8 +3,15 @@ import { workerTerm } from "../utils/worker-terms";
|
|
|
3
3
|
/**
|
|
4
4
|
* The worker noun for the app this page runs in ("Cleaner" in Hygiene,
|
|
5
5
|
* "Security officer" in Security, ...). See utils/worker-terms.ts.
|
|
6
|
-
*
|
|
6
|
+
* Keyed by `runtimeConfig.public.appSlug`, else the deployment's `APP` code
|
|
7
|
+
* ("cleaning_services", "security_agency", ...) - hygiene, M&E, pest, pool and
|
|
8
|
+
* landscape set only `APP`. Neither = the neutral "Staff".
|
|
7
9
|
*/
|
|
10
|
+
export function workerAppKey(): string | undefined {
|
|
11
|
+
const pub = useRuntimeConfig().public ?? {};
|
|
12
|
+
return (pub.appSlug || pub.APP || undefined) as string | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
export default function useWorkerTerm(opts?: { providers?: boolean }) {
|
|
9
|
-
return workerTerm(
|
|
16
|
+
return workerTerm(workerAppKey(), opts);
|
|
10
17
|
}
|
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.64.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.",
|
package/types/inventory.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ declare type TInventoryStore = {
|
|
|
12
12
|
ownerType: "property" | "service_provider";
|
|
13
13
|
name: string;
|
|
14
14
|
sites: string[];
|
|
15
|
+
/** Names of `sites`, from the server (a provider cannot list the owner's sites). */
|
|
16
|
+
siteNames?: { _id: string; name: string }[];
|
|
15
17
|
showInResidentApp: boolean;
|
|
16
18
|
showCostInResidentApp: boolean;
|
|
17
19
|
access: TInventoryAccess;
|
|
@@ -40,6 +42,7 @@ declare type TInventoryRequest = {
|
|
|
40
42
|
_id: string;
|
|
41
43
|
store: string;
|
|
42
44
|
site: string;
|
|
45
|
+
siteName?: string;
|
|
43
46
|
requestedBy: string;
|
|
44
47
|
requestedByName: string;
|
|
45
48
|
lines: { item: string; itemName: string; unit: string; qty: number; issuedQty: number }[];
|
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
type TStoreLike = { ownerType: string; orgName?: string };
|
|
8
8
|
|
|
9
|
+
/** "Supplies Winsland House I and Winsland House II" for a store that supplies several sites. */
|
|
10
|
+
export function inventorySuppliesText(names: string[]) {
|
|
11
|
+
const named = names.map((n) => n || "another property");
|
|
12
|
+
if (named.length < 2) return "";
|
|
13
|
+
return `Supplies ${named.slice(0, -1).join(", ")} and ${named[named.length - 1]}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export function inventoryStoreLabel(store: TStoreLike, all: TStoreLike[]) {
|
|
10
17
|
const kind = store.ownerType === "property" ? "Property store" : "Our store";
|
|
11
18
|
const sameKind = all.filter((s) => s.ownerType === store.ownerType).length;
|
package/utils/worker-terms.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* A cleaning company's workers are cleaners, not technicians. Every screen,
|
|
5
5
|
* export and mobile app takes the noun from here, keyed by the app it runs in
|
|
6
6
|
* (`appSlug`, e.g. "iservice365-web-app-hygiene" or
|
|
7
|
-
* "iservice365-hygiene-mobile-app"); a service-type code
|
|
8
|
-
* "security_agency", ...) resolves the same way. Only the words change -
|
|
7
|
+
* "iservice365-hygiene-mobile-app"); a service-type or deployment `APP` code
|
|
8
|
+
* ("cleaning_services", "security_agency", ...) resolves the same way. Only the words change -
|
|
9
9
|
* field names and API keys (`technician`, `usedByTechnicians`,
|
|
10
10
|
* `/reports/technicians`) stay as they are.
|
|
11
11
|
*
|