@7365admin1/core 3.52.4 → 3.52.5-staging.249
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/expired-vehicle-cron-per-site-fail-closed.md +27 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +134 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +134 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/expired-vehicle-sweep.test.mjs +175 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Revoke each expired vehicle's plate only from its own site's cameras, and fail closed
|
|
6
|
+
|
|
7
|
+
The hourly expired-vehicle sweep declared its `siteCameras` array outside the
|
|
8
|
+
per-vehicle loop and only ever pushed to it, so the list grew as the run went
|
|
9
|
+
on: the second vehicle was revoked from its own site's cameras AND from the
|
|
10
|
+
first vehicle's. `recNo` is a per-device sequence number, so those extra calls
|
|
11
|
+
did not simply miss - they addressed whatever record carried that number on the
|
|
12
|
+
other site's camera and took an unrelated, still-valid plate off the barrier.
|
|
13
|
+
Cameras are now resolved per vehicle, cached by site id so each site is still
|
|
14
|
+
paged only once per run.
|
|
15
|
+
|
|
16
|
+
The sweep also marked every expired vehicle deleted with one blanket
|
|
17
|
+
`updateMany`, regardless of what the cameras answered, so a record could read
|
|
18
|
+
"deleted" while its plate still opened the gate. Only vehicles whose every
|
|
19
|
+
camera confirmed are now marked deleted; the rest are left expired, logged at
|
|
20
|
+
error level naming the vehicle, plate, site and camera, and retried on the next
|
|
21
|
+
hourly run. `deleteExpiredVehicles` now requires the list of ids.
|
|
22
|
+
|
|
23
|
+
Also: each camera is asked for its own record id before the removal (the same
|
|
24
|
+
thing manual delete does), the ANPR list is chosen from the vehicle's type
|
|
25
|
+
instead of always TRAFFIC_REDLIST, a site with no ANPR camera no longer aborts
|
|
26
|
+
the whole run, and the transaction that wrapped nothing - the only write was
|
|
27
|
+
issued without the session - has been removed.
|
package/dist/index.d.ts
CHANGED
|
@@ -2818,7 +2818,7 @@ declare function useVehicleRepo(): {
|
|
|
2818
2818
|
pages: number;
|
|
2819
2819
|
pageRange: string;
|
|
2820
2820
|
}>;
|
|
2821
|
-
deleteExpiredVehicles: (session?: ClientSession) => Promise<number>;
|
|
2821
|
+
deleteExpiredVehicles: (ids: Array<string | ObjectId>, session?: ClientSession) => Promise<number>;
|
|
2822
2822
|
getAllVehiclesByUnitId: ({ unitId, page, limit, sort, }: {
|
|
2823
2823
|
unitId: string | ObjectId;
|
|
2824
2824
|
page?: number | undefined;
|
package/dist/index.js
CHANGED
|
@@ -22041,11 +22041,15 @@ function useVehicleRepo() {
|
|
|
22041
22041
|
throw error;
|
|
22042
22042
|
}
|
|
22043
22043
|
}
|
|
22044
|
-
async function deleteExpiredVehicles(session) {
|
|
22044
|
+
async function deleteExpiredVehicles(ids2, session) {
|
|
22045
22045
|
try {
|
|
22046
22046
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22047
|
+
const _ids = (ids2 ?? []).map((id) => (0, import_node_server_utils50.toObjectId)(id)).filter((id) => Boolean(id));
|
|
22048
|
+
if (!_ids.length)
|
|
22049
|
+
return 0;
|
|
22047
22050
|
const res = await collection.updateMany(
|
|
22048
22051
|
{
|
|
22052
|
+
_id: { $in: _ids },
|
|
22049
22053
|
status: { $ne: "deleted" },
|
|
22050
22054
|
end: { $exists: true, $lte: now }
|
|
22051
22055
|
// check only end
|
|
@@ -22528,6 +22532,10 @@ function anprRevokeThrew(error) {
|
|
|
22528
22532
|
detail: message || "the request threw with no message"
|
|
22529
22533
|
};
|
|
22530
22534
|
}
|
|
22535
|
+
var ANPR_NOTHING_TO_REVOKE = {
|
|
22536
|
+
ok: true,
|
|
22537
|
+
reason: ""
|
|
22538
|
+
};
|
|
22531
22539
|
function cameraFailureSentence(options) {
|
|
22532
22540
|
const { failures, operation, outcome, consequence } = options;
|
|
22533
22541
|
return `The plate number could not be ${operation} ${cameraCount(failures.length)}: ${cameraDetail(failures)}. The vehicle has NOT been ${outcome} \u2014 ${consequence}. Please check the ${failures.length === 1 ? "camera" : "cameras"} and try again.`;
|
|
@@ -35317,6 +35325,70 @@ function useBuildingUnitRepo() {
|
|
|
35317
35325
|
};
|
|
35318
35326
|
}
|
|
35319
35327
|
|
|
35328
|
+
// src/utils/expired-vehicle-sweep.util.ts
|
|
35329
|
+
async function sweepExpiredVehicles(vehicles, deps) {
|
|
35330
|
+
const { camerasForSite, revoke, cameraLabel: cameraLabel2 } = deps;
|
|
35331
|
+
const deletableIds = [];
|
|
35332
|
+
const skipped = [];
|
|
35333
|
+
const bySite = /* @__PURE__ */ new Map();
|
|
35334
|
+
for (const vehicle of vehicles) {
|
|
35335
|
+
const id = String(vehicle?._id ?? "");
|
|
35336
|
+
const site = String(vehicle?.site ?? "");
|
|
35337
|
+
const plateNumber = String(vehicle?.plateNumber ?? "");
|
|
35338
|
+
if (!site) {
|
|
35339
|
+
skipped.push({
|
|
35340
|
+
id,
|
|
35341
|
+
site,
|
|
35342
|
+
plateNumber,
|
|
35343
|
+
failures: [
|
|
35344
|
+
{ camera: "unknown", reason: "the vehicle has no site on record" }
|
|
35345
|
+
]
|
|
35346
|
+
});
|
|
35347
|
+
continue;
|
|
35348
|
+
}
|
|
35349
|
+
let cameras = bySite.get(site);
|
|
35350
|
+
if (!cameras) {
|
|
35351
|
+
try {
|
|
35352
|
+
cameras = await camerasForSite(site);
|
|
35353
|
+
} catch (error) {
|
|
35354
|
+
skipped.push({
|
|
35355
|
+
id,
|
|
35356
|
+
site,
|
|
35357
|
+
plateNumber,
|
|
35358
|
+
failures: [
|
|
35359
|
+
{
|
|
35360
|
+
camera: "unknown",
|
|
35361
|
+
reason: "the site's cameras could not be listed"
|
|
35362
|
+
}
|
|
35363
|
+
]
|
|
35364
|
+
});
|
|
35365
|
+
continue;
|
|
35366
|
+
}
|
|
35367
|
+
bySite.set(site, cameras);
|
|
35368
|
+
}
|
|
35369
|
+
const failures = [];
|
|
35370
|
+
for (const camera of cameras) {
|
|
35371
|
+
const outcome = await revoke(camera, vehicle);
|
|
35372
|
+
if (!outcome?.ok) {
|
|
35373
|
+
failures.push({
|
|
35374
|
+
camera: cameraLabel2(camera),
|
|
35375
|
+
reason: outcome?.reason || "the camera could not be reached"
|
|
35376
|
+
});
|
|
35377
|
+
}
|
|
35378
|
+
}
|
|
35379
|
+
if (failures.length) {
|
|
35380
|
+
skipped.push({ id, site, plateNumber, failures });
|
|
35381
|
+
continue;
|
|
35382
|
+
}
|
|
35383
|
+
deletableIds.push(id);
|
|
35384
|
+
}
|
|
35385
|
+
return { deletableIds, skipped };
|
|
35386
|
+
}
|
|
35387
|
+
function sweepSkipLine(entry) {
|
|
35388
|
+
const detail = entry.failures.map((failure) => `${failure.camera} (${failure.reason})`).join(", ");
|
|
35389
|
+
return `processDeletingExpiredVehicles NOT deleting vehicle ${entry.id} (plate ${JSON.stringify(entry.plateNumber)}, site ${entry.site}): ${detail}. The plate may still open the barrier, so the record is left expired and will be retried on the next run.`;
|
|
35390
|
+
}
|
|
35391
|
+
|
|
35320
35392
|
// src/services/vehicle.service.ts
|
|
35321
35393
|
function formatDahuaDate(date) {
|
|
35322
35394
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -35793,17 +35865,10 @@ function useVehicleService() {
|
|
|
35793
35865
|
}
|
|
35794
35866
|
}
|
|
35795
35867
|
async function processDeletingExpiredVehicles() {
|
|
35796
|
-
const
|
|
35797
|
-
|
|
35798
|
-
|
|
35799
|
-
|
|
35800
|
-
try {
|
|
35801
|
-
session.startTransaction();
|
|
35802
|
-
const vehicles = await _getAllExpiredVehicles();
|
|
35803
|
-
let siteCameras = [];
|
|
35804
|
-
for (const vehicle of vehicles) {
|
|
35805
|
-
const site = vehicle.site;
|
|
35806
|
-
const recno = vehicle.recNo;
|
|
35868
|
+
const vehicles = await _getAllExpiredVehicles();
|
|
35869
|
+
const { deletableIds, skipped } = await sweepExpiredVehicles(vehicles, {
|
|
35870
|
+
camerasForSite: async (site) => {
|
|
35871
|
+
const cameras = [];
|
|
35807
35872
|
let page = 1;
|
|
35808
35873
|
let pages = 1;
|
|
35809
35874
|
const limit = 20;
|
|
@@ -35811,44 +35876,72 @@ function useVehicleService() {
|
|
|
35811
35876
|
const siteCameraReq = await _getAllSiteCameras({
|
|
35812
35877
|
site,
|
|
35813
35878
|
type: "anpr",
|
|
35814
|
-
direction: ["both", "entry"],
|
|
35815
35879
|
page,
|
|
35816
35880
|
limit
|
|
35817
35881
|
});
|
|
35818
35882
|
pages = siteCameraReq.pages || 1;
|
|
35819
|
-
|
|
35883
|
+
cameras.push(...siteCameraReq.items);
|
|
35820
35884
|
page++;
|
|
35821
35885
|
} while (page <= pages);
|
|
35822
|
-
|
|
35823
|
-
|
|
35886
|
+
return cameras;
|
|
35887
|
+
},
|
|
35888
|
+
revoke: async (camera, vehicle) => {
|
|
35889
|
+
const host = camera.host;
|
|
35890
|
+
const username = camera.username;
|
|
35891
|
+
const password = camera.password;
|
|
35892
|
+
const mode = vehicle?.type !== "whitelist" /* WHITELIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
|
|
35893
|
+
const plateNumber = String(vehicle?.plateNumber ?? "");
|
|
35894
|
+
let recnoForCamera = String(vehicle?.recNo ?? "");
|
|
35895
|
+
if (plateNumber) {
|
|
35896
|
+
try {
|
|
35897
|
+
const found = parseDahuaFind(
|
|
35898
|
+
await _getPlateNumber({
|
|
35899
|
+
host,
|
|
35900
|
+
username,
|
|
35901
|
+
password,
|
|
35902
|
+
mode,
|
|
35903
|
+
plateNumber,
|
|
35904
|
+
requireOk: true
|
|
35905
|
+
}) ?? ""
|
|
35906
|
+
);
|
|
35907
|
+
if (!found.exists)
|
|
35908
|
+
return ANPR_NOTHING_TO_REVOKE;
|
|
35909
|
+
if (found.recNo)
|
|
35910
|
+
recnoForCamera = found.recNo;
|
|
35911
|
+
} catch (error) {
|
|
35912
|
+
const outcome2 = anprRevokeThrew(error);
|
|
35913
|
+
logCameraFailure("expiredVehicle lookup", camera, outcome2.detail);
|
|
35914
|
+
return outcome2;
|
|
35915
|
+
}
|
|
35824
35916
|
}
|
|
35825
|
-
|
|
35826
|
-
|
|
35827
|
-
|
|
35828
|
-
|
|
35829
|
-
|
|
35830
|
-
|
|
35831
|
-
|
|
35832
|
-
|
|
35833
|
-
|
|
35834
|
-
|
|
35835
|
-
|
|
35836
|
-
|
|
35917
|
+
if (!recnoForCamera)
|
|
35918
|
+
return ANPR_NOTHING_TO_REVOKE;
|
|
35919
|
+
const outcome = await _removePlateNumber({
|
|
35920
|
+
host,
|
|
35921
|
+
username,
|
|
35922
|
+
password,
|
|
35923
|
+
recno: recnoForCamera,
|
|
35924
|
+
mode
|
|
35925
|
+
}).catch((error) => anprRevokeThrew(error));
|
|
35926
|
+
if (!outcome?.ok) {
|
|
35927
|
+
logCameraFailure(
|
|
35928
|
+
"expiredVehicle revoke",
|
|
35929
|
+
camera,
|
|
35930
|
+
outcome?.detail ?? "no outcome returned"
|
|
35931
|
+
);
|
|
35837
35932
|
}
|
|
35838
|
-
|
|
35839
|
-
|
|
35840
|
-
|
|
35841
|
-
|
|
35842
|
-
|
|
35843
|
-
import_node_server_utils99.logger.error(
|
|
35844
|
-
"Error in vehicle service process deleting expired vehicles:",
|
|
35845
|
-
error
|
|
35846
|
-
);
|
|
35847
|
-
await session.abortTransaction();
|
|
35848
|
-
throw error;
|
|
35849
|
-
} finally {
|
|
35850
|
-
session.endSession();
|
|
35933
|
+
return outcome ?? anprRevokeThrew("no outcome returned");
|
|
35934
|
+
},
|
|
35935
|
+
cameraLabel
|
|
35936
|
+
});
|
|
35937
|
+
for (const entry of skipped) {
|
|
35938
|
+
import_node_server_utils99.logger.error(sweepSkipLine(entry));
|
|
35851
35939
|
}
|
|
35940
|
+
const deleted = deletableIds.length ? await _deleteExpiredVehicles(deletableIds) : 0;
|
|
35941
|
+
import_node_server_utils99.logger.info(
|
|
35942
|
+
`processDeletingExpiredVehicles: ${vehicles.length} expired, ${deleted} deleted, ${skipped.length} left for the next run.`
|
|
35943
|
+
);
|
|
35944
|
+
return `Expired Vehicle plate numbers deleted successfully.`;
|
|
35852
35945
|
}
|
|
35853
35946
|
async function reactivateVehicleById(id, orgId, siteId) {
|
|
35854
35947
|
const session = import_node_server_utils99.useAtlas.getClient()?.startSession();
|