@7365admin1/layer-common 4.63.1 → 4.64.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,17 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 4.64.1
4
+
5
+ ### Patch Changes
6
+
7
+ - bcc3b37: useSite().getSites lists an organisation's sites from GET /api/sites?org= (the old /api/sites/organization/:org route does not exist and returned 404), so Inventory's "Properties this store supplies" and the set-up form offer every property of the organisation again.
8
+
9
+ ## 4.64.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 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.
14
+
3
15
  ## 4.63.1
4
16
 
5
17
  ### Patch Changes
@@ -1691,11 +1703,11 @@
1691
1703
  `utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
1692
1704
  own rule from two endpoints the console already calls, unprojected:
1693
1705
 
1694
- GET /api/members/user/:user/app/admin the Seven365 staff membership
1695
- GET /api/roles/id/:role that membership's role document
1706
+ GET /api/members/user/:user/app/admin the Seven365 staff membership
1707
+ GET /api/roles/id/:role that membership's role document
1696
1708
 
1697
- owner = member.type === "admin" && role.type === "admin" && role.default === true
1698
- staff = member.type === "admin" && role.type === "admin"
1709
+ owner = member.type === "admin" && role.type === "admin" && role.default === true
1710
+ staff = member.type === "admin" && role.type === "admin"
1699
1711
 
1700
1712
  `role.default` is the marker because it is the only property of a platform
1701
1713
  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) => ({ _id: id, name: orgSites.value.find((s) => s._id === id)?.name ?? "This property" })),
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) return `Supplies ${storeSites.value.map((s) => s.name).join(", ")}`;
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="headers[report]"
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">
@@ -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>
@@ -41,13 +41,13 @@ export default function useSite() {
41
41
  try {
42
42
  const organizationId = orgId || currentUser.value?.defaultOrg;
43
43
 
44
- const response = await useNuxtApp().$api<Record<string, any>>(
45
- `/api/sites/organization/${organizationId}`,
46
- {
47
- method: "GET",
48
- query: { page: requestPage, search: searchQuery, limit, sort, order },
49
- }
50
- );
44
+ // API-core lists an organisation's sites at GET /api/sites?org= (core
45
+ // `getSites`); there is no /api/sites/organization/:org route, so the old
46
+ // path 404'd and Inventory never offered a second property to supply.
47
+ const response = await useNuxtApp().$api<Record<string, any>>(`/api/sites`, {
48
+ method: "GET",
49
+ query: { org: organizationId, page: requestPage, search: searchQuery, limit, sort, order },
50
+ });
51
51
 
52
52
  sites.value = response.items;
53
53
  page.value = requestPage;
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.63.1",
5
+ "version": "4.64.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.",
@@ -36,7 +36,7 @@
36
36
  "build": "nuxt build .playground",
37
37
  "generate": "nuxt generate .playground",
38
38
  "preview": "nuxt preview .playground",
39
- "test": "esbuild composables/useANPRSettings.ts --format=esm --outfile=test/.build/useANPRSettings.mjs --log-level=error && esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && esbuild composables/usePlateToastWindow.ts --format=esm --outfile=test/.build/usePlateToastWindow.mjs --log-level=error && esbuild utils/client-invite-modules.ts --bundle --platform=node --format=esm --outfile=test/.build/client-invite-modules.mjs --log-level=error && esbuild utils/robot-display.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-display.mjs --log-level=error && esbuild utils/building-map.ts --bundle --platform=node --format=esm --outfile=test/.build/building-map.mjs --log-level=error && esbuild utils/robot-building.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-building.mjs --log-level=error && esbuild utils/robot-fleet-core.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-fleet-core.mjs --log-level=error && esbuild utils/rmf-editor.ts --bundle --platform=node --format=esm --outfile=test/.build/rmf-editor.mjs --log-level=error && node --experimental-strip-types --test \"test/*.test.mjs\" && yarn test:units",
39
+ "test": "esbuild composables/useANPRSettings.ts --format=esm --outfile=test/.build/useANPRSettings.mjs --log-level=error && esbuild composables/useSite.ts --format=esm --outfile=test/.build/useSite.mjs --log-level=error && esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && esbuild composables/usePlateToastWindow.ts --format=esm --outfile=test/.build/usePlateToastWindow.mjs --log-level=error && esbuild utils/client-invite-modules.ts --bundle --platform=node --format=esm --outfile=test/.build/client-invite-modules.mjs --log-level=error && esbuild utils/robot-display.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-display.mjs --log-level=error && esbuild utils/building-map.ts --bundle --platform=node --format=esm --outfile=test/.build/building-map.mjs --log-level=error && esbuild utils/robot-building.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-building.mjs --log-level=error && esbuild utils/robot-fleet-core.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-fleet-core.mjs --log-level=error && esbuild utils/rmf-editor.ts --bundle --platform=node --format=esm --outfile=test/.build/rmf-editor.mjs --log-level=error && node --experimental-strip-types --test \"test/*.test.mjs\" && yarn test:units",
40
40
  "test:units": "node --experimental-strip-types --test \"utils/*.test.ts\"",
41
41
  "release": "yarn run build && changeset publish"
42
42
  },
@@ -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;