@7365admin1/core 3.72.1-staging.326 → 3.72.1-staging.328
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/.changeset/inventory-notification-category.md +5 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +97 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/dashboard-inventory-low-stock.e2e.test.mjs +59 -0
- package/test/notification-category.util.test.mjs +21 -1
package/dist/index.d.ts
CHANGED
|
@@ -10567,12 +10567,12 @@ declare function useNewDashboardRepo(): {
|
|
|
10567
10567
|
percentage: number | null;
|
|
10568
10568
|
};
|
|
10569
10569
|
supplyAlert: {
|
|
10570
|
+
thresholdUnavailableReason?: string | undefined;
|
|
10570
10571
|
items: {
|
|
10571
10572
|
name: any;
|
|
10572
10573
|
qty: any;
|
|
10573
10574
|
}[];
|
|
10574
|
-
basis: "lowest-quantity";
|
|
10575
|
-
thresholdUnavailableReason: string;
|
|
10575
|
+
basis: "lowest-quantity" | "below-alert-level";
|
|
10576
10576
|
};
|
|
10577
10577
|
taskCompleted: {
|
|
10578
10578
|
completed: any;
|
|
@@ -13237,6 +13237,8 @@ declare function useHidAmicoRepo(): {
|
|
|
13237
13237
|
findIdentitiesByHidUserIds: (reader: string | ObjectId, hidUserIds: Array<string | number>) => Promise<mongodb.WithId<bson.Document>[]>;
|
|
13238
13238
|
acquireEnrollmentLock: (reader: string | ObjectId, owner: string, ttlSeconds: number) => Promise<boolean>;
|
|
13239
13239
|
releaseEnrollmentLock: (reader: string | ObjectId, owner?: string) => Promise<boolean>;
|
|
13240
|
+
setEnrollmentSession: (reader: string | ObjectId, owner: string, session: string) => Promise<boolean>;
|
|
13241
|
+
getEnrollmentSession: (reader: string | ObjectId) => Promise<string>;
|
|
13240
13242
|
assertOrganizationSiteAccess: (site: string | ObjectId, org: string | ObjectId) => Promise<{
|
|
13241
13243
|
siteId: ObjectId;
|
|
13242
13244
|
orgId: ObjectId;
|
package/dist/index.js
CHANGED
|
@@ -9680,6 +9680,20 @@ var NOTIFICATION_CATEGORIES = [
|
|
|
9680
9680
|
vendorDefault: true,
|
|
9681
9681
|
rendered: true
|
|
9682
9682
|
},
|
|
9683
|
+
// Inventory (2026-09-17). Sent from `iservice365-API-core`'s inventory
|
|
9684
|
+
// module: a stock request waiting for approval and stock falling to its alert
|
|
9685
|
+
// level go to that store's staff; a decided request goes to the person who
|
|
9686
|
+
// asked. The caller checks `rendered` before it sends, so the sender and this
|
|
9687
|
+
// switch cannot land out of order.
|
|
9688
|
+
{
|
|
9689
|
+
key: "inventory",
|
|
9690
|
+
label: "Inventory",
|
|
9691
|
+
description: "When a stock request needs your approval or has been decided, or stock you look after runs low.",
|
|
9692
|
+
channels: { inApp: true, push: true, email: false },
|
|
9693
|
+
channelNote: { email: NO_EMAIL_RECIPIENT },
|
|
9694
|
+
permissionResources: ["inventory"],
|
|
9695
|
+
rendered: true
|
|
9696
|
+
},
|
|
9683
9697
|
{
|
|
9684
9698
|
key: "serviceProviderInvite",
|
|
9685
9699
|
label: "Service Provider Invitations",
|
|
@@ -45115,6 +45129,20 @@ function useHidAmicoRepo() {
|
|
|
45115
45129
|
throw error;
|
|
45116
45130
|
}
|
|
45117
45131
|
}
|
|
45132
|
+
async function setEnrollmentSession(reader, owner, session) {
|
|
45133
|
+
const result = await enrollmentLocks().updateOne(
|
|
45134
|
+
{ _id: toId3(reader, "reader ID"), owner },
|
|
45135
|
+
{ $set: { session, sessionAt: /* @__PURE__ */ new Date() } }
|
|
45136
|
+
);
|
|
45137
|
+
return result.matchedCount > 0;
|
|
45138
|
+
}
|
|
45139
|
+
async function getEnrollmentSession(reader) {
|
|
45140
|
+
const lock = await enrollmentLocks().findOne({
|
|
45141
|
+
_id: toId3(reader, "reader ID"),
|
|
45142
|
+
expiresAt: { $gt: /* @__PURE__ */ new Date() }
|
|
45143
|
+
});
|
|
45144
|
+
return String(lock?.session ?? "");
|
|
45145
|
+
}
|
|
45118
45146
|
async function releaseEnrollmentLock(reader, owner) {
|
|
45119
45147
|
const result = await enrollmentLocks().deleteOne({
|
|
45120
45148
|
_id: toId3(reader, "reader ID"),
|
|
@@ -45673,6 +45701,8 @@ function useHidAmicoRepo() {
|
|
|
45673
45701
|
findIdentitiesByHidUserIds,
|
|
45674
45702
|
acquireEnrollmentLock,
|
|
45675
45703
|
releaseEnrollmentLock,
|
|
45704
|
+
setEnrollmentSession,
|
|
45705
|
+
getEnrollmentSession,
|
|
45676
45706
|
assertOrganizationSiteAccess,
|
|
45677
45707
|
getSitePermissions,
|
|
45678
45708
|
listSitePermissionDocuments,
|
|
@@ -46921,6 +46951,8 @@ var HidAmicoClient = class {
|
|
|
46921
46951
|
constructor(reader) {
|
|
46922
46952
|
this.reader = reader;
|
|
46923
46953
|
this.session = "";
|
|
46954
|
+
/** True while `session` belongs to another request. See `adoptSession`. */
|
|
46955
|
+
this.borrowedSession = false;
|
|
46924
46956
|
const relayAuth = getRelayAuth();
|
|
46925
46957
|
this.http = import_axios.default.create({
|
|
46926
46958
|
baseURL: normalizeHidReaderBaseUrl(reader.baseUrl),
|
|
@@ -46980,6 +47012,7 @@ var HidAmicoClient = class {
|
|
|
46980
47012
|
throw new import_node_server_utils141.BadRequestError("HID Amico login failed: missing session.");
|
|
46981
47013
|
}
|
|
46982
47014
|
this.session = res.data.session;
|
|
47015
|
+
this.borrowedSession = false;
|
|
46983
47016
|
return this.session;
|
|
46984
47017
|
}
|
|
46985
47018
|
async sessionIsValid() {
|
|
@@ -47132,10 +47165,34 @@ var HidAmicoClient = class {
|
|
|
47132
47165
|
verified: true
|
|
47133
47166
|
};
|
|
47134
47167
|
}
|
|
47168
|
+
/**
|
|
47169
|
+
* Take over a session somebody else opened, without logging in.
|
|
47170
|
+
*
|
|
47171
|
+
* For the cancel path only. While an enrolment is listening the reader will
|
|
47172
|
+
* not serve a second `hidlogin.fcgi` inside the client's 15 second timeout, so
|
|
47173
|
+
* the cancel has to ride the enrolment's session or it never reaches the
|
|
47174
|
+
* device at all.
|
|
47175
|
+
*
|
|
47176
|
+
* A borrowed session is NOT this client's to close - see `logout`.
|
|
47177
|
+
*/
|
|
47178
|
+
adoptSession(session) {
|
|
47179
|
+
this.session = String(session ?? "");
|
|
47180
|
+
this.borrowedSession = Boolean(this.session);
|
|
47181
|
+
return this.session;
|
|
47182
|
+
}
|
|
47183
|
+
/** Whether this client may end the session it is holding. */
|
|
47184
|
+
get ownsSession() {
|
|
47185
|
+
return Boolean(this.session) && !this.borrowedSession;
|
|
47186
|
+
}
|
|
47135
47187
|
async logout() {
|
|
47136
47188
|
if (!this.session) {
|
|
47137
47189
|
return;
|
|
47138
47190
|
}
|
|
47191
|
+
if (this.borrowedSession) {
|
|
47192
|
+
this.session = "";
|
|
47193
|
+
this.borrowedSession = false;
|
|
47194
|
+
return;
|
|
47195
|
+
}
|
|
47139
47196
|
const logoutUrl = this.url("/logout.fcgi");
|
|
47140
47197
|
try {
|
|
47141
47198
|
this.session = "";
|
|
@@ -49852,7 +49909,8 @@ function useHidAmicoService() {
|
|
|
49852
49909
|
throw new import_node_server_utils141.BadRequestError("Another physical HID card enrollment is already running on this reader.");
|
|
49853
49910
|
}
|
|
49854
49911
|
try {
|
|
49855
|
-
await client.login();
|
|
49912
|
+
const session = await client.login();
|
|
49913
|
+
await repo.setEnrollmentSession(readerId, lockOwner, session);
|
|
49856
49914
|
await assertHidUserExists(client, hidUserId);
|
|
49857
49915
|
await ensurePhysicalCardIdentification(client);
|
|
49858
49916
|
const beforeRows = await loadPhysicalCardRows(client, hidUserId);
|
|
@@ -49933,15 +49991,20 @@ function useHidAmicoService() {
|
|
|
49933
49991
|
async function cancelUserCardEnrollment(readerId) {
|
|
49934
49992
|
const reader = await getActiveReader(readerId);
|
|
49935
49993
|
const client = new HidAmicoClient(reader);
|
|
49994
|
+
const runningSession = await repo.getEnrollmentSession(readerId);
|
|
49936
49995
|
try {
|
|
49937
|
-
|
|
49996
|
+
if (runningSession) {
|
|
49997
|
+
client.adoptSession(runningSession);
|
|
49998
|
+
} else {
|
|
49999
|
+
await client.login();
|
|
50000
|
+
}
|
|
49938
50001
|
await client.cancelRemoteEnrollment();
|
|
49939
50002
|
const enrollmentWasRunning = await repo.releaseEnrollmentLock(readerId);
|
|
49940
50003
|
await repo.addEvent({
|
|
49941
50004
|
reader: readerId,
|
|
49942
50005
|
site: reader.site,
|
|
49943
50006
|
type: "user_card_enrollment_cancelled",
|
|
49944
|
-
payload: { enrollmentWasRunning }
|
|
50007
|
+
payload: { enrollmentWasRunning, borrowedSession: Boolean(runningSession) }
|
|
49945
50008
|
});
|
|
49946
50009
|
return { readerId, cancelled: true };
|
|
49947
50010
|
} finally {
|
|
@@ -77824,6 +77887,7 @@ function highSeverityIncidentCount(severities) {
|
|
|
77824
77887
|
).length;
|
|
77825
77888
|
}
|
|
77826
77889
|
var SUPPLY_BASIS_LOWEST_QUANTITY = "lowest-quantity";
|
|
77890
|
+
var SUPPLY_BASIS_BELOW_ALERT_LEVEL = "below-alert-level";
|
|
77827
77891
|
var SUPPLY_THRESHOLD_NOT_IN_MODEL = "Supplies do not record a stock threshold, so these are the lowest-quantity items rather than items below a level.";
|
|
77828
77892
|
|
|
77829
77893
|
// src/repositories/new-dashboard.repo.ts
|
|
@@ -78682,6 +78746,32 @@ function useNewDashboardRepo() {
|
|
|
78682
78746
|
{ $unwind: { path: "$scheduleDoc", preserveNullAndEmptyArrays: false } }
|
|
78683
78747
|
];
|
|
78684
78748
|
}
|
|
78749
|
+
async function lowStockItems(site, serviceType) {
|
|
78750
|
+
const stores = await db2.collection("inventory.stores").find({ sites: site, status: "active" }, { projection: { _id: 1 } }).toArray();
|
|
78751
|
+
if (stores.length) {
|
|
78752
|
+
const items2 = await db2.collection("inventory.items").aggregate([
|
|
78753
|
+
{
|
|
78754
|
+
$match: {
|
|
78755
|
+
store: { $in: stores.map((store) => store._id) },
|
|
78756
|
+
status: "active",
|
|
78757
|
+
reorderLevel: { $gt: 0 },
|
|
78758
|
+
$expr: { $lte: ["$onHand", "$reorderLevel"] }
|
|
78759
|
+
}
|
|
78760
|
+
},
|
|
78761
|
+
{ $sort: { onHand: 1 } },
|
|
78762
|
+
{ $limit: 5 },
|
|
78763
|
+
{ $project: { _id: 1, name: 1, qty: "$onHand" } }
|
|
78764
|
+
]).toArray();
|
|
78765
|
+
return { items: items2, basis: SUPPLY_BASIS_BELOW_ALERT_LEVEL };
|
|
78766
|
+
}
|
|
78767
|
+
const items = await supplyCollection.aggregate([
|
|
78768
|
+
{ $match: { site, serviceType, status: "active" } },
|
|
78769
|
+
{ $sort: { qty: 1 } },
|
|
78770
|
+
{ $limit: 3 },
|
|
78771
|
+
{ $project: { _id: 1, name: 1, qty: 1 } }
|
|
78772
|
+
]).toArray();
|
|
78773
|
+
return { items, basis: SUPPLY_BASIS_LOWEST_QUANTITY };
|
|
78774
|
+
}
|
|
78685
78775
|
async function getServiceDashboard({
|
|
78686
78776
|
site,
|
|
78687
78777
|
serviceType,
|
|
@@ -78739,12 +78829,7 @@ function useNewDashboardRepo() {
|
|
|
78739
78829
|
}
|
|
78740
78830
|
}
|
|
78741
78831
|
]).toArray(),
|
|
78742
|
-
|
|
78743
|
-
{ $match: { site, serviceType, status: "active" } },
|
|
78744
|
-
{ $sort: { qty: 1 } },
|
|
78745
|
-
{ $limit: 3 },
|
|
78746
|
-
{ $project: { _id: 1, name: 1, qty: 1 } }
|
|
78747
|
-
]).toArray(),
|
|
78832
|
+
lowStockItems(site, serviceType),
|
|
78748
78833
|
areaChecklistCollection.aggregate([
|
|
78749
78834
|
...areaChecklistScheduleLookup(site, serviceType, periodRange),
|
|
78750
78835
|
{
|
|
@@ -78806,7 +78891,7 @@ function useNewDashboardRepo() {
|
|
|
78806
78891
|
)
|
|
78807
78892
|
},
|
|
78808
78893
|
supplyAlert: {
|
|
78809
|
-
items: supplyAlertReport.map((item) => ({
|
|
78894
|
+
items: supplyAlertReport.items.map((item) => ({
|
|
78810
78895
|
name: item.name,
|
|
78811
78896
|
qty: item.qty
|
|
78812
78897
|
})),
|
|
@@ -78814,8 +78899,8 @@ function useNewDashboardRepo() {
|
|
|
78814
78899
|
// with no threshold, because `site.supplies` records no stock level to
|
|
78815
78900
|
// compare against. Naming the ordering stops 900 gloves being drawn as
|
|
78816
78901
|
// a warning. No threshold is invented here.
|
|
78817
|
-
basis:
|
|
78818
|
-
thresholdUnavailableReason: SUPPLY_THRESHOLD_NOT_IN_MODEL
|
|
78902
|
+
basis: supplyAlertReport.basis,
|
|
78903
|
+
...supplyAlertReport.basis === SUPPLY_BASIS_LOWEST_QUANTITY ? { thresholdUnavailableReason: SUPPLY_THRESHOLD_NOT_IN_MODEL } : {}
|
|
78819
78904
|
},
|
|
78820
78905
|
taskCompleted: {
|
|
78821
78906
|
completed: tFacet.completed[0]?.count ?? 0,
|