@7365admin1/core 3.133.1 → 3.135.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
@@ -4286,7 +4286,7 @@ function useMemberRepo() {
4286
4286
  throw error;
4287
4287
  }
4288
4288
  }
4289
- async function updateRoleById(_id, role) {
4289
+ async function updateRoleById(_id, role, createdBy) {
4290
4290
  try {
4291
4291
  _id = new ObjectId14(_id);
4292
4292
  } catch (error) {
@@ -4302,6 +4302,9 @@ function useMemberRepo() {
4302
4302
  role,
4303
4303
  updatedAt: /* @__PURE__ */ new Date()
4304
4304
  };
4305
+ if (createdBy && ObjectId14.isValid(String(createdBy))) {
4306
+ updateValue.createdBy = new ObjectId14(String(createdBy));
4307
+ }
4305
4308
  const res = await collection.updateOne({ _id }, { $set: updateValue });
4306
4309
  if (res.modifiedCount === 0) {
4307
4310
  throw new InternalServerError6("Unable to update feedback status.");
@@ -8833,11 +8836,27 @@ async function siteInvitedPins(db2, userId, memberships) {
8833
8836
  const keys = /* @__PURE__ */ new Set();
8834
8837
  if (!pinned.length || !ObjectId26.isValid(userId))
8835
8838
  return keys;
8839
+ const both3 = (ids2) => [...new Set(ids2.map(String))].flatMap((v) => [v, new ObjectId26(v)]);
8840
+ const wanted = new Set(pinned.map((m) => pinKey(m.org, m.siteId)));
8841
+ const placed = await db2.collection("members").find(
8842
+ {
8843
+ user: new ObjectId26(userId),
8844
+ status: { $ne: "deleted" },
8845
+ org: { $in: both3(pinned.map((m) => m.org)) },
8846
+ siteId: { $in: both3(pinned.map((m) => m.siteId)) },
8847
+ createdBy: { $type: "objectId", $ne: new ObjectId26(userId) }
8848
+ },
8849
+ { projection: { org: 1, siteId: 1 } }
8850
+ ).toArray();
8851
+ for (const row of placed) {
8852
+ const key = pinKey(row.org, row.siteId);
8853
+ if (wanted.has(key))
8854
+ keys.add(key);
8855
+ }
8836
8856
  const user = await db2.collection("users").findOne({ _id: new ObjectId26(userId) }, { projection: { email: 1 } });
8837
8857
  const email = String(user?.email ?? "").trim();
8838
8858
  if (!email)
8839
8859
  return keys;
8840
- const both3 = (ids2) => [...new Set(ids2.map(String))].flatMap((v) => [v, new ObjectId26(v)]);
8841
8860
  const invites = await db2.collection("verifications").find(
8842
8861
  {
8843
8862
  email,
@@ -8853,7 +8872,6 @@ async function siteInvitedPins(db2, userId, memberships) {
8853
8872
  collation: { locale: "en", strength: 2 }
8854
8873
  }
8855
8874
  ).toArray();
8856
- const wanted = new Set(pinned.map((m) => pinKey(m.org, m.siteId)));
8857
8875
  for (const invite of invites) {
8858
8876
  const key = pinKey(invite.metadata?.org, invite.metadata?.siteId);
8859
8877
  if (wanted.has(key))
@@ -24813,7 +24831,8 @@ function useRoleRepo() {
24813
24831
  sort = {},
24814
24832
  type = "",
24815
24833
  org = "",
24816
- site = ""
24834
+ site = "",
24835
+ scope = ""
24817
24836
  }) {
24818
24837
  page2 = page2 > 0 ? page2 - 1 : 0;
24819
24838
  if (org && typeof org === "string" && org.length === 24) {
@@ -24835,6 +24854,9 @@ function useRoleRepo() {
24835
24854
  if (site) {
24836
24855
  query2.$or = [{ site }, { site: "" }, { site: null }];
24837
24856
  cacheOptions.site = site.toString();
24857
+ } else if (scope === "organisation") {
24858
+ query2.site = { $in: ["", null] };
24859
+ cacheOptions.scope = scope;
24838
24860
  }
24839
24861
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
24840
24862
  cacheOptions.sort = JSON.stringify(sort);
@@ -27290,7 +27312,9 @@ function useRoleController() {
27290
27312
  limit: Joi28.number().integer().min(1).max(100).allow("", null).default(10),
27291
27313
  type: Joi28.string().optional().allow("", null),
27292
27314
  org: Joi28.string().hex().optional().allow("", null),
27293
- site: Joi28.string().hex().optional().allow("", null)
27315
+ site: Joi28.string().hex().optional().allow("", null),
27316
+ // "organisation": only the roles with no site (Organization app, service roles made once)
27317
+ scope: Joi28.string().valid("organisation").optional().allow("", null)
27294
27318
  });
27295
27319
  const query2 = { ...req.query };
27296
27320
  const { error } = validation.validate(query2);
@@ -27305,18 +27329,19 @@ function useRoleController() {
27305
27329
  const type = req.query.type ?? "";
27306
27330
  const org = req.query.org ?? "";
27307
27331
  const site = req.query.site ?? "";
27332
+ const scope = req.query.scope ?? "";
27308
27333
  try {
27309
27334
  if (org) {
27310
27335
  await requireOrgReach(req, org);
27311
27336
  }
27312
- let data = await _getRoles({ search, page: page2, limit, type, org, site });
27313
- const listIsWholeOrg = org && !search && !site && page2 === 1;
27337
+ let data = await _getRoles({ search, page: page2, limit, type, org, site, scope });
27338
+ const listIsWholeOrg = org && !search && !site && !scope && page2 === 1;
27314
27339
  const listed = data?.items;
27315
27340
  if (listIsWholeOrg && listed?.length === 0) {
27316
27341
  const seeded = await healDefaultRoles(org).catch(() => 0);
27317
27342
  if (seeded > 0) {
27318
27343
  try {
27319
- data = await _getRoles({ search, page: page2, limit, type, org, site });
27344
+ data = await _getRoles({ search, page: page2, limit, type, org, site, scope });
27320
27345
  } catch (error2) {
27321
27346
  logger42.log({ level: "error", message: error2.message });
27322
27347
  }
@@ -27768,7 +27793,8 @@ function useMemberService() {
27768
27793
  }
27769
27794
  }
27770
27795
  }
27771
- await _updateRoleById(id, role);
27796
+ const placedBy = member.siteId && !newRole?.site && callerId5 && ObjectId60.isValid(callerId5) && String(member.user ?? "") !== callerId5 ? callerId5 : void 0;
27797
+ await _updateRoleById(id, role, placedBy);
27772
27798
  return { message: "Member role updated successfully." };
27773
27799
  }
27774
27800
  async function updateSiteById(id, siteId, siteName) {
@@ -27816,6 +27842,7 @@ function useMemberService() {
27816
27842
  import { BadRequestError as BadRequestError56, UnauthorizedError as UnauthorizedError7, useAtlas as useAtlas44 } from "@7365admin1/node-server-utils";
27817
27843
  import { ObjectId as ObjectId61 } from "mongodb";
27818
27844
  var EXCLUDED_TYPES = ["resident", PLATFORM_STAFF_MEMBER_TYPE];
27845
+ var ORG_ROLE_EXCLUDED = [...EXCLUDED_TYPES, "organization"];
27819
27846
  var both = (id) => [id, new ObjectId61(id)];
27820
27847
  function useMemberSiteAccessService() {
27821
27848
  const { add, updateMemberStatus, updateRoleById } = useMemberRepo();
@@ -27855,10 +27882,13 @@ function useMemberSiteAccessService() {
27855
27882
  const roles = await db2().collection("roles").find({
27856
27883
  $or: [
27857
27884
  { _id: { $in: roleIds.map((r) => new ObjectId61(r)) } },
27858
- { org: { $in: both(org) }, site: { $in: [...sites.keys()].flatMap(both) }, status: "active" }
27885
+ { org: { $in: both(org) }, site: { $in: [...sites.keys()].flatMap(both) }, status: "active" },
27886
+ // organisation-level service roles (no site; never the org-console type)
27887
+ { org: { $in: both(org) }, site: { $in: ["", null] }, status: "active", type: { $nin: ORG_ROLE_EXCLUDED } }
27859
27888
  ]
27860
27889
  }).project({ name: 1, type: 1, site: 1, org: 1, status: 1 }).toArray();
27861
27890
  const byId = new Map(roles.map((r) => [String(r._id), r]));
27891
+ const orgRoles = roles.filter((r) => !String(r.site ?? "") && String(r.org ?? "") === org && r.status === "active" && !ORG_ROLE_EXCLUDED.includes(r.type)).map((r) => ({ _id: String(r._id), name: r.name ?? "", type: r.type ?? "", scope: "organisation" })).sort((a, b) => a.name.localeCompare(b.name));
27862
27892
  const view = (m) => ({
27863
27893
  _id: String(m._id),
27864
27894
  role: String(m.role ?? ""),
@@ -27872,7 +27902,7 @@ function useMemberSiteAccessService() {
27872
27902
  ...s,
27873
27903
  membership: here[0] ?? null,
27874
27904
  memberships: here,
27875
- roles: roles.filter((r) => String(r.site ?? "") === s.site && String(r.org ?? "") === org && r.status === "active" && !EXCLUDED_TYPES.includes(r.type)).map((r) => ({ _id: String(r._id), name: r.name ?? "", type: r.type ?? "" }))
27905
+ roles: roles.filter((r) => String(r.site ?? "") === s.site && String(r.org ?? "") === org && r.status === "active" && !EXCLUDED_TYPES.includes(r.type)).map((r) => ({ _id: String(r._id), name: r.name ?? "", type: r.type ?? "", scope: "site" })).concat(orgRoles)
27876
27906
  };
27877
27907
  }).sort((a, b) => a.name.localeCompare(b.name));
27878
27908
  const orgWide = members.filter((m) => !String(m.siteId ?? "")).map(view);
@@ -27898,7 +27928,7 @@ function useMemberSiteAccessService() {
27898
27928
  throw new BadRequestError56("A site is listed twice.");
27899
27929
  seen2.add(choice.site);
27900
27930
  if (!row.roles.some((r) => r._id === choice.role)) {
27901
- throw new BadRequestError56(`Choose a role created for ${row.name || "that site"}.`);
27931
+ throw new BadRequestError56(`Choose a role created for ${row.name || "that site"}, or one of the organisation's service roles.`);
27902
27932
  }
27903
27933
  }
27904
27934
  const userDoc = await db2().collection("users").findOne({ _id: new ObjectId61(user) }, { projection: { email: 1, name: 1 } });
@@ -27912,10 +27942,11 @@ function useMemberSiteAccessService() {
27912
27942
  const choice = sites.find((c) => c.site === row.site);
27913
27943
  const role = choice ? row.roles.find((r) => r._id === choice.role) : null;
27914
27944
  const keep = role ? row.memberships.find((m) => m.type === role.type) : void 0;
27915
- if (role && keep && keep.role !== role._id) {
27945
+ if (role && keep && (keep.role !== role._id || role.scope === "organisation")) {
27916
27946
  reroles.push(async () => {
27917
- await updateRoleById(keep._id, role._id);
27918
- applied.changed.push(row.site);
27947
+ await updateRoleById(keep._id, role._id, actor);
27948
+ if (keep.role !== role._id)
27949
+ applied.changed.push(row.site);
27919
27950
  });
27920
27951
  } else if (role && !keep) {
27921
27952
  adds.push(async () => {
@@ -27924,7 +27955,7 @@ function useMemberSiteAccessService() {
27924
27955
  { projection: { _id: 1 } }
27925
27956
  );
27926
27957
  if (old) {
27927
- await updateRoleById(old._id, role._id);
27958
+ await updateRoleById(old._id, role._id, actor);
27928
27959
  await updateMemberStatus(old._id, { status: suspended ? "suspended" : "active" });
27929
27960
  } else {
27930
27961
  await add({
@@ -27938,6 +27969,8 @@ function useMemberSiteAccessService() {
27938
27969
  siteId: row.site,
27939
27970
  siteName: row.name,
27940
27971
  dateInvited: (/* @__PURE__ */ new Date()).toISOString(),
27972
+ // "placed at this site by the editor": confines an organisation role to it (siteInvitedPins)
27973
+ createdBy: actor,
27941
27974
  ...suspended ? { status: "suspended" } : {}
27942
27975
  });
27943
27976
  }
@@ -63571,6 +63604,17 @@ var ROBOT_CLOUD_PATHS = {
63571
63604
  workHours: "/portal/workhours/get-work-hours",
63572
63605
  haltPlans: "/portal/halt-plan/get",
63573
63606
  stationState: "/portal/detailstate/get-station-detail-state",
63607
+ // Read-only map layers and logs (2026-09-30: every read in the document is used).
63608
+ wallList: "/portal/maps/get-wall-list",
63609
+ safeZoneList: "/portal/maps/get-safezone-list",
63610
+ slowZoneList: "/portal/maps/get-slowzone-list",
63611
+ obstacleLayer: "/portal/obstacles/get-obstacle-layer",
63612
+ teachTrack: "/portal/teach/get-teach-track",
63613
+ mapVersions: "/portal/maps/get-map-version-list",
63614
+ floorMapHistory: "/portal/maps/v2405/get-floor-maplist",
63615
+ robotLog: "/portal/report/get-robot-log-list",
63616
+ userLog: "/portal/userlog/get-user-logs",
63617
+ cleaningSetting: "/portal/setting/get-cleaning-setting",
63574
63618
  mapList: "/portal/maps/get-map-list",
63575
63619
  fileUrl: "/portal/maps/get-file-url",
63576
63620
  tracking: "/portal/robots/tracking",
@@ -63871,7 +63915,17 @@ var STEP_NAMES = {
63871
63915
  [ROBOT_CLOUD_PATHS.haltPlans]: "the power schedule",
63872
63916
  [ROBOT_CLOUD_PATHS.stationState]: "the base station state",
63873
63917
  [ROBOT_CLOUD_PATHS.alertList]: "the alert history",
63874
- [ROBOT_CLOUD_PATHS.liftList]: "the lift list"
63918
+ [ROBOT_CLOUD_PATHS.liftList]: "the lift list",
63919
+ [ROBOT_CLOUD_PATHS.wallList]: "the virtual walls",
63920
+ [ROBOT_CLOUD_PATHS.safeZoneList]: "the safe zones",
63921
+ [ROBOT_CLOUD_PATHS.slowZoneList]: "the slow zones",
63922
+ [ROBOT_CLOUD_PATHS.obstacleLayer]: "the obstacle layer",
63923
+ [ROBOT_CLOUD_PATHS.teachTrack]: "the taught route",
63924
+ [ROBOT_CLOUD_PATHS.mapVersions]: "the map versions",
63925
+ [ROBOT_CLOUD_PATHS.floorMapHistory]: "the floor's map history",
63926
+ [ROBOT_CLOUD_PATHS.robotLog]: "the robot status log",
63927
+ [ROBOT_CLOUD_PATHS.userLog]: "the app action log",
63928
+ [ROBOT_CLOUD_PATHS.cleaningSetting]: "the robot settings"
63875
63929
  };
63876
63930
  var robotCloudStepName = (path5) => STEP_NAMES[path5] ?? path5;
63877
63931
  var SUCCESS_CODES = /* @__PURE__ */ new Set(["0", "200"]);
@@ -64091,6 +64145,40 @@ function useRobotCloudService(credentials, options = {}) {
64091
64145
  );
64092
64146
  return data.state ?? null;
64093
64147
  }
64148
+ async function getMapShapes(kind, mapId) {
64149
+ const [path5, list] = kind === "walls" ? [ROBOT_CLOUD_PATHS.wallList, "wallList"] : kind === "safe" ? [ROBOT_CLOUD_PATHS.safeZoneList, "safeZoneList"] : [ROBOT_CLOUD_PATHS.slowZoneList, "slowZoneList"];
64150
+ const data = await post(path5, { mapId }, list);
64151
+ return Array.isArray(data[list]) ? data[list] : [];
64152
+ }
64153
+ async function getObstacleLayer(robotId) {
64154
+ return post(ROBOT_CLOUD_PATHS.obstacleLayer, { robotId, version: 0 });
64155
+ }
64156
+ async function getTeachTrack(robotId, offset) {
64157
+ return post(ROBOT_CLOUD_PATHS.teachTrack, { robotId, offset }, "trackList");
64158
+ }
64159
+ async function getMapVersions(mapId) {
64160
+ return post(ROBOT_CLOUD_PATHS.mapVersions, { mapId }, "versionList");
64161
+ }
64162
+ async function getFloorMapHistory(sceneId, floorNo2) {
64163
+ return post(ROBOT_CLOUD_PATHS.floorMapHistory, { sceneId, floorNo: floorNo2 }, "mapList");
64164
+ }
64165
+ async function getRobotLog(robotId, sceneId, timeFrom, timeTo) {
64166
+ return post(
64167
+ ROBOT_CLOUD_PATHS.robotLog,
64168
+ { robotId: String(robotId), sceneId: String(sceneId), timeFrom, timeTo, key: "", pageNo: 1, pageSize: 50 },
64169
+ "logList"
64170
+ );
64171
+ }
64172
+ async function getUserLogs(robotId, startTime, endTime) {
64173
+ return post(
64174
+ ROBOT_CLOUD_PATHS.userLog,
64175
+ { robotId, pageNo: 1, pageSize: 50, startTime, endTime, keyword: "" },
64176
+ "actionList"
64177
+ );
64178
+ }
64179
+ async function getCleaningSetting(robotId) {
64180
+ return post(ROBOT_CLOUD_PATHS.cleaningSetting, { robotId });
64181
+ }
64094
64182
  async function getMaps(robotSn) {
64095
64183
  const data = await post(
64096
64184
  ROBOT_CLOUD_PATHS.mapList,
@@ -64176,6 +64264,14 @@ function useRobotCloudService(credentials, options = {}) {
64176
64264
  getHaltPlans,
64177
64265
  getStationState,
64178
64266
  getLiftList,
64267
+ getMapShapes,
64268
+ getObstacleLayer,
64269
+ getTeachTrack,
64270
+ getMapVersions,
64271
+ getFloorMapHistory,
64272
+ getRobotLog,
64273
+ getUserLogs,
64274
+ getCleaningSetting,
64179
64275
  getMaps,
64180
64276
  getFileUrls,
64181
64277
  getPlans,
@@ -64370,22 +64466,48 @@ function ounitechFault(list) {
64370
64466
  return { code: "error", message: String(list[0]).slice(0, 200) };
64371
64467
  return { code: "error", message: "The robot reports an error" };
64372
64468
  }
64373
- function ounitechMapShare(map, x, y) {
64469
+ function ounitechMapShare(map, x, y, clamp4 = false) {
64374
64470
  const w = map?.width, h = map?.height, r = map?.resolution;
64375
64471
  const wx = num(x), wy = num(y);
64376
64472
  if (!finite(w) || !finite(h) || !finite(r) || w <= 0 || h <= 0 || r <= 0 || wx === void 0 || wy === void 0)
64377
64473
  return null;
64378
- const sx = (wx - (num(map?.originX) ?? 0)) / r / w * 100;
64379
- const sy = 100 - (wy - (num(map?.originY) ?? 0)) / r / h * 100;
64474
+ let sx = (wx - (num(map?.originX) ?? 0)) / r / w * 100;
64475
+ let sy = 100 - (wy - (num(map?.originY) ?? 0)) / r / h * 100;
64476
+ if (clamp4)
64477
+ sx = Math.min(100, Math.max(0, sx)), sy = Math.min(100, Math.max(0, sy));
64380
64478
  if (sx < 0 || sx > 100 || sy < 0 || sy > 100)
64381
64479
  return null;
64382
64480
  return { x: Math.round(sx * 100) / 100, y: Math.round(sy * 100) / 100 };
64383
64481
  }
64384
- function ounitechMapView(m, imageUrl, partitionImageUrl, size) {
64482
+ var SHAPE_WORDS = { wall: "Virtual wall", safe: "Safe zone", slow: "Slow zone" };
64483
+ function ounitechShapes(map, kind, list) {
64484
+ return (Array.isArray(list) ? list : []).flatMap((z5, i) => {
64485
+ const points = (Array.isArray(z5?.pointList) ? z5.pointList : []).flatMap((p) => {
64486
+ const at2 = Array.isArray(p) ? ounitechMapShare(map, p[0], p[1], true) : null;
64487
+ return at2 ? [at2] : [];
64488
+ });
64489
+ const name = String(z5?.wallName ?? z5?.zoneName ?? "").trim();
64490
+ return points.length >= 2 ? [{ kind, name: name || `${SHAPE_WORDS[kind]} ${i + 1}`, points }] : [];
64491
+ });
64492
+ }
64493
+ function ounitechTrack(map, list) {
64494
+ const on = (Array.isArray(list) ? list : []).filter((p) => p && (p.mapid == null || String(p.mapid) === map?.mapId));
64495
+ const step = Math.max(1, Math.ceil(on.length / 500));
64496
+ return on.filter((_, i) => i % step === 0).flatMap((p) => {
64497
+ const at2 = ounitechMapShare(map, p.x, p.y, true);
64498
+ return at2 ? [at2] : [];
64499
+ });
64500
+ }
64501
+ function ounitechMapView(m, imageUrl, partitionImageUrl, size, shapes = {}) {
64385
64502
  const floorNo2 = Number.isInteger(m.floorNo) ? m.floorNo : null;
64386
64503
  const width = size?.width ?? null;
64387
64504
  const height = size?.height ?? null;
64388
64505
  const frame2 = { width, height, resolution: m.resolution, originX: m.originX, originY: m.originY };
64506
+ const zones = [
64507
+ ...ounitechShapes(frame2, "wall", Array.isArray(shapes.walls) && shapes.walls.length ? shapes.walls : m.wallList),
64508
+ ...ounitechShapes(frame2, "safe", shapes.safe),
64509
+ ...ounitechShapes(frame2, "slow", shapes.slow)
64510
+ ];
64389
64511
  const areas = (Array.isArray(m.areaList) ? m.areaList : []).filter((a) => typeof a?.areaName === "string" && a.areaName.trim()).map((a) => {
64390
64512
  const px = num(a.x), py = num(a.y);
64391
64513
  const at2 = width && height && px !== void 0 && py !== void 0 && (px > 0 || py > 0) && px <= width && py <= height ? { x: Math.round(px / width * 1e4) / 100, y: Math.round(py / height * 1e4) / 100 } : null;
@@ -64415,7 +64537,11 @@ function ounitechMapView(m, imageUrl, partitionImageUrl, size) {
64415
64537
  positionApproximate: true,
64416
64538
  // The raw areas, kept for older screens.
64417
64539
  areaList: areas.map(({ at: _at, ...a }) => a),
64418
- areas: areas.map((a) => ({ name: a.areaName, at: a.at }))
64540
+ areas: areas.map((a) => ({ name: a.areaName, at: a.at })),
64541
+ /** Virtual walls (the robot never crosses), safe zones and slow zones, drawn on the map. */
64542
+ zones,
64543
+ /** The route the robot was taught (get-teach-track), set per robot by getMaps. */
64544
+ taughtRoute: []
64419
64545
  };
64420
64546
  }
64421
64547
  function ounitechCurrentMap(maps, taskMapId, floorNo2) {
@@ -64642,10 +64768,115 @@ function ounitechPowerPlan(p) {
64642
64768
  enabled: p.enableFlag !== false
64643
64769
  };
64644
64770
  }
64771
+ function ounitechStatusLog(l, tz = 480) {
64772
+ const code = num(l.typeFlag);
64773
+ const words = code !== void 0 ? ROBOT_STATUS_NAMES[code] : void 0;
64774
+ return {
64775
+ at: ounitechTime(l.timeFrom, tz),
64776
+ status: words ? code === 15 || code === 16 ? `${words} (mid-job)` : words : String(l.typeName ?? "").trim() || `Status ${l.typeFlag ?? "?"}`
64777
+ };
64778
+ }
64779
+ var ACTION_WORDS = {
64780
+ \u539F\u5730\u505C\u6B62: "Stopped in place",
64781
+ \u4E0B\u53D1\u6E05\u6D01: "Started a cleaning job",
64782
+ \u793A\u6559\u8DEF\u5F84\u63A7\u5236: "Controlled a taught route",
64783
+ \u6682\u505C: "Paused",
64784
+ \u7EE7\u7EED: "Resumed",
64785
+ \u56DE\u5145: "Sent back to charge",
64786
+ \u7ED3\u675F\u6E05\u6D01: "Ended the cleaning job",
64787
+ \u5F00\u673A: "Switched on",
64788
+ \u5173\u673A: "Switched off"
64789
+ };
64790
+ function ounitechAppAction(a, tz = 480) {
64791
+ const raw = String(a.actionName ?? "").trim();
64792
+ return {
64793
+ at: ounitechTime(a.createTime, tz),
64794
+ action: ACTION_WORDS[raw] ?? (raw ? /[㐀-鿿]/.test(raw) ? `App action: ${raw}` : raw : "App action"),
64795
+ by: String(a.userName ?? "").trim() || null
64796
+ };
64797
+ }
64798
+ function ounitechMapVersion(v, tz = 480) {
64799
+ return { version: num(v.versionNo) ?? null, latest: v.latestFlag === true, createdAt: ounitechTime(v.createTime, tz) };
64800
+ }
64801
+ function ounitechFloorMapHistory(m, tz = 480) {
64802
+ const area = num(m.totalArea);
64803
+ return {
64804
+ name: String(m.mapName ?? "").trim() || `Map ${m.mapId ?? ""}`.trim(),
64805
+ areaM2: area !== void 0 ? Math.round(area * 10) / 10 : null,
64806
+ latest: m.latestFlag === true,
64807
+ inUse: m.enableFlag === true,
64808
+ createdAt: ounitechTime(m.createTime, tz),
64809
+ updatedAt: ounitechTime(m.updateTime, tz)
64810
+ };
64811
+ }
64812
+ var SETTINGS = {
64813
+ soundLevel: { group: "Sound", label: "Sound level", unit: "%" },
64814
+ enableWorkSound: { group: "Sound", label: "Sounds while working" },
64815
+ clean_speed_mode: { group: "Cleaning", label: "Cleaning speed" },
64816
+ fanLevel: { group: "Cleaning", label: "Suction level" },
64817
+ waterGear: { group: "Cleaning", label: "Water level" },
64818
+ planningWidth: { group: "Cleaning", label: "Cleaning path width" },
64819
+ alongWallFlag: { group: "Cleaning", label: "Edge cleaning" },
64820
+ alongWallDistance: { group: "Cleaning", label: "Edge distance" },
64821
+ enablePlanFlag: { group: "Cleaning", label: "Schedules switched on" },
64822
+ slowZoneSpeed: { group: "Cleaning", label: "Speed in slow zones", unit: "m/s" },
64823
+ minCleanBatteryPerc: { group: "Battery", label: "Lowest battery to start cleaning", unit: "%" },
64824
+ normalCleanBatteryPerc: { group: "Battery", label: "Normal cleaning battery", unit: "%" },
64825
+ backChargeThreshold: { group: "Battery", label: "Return-to-charge battery level", unit: "%" },
64826
+ chargeCompIntervalDay: { group: "Battery", label: "Full charge every", unit: "days" },
64827
+ obsAvoidSens: { group: "Safety", label: "Obstacle avoidance sensitivity" },
64828
+ obsDetectionHeight: { group: "Safety", label: "Obstacle detection height" },
64829
+ enableCliffDetect: { group: "Safety", label: "Cliff (drop) detection" },
64830
+ ultrasonicSwitch: { group: "Safety", label: "Ultrasonic sensors" },
64831
+ panicButtonMode: { group: "Safety", label: "Emergency button mode" },
64832
+ autoUpdateMap: { group: "Maps", label: "Update the map automatically" },
64833
+ autoUpdateVirtualWall: { group: "Maps", label: "Update virtual walls automatically" },
64834
+ autoUpdateZoneMap: { group: "Maps", label: "Update the area map automatically" },
64835
+ stationAutoDrain: { group: "Base station", label: "Station drains automatically" },
64836
+ language: { group: "Robot", label: "Robot language" },
64837
+ timeZone: { group: "Robot", label: "Robot time zone" }
64838
+ };
64839
+ var SECRET_SETTING = /wifi|pwd|password|secret|token/i;
64840
+ var LANG_WORDS = { zh: "Chinese", en: "English" };
64841
+ function ounitechSettings(s) {
64842
+ const out = [];
64843
+ for (const [key, v] of Object.entries(s ?? {})) {
64844
+ if (key === "ok" || key === "msg" || SECRET_SETTING.test(key) || v == null || typeof v === "object")
64845
+ continue;
64846
+ const raw = String(v).trim();
64847
+ if (raw === "")
64848
+ continue;
64849
+ const d = SETTINGS[key] ?? { group: "Other settings", label: key };
64850
+ let value;
64851
+ if (typeof v === "boolean")
64852
+ value = v ? "On" : "Off";
64853
+ else if (key === "language")
64854
+ value = LANG_WORDS[raw] ?? raw;
64855
+ else if (key === "timeZone" && num(raw) !== void 0)
64856
+ value = `UTC${num(raw) >= 0 ? "+" : ""}${raw}`;
64857
+ else
64858
+ value = d.unit ? `${raw} ${d.unit}` : raw;
64859
+ out.push({ key, group: d.group, label: d.label, value, attention: false });
64860
+ }
64861
+ const order = ["Cleaning", "Battery", "Safety", "Maps", "Sound", "Base station", "Robot", "Other settings"];
64862
+ return out.sort((a, b) => order.indexOf(a.group) - order.indexOf(b.group));
64863
+ }
64864
+ function ounitechObstacleImage(o) {
64865
+ const b64 = typeof o?.image === "string" ? o.image.replace(/^data:image\/\w+;base64,/, "").trim() : "";
64866
+ if (!b64 || !/^[A-Za-z0-9+/=\s]+$/.test(b64.slice(0, 200)))
64867
+ return null;
64868
+ if (b64.length > 15e5)
64869
+ return { version: num(o?.version) ?? null, image: null, tooLarge: true };
64870
+ const type = b64.startsWith("/9j/") ? "jpeg" : "png";
64871
+ return { version: num(o?.version) ?? null, image: `data:image/${type};base64,${b64}`, tooLarge: false };
64872
+ }
64645
64873
  var MAP_TTL_MS = 10 * 6e4;
64646
64874
  var RECORDS_TTL_MS = 2 * 6e4;
64647
64875
  var mapCache = /* @__PURE__ */ new Map();
64648
64876
  var recordsCache = /* @__PURE__ */ new Map();
64877
+ var SLOW_TTL_MS = 10 * 6e4;
64878
+ var slowCache = /* @__PURE__ */ new Map();
64879
+ var trackCache = /* @__PURE__ */ new Map();
64649
64880
  function cached2(cache3, field, key, ttl, load) {
64650
64881
  const hit = cache3.get(key);
64651
64882
  if (hit && Date.now() - hit.at < ttl)
@@ -64658,6 +64889,8 @@ function cached2(cache3, field, key, ttl, load) {
64658
64889
  function clearOunitechCaches() {
64659
64890
  mapCache.clear();
64660
64891
  recordsCache.clear();
64892
+ slowCache.clear();
64893
+ trackCache.clear();
64661
64894
  }
64662
64895
  var robotTime = (d) => robotCloudTimestamp(d, "yyyy-MM-dd HH:mm:ss", "Asia/Shanghai");
64663
64896
  function createOunitechAdapter(credentials, options) {
@@ -64671,12 +64904,15 @@ function createOunitechAdapter(credentials, options) {
64671
64904
  const keys = list.flatMap((m) => [m.backgroundFileKey, m.partitionFileKey]).filter((k) => typeof k === "string" && !!k);
64672
64905
  const files = keys.length ? await cloud.previewFiles(keys).catch(() => []) : [];
64673
64906
  const url2 = (k) => files.find((f) => f.fileKey === k)?.url || null;
64674
- return Promise.all(
64675
- list.map(async (m) => {
64676
- const imageUrl = url2(m.backgroundFileKey);
64677
- return ounitechMapView(m, imageUrl, url2(m.partitionFileKey), imageUrl ? await fetchPngSize(imageUrl) : null);
64678
- })
64679
- );
64907
+ const views = [];
64908
+ for (const m of list) {
64909
+ const imageUrl = url2(m.backgroundFileKey);
64910
+ const id = num(m.mapId);
64911
+ const layer = (kind) => id !== void 0 ? cloud.getMapShapes(kind, id).catch(() => []) : Promise.resolve([]);
64912
+ const shapes = { walls: await layer("walls"), safe: await layer("safe"), slow: await layer("slow") };
64913
+ views.push(ounitechMapView(m, imageUrl, url2(m.partitionFileKey), imageUrl ? await fetchPngSize(imageUrl) : null, shapes));
64914
+ }
64915
+ return views;
64680
64916
  });
64681
64917
  }
64682
64918
  async function placed(row, robot) {
@@ -64690,6 +64926,40 @@ function createOunitechAdapter(credentials, options) {
64690
64926
  robot.planPosition = robot.position ? ounitechMapShare(map, robot.position.x, robot.position.y) : null;
64691
64927
  return robot;
64692
64928
  }
64929
+ async function slowRecords(robotSn) {
64930
+ const row = await rowFor(robotSn);
64931
+ if (!row || !finite(row.robotId))
64932
+ return {};
64933
+ const robotId = row.robotId;
64934
+ const tz = finite(row.timezone) ? row.timezone : 480;
64935
+ const now = /* @__PURE__ */ new Date();
64936
+ const since = (days) => robotTime(new Date(now.getTime() - days * 864e5));
64937
+ const part = async (read2) => {
64938
+ try {
64939
+ return { items: await read2() };
64940
+ } catch (e) {
64941
+ return { items: [], error: String(e?.message ?? e).slice(0, 300) };
64942
+ }
64943
+ };
64944
+ const maps = finite(row.sceneId) ? await floorMaps(row.sceneId).catch(() => []) : [];
64945
+ const map = ounitechCurrentMap(maps, row.taskState?.mapId, Number.isInteger(row.floorNo) ? row.floorNo : null);
64946
+ const mapId = map ? num(map.mapId) : void 0;
64947
+ const floorNo2 = map?.floorNo ?? (Number.isInteger(row.floorNo) ? row.floorNo : null);
64948
+ const sceneId = finite(row.sceneId) ? row.sceneId : null;
64949
+ const statusLog = sceneId !== null ? await part(async () => ((await cloud.getRobotLog(robotId, sceneId, since(7), robotTime(now))).logList ?? []).map((l) => ounitechStatusLog(l, tz))) : { items: [] };
64950
+ const appActions = await part(async () => ((await cloud.getUserLogs(robotId, since(30), robotTime(now))).actionList ?? []).map((a) => ounitechAppAction(a, tz)));
64951
+ const settings = await part(async () => ounitechSettings(await cloud.getCleaningSetting(robotId)));
64952
+ const obstacles = await part(async () => {
64953
+ const o = ounitechObstacleImage(await cloud.getObstacleLayer(robotId));
64954
+ return o ? [o] : [];
64955
+ });
64956
+ const mapVersions = mapId !== void 0 ? await part(async () => ((await cloud.getMapVersions(mapId)).versionList ?? []).map((v) => ounitechMapVersion(v, tz))) : { items: [] };
64957
+ const mapHistory = sceneId !== null && floorNo2 !== null ? await part(async () => ((await cloud.getFloorMapHistory(sceneId, floorNo2)).mapList ?? []).map((m) => ounitechFloorMapHistory(m, tz))) : { items: [] };
64958
+ const mapLayers = {
64959
+ items: map ? [{ map: map.mapDisplayName || null, zones: map.zones.map((z5) => ({ kind: z5.kind, name: z5.name })) }] : []
64960
+ };
64961
+ return { statusLog, appActions, settings, obstacles, mapVersions, mapHistory, mapLayers };
64962
+ }
64693
64963
  async function rowFor(robotSn) {
64694
64964
  return (await cloud.getHomeRobots()).find((r) => r.robotSn === robotSn) ?? null;
64695
64965
  }
@@ -64753,7 +65023,18 @@ function createOunitechAdapter(credentials, options) {
64753
65023
  const row = await rowFor(robotSn);
64754
65024
  if (!row || !finite(row.sceneId))
64755
65025
  return [];
64756
- return floorMaps(row.sceneId);
65026
+ const maps = await floorMaps(row.sceneId);
65027
+ const robotId = row.robotId;
65028
+ if (!finite(robotId))
65029
+ return maps;
65030
+ const track = await cached2(
65031
+ trackCache,
65032
+ "value",
65033
+ `${options?.baseUrl ?? ""}|${credentials.appid}|${robotId}`,
65034
+ SLOW_TTL_MS,
65035
+ async () => (await cloud.getTeachTrack(robotId, robotTime(new Date(Date.now() - 365 * 864e5)))).trackList ?? []
65036
+ ).catch(() => []);
65037
+ return maps.map((m) => ({ ...m, taughtRoute: ounitechTrack(m, track) }));
64757
65038
  },
64758
65039
  async getMapImages(_robotSn, fileKeys) {
64759
65040
  return cloud.previewFiles(fileKeys);
@@ -64779,7 +65060,7 @@ function createOunitechAdapter(credentials, options) {
64779
65060
  * after another (gentler on the rate limit); each part carries its own error.
64780
65061
  */
64781
65062
  async getRecords(robotSn) {
64782
- return cached2(recordsCache, "value", `${options?.baseUrl ?? ""}|${credentials.appid}|${robotSn}`, RECORDS_TTL_MS, async () => {
65063
+ const fast = cached2(recordsCache, "value", `${options?.baseUrl ?? ""}|${credentials.appid}|${robotSn}`, RECORDS_TTL_MS, async () => {
64783
65064
  const row = await rowFor(robotSn);
64784
65065
  if (!row || !finite(row.robotId))
64785
65066
  return { supported: true, error: `Serial ${robotSn} is not on this Ounitech account.` };
@@ -64822,6 +65103,8 @@ function createOunitechAdapter(credentials, options) {
64822
65103
  });
64823
65104
  return { supported: true, readAt: now.toISOString(), tasks, alerts, consumables, power, lifts, readings, station };
64824
65105
  });
65106
+ const slow = await cached2(slowCache, "value", `${options?.baseUrl ?? ""}|${credentials.appid}|${robotSn}`, SLOW_TTL_MS, () => slowRecords(robotSn));
65107
+ return { ...slow, ...await fast };
64825
65108
  },
64826
65109
  /**
64827
65110
  * Staff Diagnose: every other read-only chapter-6 call, with the vendor's
@@ -64853,8 +65136,8 @@ function createOunitechAdapter(credentials, options) {
64853
65136
  return { count: list.length, robots: list.slice(0, 3).map((r) => ({ robotSn: r.robotSn, statusName: r.statusName })) };
64854
65137
  });
64855
65138
  await run("serial-based map list (chapter 2)", ROBOT_CLOUD_PATHS.mapList, { robotSn }, async () => {
64856
- const maps = await cloud.getMaps(robotSn);
64857
- return { count: maps.length, floors: maps.map((m) => m.floorNo) };
65139
+ const maps2 = await cloud.getMaps(robotSn);
65140
+ return { count: maps2.length, floors: maps2.map((m) => m.floorNo) };
64858
65141
  });
64859
65142
  const row = await rowFor(robotSn).catch(() => null);
64860
65143
  if (!row || !finite(row.robotId)) {
@@ -64906,6 +65189,50 @@ function createOunitechAdapter(credentials, options) {
64906
65189
  return (r.liftlist ?? []).map((l) => ({ liftno: l.liftno, liftname: l.liftname, floorno: l.floorno, onlineflag: l.onlineflag, enableflag: l.enableflag }));
64907
65190
  });
64908
65191
  }
65192
+ const maps = finite(row.sceneId) ? await floorMaps(row.sceneId).catch(() => []) : [];
65193
+ const map = ounitechCurrentMap(maps, row.taskState?.mapId, Number.isInteger(row.floorNo) ? row.floorNo : null);
65194
+ const mapId = map ? num(map.mapId) : void 0;
65195
+ if (mapId !== void 0) {
65196
+ await run("virtual walls (get-wall-list)", ROBOT_CLOUD_PATHS.wallList, { mapId }, async () => cut(await cloud.getMapShapes("walls", mapId)));
65197
+ await run("safe zones (get-safezone-list)", ROBOT_CLOUD_PATHS.safeZoneList, { mapId }, async () => cut(await cloud.getMapShapes("safe", mapId)));
65198
+ await run("slow zones (get-slowzone-list)", ROBOT_CLOUD_PATHS.slowZoneList, { mapId }, async () => cut(await cloud.getMapShapes("slow", mapId)));
65199
+ await run("map versions (get-map-version-list)", ROBOT_CLOUD_PATHS.mapVersions, { mapId }, async () => cut((await cloud.getMapVersions(mapId)).versionList ?? []));
65200
+ }
65201
+ if (finite(row.sceneId) && map?.floorNo != null) {
65202
+ const sceneId = row.sceneId;
65203
+ const floorNo2 = map.floorNo;
65204
+ await run(
65205
+ "floor map history (v2405/get-floor-maplist)",
65206
+ ROBOT_CLOUD_PATHS.floorMapHistory,
65207
+ { sceneId, floorNo: floorNo2 },
65208
+ async () => cut((await cloud.getFloorMapHistory(sceneId, floorNo2)).mapList ?? [])
65209
+ );
65210
+ }
65211
+ await run("obstacle layer (get-obstacle-layer)", ROBOT_CLOUD_PATHS.obstacleLayer, { robotId, version: 0 }, async () => {
65212
+ const o = await cloud.getObstacleLayer(robotId);
65213
+ return { version: o.version ?? null, imageChars: typeof o.image === "string" ? o.image.length : 0 };
65214
+ });
65215
+ await run("taught route (get-teach-track)", ROBOT_CLOUD_PATHS.teachTrack, { robotId, offset: window.timeFrom }, async () => {
65216
+ const r = await cloud.getTeachTrack(robotId, window.timeFrom);
65217
+ return { points: r.trackList?.length ?? 0, since: r.since ?? null, first: cut(r.trackList ?? []) };
65218
+ });
65219
+ if (finite(row.sceneId)) {
65220
+ const sceneId = row.sceneId;
65221
+ await run("robot status log, last 7 days (get-robot-log-list)", ROBOT_CLOUD_PATHS.robotLog, { robotId, sceneId, ...window }, async () => {
65222
+ const r = await cloud.getRobotLog(robotId, sceneId, window.timeFrom, window.timeTo);
65223
+ return { total: r.total ?? r.logList?.length ?? 0, latest: cut(r.logList ?? []) };
65224
+ });
65225
+ }
65226
+ await run("app action log, last 7 days (get-user-logs)", ROBOT_CLOUD_PATHS.userLog, { robotId, startTime: window.timeFrom, endTime: window.timeTo }, async () => {
65227
+ const r = await cloud.getUserLogs(robotId, window.timeFrom, window.timeTo);
65228
+ return { total: r.total ?? r.actionList?.length ?? 0, latest: cut((r.actionList ?? []).map(({ userPhone: _p, ...a }) => a)) };
65229
+ });
65230
+ await run(
65231
+ "robot settings (get-cleaning-setting)",
65232
+ ROBOT_CLOUD_PATHS.cleaningSetting,
65233
+ { robotId },
65234
+ async () => Object.fromEntries(Object.entries(await cloud.getCleaningSetting(robotId)).filter(([k]) => !SECRET_SETTING.test(k) && k !== "ok" && k !== "msg"))
65235
+ );
64909
65236
  steps.push({
64910
65237
  step: "current job (robot list taskState)",
64911
65238
  path: ROBOT_CLOUD_PATHS.homeRobotList,
@@ -107329,6 +107656,7 @@ export {
107329
107656
  ROBOT_TYPES,
107330
107657
  RobotCloudError,
107331
107658
  RobotCredentialsError,
107659
+ SECRET_SETTING,
107332
107660
  SELF_SERVICE_RESEND_COOLDOWN_MS,
107333
107661
  SELF_SIGNUP_PLATFORM,
107334
107662
  SELF_SIGNUP_STATUS,
@@ -107625,18 +107953,26 @@ export {
107625
107953
  orgRoleSets,
107626
107954
  orgSchema,
107627
107955
  ounitechAlertRecord,
107956
+ ounitechAppAction,
107628
107957
  ounitechCleaning,
107629
107958
  ounitechConsumable,
107630
107959
  ounitechCurrentMap,
107631
107960
  ounitechFault,
107961
+ ounitechFloorMapHistory,
107632
107962
  ounitechJobWords,
107633
107963
  ounitechMapShare,
107964
+ ounitechMapVersion,
107634
107965
  ounitechMapView,
107966
+ ounitechObstacleImage,
107635
107967
  ounitechPowerPlan,
107636
107968
  ounitechReadings,
107637
107969
  ounitechRobotFromHomeRow,
107970
+ ounitechSettings,
107971
+ ounitechShapes,
107972
+ ounitechStatusLog,
107638
107973
  ounitechTaskRecord,
107639
107974
  ounitechTime,
107975
+ ounitechTrack,
107640
107976
  overnight_parking_requests_namespace_collection,
107641
107977
  padbotAccountKey,
107642
107978
  padbotAlarmFault,