@7365admin1/core 3.51.1-staging.229 → 3.52.1-staging.230
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 +73 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +103 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +103 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/.changeset/camera-edit-audit-trail.md +0 -16
- package/.changeset/camera-health-decouple-from-wall-cap.md +0 -17
- package/.changeset/hid-amico-secret-loud.md +0 -9
- package/.changeset/hid-face-photo-retention.md +0 -9
- package/.changeset/site-camera-bulk-import.md +0 -14
- package/.changeset/site-camera-direction-valid.md +0 -15
- package/.changeset/visitor-guest-status-filter.md +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# @iservice365/core
|
|
2
2
|
|
|
3
|
+
## 3.52.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bd4bd88: Add a bulk camera import on the existing guided path.
|
|
8
|
+
|
|
9
|
+
`POST /api/site-cameras/bulk` accepts a site and up to 200 rows from the
|
|
10
|
+
technician survey sheet, derives each camera's relay address from its recorder
|
|
11
|
+
and channel number, and returns a per-row result. `commit: false` previews and
|
|
12
|
+
writes nothing; `commit: true` writes the rows that passed. Rows that are ANPR,
|
|
13
|
+
that carry a hand-written address, or whose channel is outside 1-256 are refused
|
|
14
|
+
by name, and a camera already at the site is reported as a duplicate rather than
|
|
15
|
+
as a write error. Creates `type: "ip"` cameras only and needs the same write
|
|
16
|
+
gate as the single-camera form.
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 5af369c: Record who added, changed or removed a camera.
|
|
21
|
+
|
|
22
|
+
"Who changed this camera's address, and when" had no answer anywhere in the system: a camera record
|
|
23
|
+
carries `createdAt`/`updatedAt` and no actor, and a write overwrites the previous `host` in place. At
|
|
24
|
+
Seventh Condominium that turned a one-line port correction into nine days.
|
|
25
|
+
|
|
26
|
+
`add`, `updateById` and `deleteById` on site cameras now append a row to the existing append-only
|
|
27
|
+
`console-audit` collection, with the actor from the session, the camera id, and `before`/`after`
|
|
28
|
+
values drawn from a per-action allow-list. No schema changes, no migration, and no new collection.
|
|
29
|
+
|
|
30
|
+
The allow-list is the boundary that matters: a camera document holds a device credential, and
|
|
31
|
+
`username` and `password` are not on it. `loadForWrite` still never reads them.
|
|
32
|
+
|
|
33
|
+
- 16a9105: Decouple the camera health sweep from the wall's tile ceiling.
|
|
34
|
+
|
|
35
|
+
`getSiteHealth` sliced its input to `maxTiles` (9), so a health request covering a 150-camera site
|
|
36
|
+
answered for the first nine cameras and said nothing at all about the other 141 — and did not report
|
|
37
|
+
that it had shortened the list. No client could build a site-wide "which of my cameras is down" view,
|
|
38
|
+
however it asked.
|
|
39
|
+
|
|
40
|
+
Health is now bounded by its own `maxHealthCameras` (`CAMERA_HEALTH_MAX_CAMERAS`, default 200), and a
|
|
41
|
+
request past that cap comes back with `requested` / `returned` / `truncated` instead of a quietly
|
|
42
|
+
partial list. The device fan-out is unchanged: `maxConcurrentProbes` still bounds probes in flight at
|
|
43
|
+
4, the reachability probe is still cached per recorder, and `probeCamera` still reads a cached device
|
|
44
|
+
probe rather than making a fresh one — so a 150-camera sweep costs a recorder no more than a 9-camera
|
|
45
|
+
one after the first pass.
|
|
46
|
+
|
|
47
|
+
- 398b374: HID Amico: stop silently deriving the credential encryption key from
|
|
48
|
+
ACCESS_TOKEN_SECRET. Reading still accepts the legacy key so existing credentials
|
|
49
|
+
keep working (and now logs an error saying so), but writing a new credential
|
|
50
|
+
refuses with a message naming HID_AMICO_SECRET, so a later rotation of the JWT
|
|
51
|
+
signing secret cannot orphan credentials that were written after this.
|
|
52
|
+
- 8113e31: HID face registration: never retain the captured photo. Identity `metadata` is stripped of
|
|
53
|
+
`photo` and of any inline `data:image/...` value on every write, and reader notification
|
|
54
|
+
payloads (`user_image`, `access_photo`) are redacted of image data before they are persisted
|
|
55
|
+
as audit events. Enrollment state (`facialEnrolled`, `facialEnrolledAt`, `facialData`,
|
|
56
|
+
`imageTimestamp`) is unchanged.
|
|
57
|
+
- b097964: Constrain a site camera's `direction` to a fixed set instead of accepting any string.
|
|
58
|
+
|
|
59
|
+
`Joi.string().allow("entry", "exit", "both", "none")` reads like a whitelist but is not one — on a
|
|
60
|
+
validator that already accepts every non-empty string, `.allow()` only adds permitted values, so the
|
|
61
|
+
field rejected nothing. It is now `.valid()`.
|
|
62
|
+
|
|
63
|
+
The accepted set is `entry | exit | both | none | visitors | residents`: the four values the staging
|
|
64
|
+
collection actually holds (measured read-only across all 22 rows), plus the two extra options
|
|
65
|
+
layer-common's live `CameraForm.vue` offers for ANPR cameras. No stored camera and no reachable form
|
|
66
|
+
is refused. Whether `visitors` and `residents` are directions at all is a product question; dropping
|
|
67
|
+
them later is a zero-row migration.
|
|
68
|
+
|
|
69
|
+
- Visitor list: decide the Guests tab by "every requested status is pre-arrival"
|
|
70
|
+
instead of matching one specific status set, so the tab keeps filtering on the
|
|
71
|
+
expected arrival date for both the approved+pending request sent by
|
|
72
|
+
layer-common `main` and the approved+pending+rejected request sent by
|
|
73
|
+
layer-common `staging` (DV-0080). Extracted as `isPreCheckInStatusOnly` with
|
|
74
|
+
tests pinning each case.
|
|
75
|
+
|
|
3
76
|
## 3.51.0
|
|
4
77
|
|
|
5
78
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -4822,7 +4822,7 @@ declare function useVisitorTransactionRepo(): {
|
|
|
4822
4822
|
checkOutBySiteAndPlate: (site: string | ObjectId, plateNumber: string, session?: ClientSession) => Promise<mongodb.WithId<bson.Document> | null>;
|
|
4823
4823
|
resendEmailInvitedResident: (visitorId: string, updatedBy: string | ObjectId) => Promise<any>;
|
|
4824
4824
|
getSelfServiceVisitorPreview: (id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
|
|
4825
|
-
patchFields: (id: string | ObjectId, fields: Record<string, any
|
|
4825
|
+
patchFields: (id: string | ObjectId, fields: Record<string, any>, session?: ClientSession) => Promise<void>;
|
|
4826
4826
|
updateStatusByLeadId: (leadId: string | ObjectId, status: string) => Promise<void>;
|
|
4827
4827
|
};
|
|
4828
4828
|
|
|
@@ -4831,6 +4831,7 @@ declare function useVisitorTransactionService(): {
|
|
|
4831
4831
|
updateVisitorTransactionById: (id: string | ObjectId, value: TVisitorTransaction) => Promise<string>;
|
|
4832
4832
|
processTransactionDahuaStatus: () => Promise<void>;
|
|
4833
4833
|
inviteVisitor: (value: TVisitorTransaction, userId: string) => Promise<ObjectId[]>;
|
|
4834
|
+
createInvitedMemberDocuments: (leadId: string | ObjectId, status: VisitorStatus) => Promise<ObjectId[]>;
|
|
4834
4835
|
deleteVisitorTransaction: (id: string) => Promise<void>;
|
|
4835
4836
|
};
|
|
4836
4837
|
|
package/dist/index.js
CHANGED
|
@@ -20850,10 +20850,10 @@ function useVisitorTransactionRepo() {
|
|
|
20850
20850
|
throw new Error(error.message);
|
|
20851
20851
|
}
|
|
20852
20852
|
}
|
|
20853
|
-
async function patchFields(id, fields) {
|
|
20853
|
+
async function patchFields(id, fields, session) {
|
|
20854
20854
|
const _id = (0, import_node_server_utils48.toObjectId)(id);
|
|
20855
20855
|
try {
|
|
20856
|
-
await collection.updateOne({ _id }, { $set: fields });
|
|
20856
|
+
await collection.updateOne({ _id }, { $set: fields }, { session });
|
|
20857
20857
|
} catch (error) {
|
|
20858
20858
|
throw new Error(error.message);
|
|
20859
20859
|
}
|
|
@@ -50480,7 +50480,8 @@ function useVisitorTransactionService() {
|
|
|
50480
50480
|
getExpiredCheckedOutTransactionsBySite: _getExpiredCheckedOutTransactionsBySite,
|
|
50481
50481
|
updateManyDahuaSyncStatus: _updateManyDahuaSyncStatus,
|
|
50482
50482
|
getVisitorTransactionById: _getVisitorTransactionById,
|
|
50483
|
-
deleteVisitorTransaction: _deleteVisitorTransaction
|
|
50483
|
+
deleteVisitorTransaction: _deleteVisitorTransaction,
|
|
50484
|
+
patchFields: _patchFields
|
|
50484
50485
|
} = useVisitorTransactionRepo();
|
|
50485
50486
|
const { getById } = useBuildingUnitRepo();
|
|
50486
50487
|
const {
|
|
@@ -51321,6 +51322,74 @@ function useVisitorTransactionService() {
|
|
|
51321
51322
|
session?.endSession();
|
|
51322
51323
|
}
|
|
51323
51324
|
}
|
|
51325
|
+
async function createInvitedMemberDocuments(leadId, status) {
|
|
51326
|
+
const lead = await _getVisitorTransactionById(String(leadId));
|
|
51327
|
+
if (!lead?.inviterId)
|
|
51328
|
+
return [];
|
|
51329
|
+
const members = Array.isArray(lead.members) ? lead.members : [];
|
|
51330
|
+
if (members.length === 0)
|
|
51331
|
+
return [];
|
|
51332
|
+
if (!members.every((member) => !member._id))
|
|
51333
|
+
return [];
|
|
51334
|
+
const session = import_node_server_utils134.useAtlas.getClient()?.startSession();
|
|
51335
|
+
if (!session) {
|
|
51336
|
+
throw new Error("Unable to start session for people service.");
|
|
51337
|
+
}
|
|
51338
|
+
try {
|
|
51339
|
+
session.startTransaction();
|
|
51340
|
+
const memberPayload = {
|
|
51341
|
+
block: lead.block ? String(lead.block) : void 0,
|
|
51342
|
+
level: lead.level ? String(lead.level) : void 0,
|
|
51343
|
+
unit: lead.unit ? String(lead.unit) : void 0,
|
|
51344
|
+
unitName: lead.unitName,
|
|
51345
|
+
org: lead.org ? String(lead.org) : void 0,
|
|
51346
|
+
site: lead.site ? String(lead.site) : void 0,
|
|
51347
|
+
type: lead.type,
|
|
51348
|
+
contractorType: lead.contractorType,
|
|
51349
|
+
company: lead.company,
|
|
51350
|
+
purpose: lead.purpose,
|
|
51351
|
+
expectedCheckIn: lead.expectedCheckIn ? new Date(lead.expectedCheckIn).toISOString() : void 0,
|
|
51352
|
+
arrivalTime: lead.arrivalTime,
|
|
51353
|
+
duration: lead.duration,
|
|
51354
|
+
isOvernightParking: lead.isOvernightParking,
|
|
51355
|
+
numberOfPassengers: lead.numberOfPassengers,
|
|
51356
|
+
status,
|
|
51357
|
+
inviterId: String(lead.inviterId),
|
|
51358
|
+
leadId: String(lead._id),
|
|
51359
|
+
// Explicit - MVisitorTransaction defaults an undefined checkIn to now,
|
|
51360
|
+
// and an approved invitation hasn't arrived yet.
|
|
51361
|
+
checkIn: null
|
|
51362
|
+
};
|
|
51363
|
+
const insertedIds = [];
|
|
51364
|
+
for (const member of members) {
|
|
51365
|
+
const memberId = await _add(
|
|
51366
|
+
{
|
|
51367
|
+
...memberPayload,
|
|
51368
|
+
name: member.name ?? "",
|
|
51369
|
+
nric: member.nric ?? "",
|
|
51370
|
+
contact: member.contact ?? "",
|
|
51371
|
+
email: member.email ?? "",
|
|
51372
|
+
plateNumber: member.plateNumber ?? ""
|
|
51373
|
+
},
|
|
51374
|
+
session
|
|
51375
|
+
);
|
|
51376
|
+
member._id = memberId;
|
|
51377
|
+
insertedIds.push(memberId);
|
|
51378
|
+
}
|
|
51379
|
+
await _patchFields(leadId, { members }, session);
|
|
51380
|
+
await session.commitTransaction();
|
|
51381
|
+
return insertedIds;
|
|
51382
|
+
} catch (error) {
|
|
51383
|
+
import_node_server_utils134.logger.error(
|
|
51384
|
+
"Error creating member documents for approved invitation:",
|
|
51385
|
+
error
|
|
51386
|
+
);
|
|
51387
|
+
await session.abortTransaction();
|
|
51388
|
+
throw error;
|
|
51389
|
+
} finally {
|
|
51390
|
+
session?.endSession();
|
|
51391
|
+
}
|
|
51392
|
+
}
|
|
51324
51393
|
async function deleteVisitorTransaction(id) {
|
|
51325
51394
|
const visitor = await _getVisitorTransactionById(id);
|
|
51326
51395
|
if (!visitor) {
|
|
@@ -51353,6 +51422,7 @@ function useVisitorTransactionService() {
|
|
|
51353
51422
|
updateVisitorTransactionById,
|
|
51354
51423
|
processTransactionDahuaStatus,
|
|
51355
51424
|
inviteVisitor,
|
|
51425
|
+
createInvitedMemberDocuments,
|
|
51356
51426
|
deleteVisitorTransaction
|
|
51357
51427
|
// changeVisitorTransactionKeysById,
|
|
51358
51428
|
};
|
|
@@ -55312,6 +55382,7 @@ function useVisitorTransactionController() {
|
|
|
55312
55382
|
add: _add,
|
|
55313
55383
|
updateVisitorTransactionById: _updateVisitorTransactionById,
|
|
55314
55384
|
inviteVisitor: _inviteVisitor,
|
|
55385
|
+
createInvitedMemberDocuments: _createInvitedMemberDocuments,
|
|
55315
55386
|
deleteVisitorTransaction: _deleteVisitorTransaction
|
|
55316
55387
|
// changeVisitorTransactionKeysById: _changeVisitorTransactionKeysById,
|
|
55317
55388
|
} = useVisitorTransactionService();
|
|
@@ -55636,8 +55707,36 @@ function useVisitorTransactionController() {
|
|
|
55636
55707
|
const remarks = approval?.remarks ?? req.body.remarks;
|
|
55637
55708
|
const isApprovalTransition = nextStatus === "approved" /* APPROVED */ || nextStatus === "rejected" /* REJECTED */;
|
|
55638
55709
|
const current = isApprovalTransition ? await _getVisitorTransactionById(_id) : null;
|
|
55639
|
-
const
|
|
55710
|
+
const needsMemberFanOut = nextStatus === "approved" /* APPROVED */ && !!current?.inviterId && Array.isArray(current.members) && current.members.length > 0 && current.members.every((member) => !member._id);
|
|
55711
|
+
const { members: _unusedMembers, ...bodyWithoutMembers } = req.body;
|
|
55712
|
+
const baseBody = needsMemberFanOut ? bodyWithoutMembers : req.body;
|
|
55713
|
+
const updateBody = approval ? { ...baseBody, status: nextStatus } : baseBody;
|
|
55640
55714
|
await _updateVisitorTransactionById(_id, updateBody);
|
|
55715
|
+
let invitedMemberIds = [];
|
|
55716
|
+
if (needsMemberFanOut) {
|
|
55717
|
+
try {
|
|
55718
|
+
invitedMemberIds = await _createInvitedMemberDocuments(
|
|
55719
|
+
_id,
|
|
55720
|
+
nextStatus
|
|
55721
|
+
);
|
|
55722
|
+
} catch (fanOutError) {
|
|
55723
|
+
import_node_server_utils137.logger.log({
|
|
55724
|
+
level: "error",
|
|
55725
|
+
message: `Failed to create member documents for approved invitation ${_id}: ${fanOutError?.message}`
|
|
55726
|
+
});
|
|
55727
|
+
}
|
|
55728
|
+
}
|
|
55729
|
+
for (const insertedId of invitedMemberIds) {
|
|
55730
|
+
resendEmailInvitedResident(
|
|
55731
|
+
String(insertedId),
|
|
55732
|
+
String(current?.inviterId)
|
|
55733
|
+
).catch((error2) => {
|
|
55734
|
+
import_node_server_utils137.logger.log({
|
|
55735
|
+
level: "error",
|
|
55736
|
+
message: `Failed to send invitation email: ${error2?.message}`
|
|
55737
|
+
});
|
|
55738
|
+
});
|
|
55739
|
+
}
|
|
55641
55740
|
if (isApprovalTransition && current && current.type === "contractor" /* CONTRACTOR */) {
|
|
55642
55741
|
try {
|
|
55643
55742
|
await _updateStatusByLeadId(_id, nextStatus);
|