@7365admin1/core 3.21.0 → 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/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 dahuaQuery = {
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 raw = await _getPlateNumber(dahuaQuery);
26809
- const parsed = parseDahuaFind(raw);
26810
- if (!parsed.exists) {
26811
- const dahuaPayload = {
26812
- host,
26813
- username,
26814
- password,
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
- localField: "levels",
28398
- foreignField: "_id",
28365
+ let: {
28366
+ buildingId: "$_id",
28367
+ buildingLevelIds: { $ifNull: ["$levels", []] }
28368
+ },
28399
28369
  pipeline: [
28400
- { $match: { status: "active" /* ACTIVE */ } },
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, { name, levels: [], unitsByLevel: /* @__PURE__ */ new Map() });
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.includes(normalizedLevel)) {
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
- if (!units.includes(normalizedUnit)) {
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 buildingId = new ObjectId54();
29531
- const levelsPayload = (levels ?? []).map((levelName) => ({
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 buildingPayload = {
29551
- _id: buildingId,
29552
- site,
29553
- block,
29554
- name,
29555
- levels: insertedLevels ? Object.values(insertedLevels.insertedIds) : []
29556
- };
29557
- const buildingResult = await _add(buildingPayload, session);
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
- await _bulkAddBuildingUnits(buildingUnitPayloads);
29749
+ if (buildingUnitPayloads.length > 0) {
29750
+ await _bulkAddBuildingUnits(buildingUnitPayloads, session);
29751
+ }
29587
29752
  await session.commitTransaction();
29588
- return { buildingPayloads, buildingUnitPayloads };
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
- (header) => String(header ?? "").trim().replace(/\(.*\)/, "").trim()
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.trim().replace(/\(.*\)/, "").trim()
30114
+ mapHeaders: ({ header }) => normalizeSpreadsheetHeader(header)
29920
30115
  })
29921
- ).on("data", (row) => parsed.push(row)).on("end", () => resolve(parsed)).on("error", reject);
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
- await _uploadSpreadsheetBuilding(validRows, site);
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: "Spreadsheet import completed (buildings).",
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
- data: validRows
29956
- // testing: return the validated building rows directly
30231
+ skippedErrors: result.skippedRows,
30232
+ skippedItemsSummary: allSkippedItems,
30233
+ data: result
29957
30234
  });
29958
30235
  fs2.unlink(path5, () => {
29959
30236
  });