@7365admin1/core 3.21.1 → 3.22.0
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 +6 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +362 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +362 -85
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -23890,6 +23890,7 @@ function useDahuaService() {
|
|
|
23890
23890
|
return match ? match[1] : null;
|
|
23891
23891
|
}
|
|
23892
23892
|
async function addPlateNumber(value) {
|
|
23893
|
+
console.log("addPlateNumber called with value:", value);
|
|
23893
23894
|
let recno = null;
|
|
23894
23895
|
const validation = Joi40.object({
|
|
23895
23896
|
host: Joi40.string().required(),
|
|
@@ -23907,7 +23908,7 @@ function useDahuaService() {
|
|
|
23907
23908
|
throw new BadRequestError70(`Validation error: ${validationError.message}`);
|
|
23908
23909
|
}
|
|
23909
23910
|
try {
|
|
23910
|
-
value.owner = String(value.owner ?? "").substring(0, 15) || "unknown";
|
|
23911
|
+
value.owner = String(value.owner ?? "").replace(/['"]/g, "").replace(/[\/\\]/g, " - ").substring(0, 15).trim() || "unknown";
|
|
23911
23912
|
const formatDahuaDate2 = (dateStr, fallbackYearsAhead = 0) => {
|
|
23912
23913
|
const date = dateStr ? new Date(dateStr) : /* @__PURE__ */ new Date();
|
|
23913
23914
|
if (!dateStr) {
|
|
@@ -26798,58 +26799,25 @@ function useVehicleService() {
|
|
|
26798
26799
|
for (const camera of siteCameras) {
|
|
26799
26800
|
const { host, username, password } = camera;
|
|
26800
26801
|
const plateNumber2 = vehicleValue.plateNumber;
|
|
26801
|
-
const
|
|
26802
|
+
const dahuaPayload = {
|
|
26802
26803
|
host,
|
|
26803
26804
|
username,
|
|
26804
26805
|
password,
|
|
26805
26806
|
plateNumber: plateNumber2,
|
|
26806
|
-
mode: _mode
|
|
26807
|
+
mode: _mode,
|
|
26808
|
+
owner,
|
|
26809
|
+
...startDateDahua ? { start: startDateDahua } : {},
|
|
26810
|
+
...endDateDahua ? { end: endDateDahua } : {}
|
|
26807
26811
|
};
|
|
26808
|
-
const
|
|
26809
|
-
const
|
|
26810
|
-
|
|
26811
|
-
|
|
26812
|
-
|
|
26813
|
-
|
|
26814
|
-
|
|
26815
|
-
plateNumber: plateNumber2,
|
|
26816
|
-
mode: _mode,
|
|
26817
|
-
owner,
|
|
26818
|
-
...startDateDahua ? { start: startDateDahua } : {},
|
|
26819
|
-
...endDateDahua ? { end: endDateDahua } : {}
|
|
26820
|
-
};
|
|
26821
|
-
const dahuaResponse = await _addPlateNumber(dahuaPayload);
|
|
26822
|
-
const responseStatus = dahuaResponse?.statusCode || dahuaResponse?.status || dahuaResponse?.res?.status || "unknown";
|
|
26823
|
-
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
26824
|
-
if (responseStatus !== 200) {
|
|
26825
|
-
throw new BadRequestError75(
|
|
26826
|
-
`Failed to add plate number to ANPR ${_type}: status=${responseStatus}, response=${responseData}`
|
|
26827
|
-
);
|
|
26828
|
-
}
|
|
26829
|
-
vehicleValue.recNo = responseData.split("=")[1]?.trim();
|
|
26830
|
-
} else {
|
|
26831
|
-
const dahuaPayload = {
|
|
26832
|
-
host,
|
|
26833
|
-
username,
|
|
26834
|
-
password,
|
|
26835
|
-
plateNumber: plateNumber2,
|
|
26836
|
-
recno: parsed.recNo,
|
|
26837
|
-
mode: _mode,
|
|
26838
|
-
...startDateDahua ? { start: startDateDahua } : {},
|
|
26839
|
-
...endDateDahua ? { end: endDateDahua } : {},
|
|
26840
|
-
owner,
|
|
26841
|
-
isOpenGate: true
|
|
26842
|
-
};
|
|
26843
|
-
const dahuaResponse = await _updatePlateNumber(dahuaPayload);
|
|
26844
|
-
const responseStatus = dahuaResponse?.statusCode || dahuaResponse?.status || dahuaResponse?.res?.status || "unknown";
|
|
26845
|
-
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
26846
|
-
if (responseStatus !== 200) {
|
|
26847
|
-
throw new BadRequestError75(
|
|
26848
|
-
`Failed to update plate number to ANPR ${_type}: status=${responseStatus}, response=${responseData}`
|
|
26849
|
-
);
|
|
26850
|
-
}
|
|
26851
|
-
vehicleValue.recNo = parsed.recNo;
|
|
26812
|
+
const dahuaResponse = await _addPlateNumber(dahuaPayload);
|
|
26813
|
+
const responseStatus = dahuaResponse?.statusCode || dahuaResponse?.status || dahuaResponse?.res?.status || "unknown";
|
|
26814
|
+
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
26815
|
+
if (responseStatus !== 200) {
|
|
26816
|
+
throw new BadRequestError75(
|
|
26817
|
+
`Failed to add plate number to ANPR ${_type}: status=${responseStatus}, response=${responseData}`
|
|
26818
|
+
);
|
|
26852
26819
|
}
|
|
26820
|
+
vehicleValue.recNo = responseData.split("=")[1]?.trim();
|
|
26853
26821
|
if (value.peopleId && vehicleValue.recNo) {
|
|
26854
26822
|
await _pushVehicleById(
|
|
26855
26823
|
value.peopleId,
|
|
@@ -28394,10 +28362,22 @@ function useBuildingRepo() {
|
|
|
28394
28362
|
{
|
|
28395
28363
|
$lookup: {
|
|
28396
28364
|
from: "building-levels",
|
|
28397
|
-
|
|
28398
|
-
|
|
28365
|
+
let: {
|
|
28366
|
+
buildingId: "$_id",
|
|
28367
|
+
buildingLevelIds: { $ifNull: ["$levels", []] }
|
|
28368
|
+
},
|
|
28399
28369
|
pipeline: [
|
|
28400
|
-
{
|
|
28370
|
+
{
|
|
28371
|
+
$match: {
|
|
28372
|
+
status: "active" /* ACTIVE */,
|
|
28373
|
+
$expr: {
|
|
28374
|
+
$or: [
|
|
28375
|
+
{ $in: ["$_id", "$$buildingLevelIds"] },
|
|
28376
|
+
{ $eq: ["$blockId", "$$buildingId"] }
|
|
28377
|
+
]
|
|
28378
|
+
}
|
|
28379
|
+
}
|
|
28380
|
+
},
|
|
28401
28381
|
{ $project: { _id: 1, name: 1 } }
|
|
28402
28382
|
],
|
|
28403
28383
|
as: "levels"
|
|
@@ -28979,6 +28959,12 @@ function useBuildingLevelRepo() {
|
|
|
28979
28959
|
message: `Failed to clear cache namespace for ${building_level_namespace_collection}: ${err.message}`
|
|
28980
28960
|
});
|
|
28981
28961
|
});
|
|
28962
|
+
delBuildingNamespace().catch((err) => {
|
|
28963
|
+
logger63.log({
|
|
28964
|
+
level: "error",
|
|
28965
|
+
message: `Failed to clear buildings cache: ${err.message}`
|
|
28966
|
+
});
|
|
28967
|
+
});
|
|
28982
28968
|
}
|
|
28983
28969
|
async function add(value, session) {
|
|
28984
28970
|
try {
|
|
@@ -29178,12 +29164,6 @@ function useBuildingLevelRepo() {
|
|
|
29178
29164
|
{ session }
|
|
29179
29165
|
);
|
|
29180
29166
|
delCachedData();
|
|
29181
|
-
delBuildingNamespace().catch((err) => {
|
|
29182
|
-
logger63.log({
|
|
29183
|
-
level: "error",
|
|
29184
|
-
message: `Failed to clear buildings cache: ${err.message}`
|
|
29185
|
-
});
|
|
29186
|
-
});
|
|
29187
29167
|
return "Successfully deleted building level";
|
|
29188
29168
|
} catch (error) {
|
|
29189
29169
|
logger63.log({
|
|
@@ -29346,6 +29326,18 @@ function useBuildingService() {
|
|
|
29346
29326
|
const { updateStatusById } = useFileRepo();
|
|
29347
29327
|
const { deleteFile } = useFileService();
|
|
29348
29328
|
const { bulkWriteLevels: _bulkWriteLevels } = useBuildingLevelRepo();
|
|
29329
|
+
function normalizeImportKey(value) {
|
|
29330
|
+
return String(value ?? "").trim().replace(/\s+/g, " ").toLowerCase();
|
|
29331
|
+
}
|
|
29332
|
+
function toObjectIdOrNull(value) {
|
|
29333
|
+
if (value instanceof ObjectId54) {
|
|
29334
|
+
return value;
|
|
29335
|
+
}
|
|
29336
|
+
if (typeof value === "string" && ObjectId54.isValid(value)) {
|
|
29337
|
+
return new ObjectId54(value);
|
|
29338
|
+
}
|
|
29339
|
+
return null;
|
|
29340
|
+
}
|
|
29349
29341
|
async function add(value) {
|
|
29350
29342
|
const session = useAtlas41.getClient()?.startSession();
|
|
29351
29343
|
if (!session) {
|
|
@@ -29488,47 +29480,161 @@ function useBuildingService() {
|
|
|
29488
29480
|
}
|
|
29489
29481
|
async function uploadSpreadsheetBuilding(data, site) {
|
|
29490
29482
|
const session = useAtlas41.getClient()?.startSession();
|
|
29483
|
+
const db = useAtlas41.getDb();
|
|
29491
29484
|
if (!session) {
|
|
29492
29485
|
throw new BadRequestError83("Database session not available.");
|
|
29493
29486
|
}
|
|
29487
|
+
if (!db) {
|
|
29488
|
+
throw new BadRequestError83("Database not available.");
|
|
29489
|
+
}
|
|
29494
29490
|
try {
|
|
29495
29491
|
session.startTransaction();
|
|
29496
29492
|
const blockMap = /* @__PURE__ */ new Map();
|
|
29493
|
+
const skippedRows = [];
|
|
29497
29494
|
for (const row of data) {
|
|
29498
29495
|
const block = row.block;
|
|
29499
29496
|
const name = row.name;
|
|
29497
|
+
const rowNumber = row.__rowNumber;
|
|
29500
29498
|
const rawLevel = row.level?.toString().trim();
|
|
29501
29499
|
const rawUnit = row.unit?.toString().trim();
|
|
29502
29500
|
const normalizedLevel = /^(na|n\/a)$/i.test(rawLevel) ? null : rawLevel;
|
|
29503
29501
|
const normalizedUnit = /^(na|n\/a)$/i.test(rawUnit) ? null : rawUnit;
|
|
29504
29502
|
if (!blockMap.has(block)) {
|
|
29505
|
-
blockMap.set(block, {
|
|
29503
|
+
blockMap.set(block, {
|
|
29504
|
+
name,
|
|
29505
|
+
levels: [],
|
|
29506
|
+
rows: [],
|
|
29507
|
+
unitsByLevel: /* @__PURE__ */ new Map(),
|
|
29508
|
+
unitRowsByLevel: /* @__PURE__ */ new Map()
|
|
29509
|
+
});
|
|
29506
29510
|
}
|
|
29507
29511
|
const blockEntry = blockMap.get(block);
|
|
29512
|
+
blockEntry.rows.push(rowNumber);
|
|
29508
29513
|
if (blockEntry.name !== name) {
|
|
29509
29514
|
blockEntry.name = `${blockEntry.name}, ${name}`;
|
|
29510
29515
|
}
|
|
29511
|
-
if (normalizedLevel && !blockEntry.levels.
|
|
29516
|
+
if (normalizedLevel && !blockEntry.levels.some(
|
|
29517
|
+
(level) => normalizeImportKey(level) === normalizeImportKey(normalizedLevel)
|
|
29518
|
+
)) {
|
|
29512
29519
|
blockEntry.levels.push(normalizedLevel);
|
|
29513
29520
|
}
|
|
29514
29521
|
if (normalizedLevel && normalizedUnit) {
|
|
29515
29522
|
if (!blockEntry.unitsByLevel.has(normalizedLevel)) {
|
|
29516
29523
|
blockEntry.unitsByLevel.set(normalizedLevel, []);
|
|
29517
29524
|
}
|
|
29525
|
+
if (!blockEntry.unitRowsByLevel.has(normalizedLevel)) {
|
|
29526
|
+
blockEntry.unitRowsByLevel.set(normalizedLevel, /* @__PURE__ */ new Map());
|
|
29527
|
+
}
|
|
29518
29528
|
const units = blockEntry.unitsByLevel.get(normalizedLevel);
|
|
29519
|
-
|
|
29529
|
+
const unitRows = blockEntry.unitRowsByLevel.get(normalizedLevel);
|
|
29530
|
+
if (!units.some(
|
|
29531
|
+
(unit) => normalizeImportKey(unit) === normalizeImportKey(normalizedUnit)
|
|
29532
|
+
)) {
|
|
29520
29533
|
units.push(normalizedUnit);
|
|
29521
29534
|
}
|
|
29535
|
+
unitRows.set(normalizedUnit, [
|
|
29536
|
+
...unitRows.get(normalizedUnit) ?? [],
|
|
29537
|
+
rowNumber
|
|
29538
|
+
]);
|
|
29522
29539
|
}
|
|
29523
29540
|
}
|
|
29524
29541
|
const buildingPayloads = [];
|
|
29525
29542
|
const buildingUnitPayloads = [];
|
|
29543
|
+
if (blockMap.size === 0) {
|
|
29544
|
+
await session.commitTransaction();
|
|
29545
|
+
return {
|
|
29546
|
+
buildingPayloads,
|
|
29547
|
+
buildingUnitPayloads,
|
|
29548
|
+
insertedBuildings: 0,
|
|
29549
|
+
insertedUnits: 0,
|
|
29550
|
+
skippedRows
|
|
29551
|
+
};
|
|
29552
|
+
}
|
|
29553
|
+
const buildingsCollection = db.collection(buildings_namespace_collection);
|
|
29554
|
+
const buildingLevelsCollection = db.collection(
|
|
29555
|
+
building_level_namespace_collection
|
|
29556
|
+
);
|
|
29557
|
+
const buildingUnitsCollection = db.collection(
|
|
29558
|
+
building_units_namespace_collection
|
|
29559
|
+
);
|
|
29560
|
+
const existingBuildings = await buildingsCollection.find(
|
|
29561
|
+
{
|
|
29562
|
+
site: new ObjectId54(site),
|
|
29563
|
+
status: "active" /* ACTIVE */,
|
|
29564
|
+
$or: Array.from(blockMap.entries()).flatMap(([block, entry]) => [
|
|
29565
|
+
{ block },
|
|
29566
|
+
{ name: entry.name }
|
|
29567
|
+
])
|
|
29568
|
+
},
|
|
29569
|
+
{ session, projection: { block: 1, name: 1, levels: 1 } }
|
|
29570
|
+
).toArray();
|
|
29571
|
+
const existingBuildingsByBlock = new Map(
|
|
29572
|
+
existingBuildings.map((building) => [building.block, building])
|
|
29573
|
+
);
|
|
29574
|
+
const existingBuildingsByName = new Map(
|
|
29575
|
+
existingBuildings.map((building) => [building.name, building])
|
|
29576
|
+
);
|
|
29577
|
+
const uploadedNames = /* @__PURE__ */ new Set();
|
|
29526
29578
|
for (const [
|
|
29527
29579
|
block,
|
|
29528
|
-
{ name, levels, unitsByLevel }
|
|
29580
|
+
{ name, levels, rows, unitsByLevel, unitRowsByLevel }
|
|
29529
29581
|
] of blockMap.entries()) {
|
|
29530
|
-
const
|
|
29531
|
-
const
|
|
29582
|
+
const conflictReasons = [];
|
|
29583
|
+
const existingBuildingByBlock = existingBuildingsByBlock.get(block);
|
|
29584
|
+
const existingBuildingByName = existingBuildingsByName.get(name);
|
|
29585
|
+
if (existingBuildingByBlock && existingBuildingByBlock.name !== name) {
|
|
29586
|
+
conflictReasons.push(
|
|
29587
|
+
`Block ${block} already exists with building name "${existingBuildingByBlock.name}".`
|
|
29588
|
+
);
|
|
29589
|
+
}
|
|
29590
|
+
if (existingBuildingByName && existingBuildingByName.block !== block) {
|
|
29591
|
+
conflictReasons.push(
|
|
29592
|
+
`Building name "${name}" already exists in block ${existingBuildingByName.block}.`
|
|
29593
|
+
);
|
|
29594
|
+
}
|
|
29595
|
+
if (uploadedNames.has(name)) {
|
|
29596
|
+
conflictReasons.push(
|
|
29597
|
+
`Building name "${name}" is duplicated in this spreadsheet.`
|
|
29598
|
+
);
|
|
29599
|
+
}
|
|
29600
|
+
if (conflictReasons.length) {
|
|
29601
|
+
rows.forEach((row) => {
|
|
29602
|
+
skippedRows.push({
|
|
29603
|
+
row,
|
|
29604
|
+
block,
|
|
29605
|
+
name,
|
|
29606
|
+
reason: conflictReasons.join(" ")
|
|
29607
|
+
});
|
|
29608
|
+
});
|
|
29609
|
+
continue;
|
|
29610
|
+
}
|
|
29611
|
+
uploadedNames.add(name);
|
|
29612
|
+
const existingBuilding = existingBuildingByBlock ?? null;
|
|
29613
|
+
const buildingId = existingBuilding?._id ?? new ObjectId54();
|
|
29614
|
+
const existingLevelIds = (existingBuilding?.levels ?? []).map(toObjectIdOrNull).filter(
|
|
29615
|
+
(levelId) => Boolean(levelId)
|
|
29616
|
+
);
|
|
29617
|
+
const existingLevels = existingBuilding || existingLevelIds.length > 0 ? await buildingLevelsCollection.find(
|
|
29618
|
+
{
|
|
29619
|
+
$or: [
|
|
29620
|
+
{ blockId: buildingId },
|
|
29621
|
+
{ blockId: buildingId.toString() },
|
|
29622
|
+
...existingLevelIds.length > 0 ? [{ _id: { $in: existingLevelIds } }] : []
|
|
29623
|
+
],
|
|
29624
|
+
status: "active" /* ACTIVE */
|
|
29625
|
+
},
|
|
29626
|
+
{ session, projection: { name: 1 } }
|
|
29627
|
+
).toArray() : [];
|
|
29628
|
+
const levelNameToId = new Map(
|
|
29629
|
+
existingLevels.map((level) => [
|
|
29630
|
+
normalizeImportKey(level.name),
|
|
29631
|
+
level._id
|
|
29632
|
+
])
|
|
29633
|
+
);
|
|
29634
|
+
const missingLevels = (levels ?? []).filter(
|
|
29635
|
+
(levelName) => !levelNameToId.has(normalizeImportKey(levelName))
|
|
29636
|
+
);
|
|
29637
|
+
const levelsPayload = missingLevels.map((levelName) => ({
|
|
29532
29638
|
blockId: buildingId.toString(),
|
|
29533
29639
|
site: site.toString(),
|
|
29534
29640
|
name: levelName,
|
|
@@ -29541,23 +29647,80 @@ function useBuildingService() {
|
|
|
29541
29647
|
if (levelsPayload.length > 0) {
|
|
29542
29648
|
insertedLevels = await _bulkWriteLevels(levelsPayload, session);
|
|
29543
29649
|
}
|
|
29544
|
-
const levelNameToId = /* @__PURE__ */ new Map();
|
|
29545
29650
|
if (insertedLevels) {
|
|
29546
29651
|
insertedLevels.levels.forEach((lvl) => {
|
|
29547
|
-
levelNameToId.set(lvl.name, lvl._id);
|
|
29652
|
+
levelNameToId.set(normalizeImportKey(lvl.name), lvl._id);
|
|
29548
29653
|
});
|
|
29549
29654
|
}
|
|
29550
|
-
const
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
|
|
29555
|
-
|
|
29556
|
-
|
|
29557
|
-
|
|
29655
|
+
const levelIds = Array.from(levelNameToId.values());
|
|
29656
|
+
if (existingBuilding) {
|
|
29657
|
+
if (insertedLevels) {
|
|
29658
|
+
await _updateById(
|
|
29659
|
+
buildingId,
|
|
29660
|
+
{
|
|
29661
|
+
levels: [
|
|
29662
|
+
...existingLevelIds,
|
|
29663
|
+
...Object.values(insertedLevels.insertedIds)
|
|
29664
|
+
]
|
|
29665
|
+
},
|
|
29666
|
+
session
|
|
29667
|
+
);
|
|
29668
|
+
}
|
|
29669
|
+
} else {
|
|
29670
|
+
const buildingPayload = {
|
|
29671
|
+
_id: buildingId,
|
|
29672
|
+
site,
|
|
29673
|
+
block,
|
|
29674
|
+
name,
|
|
29675
|
+
levels: levelIds
|
|
29676
|
+
};
|
|
29677
|
+
const buildingResult = await _add(buildingPayload, session);
|
|
29678
|
+
buildingPayloads.push({ ...buildingPayload, _id: buildingResult });
|
|
29679
|
+
}
|
|
29680
|
+
const existingUnits = levelIds.length > 0 ? await buildingUnitsCollection.find(
|
|
29681
|
+
{
|
|
29682
|
+
building: { $in: [buildingId, buildingId.toString()] },
|
|
29683
|
+
level: {
|
|
29684
|
+
$in: [
|
|
29685
|
+
...levelIds,
|
|
29686
|
+
...levelIds.map((levelId) => levelId.toString())
|
|
29687
|
+
]
|
|
29688
|
+
}
|
|
29689
|
+
},
|
|
29690
|
+
{ session, projection: { name: 1, level: 1 } }
|
|
29691
|
+
).toArray() : [];
|
|
29692
|
+
const existingUnitKeys = new Set(
|
|
29693
|
+
existingUnits.map(
|
|
29694
|
+
(unit) => `${unit.level.toString()}::${normalizeImportKey(unit.name)}`
|
|
29695
|
+
)
|
|
29696
|
+
);
|
|
29558
29697
|
Array.from(unitsByLevel.entries()).forEach(([levelName, units]) => {
|
|
29559
|
-
const levelId = levelNameToId.get(levelName);
|
|
29698
|
+
const levelId = levelNameToId.get(normalizeImportKey(levelName));
|
|
29699
|
+
if (!levelId)
|
|
29700
|
+
return;
|
|
29560
29701
|
units.forEach((unit) => {
|
|
29702
|
+
const unitKey = `${levelId.toString()}::${normalizeImportKey(unit)}`;
|
|
29703
|
+
const unitRows = unitRowsByLevel.get(levelName)?.get(unit) ?? rows;
|
|
29704
|
+
if (existingUnitKeys.has(unitKey)) {
|
|
29705
|
+
unitRows.forEach((row) => {
|
|
29706
|
+
skippedRows.push({
|
|
29707
|
+
row,
|
|
29708
|
+
block,
|
|
29709
|
+
name,
|
|
29710
|
+
reason: `Unit "${unit}" already exists in level "${levelName}".`
|
|
29711
|
+
});
|
|
29712
|
+
});
|
|
29713
|
+
return;
|
|
29714
|
+
}
|
|
29715
|
+
existingUnitKeys.add(unitKey);
|
|
29716
|
+
unitRows.slice(1).forEach((row) => {
|
|
29717
|
+
skippedRows.push({
|
|
29718
|
+
row,
|
|
29719
|
+
block,
|
|
29720
|
+
name,
|
|
29721
|
+
reason: `Unit "${unit}" is duplicated in this spreadsheet for level "${levelName}".`
|
|
29722
|
+
});
|
|
29723
|
+
});
|
|
29561
29724
|
buildingUnitPayloads.push({
|
|
29562
29725
|
site: new ObjectId54(site),
|
|
29563
29726
|
name: unit,
|
|
@@ -29583,9 +29746,17 @@ function useBuildingService() {
|
|
|
29583
29746
|
});
|
|
29584
29747
|
});
|
|
29585
29748
|
}
|
|
29586
|
-
|
|
29749
|
+
if (buildingUnitPayloads.length > 0) {
|
|
29750
|
+
await _bulkAddBuildingUnits(buildingUnitPayloads, session);
|
|
29751
|
+
}
|
|
29587
29752
|
await session.commitTransaction();
|
|
29588
|
-
return {
|
|
29753
|
+
return {
|
|
29754
|
+
buildingPayloads,
|
|
29755
|
+
buildingUnitPayloads,
|
|
29756
|
+
insertedBuildings: buildingPayloads.length,
|
|
29757
|
+
insertedUnits: buildingUnitPayloads.length,
|
|
29758
|
+
skippedRows
|
|
29759
|
+
};
|
|
29589
29760
|
} catch (error) {
|
|
29590
29761
|
await session.abortTransaction();
|
|
29591
29762
|
throw error;
|
|
@@ -29607,6 +29778,19 @@ import Joi46 from "joi";
|
|
|
29607
29778
|
import ExcelJS from "exceljs";
|
|
29608
29779
|
import csv from "csv-parser";
|
|
29609
29780
|
import fs2 from "fs";
|
|
29781
|
+
function normalizeSpreadsheetHeader(header) {
|
|
29782
|
+
let value = "";
|
|
29783
|
+
if (header && typeof header === "object" && "richText" in header && Array.isArray(header.richText)) {
|
|
29784
|
+
value = header.richText.map((part) => part.text ?? "").join("");
|
|
29785
|
+
} else if (header && typeof header === "object" && "text" in header) {
|
|
29786
|
+
value = String(header.text ?? "");
|
|
29787
|
+
} else if (header && typeof header === "object" && "result" in header) {
|
|
29788
|
+
value = String(header.result ?? "");
|
|
29789
|
+
} else {
|
|
29790
|
+
value = String(header ?? "");
|
|
29791
|
+
}
|
|
29792
|
+
return value.replace(/^\uFEFF/, "").trim().replace(/\(.*\)/, "").trim().toLowerCase();
|
|
29793
|
+
}
|
|
29610
29794
|
function useBuildingController() {
|
|
29611
29795
|
const {
|
|
29612
29796
|
getAll: _getAll,
|
|
@@ -29859,12 +30043,14 @@ function useBuildingController() {
|
|
|
29859
30043
|
}
|
|
29860
30044
|
const { originalname, path: path5 } = req.file;
|
|
29861
30045
|
const lowerName = originalname.toLowerCase();
|
|
30046
|
+
const expectedHeaders = ["block", "name", "level", "unit"];
|
|
30047
|
+
const requiredFields = ["block", "name"];
|
|
29862
30048
|
const rowSchema = Joi46.object({
|
|
29863
30049
|
block: Joi46.number().integer().min(0).required(),
|
|
30050
|
+
name: Joi46.string().trim().required(),
|
|
29864
30051
|
level: Joi46.alternatives().try(Joi46.string(), Joi46.number()).custom((value) => String(value)).optional().allow(null, ""),
|
|
29865
30052
|
unit: Joi46.alternatives().try(Joi46.string(), Joi46.number()).custom((value) => String(value)).optional().allow(null, ""),
|
|
29866
30053
|
category: Joi46.string().trim().optional().allow(null, ""),
|
|
29867
|
-
name: Joi46.string().trim().optional().allow(null, ""),
|
|
29868
30054
|
email: Joi46.string().email().lowercase().optional().allow(null, ""),
|
|
29869
30055
|
phoneNumber: Joi46.string().trim().optional().allow(null, "")
|
|
29870
30056
|
});
|
|
@@ -29895,9 +30081,18 @@ function useBuildingController() {
|
|
|
29895
30081
|
return;
|
|
29896
30082
|
}
|
|
29897
30083
|
const headerRow = worksheet.getRow(1);
|
|
29898
|
-
const headers = (headerRow.values || []).slice(1).map(
|
|
29899
|
-
|
|
30084
|
+
const headers = (headerRow.values || []).slice(1).map((header) => normalizeSpreadsheetHeader(header));
|
|
30085
|
+
const missingHeaders = expectedHeaders.filter(
|
|
30086
|
+
(expected) => !headers.some((h) => h === expected)
|
|
29900
30087
|
);
|
|
30088
|
+
if (missingHeaders.length > 0) {
|
|
30089
|
+
next(
|
|
30090
|
+
new BadRequestError84(
|
|
30091
|
+
`Invalid Excel format. Missing required columns: ${missingHeaders.join(", ")}. Expected columns: ${expectedHeaders.join(", ")}`
|
|
30092
|
+
)
|
|
30093
|
+
);
|
|
30094
|
+
return;
|
|
30095
|
+
}
|
|
29901
30096
|
worksheet.eachRow((row, rowNumber) => {
|
|
29902
30097
|
if (rowNumber === 1)
|
|
29903
30098
|
return;
|
|
@@ -29916,10 +30111,30 @@ function useBuildingController() {
|
|
|
29916
30111
|
const parsed = [];
|
|
29917
30112
|
fs2.createReadStream(path5).pipe(
|
|
29918
30113
|
csv({
|
|
29919
|
-
mapHeaders: ({ header }) => header
|
|
30114
|
+
mapHeaders: ({ header }) => normalizeSpreadsheetHeader(header)
|
|
29920
30115
|
})
|
|
29921
|
-
).on("data", (row) =>
|
|
30116
|
+
).on("data", (row) => {
|
|
30117
|
+
if (Object.values(row).some(
|
|
30118
|
+
(v) => v !== "" && v !== null && v !== void 0
|
|
30119
|
+
)) {
|
|
30120
|
+
parsed.push(row);
|
|
30121
|
+
}
|
|
30122
|
+
}).on("end", () => resolve(parsed)).on("error", reject);
|
|
29922
30123
|
});
|
|
30124
|
+
if (rows.length > 0) {
|
|
30125
|
+
const csvHeaders = Object.keys(rows[0]);
|
|
30126
|
+
const missingHeaders = expectedHeaders.filter(
|
|
30127
|
+
(expected) => !csvHeaders.some((h) => h === expected)
|
|
30128
|
+
);
|
|
30129
|
+
if (missingHeaders.length > 0) {
|
|
30130
|
+
next(
|
|
30131
|
+
new BadRequestError84(
|
|
30132
|
+
`Invalid CSV format. Missing required columns: ${missingHeaders.join(", ")}. Expected columns: ${expectedHeaders.join(", ")}`
|
|
30133
|
+
)
|
|
30134
|
+
);
|
|
30135
|
+
return;
|
|
30136
|
+
}
|
|
30137
|
+
}
|
|
29923
30138
|
} else {
|
|
29924
30139
|
next(
|
|
29925
30140
|
new BadRequestError84("Only .xlsx, .xls, or .csv files are allowed.")
|
|
@@ -29929,6 +30144,23 @@ function useBuildingController() {
|
|
|
29929
30144
|
const validRows = [];
|
|
29930
30145
|
const invalidRows = [];
|
|
29931
30146
|
rows.forEach((row, index) => {
|
|
30147
|
+
const emptyCells = Object.entries(row).filter(
|
|
30148
|
+
([, value2]) => value2 === "" || value2 === null || value2 === void 0
|
|
30149
|
+
).map(([key]) => key);
|
|
30150
|
+
const emptyRequiredFields = emptyCells.filter(
|
|
30151
|
+
(cell) => requiredFields.includes(cell)
|
|
30152
|
+
);
|
|
30153
|
+
if (emptyRequiredFields.length > 0) {
|
|
30154
|
+
invalidRows.push({
|
|
30155
|
+
row: index + 2,
|
|
30156
|
+
// +2 because header is row 1
|
|
30157
|
+
data: row,
|
|
30158
|
+
errors: [
|
|
30159
|
+
`Required field(s) empty: ${emptyRequiredFields.join(", ")}`
|
|
30160
|
+
]
|
|
30161
|
+
});
|
|
30162
|
+
return;
|
|
30163
|
+
}
|
|
29932
30164
|
const { error, value } = rowSchema.validate(row, {
|
|
29933
30165
|
abortEarly: false,
|
|
29934
30166
|
stripUnknown: true
|
|
@@ -29941,19 +30173,64 @@ function useBuildingController() {
|
|
|
29941
30173
|
errors: error.details.map((d) => d.message)
|
|
29942
30174
|
});
|
|
29943
30175
|
} else {
|
|
29944
|
-
validRows.push(value);
|
|
30176
|
+
validRows.push({ ...value, __rowNumber: index + 2 });
|
|
29945
30177
|
}
|
|
29946
30178
|
});
|
|
29947
|
-
|
|
30179
|
+
let result;
|
|
30180
|
+
try {
|
|
30181
|
+
result = await _uploadSpreadsheetBuilding(validRows, site);
|
|
30182
|
+
} catch (uploadError) {
|
|
30183
|
+
logger65.log({
|
|
30184
|
+
level: "error",
|
|
30185
|
+
message: `Upload error: ${uploadError.message}`
|
|
30186
|
+
});
|
|
30187
|
+
result = {
|
|
30188
|
+
insertedBuildings: 0,
|
|
30189
|
+
insertedUnits: 0,
|
|
30190
|
+
skippedRows: [
|
|
30191
|
+
{
|
|
30192
|
+
rowNumber: "all",
|
|
30193
|
+
reason: "Critical Upload Error",
|
|
30194
|
+
error: uploadError.message
|
|
30195
|
+
}
|
|
30196
|
+
]
|
|
30197
|
+
};
|
|
30198
|
+
}
|
|
30199
|
+
const allSkippedItems = [
|
|
30200
|
+
...invalidRows.map((item) => ({
|
|
30201
|
+
rowNumber: item.row,
|
|
30202
|
+
reason: "Validation Error",
|
|
30203
|
+
errors: item.errors,
|
|
30204
|
+
data: item.data
|
|
30205
|
+
})),
|
|
30206
|
+
...(result.skippedRows || []).map((item) => ({
|
|
30207
|
+
rowNumber: "row" in item ? item.row : item.rowNumber,
|
|
30208
|
+
reason: item.reason,
|
|
30209
|
+
block: "block" in item ? item.block : void 0,
|
|
30210
|
+
name: "name" in item ? item.name : void 0,
|
|
30211
|
+
data: item
|
|
30212
|
+
}))
|
|
30213
|
+
].sort((a, b) => {
|
|
30214
|
+
if (typeof a.rowNumber === "string")
|
|
30215
|
+
return 1;
|
|
30216
|
+
if (typeof b.rowNumber === "string")
|
|
30217
|
+
return -1;
|
|
30218
|
+
return a.rowNumber - b.rowNumber;
|
|
30219
|
+
});
|
|
30220
|
+
const skippedSummary = allSkippedItems.map((item, idx) => `${idx + 1}. Row ${item.rowNumber}: ${item.reason}`).join("\n");
|
|
29948
30221
|
res.status(200).json({
|
|
29949
|
-
message:
|
|
30222
|
+
message: `Spreadsheet import completed (buildings).${allSkippedItems.length > 0 ? "\n\nSkipped items:\n" + skippedSummary : ""}`,
|
|
29950
30223
|
fileName: originalname,
|
|
29951
30224
|
totalRows: rows.length,
|
|
29952
30225
|
validRows: validRows.length,
|
|
29953
30226
|
invalidRows: invalidRows.length,
|
|
30227
|
+
insertedBuildings: result.insertedBuildings,
|
|
30228
|
+
insertedUnits: result.insertedUnits,
|
|
30229
|
+
skippedRows: allSkippedItems.length,
|
|
29954
30230
|
validationErrors: invalidRows,
|
|
29955
|
-
|
|
29956
|
-
|
|
30231
|
+
skippedErrors: result.skippedRows,
|
|
30232
|
+
skippedItemsSummary: allSkippedItems,
|
|
30233
|
+
data: result
|
|
29957
30234
|
});
|
|
29958
30235
|
fs2.unlink(path5, () => {
|
|
29959
30236
|
});
|