@7365admin1/core 3.42.1 → 3.42.3
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 +96 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +217 -56
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +217 -56
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/test/camera-alert.util.test.mjs +109 -0
- package/test/dahua-protocol.util.test.mjs +217 -0
package/dist/index.mjs
CHANGED
|
@@ -21947,7 +21947,6 @@ async function sendEmail({ to, subject, text, html }) {
|
|
|
21947
21947
|
pass: MAILER_PASSWORD
|
|
21948
21948
|
}
|
|
21949
21949
|
});
|
|
21950
|
-
console.log(MAILER_EMAIL, MAILER_PASSWORD, MAILER_TRANSPORT_HOST, MAILER_TRANSPORT_PORT);
|
|
21951
21950
|
const mailOptions = {
|
|
21952
21951
|
from: `Seven 365 <${MAILER_EMAIL}>`,
|
|
21953
21952
|
to,
|
|
@@ -23993,6 +23992,86 @@ var loggerDahua = winston.createLogger({
|
|
|
23993
23992
|
]
|
|
23994
23993
|
});
|
|
23995
23994
|
|
|
23995
|
+
// src/utils/camera-alert.util.ts
|
|
23996
|
+
var DIRECTION_WORDS = {
|
|
23997
|
+
entry: "entry",
|
|
23998
|
+
exit: "exit",
|
|
23999
|
+
both: "entry and exit",
|
|
24000
|
+
visitors: "visitor",
|
|
24001
|
+
residents: "resident"
|
|
24002
|
+
};
|
|
24003
|
+
function describeCamera(camera) {
|
|
24004
|
+
const site = String(camera?.siteName ?? "").trim();
|
|
24005
|
+
const name = String(camera?.name ?? "").trim();
|
|
24006
|
+
const at = site ? ` at ${site}` : "";
|
|
24007
|
+
if (name)
|
|
24008
|
+
return `Camera "${name}"${at}`;
|
|
24009
|
+
const words = DIRECTION_WORDS[String(camera?.direction ?? "").trim().toLowerCase()];
|
|
24010
|
+
return words ? `The ${words} ANPR camera${at}` : `The ANPR camera${at}`;
|
|
24011
|
+
}
|
|
24012
|
+
function cameraUnreachableMessage(camera) {
|
|
24013
|
+
return `${describeCamera(camera)} is not responding. Plate reads and automatic barrier opening are stopped while it is down. The server keeps trying to reconnect on its own. If it does not come back, ask your site technician or the security-system installer to check the camera's power and network.`;
|
|
24014
|
+
}
|
|
24015
|
+
function cameraRecoveredMessage(camera) {
|
|
24016
|
+
return `${describeCamera(camera)} is responding again. Plate reads have resumed.`;
|
|
24017
|
+
}
|
|
24018
|
+
function cameraCredentialsMessage(camera) {
|
|
24019
|
+
return `${describeCamera(camera)} refused the login, so it cannot read plates. The username or password saved for it is wrong. An administrator can correct them in Site Settings > Cameras; if they are already correct, the camera's own account needs checking by your site technician.`;
|
|
24020
|
+
}
|
|
24021
|
+
function cameraNotAuthorisedMessage(camera) {
|
|
24022
|
+
return `${describeCamera(camera)} rejected our connection, so it cannot read plates. This usually means its account is not allowed to use the plate-reading feed. Please ask your site technician or the security-system installer to check the camera's account permissions.`;
|
|
24023
|
+
}
|
|
24024
|
+
var CAMERA_ALERT_AT_ATTEMPT = [1, 6, 30, 90];
|
|
24025
|
+
var CAMERA_ALERT_EVERY_AFTER = 90;
|
|
24026
|
+
function shouldAlertOnFailure(consecutiveFailures) {
|
|
24027
|
+
if (!Number.isInteger(consecutiveFailures) || consecutiveFailures < 1)
|
|
24028
|
+
return false;
|
|
24029
|
+
if (CAMERA_ALERT_AT_ATTEMPT.includes(consecutiveFailures))
|
|
24030
|
+
return true;
|
|
24031
|
+
if (consecutiveFailures < CAMERA_ALERT_EVERY_AFTER)
|
|
24032
|
+
return false;
|
|
24033
|
+
return (consecutiveFailures - CAMERA_ALERT_EVERY_AFTER) % CAMERA_ALERT_EVERY_AFTER === 0;
|
|
24034
|
+
}
|
|
24035
|
+
|
|
24036
|
+
// src/utils/dahua-protocol.util.ts
|
|
24037
|
+
var DAHUA_AUTH_ATTEMPTS_BEFORE_ALERT = 2;
|
|
24038
|
+
var AUTH_BACKOFF_MS = [6e4, 3e5, 9e5];
|
|
24039
|
+
function dahuaAuthBackoffMs(consecutiveAuthFailures) {
|
|
24040
|
+
const index = Math.min(
|
|
24041
|
+
Math.max(consecutiveAuthFailures, 1),
|
|
24042
|
+
AUTH_BACKOFF_MS.length
|
|
24043
|
+
);
|
|
24044
|
+
return AUTH_BACKOFF_MS[index - 1];
|
|
24045
|
+
}
|
|
24046
|
+
function classifyDahuaStatus(statusCode) {
|
|
24047
|
+
if (statusCode === 401)
|
|
24048
|
+
return "challenge";
|
|
24049
|
+
if (statusCode === 403)
|
|
24050
|
+
return "rejected";
|
|
24051
|
+
if (statusCode === 400 || statusCode === 500)
|
|
24052
|
+
return "fatal";
|
|
24053
|
+
return "proceed";
|
|
24054
|
+
}
|
|
24055
|
+
function classifyDahuaFailure(message) {
|
|
24056
|
+
const text = String(message ?? "");
|
|
24057
|
+
if (/\b403\b|forbidden/i.test(text))
|
|
24058
|
+
return "rejected";
|
|
24059
|
+
if (/\b401\b|unauthorized|invalid authority|digest/i.test(text)) {
|
|
24060
|
+
return "challenge";
|
|
24061
|
+
}
|
|
24062
|
+
return "transient";
|
|
24063
|
+
}
|
|
24064
|
+
var enc = (value) => encodeURIComponent(String(value));
|
|
24065
|
+
function buildPlateUpdateEndpoint(value) {
|
|
24066
|
+
return `/cgi-bin/recordUpdater.cgi?action=update&name=${enc(value.mode)}&recno=${enc(value.recno)}&PlateNumber=${enc(value.plateNumber)}&BeginTime=${enc(value.start)}&CancelTime=${enc(value.end)}&+OpenGate=${value.openGate}&MasterOfCar=${enc(value.owner)}`;
|
|
24067
|
+
}
|
|
24068
|
+
function buildPlateInsertEndpoint(value) {
|
|
24069
|
+
return `/cgi-bin/recordUpdater.cgi?action=insert&name=${enc(value.mode)}&PlateNumber=${enc(value.plateNumber)}&VehicleType=${enc(value.vehicleType)}&VehicleColor=${enc(value.vehicleColor)}&BeginTime=${enc(value.start)}&CancelTime=${enc(value.end)}&+OpenGate=${value.openGate}&MasterOfCar=${enc(value.owner)}`;
|
|
24070
|
+
}
|
|
24071
|
+
function buildPlateRemoveEndpoint(value) {
|
|
24072
|
+
return `/cgi-bin/recordUpdater.cgi?action=remove&recno=${enc(value.recno)}&name=${enc(value.mode)}`;
|
|
24073
|
+
}
|
|
24074
|
+
|
|
23996
24075
|
// src/services/dahua.service.ts
|
|
23997
24076
|
var cameraRegistry = /* @__PURE__ */ new Map();
|
|
23998
24077
|
var _savedOnDetected;
|
|
@@ -24038,7 +24117,8 @@ async function useDahuaDigestWithRetry({
|
|
|
24038
24117
|
retryDelayMs = 0
|
|
24039
24118
|
}) {
|
|
24040
24119
|
let lastError;
|
|
24041
|
-
|
|
24120
|
+
const effectiveRetries = Math.min(retries, DAHUA_AUTH_ATTEMPTS_BEFORE_ALERT);
|
|
24121
|
+
for (let attempt = 1; attempt <= effectiveRetries; attempt++) {
|
|
24042
24122
|
try {
|
|
24043
24123
|
const effectiveTimeout = streaming ? 0 : timeout;
|
|
24044
24124
|
const response = await useDahuaDigest({
|
|
@@ -24065,9 +24145,9 @@ async function useDahuaDigestWithRetry({
|
|
|
24065
24145
|
}
|
|
24066
24146
|
const errorText = streaming ? `HTTP ${statusCode}` : getDahuaResponseText(response);
|
|
24067
24147
|
const shouldRetry = isDahuaInvalidAuthority(errorText);
|
|
24068
|
-
if (shouldRetry && attempt <
|
|
24148
|
+
if (shouldRetry && attempt < effectiveRetries) {
|
|
24069
24149
|
loggerDahua.warn(
|
|
24070
|
-
`[${host}] Dahua Invalid Authority. Retrying ${attempt}/${
|
|
24150
|
+
`[${host}] Dahua Invalid Authority. Retrying ${attempt}/${effectiveRetries}`
|
|
24071
24151
|
);
|
|
24072
24152
|
if (retryDelayMs > 0) {
|
|
24073
24153
|
await sleep(retryDelayMs);
|
|
@@ -24079,9 +24159,9 @@ async function useDahuaDigestWithRetry({
|
|
|
24079
24159
|
lastError = error;
|
|
24080
24160
|
const message = error?.message || String(error);
|
|
24081
24161
|
const shouldRetry = isDahuaInvalidAuthority(message);
|
|
24082
|
-
if (shouldRetry && attempt <
|
|
24162
|
+
if (shouldRetry && attempt < effectiveRetries) {
|
|
24083
24163
|
loggerDahua.warn(
|
|
24084
|
-
`[${host}] Dahua Invalid Authority error. Retrying ${attempt}/${
|
|
24164
|
+
`[${host}] Dahua Invalid Authority error. Retrying ${attempt}/${effectiveRetries}`
|
|
24085
24165
|
);
|
|
24086
24166
|
if (retryDelayMs > 0) {
|
|
24087
24167
|
await sleep(retryDelayMs);
|
|
@@ -24185,7 +24265,9 @@ function useDahuaService() {
|
|
|
24185
24265
|
return;
|
|
24186
24266
|
try {
|
|
24187
24267
|
const result = await _checkOutBySiteAndPlate(site, plateNumber2);
|
|
24188
|
-
|
|
24268
|
+
loggerDahua.info(
|
|
24269
|
+
`checkOutBySiteAndPlate matched=${Boolean(result)} type=${result?.type ?? "none"}`
|
|
24270
|
+
);
|
|
24189
24271
|
if (onDetected2 && result?.type != "resident" && result?.site) {
|
|
24190
24272
|
onDetected2({ reload: true, site: result?.site?.toString(), siteName: camera?.siteName, cameraDirection: camera?.direction, direction });
|
|
24191
24273
|
}
|
|
@@ -24484,9 +24566,12 @@ function useDahuaService() {
|
|
|
24484
24566
|
);
|
|
24485
24567
|
}
|
|
24486
24568
|
async function getTrafficJunction(camera, signal, onDetected) {
|
|
24487
|
-
|
|
24569
|
+
loggerDahua.info(
|
|
24570
|
+
`getTrafficJunction starting for camera ${camera?._id} [${camera?.siteName}-${camera?.direction}] host ${camera?.host}`
|
|
24571
|
+
);
|
|
24488
24572
|
let authFailureCount = 0;
|
|
24489
|
-
|
|
24573
|
+
let connectionFailureCount = 0;
|
|
24574
|
+
let alertedFailureCount = 0;
|
|
24490
24575
|
while (!signal.aborted) {
|
|
24491
24576
|
let bufferQueue = null;
|
|
24492
24577
|
let response = null;
|
|
@@ -24501,31 +24586,48 @@ function useDahuaService() {
|
|
|
24501
24586
|
streaming: true
|
|
24502
24587
|
});
|
|
24503
24588
|
const statusCode = getDahuaStatusCode(response);
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
throw new Error("401 Unauthorized -
|
|
24508
|
-
} else if (
|
|
24589
|
+
const verdict = classifyDahuaStatus(statusCode);
|
|
24590
|
+
if (verdict === "challenge") {
|
|
24591
|
+
loggerDahua.warn(`[${camera?.siteName}-${camera?.direction}] 401 - digest challenge not completed; will re-authenticate after backoff.`);
|
|
24592
|
+
throw new Error("401 Unauthorized - digest challenge not completed");
|
|
24593
|
+
} else if (verdict === "rejected") {
|
|
24509
24594
|
loggerDahua.error(
|
|
24510
|
-
`[${camera?.siteName}-${camera?.direction}] 403 Forbidden
|
|
24595
|
+
`[${camera?.siteName}-${camera?.direction}] 403 Forbidden - credentials rejected by the device. Not retrying.`
|
|
24511
24596
|
);
|
|
24512
24597
|
if (onDetected) {
|
|
24513
24598
|
onDetected({
|
|
24514
24599
|
site: camera?.site,
|
|
24515
24600
|
direction: camera?.direction,
|
|
24516
|
-
|
|
24601
|
+
// Superseded #1803's own wording: cameraCredentialsMessage already
|
|
24602
|
+
// says the login was refused and where to correct it, and no longer
|
|
24603
|
+
// claims a ten-minute lockout.
|
|
24604
|
+
messagePermanent: cameraCredentialsMessage(camera)
|
|
24517
24605
|
});
|
|
24518
24606
|
}
|
|
24519
24607
|
return;
|
|
24520
|
-
} else if (
|
|
24608
|
+
} else if (verdict === "fatal") {
|
|
24521
24609
|
loggerDahua.error(`[${camera?.siteName}-${camera?.direction}] Connection error: ${statusCode}`);
|
|
24522
24610
|
if (onDetected) {
|
|
24523
|
-
onDetected({ site: camera?.site, messagePermanent:
|
|
24611
|
+
onDetected({ site: camera?.site, messagePermanent: cameraNotAuthorisedMessage(camera) });
|
|
24524
24612
|
}
|
|
24525
24613
|
return;
|
|
24526
24614
|
}
|
|
24527
24615
|
authFailureCount = 0;
|
|
24528
24616
|
isAuthFailure = false;
|
|
24617
|
+
if (alertedFailureCount > 0 && onDetected) {
|
|
24618
|
+
onDetected({
|
|
24619
|
+
site: camera?.site,
|
|
24620
|
+
direction: camera?.direction,
|
|
24621
|
+
// `event` and `camera` let a client group or clear an alert per
|
|
24622
|
+
// camera instead of matching on the message text. Older clients
|
|
24623
|
+
// ignore them and just render `message`, as they always have.
|
|
24624
|
+
event: "camera-recovered",
|
|
24625
|
+
camera: camera?._id?.toString(),
|
|
24626
|
+
message: cameraRecoveredMessage(camera)
|
|
24627
|
+
});
|
|
24628
|
+
}
|
|
24629
|
+
connectionFailureCount = 0;
|
|
24630
|
+
alertedFailureCount = 0;
|
|
24529
24631
|
loggerDahua.info(`[${camera?.siteName}-${camera?.direction}] Successfully connected to ANPR.`);
|
|
24530
24632
|
const contentType = response.res.headers["content-type"];
|
|
24531
24633
|
const boundaryMatch = contentType?.match(/boundary=(.*)$/i);
|
|
@@ -24571,50 +24673,53 @@ function useDahuaService() {
|
|
|
24571
24673
|
break;
|
|
24572
24674
|
}
|
|
24573
24675
|
const errMsg = String(error?.message || error);
|
|
24574
|
-
|
|
24676
|
+
const failure = classifyDahuaFailure(errMsg);
|
|
24677
|
+
if (failure === "rejected") {
|
|
24575
24678
|
loggerDahua.error(
|
|
24576
|
-
`[${camera?.siteName}-${camera?.direction}] 403 Forbidden thrown by Dahua client
|
|
24679
|
+
`[${camera?.siteName}-${camera?.direction}] 403 Forbidden thrown by Dahua client - credentials rejected. Not retrying.`
|
|
24577
24680
|
);
|
|
24578
24681
|
if (onDetected) {
|
|
24579
24682
|
onDetected({
|
|
24580
24683
|
site: camera?.site,
|
|
24581
24684
|
direction: camera?.direction,
|
|
24582
|
-
messagePermanent:
|
|
24685
|
+
messagePermanent: cameraCredentialsMessage(camera)
|
|
24583
24686
|
});
|
|
24584
24687
|
}
|
|
24585
24688
|
return;
|
|
24586
24689
|
}
|
|
24587
|
-
|
|
24588
|
-
if (isAuthError) {
|
|
24690
|
+
if (failure === "challenge") {
|
|
24589
24691
|
authFailureCount++;
|
|
24590
24692
|
isAuthFailure = true;
|
|
24591
24693
|
loggerDahua.warn(
|
|
24592
|
-
`[${camera?.siteName}-${camera?.direction}]
|
|
24694
|
+
`[${camera?.siteName}-${camera?.direction}] Authentication attempt ${authFailureCount} failed; next attempt in ${dahuaAuthBackoffMs(authFailureCount) / 1e3}s`
|
|
24593
24695
|
);
|
|
24594
|
-
if (authFailureCount
|
|
24696
|
+
if (authFailureCount === DAHUA_AUTH_ATTEMPTS_BEFORE_ALERT) {
|
|
24595
24697
|
loggerDahua.error(
|
|
24596
|
-
`[${camera?.siteName}-${camera?.direction}]
|
|
24698
|
+
`[${camera?.siteName}-${camera?.direction}] ${authFailureCount} consecutive authentication failures. Slowing down and alerting the operator.`
|
|
24597
24699
|
);
|
|
24598
24700
|
if (onDetected) {
|
|
24599
24701
|
onDetected({
|
|
24600
24702
|
site: camera?.site,
|
|
24601
24703
|
direction: camera?.direction,
|
|
24602
|
-
messagePermanent:
|
|
24704
|
+
messagePermanent: cameraCredentialsMessage(camera)
|
|
24603
24705
|
});
|
|
24604
24706
|
}
|
|
24605
|
-
return;
|
|
24606
24707
|
}
|
|
24607
24708
|
} else {
|
|
24608
24709
|
authFailureCount = 0;
|
|
24609
24710
|
isAuthFailure = false;
|
|
24711
|
+
connectionFailureCount++;
|
|
24610
24712
|
loggerDahua.error(
|
|
24611
|
-
`[${camera?.siteName}-${camera?.direction}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds
|
|
24713
|
+
`[${camera?.siteName}-${camera?.direction}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds... (failure ${connectionFailureCount})`
|
|
24612
24714
|
);
|
|
24613
|
-
if (onDetected) {
|
|
24715
|
+
if (onDetected && shouldAlertOnFailure(connectionFailureCount)) {
|
|
24716
|
+
alertedFailureCount = connectionFailureCount;
|
|
24614
24717
|
onDetected({
|
|
24615
24718
|
site: camera?.site,
|
|
24616
24719
|
direction: camera?.direction,
|
|
24617
|
-
|
|
24720
|
+
event: "camera-fault",
|
|
24721
|
+
camera: camera?._id?.toString(),
|
|
24722
|
+
message: cameraUnreachableMessage(camera)
|
|
24618
24723
|
});
|
|
24619
24724
|
}
|
|
24620
24725
|
}
|
|
@@ -24638,7 +24743,7 @@ function useDahuaService() {
|
|
|
24638
24743
|
}
|
|
24639
24744
|
}
|
|
24640
24745
|
if (!signal.aborted) {
|
|
24641
|
-
const waitMs = isAuthFailure ?
|
|
24746
|
+
const waitMs = isAuthFailure ? dahuaAuthBackoffMs(authFailureCount) : 1e4;
|
|
24642
24747
|
await new Promise((res) => setTimeout(res, waitMs));
|
|
24643
24748
|
}
|
|
24644
24749
|
}
|
|
@@ -24651,7 +24756,9 @@ function useDahuaService() {
|
|
|
24651
24756
|
return match ? match[1] : null;
|
|
24652
24757
|
}
|
|
24653
24758
|
async function addPlateNumber(value) {
|
|
24654
|
-
|
|
24759
|
+
loggerDahua.info(
|
|
24760
|
+
`addPlateNumber called for host ${value?.host} mode ${value?.mode}`
|
|
24761
|
+
);
|
|
24655
24762
|
let recno = null;
|
|
24656
24763
|
const validation = Joi42.object({
|
|
24657
24764
|
host: Joi42.string().required(),
|
|
@@ -24796,7 +24903,15 @@ function useDahuaService() {
|
|
|
24796
24903
|
const openGateString = String(value.isOpenGate);
|
|
24797
24904
|
_isOpenGate = openGateString ? openGateString : "true";
|
|
24798
24905
|
}
|
|
24799
|
-
const endpoint =
|
|
24906
|
+
const endpoint = buildPlateUpdateEndpoint({
|
|
24907
|
+
mode: value.mode,
|
|
24908
|
+
recno: value.recno,
|
|
24909
|
+
plateNumber: value.plateNumber,
|
|
24910
|
+
start: value.start,
|
|
24911
|
+
end: value.end,
|
|
24912
|
+
owner: value.owner,
|
|
24913
|
+
openGate: _isOpenGate
|
|
24914
|
+
});
|
|
24800
24915
|
try {
|
|
24801
24916
|
const response = await useDahuaDigest({
|
|
24802
24917
|
host: value.host,
|
|
@@ -24829,7 +24944,10 @@ function useDahuaService() {
|
|
|
24829
24944
|
host: value.host,
|
|
24830
24945
|
username: value.username,
|
|
24831
24946
|
password: value.password,
|
|
24832
|
-
endpoint:
|
|
24947
|
+
endpoint: buildPlateRemoveEndpoint({
|
|
24948
|
+
recno: value.recno,
|
|
24949
|
+
mode: value.mode
|
|
24950
|
+
})
|
|
24833
24951
|
});
|
|
24834
24952
|
return response;
|
|
24835
24953
|
} catch (error2) {
|
|
@@ -24885,7 +25003,16 @@ function useDahuaService() {
|
|
|
24885
25003
|
value.vehicleColor = String(value.vehicleColor ?? "").substring(0, 31) || "unknown";
|
|
24886
25004
|
const _openGate = String(value.isOpenGate);
|
|
24887
25005
|
const isOpenGateString = _openGate && _openGate !== "undefined" ? _openGate : "true";
|
|
24888
|
-
const endpoint =
|
|
25006
|
+
const endpoint = buildPlateInsertEndpoint({
|
|
25007
|
+
mode: value.mode,
|
|
25008
|
+
plateNumber: value.plateNumber,
|
|
25009
|
+
vehicleType: value.vehicleType,
|
|
25010
|
+
vehicleColor: value.vehicleColor,
|
|
25011
|
+
start: value.start,
|
|
25012
|
+
end: value.end,
|
|
25013
|
+
owner: value.owner,
|
|
25014
|
+
openGate: isOpenGateString
|
|
25015
|
+
});
|
|
24889
25016
|
try {
|
|
24890
25017
|
const response = await useDahuaDigest({
|
|
24891
25018
|
host: value.host,
|
|
@@ -25311,7 +25438,7 @@ function useSiteCameraRepo() {
|
|
|
25311
25438
|
}
|
|
25312
25439
|
if (result?._id) {
|
|
25313
25440
|
const result2 = await findOne({ _id: new ObjectId47(result._id) }, void 0, { session });
|
|
25314
|
-
|
|
25441
|
+
logger51.info(`Site camera ${result2?._id} re-read after update; reconnecting listener`);
|
|
25315
25442
|
if (result2?._id) {
|
|
25316
25443
|
const { listenToCamera } = useDahuaService();
|
|
25317
25444
|
await listenToCamera(result2);
|
|
@@ -27625,7 +27752,9 @@ function useVehicleService() {
|
|
|
27625
27752
|
siteCameras.push(...siteCameraReq.items);
|
|
27626
27753
|
page++;
|
|
27627
27754
|
} while (page <= pages);
|
|
27628
|
-
|
|
27755
|
+
logger55.info(
|
|
27756
|
+
`addVehicle resolved ${siteCameras.length} site camera(s) for site ${value?.site}`
|
|
27757
|
+
);
|
|
27629
27758
|
}
|
|
27630
27759
|
for (const plateNumber of plateNumbers) {
|
|
27631
27760
|
const vehicleValue = {
|
|
@@ -29960,6 +30089,20 @@ function useBuildingLevelRepo() {
|
|
|
29960
30089
|
}
|
|
29961
30090
|
}
|
|
29962
30091
|
}
|
|
30092
|
+
async function getActiveLevelsByIds(ids, session) {
|
|
30093
|
+
if (!ids || ids.length === 0)
|
|
30094
|
+
return [];
|
|
30095
|
+
let objectIds;
|
|
30096
|
+
try {
|
|
30097
|
+
objectIds = ids.map((id) => new ObjectId56(id));
|
|
30098
|
+
} catch {
|
|
30099
|
+
throw new BadRequestError83("Invalid level ID format.");
|
|
30100
|
+
}
|
|
30101
|
+
return collection.find(
|
|
30102
|
+
{ _id: { $in: objectIds }, status: "active" /* ACTIVE */ },
|
|
30103
|
+
{ projection: { _id: 1, name: 1 }, session }
|
|
30104
|
+
).toArray();
|
|
30105
|
+
}
|
|
29963
30106
|
async function updateLevelById(_id, value, session) {
|
|
29964
30107
|
try {
|
|
29965
30108
|
_id = new ObjectId56(_id);
|
|
@@ -30158,7 +30301,8 @@ function useBuildingLevelRepo() {
|
|
|
30158
30301
|
deleteById,
|
|
30159
30302
|
bulkWriteLevels,
|
|
30160
30303
|
batchUpdateByIds,
|
|
30161
|
-
getBuildingLevelList
|
|
30304
|
+
getBuildingLevelList,
|
|
30305
|
+
getActiveLevelsByIds
|
|
30162
30306
|
};
|
|
30163
30307
|
}
|
|
30164
30308
|
|
|
@@ -30179,7 +30323,10 @@ function useBuildingService() {
|
|
|
30179
30323
|
} = useBuildingUnitRepo();
|
|
30180
30324
|
const { updateStatusById } = useFileRepo();
|
|
30181
30325
|
const { deleteFile } = useFileService();
|
|
30182
|
-
const {
|
|
30326
|
+
const {
|
|
30327
|
+
bulkWriteLevels: _bulkWriteLevels,
|
|
30328
|
+
getActiveLevelsByIds: _getActiveLevelsByIds
|
|
30329
|
+
} = useBuildingLevelRepo();
|
|
30183
30330
|
function normalizeImportKey(value) {
|
|
30184
30331
|
return String(value ?? "").trim().replace(/\s+/g, " ").toLowerCase();
|
|
30185
30332
|
}
|
|
@@ -30273,23 +30420,37 @@ function useBuildingService() {
|
|
|
30273
30420
|
);
|
|
30274
30421
|
}
|
|
30275
30422
|
if (data.levels) {
|
|
30276
|
-
const
|
|
30277
|
-
|
|
30278
|
-
|
|
30279
|
-
|
|
30280
|
-
|
|
30281
|
-
|
|
30282
|
-
|
|
30283
|
-
|
|
30284
|
-
|
|
30285
|
-
|
|
30286
|
-
|
|
30287
|
-
|
|
30288
|
-
|
|
30289
|
-
|
|
30290
|
-
|
|
30291
|
-
|
|
30423
|
+
const submittedNames = data.levels ?? [];
|
|
30424
|
+
const existingLevels = await _getActiveLevelsByIds(
|
|
30425
|
+
building.levels,
|
|
30426
|
+
session
|
|
30427
|
+
);
|
|
30428
|
+
const seenKeys = new Set(
|
|
30429
|
+
existingLevels.map((level) => normalizeImportKey(level.name))
|
|
30430
|
+
);
|
|
30431
|
+
const newNames = submittedNames.filter((name) => {
|
|
30432
|
+
const key = normalizeImportKey(name);
|
|
30433
|
+
if (seenKeys.has(key))
|
|
30434
|
+
return false;
|
|
30435
|
+
seenKeys.add(key);
|
|
30436
|
+
return true;
|
|
30437
|
+
});
|
|
30438
|
+
if (newNames.length > 0) {
|
|
30439
|
+
const levelsPayload = newNames.map((name) => ({
|
|
30440
|
+
blockId: id,
|
|
30441
|
+
site: building.site.toString(),
|
|
30442
|
+
name,
|
|
30443
|
+
status: "active" /* ACTIVE */,
|
|
30444
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
30445
|
+
updatedAt: null,
|
|
30446
|
+
deletedAt: null
|
|
30447
|
+
}));
|
|
30448
|
+
const levels = await _bulkWriteLevels(levelsPayload, session);
|
|
30449
|
+
if (levels) {
|
|
30450
|
+
building.levels.push(...Object.values(levels.insertedIds));
|
|
30451
|
+
}
|
|
30292
30452
|
}
|
|
30453
|
+
data.levels = building.levels.map((levelId) => levelId.toString());
|
|
30293
30454
|
}
|
|
30294
30455
|
const buildingUnitData = {};
|
|
30295
30456
|
if (data.name && building.name !== data.name) {
|