@7365admin1/core 3.83.0 → 3.85.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
@@ -59696,6 +59696,179 @@ function createLiftSimulator(options) {
59696
59696
  };
59697
59697
  }
59698
59698
 
59699
+ // src/utils/building-map.util.ts
59700
+ var WALL_HEIGHT_M = 2.5;
59701
+ var WALL_THICKNESS_M = 0.1;
59702
+ var DEFAULT_SCALE_M_PER_PX = 0.05;
59703
+ function scaleFromMeasurement(pixelDistance, realDistanceM) {
59704
+ if (!Number.isFinite(pixelDistance) || pixelDistance <= 0) {
59705
+ throw new Error("A measurement needs a positive pixel distance.");
59706
+ }
59707
+ if (!Number.isFinite(realDistanceM) || realDistanceM <= 0) {
59708
+ throw new Error("A measurement needs a positive real distance in metres.");
59709
+ }
59710
+ return realDistanceM / pixelDistance;
59711
+ }
59712
+ function vertexDistancePx(a, b) {
59713
+ return Math.hypot(b.x - a.x, b.y - a.y);
59714
+ }
59715
+ function levelScale(level) {
59716
+ const measurement = level.measurements?.find((m) => {
59717
+ const a = level.vertices[m.start];
59718
+ const b = level.vertices[m.end];
59719
+ return a && b && m.distanceM > 0 && vertexDistancePx(a, b) > 0;
59720
+ });
59721
+ if (measurement) {
59722
+ return scaleFromMeasurement(
59723
+ vertexDistancePx(level.vertices[measurement.start], level.vertices[measurement.end]),
59724
+ measurement.distanceM
59725
+ );
59726
+ }
59727
+ return level.scaleMPerPx > 0 ? level.scaleMPerPx : DEFAULT_SCALE_M_PER_PX;
59728
+ }
59729
+ function levelPixelToMetres(level, px, py, imageHeightPx) {
59730
+ const scale = levelScale(level);
59731
+ return {
59732
+ x: level.originXM + px * scale,
59733
+ y: level.originYM + (imageHeightPx - py) * scale
59734
+ };
59735
+ }
59736
+ function levelSolids(level, imageHeightPx) {
59737
+ const toM = (v) => levelPixelToMetres(level, v.x, v.y, imageHeightPx);
59738
+ const walls = [];
59739
+ for (const wall of level.walls ?? []) {
59740
+ const a = level.vertices[wall.start];
59741
+ const b = level.vertices[wall.end];
59742
+ if (!a || !b)
59743
+ continue;
59744
+ const pa = toM(a);
59745
+ const pb = toM(b);
59746
+ const lengthM = Math.hypot(pb.x - pa.x, pb.y - pa.y);
59747
+ if (lengthM <= 0)
59748
+ continue;
59749
+ const heightM = wall.heightM ?? WALL_HEIGHT_M;
59750
+ walls.push({
59751
+ centre: {
59752
+ x: (pa.x + pb.x) / 2,
59753
+ y: (pa.y + pb.y) / 2,
59754
+ // Boxes are centred, so a wall standing ON the floor sits at half its
59755
+ // own height above the level's elevation.
59756
+ z: level.elevationM + heightM / 2
59757
+ },
59758
+ lengthM,
59759
+ thicknessM: wall.thicknessM ?? WALL_THICKNESS_M,
59760
+ heightM,
59761
+ yaw: Math.atan2(pb.y - pa.y, pb.x - pa.x)
59762
+ });
59763
+ }
59764
+ const floors = [];
59765
+ for (const floor of level.floors ?? []) {
59766
+ const points = (floor.vertices ?? []).map((i) => level.vertices[i]).filter(Boolean).map(toM);
59767
+ if (points.length < 3)
59768
+ continue;
59769
+ floors.push({ points, elevationM: level.elevationM, hole: floor.hole === true });
59770
+ }
59771
+ return { level: level.name, elevationM: level.elevationM, walls, floors };
59772
+ }
59773
+ function buildingSolids(map, imageHeightFor) {
59774
+ return (map.levels ?? []).map((level) => levelSolids(level, imageHeightFor(level)));
59775
+ }
59776
+ function wallsFromOccupancyGrid(grid, options) {
59777
+ const threshold = options?.threshold ?? 100;
59778
+ const minRun = Math.max(2, options?.minRunPx ?? 6);
59779
+ const { data, width, height } = grid;
59780
+ if (!width || !height || !data || data.length < width * height) {
59781
+ return { vertices: [], walls: [] };
59782
+ }
59783
+ const occupied = (x, y) => Number(data[y * width + x]) <= threshold;
59784
+ const consumed = new Uint8Array(width * height);
59785
+ const vertices = [];
59786
+ const walls = [];
59787
+ const index = /* @__PURE__ */ new Map();
59788
+ const vertexAt = (x, y) => {
59789
+ const key = `${x}:${y}`;
59790
+ const found = index.get(key);
59791
+ if (found !== void 0)
59792
+ return found;
59793
+ vertices.push({ x, y, z: 0 });
59794
+ index.set(key, vertices.length - 1);
59795
+ return vertices.length - 1;
59796
+ };
59797
+ const addRun = (x1, y1, x2, y2) => {
59798
+ walls.push({ start: vertexAt(x1, y1), end: vertexAt(x2, y2) });
59799
+ };
59800
+ for (let y = 0; y < height; y++) {
59801
+ let run = 0;
59802
+ for (let x = 0; x <= width; x++) {
59803
+ const on = x < width && occupied(x, y);
59804
+ if (on) {
59805
+ run++;
59806
+ continue;
59807
+ }
59808
+ if (run >= minRun) {
59809
+ const startX = x - run;
59810
+ addRun(startX, y, x - 1, y);
59811
+ for (let i = startX; i < x; i++)
59812
+ consumed[y * width + i] = 1;
59813
+ }
59814
+ run = 0;
59815
+ }
59816
+ }
59817
+ for (let x = 0; x < width; x++) {
59818
+ let run = 0;
59819
+ for (let y = 0; y <= height; y++) {
59820
+ const on = y < height && occupied(x, y) && !consumed[y * width + x];
59821
+ if (on) {
59822
+ run++;
59823
+ continue;
59824
+ }
59825
+ if (run >= minRun) {
59826
+ addRun(x, y - run, x, y - 1);
59827
+ }
59828
+ run = 0;
59829
+ }
59830
+ }
59831
+ return { vertices, walls };
59832
+ }
59833
+ function buildingMapFromRobotMaps(name, maps, floorHeightM = 3) {
59834
+ const levels = [...maps ?? []].sort((a, b) => a.floorNo - b.floorNo).map((map) => {
59835
+ const vertices = [];
59836
+ if (map.baseX || map.baseY) {
59837
+ vertices.push({
59838
+ x: map.baseX ?? 0,
59839
+ y: map.baseY ?? 0,
59840
+ z: 0,
59841
+ name: `L${map.floorNo}_charger`,
59842
+ isCharger: true,
59843
+ theta: map.baseTheta ?? 0
59844
+ });
59845
+ }
59846
+ for (const area of map.areaList ?? []) {
59847
+ vertices.push({
59848
+ x: area.x,
59849
+ y: area.y,
59850
+ z: 0,
59851
+ name: area.areaName,
59852
+ isCleaningZone: true
59853
+ });
59854
+ }
59855
+ return {
59856
+ name: map.mapName?.trim() || `L${map.floorNo}`,
59857
+ elevationM: (map.floorNo - 1) * floorHeightM,
59858
+ drawing: { fileKey: map.backgroundFileKey },
59859
+ scaleMPerPx: map.resolution && map.resolution > 0 ? map.resolution : DEFAULT_SCALE_M_PER_PX,
59860
+ originXM: map.originX ?? 0,
59861
+ originYM: map.originY ?? 0,
59862
+ vertices,
59863
+ walls: [],
59864
+ floors: [],
59865
+ doors: [],
59866
+ measurements: []
59867
+ };
59868
+ });
59869
+ return { name, levels, lifts: [] };
59870
+ }
59871
+
59699
59872
  // src/services/robot-fleet.service.ts
59700
59873
  import { BadRequestError as BadRequestError134, NotFoundError as NotFoundError43 } from "@7365admin1/node-server-utils";
59701
59874
 
@@ -60076,6 +60249,145 @@ function registerOunitechBrand() {
60076
60249
  });
60077
60250
  }
60078
60251
 
60252
+ // src/services/robot-tracking.service.ts
60253
+ import axios4 from "axios";
60254
+ async function openWithAxios(url, body) {
60255
+ const response = await axios4.post(url, body, {
60256
+ responseType: "stream",
60257
+ // No read timeout: this connection is SUPPOSED to stay open and mostly
60258
+ // silent. A request timeout here would sever a healthy stream on a quiet
60259
+ // robot. The idle watchdog is what decides a stream is dead, because it can
60260
+ // tell silence from a closed socket.
60261
+ timeout: 0,
60262
+ headers: { "Content-Type": "application/json", Accept: "text/event-stream" }
60263
+ });
60264
+ return response.data;
60265
+ }
60266
+ function startRobotTracking(options) {
60267
+ const {
60268
+ credentials,
60269
+ robotSn,
60270
+ onEvent,
60271
+ baseUrl = ROBOT_CLOUD_BASE_URL,
60272
+ idleTimeoutMs = TRACKING_IDLE_TIMEOUT_MS,
60273
+ openStream = openWithAxios,
60274
+ now = Date.now
60275
+ } = options;
60276
+ if (!baseUrl)
60277
+ throw new Error("Robot cloud: ROBOT_CLOUD_BASE_URL is not configured.");
60278
+ let stopped = false;
60279
+ let attempt = 0;
60280
+ let current = null;
60281
+ let watchdog = null;
60282
+ let retry = null;
60283
+ let lastActivityAt = now();
60284
+ let everConnected = false;
60285
+ const parser = createSseParser();
60286
+ function clearTimers() {
60287
+ if (watchdog) {
60288
+ clearInterval(watchdog);
60289
+ watchdog = null;
60290
+ }
60291
+ if (retry) {
60292
+ clearTimeout(retry);
60293
+ retry = null;
60294
+ }
60295
+ }
60296
+ function dropUpstream() {
60297
+ if (!current)
60298
+ return;
60299
+ try {
60300
+ current.destroy();
60301
+ } catch {
60302
+ }
60303
+ current = null;
60304
+ }
60305
+ function lose(reason) {
60306
+ if (stopped)
60307
+ return;
60308
+ onEvent({ type: "lost", reason, at: now() });
60309
+ dropUpstream();
60310
+ scheduleReconnect();
60311
+ }
60312
+ function scheduleReconnect() {
60313
+ if (stopped)
60314
+ return;
60315
+ clearTimers();
60316
+ const delay2 = trackingBackoffMs(attempt++);
60317
+ retry = setTimeout(connect2, delay2);
60318
+ }
60319
+ async function connect2() {
60320
+ if (stopped)
60321
+ return;
60322
+ const body = robotCloudBody(
60323
+ credentials.appid,
60324
+ credentials.secret,
60325
+ ROBOT_CLOUD_PATHS.tracking,
60326
+ { robotSn }
60327
+ );
60328
+ let stream;
60329
+ try {
60330
+ stream = await openStream(
60331
+ `${baseUrl.replace(/\/+$/, "")}${ROBOT_CLOUD_PATHS.tracking}`,
60332
+ body
60333
+ );
60334
+ } catch (error) {
60335
+ lose(error?.message ?? "Could not open the robot's tracking stream.");
60336
+ return;
60337
+ }
60338
+ if (stopped) {
60339
+ try {
60340
+ stream.destroy();
60341
+ } catch {
60342
+ }
60343
+ return;
60344
+ }
60345
+ parser.reset();
60346
+ current = stream;
60347
+ lastActivityAt = now();
60348
+ if (everConnected)
60349
+ onEvent({ type: "reconnected", at: now() });
60350
+ everConnected = true;
60351
+ attempt = 0;
60352
+ stream.on("data", (chunk) => {
60353
+ if (stopped)
60354
+ return;
60355
+ const { events, sawActivity } = parser.push(chunk.toString("utf8"));
60356
+ if (sawActivity)
60357
+ lastActivityAt = now();
60358
+ for (const event of events) {
60359
+ const pose = decodeTrackingFrame(event.data);
60360
+ if (pose)
60361
+ onEvent({ type: "pose", pose, at: now() });
60362
+ }
60363
+ });
60364
+ stream.on("error", (error) => lose(error?.message ?? "The tracking stream failed."));
60365
+ stream.on("end", () => lose("The robot cloud closed the tracking stream."));
60366
+ stream.on("close", () => {
60367
+ if (!stopped && current === stream)
60368
+ lose("The tracking stream closed.");
60369
+ });
60370
+ clearTimers();
60371
+ watchdog = setInterval(() => {
60372
+ if (stopped)
60373
+ return;
60374
+ if (now() - lastActivityAt >= idleTimeoutMs) {
60375
+ lose("The robot stopped reporting.");
60376
+ }
60377
+ }, Math.max(1e3, Math.floor(idleTimeoutMs / 3)));
60378
+ }
60379
+ void connect2();
60380
+ return {
60381
+ stop() {
60382
+ if (stopped)
60383
+ return;
60384
+ stopped = true;
60385
+ clearTimers();
60386
+ dropUpstream();
60387
+ }
60388
+ };
60389
+ }
60390
+
60079
60391
  // src/controllers/robot-fleet.controller.ts
60080
60392
  import Joi76 from "joi";
60081
60393
  import { BadRequestError as BadRequestError135, logger as logger106 } from "@7365admin1/node-server-utils";
@@ -60154,7 +60466,55 @@ function useRobotFleetController(deps) {
60154
60466
  fail(next, error);
60155
60467
  }
60156
60468
  }
60157
- return { getState, getMaps, getMapImages, getPlans, sendCommand: sendCommand2 };
60469
+ async function trackRobot(req, res, next) {
60470
+ let robot;
60471
+ let robotSn;
60472
+ try {
60473
+ robot = await entitleRobot(req, req.params.id);
60474
+ robotSn = await getFleet().resolveRobotSn(robot);
60475
+ } catch (error) {
60476
+ fail(next, error);
60477
+ return;
60478
+ }
60479
+ res.writeHead(200, {
60480
+ "Content-Type": "text/event-stream",
60481
+ "Cache-Control": "no-cache, no-transform",
60482
+ Connection: "keep-alive",
60483
+ // Nginx buffers proxied responses by default, which holds an SSE stream
60484
+ // until the buffer fills - the stream appears dead for minutes.
60485
+ "X-Accel-Buffering": "no"
60486
+ });
60487
+ res.flushHeaders?.();
60488
+ const send2 = (event) => {
60489
+ if (res.writableEnded)
60490
+ return;
60491
+ res.write(`data: ${JSON.stringify(event)}
60492
+
60493
+ `);
60494
+ };
60495
+ const handle = startRobotTracking({
60496
+ credentials: {
60497
+ appid: (robot.appKey ?? "").trim(),
60498
+ secret: (robot.appToken ?? "").trim()
60499
+ },
60500
+ robotSn,
60501
+ onEvent: send2
60502
+ });
60503
+ const keepAlive = setInterval(() => {
60504
+ if (!res.writableEnded)
60505
+ res.write(": keep-alive\n\n");
60506
+ }, 15e3);
60507
+ const close = () => {
60508
+ clearInterval(keepAlive);
60509
+ handle.stop();
60510
+ if (!res.writableEnded)
60511
+ res.end();
60512
+ };
60513
+ req.on("close", close);
60514
+ req.on("aborted", close);
60515
+ res.on("error", close);
60516
+ }
60517
+ return { getState, getMaps, getMapImages, getPlans, sendCommand: sendCommand2, trackRobot };
60158
60518
  }
60159
60519
 
60160
60520
  // src/models/patrol-question.model.ts
@@ -80780,7 +81140,7 @@ import {
80780
81140
  } from "@7365admin1/node-server-utils";
80781
81141
 
80782
81142
  // src/utils/hrmlabs-attendance.util.ts
80783
- import axios4 from "axios";
81143
+ import axios5 from "axios";
80784
81144
  import moment8 from "moment-timezone";
80785
81145
  var default_early_checkIn = 4;
80786
81146
  async function hrmLabsAuthentication({
@@ -80789,7 +81149,7 @@ async function hrmLabsAuthentication({
80789
81149
  password,
80790
81150
  domain
80791
81151
  }) {
80792
- const res = await axios4.post(
81152
+ const res = await axios5.post(
80793
81153
  authUrl,
80794
81154
  {
80795
81155
  grant_type: "password",
@@ -80819,7 +81179,7 @@ async function fetchAttendanceData({
80819
81179
  search
80820
81180
  }) {
80821
81181
  try {
80822
- const res = await axios4.post(
81182
+ const res = await axios5.post(
80823
81183
  attendanceUrl,
80824
81184
  {
80825
81185
  start: startDate,
@@ -81019,7 +81379,7 @@ async function fetchSites({ siteUrl, token }) {
81019
81379
  do {
81020
81380
  try {
81021
81381
  attempt++;
81022
- const res = await axios4.get(siteUrl, {
81382
+ const res = await axios5.get(siteUrl, {
81023
81383
  headers: { Authorization: `Bearer ${token}` },
81024
81384
  timeout: 15e3
81025
81385
  });
@@ -84667,7 +85027,7 @@ async function manpowerEvents(io) {
84667
85027
  }
84668
85028
 
84669
85029
  // src/services/reddot-payment.service.ts
84670
- import axios5 from "axios";
85030
+ import axios6 from "axios";
84671
85031
 
84672
85032
  // src/utils/payment-signature.util.ts
84673
85033
  import * as crypto9 from "crypto";
@@ -85309,7 +85669,7 @@ var useRedDotPaymentSvc = () => {
85309
85669
  );
85310
85670
  }
85311
85671
  const url = process.env.MERCHANT_PAYMENT;
85312
- const response = await axios5.post(url, dataString, { headers });
85672
+ const response = await axios6.post(url, dataString, { headers });
85313
85673
  const result = response.data;
85314
85674
  return result;
85315
85675
  } catch (error) {
@@ -85343,7 +85703,7 @@ var useRedDotPaymentSvc = () => {
85343
85703
  );
85344
85704
  }
85345
85705
  const url = process.env.MERCHANT_ENQUIRY;
85346
- const response = await axios5.post(url, dataString, { headers });
85706
+ const response = await axios6.post(url, dataString, { headers });
85347
85707
  const result = response.data;
85348
85708
  const transactionId = result.transaction_id;
85349
85709
  const amount = result.request_amount || 0;
@@ -93990,6 +94350,7 @@ export {
93990
94350
  ConsoleAuditAction,
93991
94351
  ConsoleAuditTarget,
93992
94352
  DEFAULT_RING_SECONDS,
94353
+ DEFAULT_SCALE_M_PER_PX,
93993
94354
  DEFAULT_SITE_TIMEZONE,
93994
94355
  DEVICE_STATUS,
93995
94356
  DOBStatus,
@@ -94160,6 +94521,7 @@ export {
94160
94521
  ROBOT_CAPABILITIES,
94161
94522
  ROBOT_CLOUD_DOC_CONFIRMATIONS,
94162
94523
  ROBOT_CLOUD_PATHS,
94524
+ ROBOT_CLOUD_TIMEOUT_MS,
94163
94525
  ROBOT_MODULE,
94164
94526
  ROBOT_PERMISSIONS,
94165
94527
  ROBOT_STATUS_NAMES,
@@ -94209,6 +94571,8 @@ export {
94209
94571
  VerificationType,
94210
94572
  VisitorSort,
94211
94573
  VisitorStatus,
94574
+ WALL_HEIGHT_M,
94575
+ WALL_THICKNESS_M,
94212
94576
  addressSchema,
94213
94577
  allowedFieldsSite,
94214
94578
  allowedNatures,
@@ -94220,6 +94584,8 @@ export {
94220
94584
  attendanceSchema,
94221
94585
  attendanceSettingsSchema,
94222
94586
  buildSelfServiceEmailContext,
94587
+ buildingMapFromRobotMaps,
94588
+ buildingSolids,
94223
94589
  building_level_namespace_collection,
94224
94590
  building_units_namespace_collection,
94225
94591
  buildings_namespace_collection,
@@ -94326,6 +94692,9 @@ export {
94326
94692
  isTermsCurrent,
94327
94693
  isTrackingStale,
94328
94694
  isValidTimezone,
94695
+ levelPixelToMetres,
94696
+ levelScale,
94697
+ levelSolids,
94329
94698
  listStaffUserIds,
94330
94699
  liveStatusFor,
94331
94700
  manpowerDesignationsSchema,
@@ -94424,6 +94793,7 @@ export {
94424
94793
  robotTier,
94425
94794
  robotTypesVisibleTo,
94426
94795
  rtspUrl,
94796
+ scaleFromMeasurement,
94427
94797
  schema,
94428
94798
  schemaAppSlugNotification,
94429
94799
  schemaApprovedBy,
@@ -94590,6 +94960,7 @@ export {
94590
94960
  snapshotEndpoint,
94591
94961
  snapshotRefusalReason,
94592
94962
  staffMayBypass,
94963
+ startRobotTracking,
94593
94964
  startTodaysBookForSite,
94594
94965
  stringifyHidJson,
94595
94966
  stripFacialImageMetadata,
@@ -94841,8 +95212,10 @@ export {
94841
95212
  vehicleSchema,
94842
95213
  vehicleViewOf,
94843
95214
  vehicles_namespace_collection,
95215
+ vertexDistancePx,
94844
95216
  visitors_namespace_collection,
94845
95217
  wallConfig,
95218
+ wallsFromOccupancyGrid,
94846
95219
  withLiveLink,
94847
95220
  workOrderSchema,
94848
95221
  work_orders2_namespace_collection,