@7365admin1/core 3.137.0 → 3.139.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.js CHANGED
@@ -4371,6 +4371,53 @@ function sameAppType(a, b) {
4371
4371
  return !!a && !!b && serviceAppType(a) === serviceAppType(b);
4372
4372
  }
4373
4373
 
4374
+ // src/utils/member-lookup.util.ts
4375
+ var idOf = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
4376
+ function isLiveMembership(m) {
4377
+ return m.status !== "deleted" && !m.deletedAt;
4378
+ }
4379
+ function pickSiteMembership(rows, type, site, org = "") {
4380
+ site = site.toLowerCase();
4381
+ org = org.toLowerCase();
4382
+ if (!site)
4383
+ return null;
4384
+ const live2 = rows.filter(
4385
+ (m) => sameAppType(m.type, type) && isLiveMembership(m) && (!org || idOf(m.org) === org)
4386
+ );
4387
+ const atSite = live2.find((m) => idOf(m.siteId) === site);
4388
+ if (atSite)
4389
+ return atSite;
4390
+ if (!org)
4391
+ return null;
4392
+ return live2.find((m) => !idOf(m.siteId)) ?? live2[0] ?? null;
4393
+ }
4394
+ function pickLookupMembership(rows, type, site = "", org = "") {
4395
+ const list = Array.isArray(rows) ? rows : [];
4396
+ const atSite = site ? pickSiteMembership(list, type, site, org) : null;
4397
+ if (atSite)
4398
+ return atSite;
4399
+ const inOrg = org ? list.find((m) => sameAppType(m.type, type) && idOf(m.org) === org.toLowerCase()) : void 0;
4400
+ if (inOrg && isLiveMembership(inOrg))
4401
+ return inOrg;
4402
+ return preferWorkingMembership(list.filter((m) => sameAppType(m.type, type)));
4403
+ }
4404
+ var WORKING = /* @__PURE__ */ new Set(["active", "approved"]);
4405
+ function preferWorkingMembership(rows, defaultOrg = "") {
4406
+ const list = Array.isArray(rows) ? rows : [];
4407
+ const working = list.filter((m) => WORKING.has(String(m.status ?? "")) && !m.deletedAt);
4408
+ const home = idOf(defaultOrg);
4409
+ return (home ? working.find((m) => idOf(m.org) === home) : void 0) ?? working[0] ?? list.find(isLiveMembership) ?? list[0] ?? null;
4410
+ }
4411
+ function memberListSiteScope(memberships, org) {
4412
+ const inOrg = (memberships ?? []).filter((m) => idOf(m.org) === idOf(org));
4413
+ const here = inOrg.filter((m) => m.type !== "resident");
4414
+ if (!inOrg.length)
4415
+ return null;
4416
+ if (here.some((m) => !idOf(m.siteId) || m.orgLevelRole))
4417
+ return null;
4418
+ return [...new Set(here.map((m) => idOf(m.siteId)))];
4419
+ }
4420
+
4374
4421
  // src/repositories/member.repo.ts
4375
4422
  var import_node_server_utils15 = require("@7365admin1/node-server-utils");
4376
4423
 
@@ -4493,10 +4540,10 @@ var import_mongodb13 = require("mongodb");
4493
4540
  function crossSiteRoleMode() {
4494
4541
  return (process.env.CROSS_SITE_ROLE_MODE ?? "").trim().toLowerCase() === "enforce" ? "enforce" : "log";
4495
4542
  }
4496
- var idOf = (value) => value === null || value === void 0 ? "" : String(value?.toString?.() ?? value);
4543
+ var idOf2 = (value) => value === null || value === void 0 ? "" : String(value?.toString?.() ?? value);
4497
4544
  function isCrossSiteRole(role, memberSite) {
4498
- const roleSite = idOf(role?.site);
4499
- return !!roleSite && roleSite !== idOf(memberSite);
4545
+ const roleSite = idOf2(role?.site);
4546
+ return !!roleSite && roleSite !== idOf2(memberSite);
4500
4547
  }
4501
4548
  var THROTTLE_MS = 10 * 60 * 1e3;
4502
4549
  var lastLogged = /* @__PURE__ */ new Map();
@@ -4505,7 +4552,7 @@ function crossSiteRoleGrants(input) {
4505
4552
  return true;
4506
4553
  const mode = crossSiteRoleMode();
4507
4554
  const granted = mode !== "enforce";
4508
- const key = [mode, idOf(input.member) || idOf(input.user), idOf(input.role?._id), input.where].join("|");
4555
+ const key = [mode, idOf2(input.member) || idOf2(input.user), idOf2(input.role?._id), input.where].join("|");
4509
4556
  const now = Date.now();
4510
4557
  if ((lastLogged.get(key) ?? 0) + THROTTLE_MS <= now) {
4511
4558
  lastLogged.set(key, now);
@@ -4513,7 +4560,7 @@ function crossSiteRoleGrants(input) {
4513
4560
  lastLogged.clear();
4514
4561
  import_node_server_utils14.logger.log({
4515
4562
  level: "warn",
4516
- message: `cross-site-role mode=${mode} ${granted ? "granted" : "refused"} where=${input.where} member=${idOf(input.member) || "-"} user=${idOf(input.user) || "-"} site=${idOf(input.memberSite) || "-"} role=${idOf(input.role?._id) || "-"} roleSite=${idOf(input.role?.site)}`
4563
+ message: `cross-site-role mode=${mode} ${granted ? "granted" : "refused"} where=${input.where} member=${idOf2(input.member) || "-"} user=${idOf2(input.user) || "-"} site=${idOf2(input.memberSite) || "-"} role=${idOf2(input.role?._id) || "-"} roleSite=${idOf2(input.role?.site)}`
4517
4564
  });
4518
4565
  }
4519
4566
  return granted;
@@ -4524,7 +4571,7 @@ function crossSiteRoleRefusal(roleName, roleSiteName) {
4524
4571
  return `${role} belongs to ${site}. Choose a role for this site, or an org-wide role.`;
4525
4572
  }
4526
4573
  async function requireRoleFitsSite(roleId, siteId) {
4527
- const id = idOf(roleId);
4574
+ const id = idOf2(roleId);
4528
4575
  if (!id || !import_mongodb13.ObjectId.isValid(id))
4529
4576
  return;
4530
4577
  const db2 = import_node_server_utils14.useAtlas.getDb();
@@ -4533,7 +4580,7 @@ async function requireRoleFitsSite(roleId, siteId) {
4533
4580
  const role = await db2.collection("roles").findOne({ _id: new import_mongodb13.ObjectId(id) }, { projection: { name: 1, site: 1 } });
4534
4581
  if (!isCrossSiteRole(role, siteId))
4535
4582
  return;
4536
- const site = import_mongodb13.ObjectId.isValid(idOf(role?.site)) ? await db2.collection("sites").findOne({ _id: new import_mongodb13.ObjectId(idOf(role?.site)) }, { projection: { name: 1 } }) : null;
4583
+ const site = import_mongodb13.ObjectId.isValid(idOf2(role?.site)) ? await db2.collection("sites").findOne({ _id: new import_mongodb13.ObjectId(idOf2(role?.site)) }, { projection: { name: 1 } }) : null;
4537
4584
  throw new import_node_server_utils14.BadRequestError(crossSiteRoleRefusal(role?.name, site?.name));
4538
4585
  }
4539
4586
  async function otherSiteRoleClauses(roles, org) {
@@ -4541,7 +4588,7 @@ async function otherSiteRoleClauses(roles, org) {
4541
4588
  { ...org ? { org } : { org: { $nin: ["", null] } }, site: { $nin: ["", null] }, status: "active" },
4542
4589
  { projection: { site: 1, org: 1 } }
4543
4590
  ).toArray();
4544
- const both3 = (v) => import_mongodb13.ObjectId.isValid(idOf(v)) ? [new import_mongodb13.ObjectId(idOf(v)), idOf(v)] : [v];
4591
+ const both3 = (v) => import_mongodb13.ObjectId.isValid(idOf2(v)) ? [new import_mongodb13.ObjectId(idOf2(v)), idOf2(v)] : [v];
4545
4592
  const clauses = (list ?? []).map((r) => ({
4546
4593
  role: r._id,
4547
4594
  org: { $in: both3(r.org) },
@@ -4724,9 +4771,12 @@ function useMemberRepo() {
4724
4771
  } catch (error) {
4725
4772
  throw new import_node_server_utils15.BadRequestError("Invalid user ID format.");
4726
4773
  }
4774
+ const account = await db2?.collection("users").findOne({ _id: user }, { projection: { defaultOrg: 1 } }).catch(() => null);
4775
+ const defaultOrg = String(account?.defaultOrg ?? "");
4727
4776
  const cacheKey = (0, import_node_server_utils15.makeCacheKey)(namespace_collection3, {
4728
4777
  user: user.toString(),
4729
- type
4778
+ type,
4779
+ defaultOrg
4730
4780
  });
4731
4781
  const cachedData = await getCache(cacheKey);
4732
4782
  if (cachedData) {
@@ -4734,7 +4784,10 @@ function useMemberRepo() {
4734
4784
  return cachedData;
4735
4785
  }
4736
4786
  try {
4737
- const data = await collection.findOne({ user, type: appTypeQuery(type) });
4787
+ const data = preferWorkingMembership(
4788
+ await collection.find({ user, type: appTypeQuery(type) }).toArray(),
4789
+ defaultOrg
4790
+ );
4738
4791
  if (!data) {
4739
4792
  throw new import_node_server_utils15.NotFoundError("Member not found.");
4740
4793
  }
@@ -5197,7 +5250,12 @@ function useMemberRepo() {
5197
5250
  const query2 = {
5198
5251
  user,
5199
5252
  org: { $nin: [null, ""] },
5200
- type: appTypeQuery(type)
5253
+ type: appTypeQuery(type),
5254
+ // The organisation switcher. A membership that was taken away (Repair
5255
+ // sets it inactive, removal deletes it) must not keep the organisation
5256
+ // on the person's list: latitude.sec@ still saw JLL Singapore beside its
5257
+ // own company after the repair (owner report 2026-09-30).
5258
+ status: { $nin: ["inactive", "deleted"] }
5201
5259
  };
5202
5260
  const cacheOptions = {
5203
5261
  page: page2,
@@ -11358,7 +11416,7 @@ function robotFaultText(robotName, fault) {
11358
11416
  }
11359
11417
  return {
11360
11418
  title: "Robot fault",
11361
- body: `${robotName || "A robot"} reports a fault: ${fault.message || fault.code}.`
11419
+ body: `${robotName || "A robot"} reports a fault: ${String(fault.message || fault.code).replace(/\.\s*$/, "")}.`
11362
11420
  };
11363
11421
  }
11364
11422
  function robotConnectionText(robotName, online, minutes) {
@@ -11619,7 +11677,7 @@ function enforcedModuleList(stored, save) {
11619
11677
  return NO_LIST;
11620
11678
  return { list, since: typeof save?.createdAt === "string" ? save.createdAt : "" };
11621
11679
  }
11622
- var idOf2 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
11680
+ var idOf3 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
11623
11681
  function deniedForMembership({
11624
11682
  membership,
11625
11683
  org,
@@ -11627,12 +11685,12 @@ function deniedForMembership({
11627
11685
  site,
11628
11686
  siteSave
11629
11687
  }) {
11630
- const memberOrg = idOf2(membership?.org);
11688
+ const memberOrg = idOf3(membership?.org);
11631
11689
  if (!membership || membership.type === "resident" || !memberOrg) {
11632
11690
  return { denied: [], since: "" };
11633
11691
  }
11634
11692
  const orgTier = enforcedModuleList(org?.modules, orgSave);
11635
- const siteTier = site && idOf2(site.orgId) === memberOrg ? enforcedModuleList(site.metadata?.modules, siteSave) : NO_LIST;
11693
+ const siteTier = site && idOf3(site.orgId) === memberOrg ? enforcedModuleList(site.metadata?.modules, siteSave) : NO_LIST;
11636
11694
  const denied = deniedModules(orgTier.list, siteTier.list);
11637
11695
  if (!denied.length)
11638
11696
  return { denied: [], since: "" };
@@ -12201,46 +12259,6 @@ function staffMayBypass(actor, grant = CROSS_CLIENT_GRANT) {
12201
12259
  return consoleRoleAllows(actor.staffPermissions, grant.resource, grant.action);
12202
12260
  }
12203
12261
 
12204
- // src/utils/member-lookup.util.ts
12205
- var idOf3 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
12206
- function isLiveMembership(m) {
12207
- return m.status !== "deleted" && !m.deletedAt;
12208
- }
12209
- function pickSiteMembership(rows, type, site, org = "") {
12210
- site = site.toLowerCase();
12211
- org = org.toLowerCase();
12212
- if (!site)
12213
- return null;
12214
- const live2 = rows.filter(
12215
- (m) => sameAppType(m.type, type) && isLiveMembership(m) && (!org || idOf3(m.org) === org)
12216
- );
12217
- const atSite = live2.find((m) => idOf3(m.siteId) === site);
12218
- if (atSite)
12219
- return atSite;
12220
- if (!org)
12221
- return null;
12222
- return live2.find((m) => !idOf3(m.siteId)) ?? live2[0] ?? null;
12223
- }
12224
- function pickLookupMembership(rows, type, site = "", org = "") {
12225
- const list = Array.isArray(rows) ? rows : [];
12226
- const atSite = site ? pickSiteMembership(list, type, site, org) : null;
12227
- if (atSite)
12228
- return atSite;
12229
- const inOrg = org ? list.find((m) => sameAppType(m.type, type) && idOf3(m.org) === org.toLowerCase()) : void 0;
12230
- if (inOrg && isLiveMembership(inOrg))
12231
- return inOrg;
12232
- return list.find((m) => sameAppType(m.type, type)) ?? null;
12233
- }
12234
- function memberListSiteScope(memberships, org) {
12235
- const inOrg = (memberships ?? []).filter((m) => idOf3(m.org) === idOf3(org));
12236
- const here = inOrg.filter((m) => m.type !== "resident");
12237
- if (!inOrg.length)
12238
- return null;
12239
- if (here.some((m) => !idOf3(m.siteId) || m.orgLevelRole))
12240
- return null;
12241
- return [...new Set(here.map((m) => idOf3(m.siteId)))];
12242
- }
12243
-
12244
12262
  // src/services/module-gate.service.ts
12245
12263
  var idOf4 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
12246
12264
  var isId = (v) => /^[0-9a-f]{24}$/i.test(v);
@@ -25553,7 +25571,7 @@ function useServiceProviderInviteService() {
25553
25571
  );
25554
25572
  }
25555
25573
  const existing = await getByCreatorAndNature(actor.id, nature).catch(() => null);
25556
- if (existing) {
25574
+ if (existing && String(existing.invitedFrom ?? "") !== String(invite._id)) {
25557
25575
  throw new import_node_server_utils61.BadRequestError(
25558
25576
  `You already have a ${serviceTypeLabel(nature)} company on iService365 (${existing.name}). Accept the invitation from that company instead.`
25559
25577
  );
@@ -25584,12 +25602,21 @@ function useServiceProviderInviteService() {
25584
25602
  country
25585
25603
  }) {
25586
25604
  const inviteeId = String(user?._id ?? "");
25587
- const name = sanitizeCompanyName(companyName) || sanitizeCompanyName(invite.metadata?.name) || sanitizeCompanyName(user?.name);
25605
+ let name = sanitizeCompanyName(companyName) || sanitizeCompanyName(invite.metadata?.name) || sanitizeCompanyName(user?.name);
25588
25606
  if (!name)
25589
25607
  throw new import_node_server_utils61.BadRequestError("Enter your company's name.");
25590
25608
  const email = String(invite.email ?? "").trim().toLowerCase();
25591
25609
  const actor = { id: inviteeId };
25592
- const orgId = String(
25610
+ const db2 = import_node_server_utils61.useAtlas.getDb();
25611
+ const actorIds = [actor.id, ...import_mongodb56.ObjectId.isValid(actor.id) ? [new import_mongodb56.ObjectId(actor.id)] : []];
25612
+ const made = db2 ? await db2.collection("organizations").findOne({
25613
+ invitedFrom: { $in: [String(invite._id), invite._id] },
25614
+ createdBy: { $in: actorIds },
25615
+ status: { $ne: "deleted" }
25616
+ }) : null;
25617
+ if (made)
25618
+ name = String(made.name ?? name);
25619
+ const orgId = made ? String(made._id) : String(
25593
25620
  await addOrg({
25594
25621
  name,
25595
25622
  type: nature,
@@ -25601,8 +25628,17 @@ function useServiceProviderInviteService() {
25601
25628
  invitedFrom: String(invite._id)
25602
25629
  })
25603
25630
  );
25604
- const roles = await seedDefaultOrgRoles(useRoleRepo().addRole, orgId, nature);
25605
25631
  const appType = serviceAppType(nature);
25632
+ const owners = made && db2 ? await db2.collection("roles").find({
25633
+ org: { $in: [new import_mongodb56.ObjectId(orgId), orgId] },
25634
+ name: "owner",
25635
+ status: { $ne: "deleted" }
25636
+ }).toArray() : [];
25637
+ const ownerRole = (type) => {
25638
+ const r = owners.find((x) => x.type === type);
25639
+ return r ? { id: String(r._id), type, name: "owner" } : null;
25640
+ };
25641
+ const roles = ownerRole("organization") && ownerRole(appType) ? [ownerRole("organization"), ownerRole(appType)] : await seedDefaultOrgRoles(useRoleRepo().addRole, orgId, nature);
25606
25642
  const orgOwner = roles.find((r) => r.type === "organization" && r.name === "owner");
25607
25643
  const appOwner = roles.find((r) => r.type === appType && r.name === "owner");
25608
25644
  if (!orgOwner || !appOwner) {
@@ -25625,8 +25661,18 @@ function useServiceProviderInviteService() {
25625
25661
  siteName: "",
25626
25662
  onboardingRequired: false
25627
25663
  };
25628
- await addMember({ ...base, role: orgOwner.id, type: "organization" });
25629
- await addMember({ ...base, role: appOwner.id, type: appType });
25664
+ const hasOwnerRow = async (type) => !!made && !!await db2?.collection("members").findOne({
25665
+ user: { $in: actorIds },
25666
+ org: { $in: [new import_mongodb56.ObjectId(orgId), orgId] },
25667
+ type,
25668
+ status: "active"
25669
+ });
25670
+ if (!await hasOwnerRow("organization")) {
25671
+ await addMember({ ...base, role: orgOwner.id, type: "organization" });
25672
+ }
25673
+ if (!await hasOwnerRow(appType)) {
25674
+ await addMember({ ...base, role: appOwner.id, type: appType });
25675
+ }
25630
25676
  await useUserRepo().updateUserFieldById({ _id: actor.id, field: "defaultOrg", value: orgId }).catch(() => void 0);
25631
25677
  import_node_server_utils61.logger.log({
25632
25678
  level: "info",
@@ -25682,10 +25728,60 @@ function useServiceProviderInviteService() {
25682
25728
  const last = [...history].reverse().find((e) => ["wrong-join-repaired", "wrong-join-undone"].includes(e?.action));
25683
25729
  return last?.action === "wrong-join-repaired" ? last : null;
25684
25730
  }
25731
+ async function leftoverRoles(invite, senderOrg, user) {
25732
+ const db2 = import_node_server_utils61.useAtlas.getDb();
25733
+ if (!db2 || !user?._id || !import_mongodb56.ObjectId.isValid(senderOrg))
25734
+ return [];
25735
+ const since = new Date(String(invite.createdAt ?? 0)).getTime();
25736
+ const rows = await db2.collection("members").find({
25737
+ user: { $in: [user._id, String(user._id)] },
25738
+ org: { $in: [new import_mongodb56.ObjectId(senderOrg), senderOrg] }
25739
+ }).project({ role: 1 }).toArray();
25740
+ const roleIds = [
25741
+ ...new Set(
25742
+ rows.map((m) => m.role?.toString?.() ?? "").filter((r) => import_mongodb56.ObjectId.isValid(r))
25743
+ )
25744
+ ].map((r) => new import_mongodb56.ObjectId(r));
25745
+ if (!roleIds.length)
25746
+ return [];
25747
+ const roles = await db2.collection("roles").find({
25748
+ _id: { $in: roleIds },
25749
+ org: { $in: [new import_mongodb56.ObjectId(senderOrg), senderOrg] },
25750
+ status: { $ne: "deleted" }
25751
+ }).project({ name: 1, permissions: 1, createdAt: 1 }).toArray();
25752
+ const out = [];
25753
+ for (const role of roles) {
25754
+ if (!(Array.isArray(role.permissions) && role.permissions.includes("*")))
25755
+ continue;
25756
+ if (role.createdAt && Number.isFinite(since) && new Date(role.createdAt).getTime() < since)
25757
+ continue;
25758
+ const inUse = await db2.collection("members").countDocuments({
25759
+ role: { $in: [role._id, role._id.toString()] },
25760
+ status: "active"
25761
+ });
25762
+ if (inUse)
25763
+ continue;
25764
+ out.push({ _id: role._id.toString(), name: String(role.name ?? "") });
25765
+ }
25766
+ return out;
25767
+ }
25768
+ async function setRoleStatus(ids2, status) {
25769
+ const db2 = import_node_server_utils61.useAtlas.getDb();
25770
+ const valid = ids2.filter((r) => import_mongodb56.ObjectId.isValid(r)).map((r) => new import_mongodb56.ObjectId(r));
25771
+ if (!db2 || !valid.length)
25772
+ return;
25773
+ const now = (/* @__PURE__ */ new Date()).toISOString();
25774
+ await db2.collection("roles").updateMany(
25775
+ { _id: { $in: valid } },
25776
+ { $set: { status, deletedAt: status === "deleted" ? now : "", updatedAt: now } }
25777
+ );
25778
+ await (0, import_node_server_utils61.useCache)("roles").delNamespace().catch(() => void 0);
25779
+ }
25685
25780
  async function wrongJoinPreview({ id, userId }) {
25686
25781
  const { invite, senderOrg, user } = await wrongJoinContext(id, userId);
25687
25782
  const org = await getOrgById(senderOrg).catch(() => null);
25688
25783
  const memberships = await wrongJoinRows(invite, senderOrg, user);
25784
+ const roles = await leftoverRoles(invite, senderOrg, user);
25689
25785
  const repaired = lastRepair(invite);
25690
25786
  return {
25691
25787
  invitationId: id,
@@ -25694,6 +25790,8 @@ function useServiceProviderInviteService() {
25694
25790
  senderOrgId: senderOrg,
25695
25791
  senderOrgName: String(org?.name ?? ""),
25696
25792
  memberships,
25793
+ // Left in the sender's role list by the old sign-up; Repair retires them.
25794
+ leftoverRoles: roles,
25697
25795
  defaultOrgIsSender: String(user?.defaultOrg ?? "") === senderOrg,
25698
25796
  repaired: repaired ? { at: repaired.at, by: repaired.byName, memberIds: repaired.memberIds ?? [] } : null
25699
25797
  };
@@ -25702,15 +25800,17 @@ function useServiceProviderInviteService() {
25702
25800
  const { actor, invite, senderOrg, user } = await wrongJoinContext(id, userId);
25703
25801
  const rows = await wrongJoinRows(invite, senderOrg, user);
25704
25802
  const defaultOrgIsSender = !!user?._id && String(user?.defaultOrg ?? "") === senderOrg;
25705
- if (!rows.length && !defaultOrgIsSender) {
25803
+ const { updateMemberStatus } = useMemberRepo();
25804
+ for (const row of rows)
25805
+ await updateMemberStatus(row._id, { status: "inactive" });
25806
+ const roles = await leftoverRoles(invite, senderOrg, user);
25807
+ if (!rows.length && !defaultOrgIsSender && !roles.length) {
25706
25808
  return {
25707
25809
  message: "Nothing to repair: this account is not in the sender's organisation.",
25708
25810
  changed: 0
25709
25811
  };
25710
25812
  }
25711
- const { updateMemberStatus } = useMemberRepo();
25712
- for (const row of rows)
25713
- await updateMemberStatus(row._id, { status: "inactive" });
25813
+ await setRoleStatus(roles.map((r) => r._id), "deleted");
25714
25814
  if (defaultOrgIsSender)
25715
25815
  await setDefaultOrg(String(user._id), "");
25716
25816
  await _transition({
@@ -25719,6 +25819,7 @@ function useServiceProviderInviteService() {
25719
25819
  event: {
25720
25820
  ...event("wrong-join-repaired", actor),
25721
25821
  memberIds: rows.map((row) => row._id),
25822
+ roleIds: roles.map((r) => r._id),
25722
25823
  ...defaultOrgIsSender ? { defaultOrgWas: senderOrg } : {}
25723
25824
  }
25724
25825
  });
@@ -25727,8 +25828,8 @@ function useServiceProviderInviteService() {
25727
25828
  message: `[sp-invite] wrong-join repaired invite=${id} memberships=${rows.length} by=${actor.id}`
25728
25829
  });
25729
25830
  return {
25730
- message: `Repaired: ${rows.length} membership${rows.length === 1 ? "" : "s"} in the sender's organisation set inactive. Undo is available.`,
25731
- changed: rows.length
25831
+ message: `Repaired: ${rows.length} membership${rows.length === 1 ? "" : "s"} in the sender's organisation set inactive` + (roles.length ? `, ${roles.length} unused full-access role${roles.length === 1 ? "" : "s"} removed from its role list` : "") + ". Undo is available.",
25832
+ changed: rows.length + roles.length
25732
25833
  };
25733
25834
  }
25734
25835
  async function undoWrongJoin({ id, userId }) {
@@ -25740,6 +25841,7 @@ function useServiceProviderInviteService() {
25740
25841
  const ids2 = (repaired.memberIds ?? []).filter((m) => import_mongodb56.ObjectId.isValid(m));
25741
25842
  for (const m of ids2)
25742
25843
  await updateMemberStatus(m, { status: "active" }).catch(() => 0);
25844
+ await setRoleStatus(repaired.roleIds ?? [], "active");
25743
25845
  const { user } = await wrongJoinContext(id, userId);
25744
25846
  if (repaired.defaultOrgWas && user?._id && !String(user.defaultOrg ?? "")) {
25745
25847
  await setDefaultOrg(String(user._id), String(repaired.defaultOrgWas));
@@ -64713,7 +64815,7 @@ function robotManifest(brand, reportedModel) {
64713
64815
  const b = robotBrandFor(brand);
64714
64816
  const reported = typeof reportedModel === "string" ? reportedModel.trim().slice(0, 60) : "";
64715
64817
  if (!b)
64716
- return { capabilities: {}, profile: { model: reported, modelKnown: false, alarms: [], makerApp: "", makerAppOnly: [] } };
64818
+ return { capabilities: {}, profile: { model: reported, modelKnown: false, alarms: [], makerApp: "", makerAppOnly: [], clearFault: [] } };
64717
64819
  const prefix = modelKey(b.brand);
64718
64820
  let key = modelKey(reported);
64719
64821
  if (prefix && key.startsWith(prefix) && key !== prefix)
@@ -64726,7 +64828,8 @@ function robotManifest(brand, reportedModel) {
64726
64828
  modelKnown: Boolean(m),
64727
64829
  alarms: [...m?.alarms ?? b.info?.alarms ?? []],
64728
64830
  makerApp: m?.makerApp ?? b.info?.makerApp ?? "",
64729
- makerAppOnly: [...m?.makerAppOnly ?? b.info?.makerAppOnly ?? []]
64831
+ makerAppOnly: [...m?.makerAppOnly ?? b.info?.makerAppOnly ?? []],
64832
+ clearFault: [...m?.clearFault ?? b.info?.clearFault ?? []]
64730
64833
  }
64731
64834
  };
64732
64835
  }
@@ -65559,6 +65662,246 @@ function useRobotCloudService(credentials, options = {}) {
65559
65662
  // src/services/ounitech.adapter.ts
65560
65663
  var import_axios4 = __toESM(require("axios"));
65561
65664
  var import_node_server_utils164 = require("@7365admin1/node-server-utils");
65665
+
65666
+ // src/utils/ounitech-codes.util.ts
65667
+ var CLEAR = "Clear the fault (press and release the emergency stop)";
65668
+ var SUPPORT = "If it keeps happening, call Ounitech support";
65669
+ var DOCK = "Push it onto its charging dock so it can find its position again";
65670
+ var WAIT = "Wait until it is ready, then try again";
65671
+ var WHEEL = "Clear anything caught in the wheel and check the floor for holes";
65672
+ var NONE = "Normal when the emergency stop is pressed or released - no action needed";
65673
+ var T = {
65674
+ "100": ["Charging dock is over-heating", "Unplug the dock and let it cool down"],
65675
+ "101": ["Charging dock's clean-water tank is empty", "Fill the dock's clean-water tank"],
65676
+ "102": ["Charging dock's waste-water tank is full", "Drain the dock (unlock the dock, long-press Drain)"],
65677
+ "103": ["Charging dock valve or pump fault", `Check the pump connections. ${SUPPORT}`],
65678
+ "104": ["Charging dock valve or pump fault", `Check the pump connections. ${SUPPORT}`],
65679
+ "105": ["Charging dock valve or pump fault", `Check the pump connections. ${SUPPORT}`],
65680
+ "106": ["Charging dock valve or pump fault", `Check the pump connections. ${SUPPORT}`],
65681
+ "107": ["Charging dock valve or pump fault", `Check the pump connections. ${SUPPORT}`],
65682
+ "200": ["Battery not responding (flat for a long time)", "Charge it on the dock, or take the battery out and put it back"],
65683
+ "204": ["Wheel driver board offline", `Check the wheel wiring. ${SUPPORT}`],
65684
+ "205": ["Side-brush driver offline (side brush won't turn)", "Check the side-brush motor wire"],
65685
+ "206": ["Button panel offline (handle lights off)", `Check the handle's cable contact. ${SUPPORT}`],
65686
+ "207": ["Vacuum fan stalled", "Clean the vacuum pipe, HEPA filter, dust bag and tray"],
65687
+ "209": ["Vacuum fan fault", `${CLEAR}. ${SUPPORT}`],
65688
+ "210": ["Roller mop motor fault", "Take out the roller and clear anything tangled in it"],
65689
+ "211": ["Roller brush motor fault", "Take out the roller and clear anything tangled in it"],
65690
+ "212": ["Left side-brush motor fault", "Clear anything tangled in the side brush"],
65691
+ "213": ["Right side-brush motor fault", "Clear anything tangled in the side brush"],
65692
+ "214": ["Clean-water pump fault", `Re-plug the pump board. ${SUPPORT}`],
65693
+ "218": ["Waste-water pump fault", `Re-plug the pump board. ${SUPPORT}`],
65694
+ "219": ["Mop lift motor fault", `Restart the robot (power switch at the back). ${SUPPORT}`],
65695
+ "220": ["Brush lift motor fault", `Restart the robot (power switch at the back). ${SUPPORT}`],
65696
+ "221": ["Wheel fault", WHEEL],
65697
+ "222": ["Wheel fault", WHEEL],
65698
+ "223": ["Wheel stalled", WHEEL],
65699
+ "224": ["Wheel stalled", WHEEL],
65700
+ "228": ["Cooling fan fault", "Call Ounitech support"],
65701
+ "233": ["Battery short circuit", "Stop using the robot and call Ounitech support to replace the battery"],
65702
+ "234": ["Battery over-charged", "Take it off charge and call Ounitech support"],
65703
+ "245": ["Water-suction fan stalled", "Unclog the suction pipe and replace the filter"],
65704
+ "248": ["Squeegee motor fault", `Check the squeegee cable and motor. ${SUPPORT}`],
65705
+ "250": ["Battery full"],
65706
+ "251": ["Battery low"],
65707
+ "252": ["Battery too low to charge", "Call Ounitech support"],
65708
+ "255": ["Battery charging current high"],
65709
+ "256": ["Battery over-charged", "Take it off charge and call Ounitech support"],
65710
+ "257": ["Battery over 70 \xB0C", "Stop charging and let it cool down"],
65711
+ "302": ["Robot can't load its map", "Call Ounitech support"],
65712
+ "303": ["The map's area layout is damaged", "Re-edit the areas in the Ounitech app and save"],
65713
+ "304": ["The area layout doesn't match the map", "In the Ounitech app, switch to another floor map and back"],
65714
+ "305": ["Lost contact with its motion controller", "Restart the robot (power switch at the back)"],
65715
+ "306": ["Robot couldn't receive the chosen area", `${CLEAR}, then send the task again`],
65716
+ "307": ["Robot is finding its position again", "If the site has changed, push it onto its charging dock"],
65717
+ "308": ["Robot couldn't find its position", DOCK],
65718
+ "310": ["No signal from the charging dock", `Check the dock is powered. ${SUPPORT}`],
65719
+ "312": ["Couldn't dock", "Check the dock hasn't been moved"],
65720
+ "314": ["Couldn't dock - trying again", "Check the dock hasn't been moved"],
65721
+ "315": ["Couldn't dock - trying again", "Check the dock hasn't been moved"],
65722
+ "316": ["Lost the wireless link to its charging dock", "Check the dock is powered"],
65723
+ "319": ["Robot was moved while charging", "Press the emergency stop and push it back onto the dock"],
65724
+ "401": ["Battery below 10% - going to charge"],
65725
+ "403": ["Robot has left its charging dock"],
65726
+ "404": ["Charging dock reports a fault", "See the dock's other alerts"],
65727
+ "405": ["Couldn't call the lift"],
65728
+ "407": ["Lift is full"],
65729
+ "409": ["Couldn't switch map", "Call Ounitech support"],
65730
+ "410": ["Internal communication problem", SUPPORT],
65731
+ "415": ["Gyro data fault", `Check the cables. ${SUPPORT}`],
65732
+ "416": ["Wheel odometer data fault", `Check the cables. ${SUPPORT}`],
65733
+ "417": ["Laser scanner data fault", `Check the cables. ${SUPPORT}`],
65734
+ "418": ["Couldn't dock - trying again", "Check the dock hasn't been moved"],
65735
+ "419": ["Couldn't dock - trying again", "Check the dock hasn't been moved"],
65736
+ "420": ["Docking failed several times", "Check the dock's position and the robot's position"],
65737
+ "423": ["Dust box blocked", "Empty the dust box and clear the pipe"],
65738
+ "431": ["Robot's waste-water tank is high - returning to empty"],
65739
+ "432": ["Robot's clean water is low - returning to refill"],
65740
+ "433": ["Robot over-heating while charging", "Let it cool down"],
65741
+ "443": ["No charging current", "Seat the battery and clean the charging contacts"],
65742
+ "445": ["Area laser fault", "Call Ounitech support"],
65743
+ "446": ["Area laser board offline", "Call Ounitech support"],
65744
+ "448": ["Wheel drawing too much current", WHEEL],
65745
+ "449": ["Wheel over-current fault", `${WHEEL}, then clear the fault (press and release the emergency stop)`],
65746
+ "450": ["Wheels slipping", "Check the wheels"],
65747
+ "453": ["Robot tried to leave a no-go area", DOCK],
65748
+ "454": ["Robot hasn't moved for a long time", "Clear its path and wipe its sensors"],
65749
+ "457": ["Left bumper hit something", "Tap the bumper if it stays stuck"],
65750
+ "458": ["Right bumper hit something", "Tap the bumper if it stays stuck"],
65751
+ "459": ["Water suction blocked", "Clean the suction pipe and filters"],
65752
+ "460": ["Front camera fault", `Check the camera cable. ${SUPPORT}`],
65753
+ "462": ["Left camera fault", `Check the camera cable. ${SUPPORT}`],
65754
+ "464": ["Right camera fault", `Check the camera cable. ${SUPPORT}`],
65755
+ "469": ["The map's floor doesn't match the floor it senses", "Check it is on the right floor; the air-pressure sensor may need recalibrating"],
65756
+ "470": ["Gyro offline", "Call Ounitech support"],
65757
+ "471": ["Roller motor needs servicing", "Book a service"],
65758
+ "472": ["Fan needs servicing", "Book a service"],
65759
+ "500": ["Scheduled job skipped: floor can't be reached", "Cross-floor jobs need lift control"],
65760
+ "501": ["Scheduled job skipped: robot not ready to clean", "Check battery, water, tanks and dust box"],
65761
+ "502": ["Waited over 20 minutes for the lift - going to charge"],
65762
+ "503": ["Scheduled job done - going to charge"],
65763
+ "504": ["Job done - going to charge"],
65764
+ "505": ["Job cancelled - going to charge"],
65765
+ "506": ["Couldn't leave the charging dock", "Check the wheels"],
65766
+ "507": ["Clean water low - returning to refill, then continuing"],
65767
+ "508": ["Waste water high - returning to empty, then continuing"],
65768
+ "509": ["Dust box missing", "Fit the dust box"],
65769
+ "510": ["Clean-water tank missing", "Fit the clean-water tank"],
65770
+ "511": ["Waste-water tank missing", "Fit the waste-water tank"],
65771
+ "512": ["Tank cover missing", "Fit the tank cover"],
65772
+ "513": ["Top cover open", "Close the top cover"],
65773
+ "514": ["Handle is up (manual mode) - the app can't start a job", "Lower the handle"],
65774
+ "515": ["Battery below 10% - can't clean", "Let it charge"],
65775
+ "517": ["Robot is starting up - not ready yet", WAIT],
65776
+ "518": ["Robot is shutting down"],
65777
+ "519": ["Robot is resuming - not ready yet", WAIT],
65778
+ "520": ["Robot is finding its position - not ready yet", WAIT],
65779
+ "521": ["Robot is downloading its map - not ready yet", WAIT],
65780
+ "522": ["Couldn't resume: clean water below half", "Wait for it to refill"],
65781
+ "523": ["Couldn't resume: waste water too high", "Wait for it to empty"],
65782
+ "524": ["Charging dock doesn't match this robot", "Re-scan the dock's QR code in the Ounitech app"],
65783
+ "525": ["Scheduled time ended before the job finished", "Give the schedule a longer time window"],
65784
+ "527": ["Software updating", "Don't use the robot until it finishes"],
65785
+ "528": ["Software updating", "Don't use the robot until it finishes"],
65786
+ "529": ["Software updating", "Don't use the robot until it finishes"],
65787
+ "530": ["Software updating", "Don't use the robot until it finishes"],
65788
+ "531": ["Manual mode - the app's command was refused", "Lower the handle"],
65789
+ "532": ["Finding its starting position"],
65790
+ "534": ["Finding its starting position after a map switch"],
65791
+ "535": ["Areas updated"],
65792
+ "536": ["Virtual walls updated"],
65793
+ "537": ["Safe zones updated"],
65794
+ "538": ["Map saved"],
65795
+ "539": ["Couldn't save the map", "Save it again"],
65796
+ "540": ["Cliff sensor switched on"],
65797
+ "541": ["Cliff sensor switched off"],
65798
+ "542": ["Stopped at a drop (cliff sensor)", `Pull it back by the handle, ${CLEAR.charAt(0).toLowerCase()}${CLEAR.slice(1)}, and add a virtual wall there`],
65799
+ "543": ["Slowing down near a drop (cliff sensor)", "Add a virtual wall there if it keeps happening"],
65800
+ "544": ["Scheduled shutdown soon - returning to the dock"],
65801
+ "545": ["Edge cleaning started"],
65802
+ "546": ["Edge cleaning finished"],
65803
+ "549": ["Asking to switch map"],
65804
+ "552": ["No floor switch needed"],
65805
+ "553": ["Area can't be reached - job ended", "In the Ounitech app, switch map and back, or re-save an area"],
65806
+ "554": ["Sensor calibration"],
65807
+ "555": ["Sensor calibration - clear obstacles around the robot"],
65808
+ "556": ["Sensor calibration - clear obstacles around the robot"],
65809
+ "557": ["Slow zones updated"],
65810
+ "560": ["Fan stalled - returning to the dock", "Press and release the emergency stop, then check the fan"],
65811
+ "561": ["Couldn't reconnect to the charging dock", "Check the dock"],
65812
+ "562": ["Scheduled shutdown soon - no new jobs"],
65813
+ "563": ["Robot is in a fault state", "Find the cause, clear it (press and release the emergency stop), and check the robot"],
65814
+ "564": ["Fault cleared - robot on standby"],
65815
+ "565": ["Handling waste water"],
65816
+ "566": ["Waste water handled"],
65817
+ "567": ["Fan stalled", "Clear the fan, then resume"],
65818
+ "568": ["No charging dock on this floor", "Set the dock's floor in the Ounitech app"],
65819
+ "570": ["Finding its starting position - can't start yet", WAIT],
65820
+ "571": ["Stopped too long - side brush switched off", "Pause, then resume"],
65821
+ "572": ["Lift ride took too long - job cancelled", "Push it back onto its charging dock"],
65822
+ "573": ["Camera saw a drop", "Add a virtual wall 1 m from the drop"],
65823
+ "574": ["Map updating", WAIT],
65824
+ "575": ["Air-pressure sensor reading invalid", "Recalibrate the air-pressure sensor"],
65825
+ "576": ["Air-pressure sensor calibration"],
65826
+ "577": ["Air-pressure sensor calibration"],
65827
+ "578": ["Air-pressure sensor calibration"],
65828
+ "581": ["Taught route started"],
65829
+ "582": ["Taught route ended"],
65830
+ "583": ["Taught route cleared"],
65831
+ "585": ["An area's positioning mode changed"],
65832
+ "587": ["Battery discharging while on charge", "Seat the battery and clean the charging contacts"],
65833
+ "588": ["Switched off with the power button"],
65834
+ "589": ["Switched off by the timed power schedule"],
65835
+ "590": ["Switched on"],
65836
+ "591": ["Switched on with the power button"],
65837
+ "592": ["Switched on by the timed power schedule"],
65838
+ "593": ["Still finding its position - can't take commands yet", WAIT],
65839
+ "600": ["Left wheel driver offline", NONE],
65840
+ "601": ["Left wheel driver reset", NONE],
65841
+ "603": ["Left wheel drawing too much current", WHEEL],
65842
+ "604": ["Left wheel drawing too much current", WHEEL],
65843
+ "605": ["Left wheel stalled", WHEEL],
65844
+ "620": ["Right wheel driver offline", NONE],
65845
+ "621": ["Right wheel driver reset", NONE],
65846
+ "622": ["Right wheel driver error", "Call Ounitech support"],
65847
+ "623": ["Right wheel drawing too much current", WHEEL],
65848
+ "624": ["Right wheel drawing too much current", WHEEL],
65849
+ "625": ["Right wheel stalled", WHEEL],
65850
+ "640": ["A part is overdue for replacement - won't clean", "Replace the part"],
65851
+ "641": ["A running job is blocking the schedule", "Cancel the job or wait"],
65852
+ "642": ["Can't find its position while charging", "Put it on standby first"],
65853
+ "643": ["Can't reset the dock while charging", "Put it on standby first"],
65854
+ "644": ["Can't set its starting position while charging", "Put it on standby first"],
65855
+ "700": ["Vacuum button pressed"],
65856
+ "701": ["Standard button pressed"],
65857
+ "702": ["Dust-mop button pressed"],
65858
+ "703": ["Cancel button pressed"],
65859
+ "704": ["Strong button pressed"],
65860
+ "705": ["Mop button pressed"],
65861
+ "706": ["Pause button pressed"],
65862
+ "707": ["Recall button pressed"],
65863
+ "708": ["Pause button held"],
65864
+ "709": ["Pause button held"],
65865
+ "710": ["Wheel drawing too much current", WHEEL],
65866
+ "711": ["Wheel over-heating", "Let it cool and check the wheel"],
65867
+ "712": ["Wheel kept drawing too much current", `${WHEEL}, then clear the fault (press and release the emergency stop)`],
65868
+ "713": ["Switching lift - timed power paused"],
65869
+ "731": ["Robot locked by the dealer", "Ask the dealer"],
65870
+ "732": ["Voice file updated"],
65871
+ "733": ["Switched off from the app"],
65872
+ "737": ["Laser scanner motion-sensor fault", "Call Ounitech support"],
65873
+ "738": ["Can't start: clean water low", "Fill the clean-water tank"],
65874
+ "739": ["Can't start: waste water high", "Empty the waste-water tank"],
65875
+ "760": ["Map file missing", "Call Ounitech support"],
65876
+ "761": ["Map file damaged", "Call Ounitech support"],
65877
+ "762": ["Started from the wrong place (it was pushed)", "Push it back onto its charging dock"],
65878
+ "775": ["Moved by a person during a lift ride", "Cancel the job and push it back onto its charging dock"],
65879
+ "776": ["Boxed in", `Clear its path, wipe its sensors. ${DOCK}`],
65880
+ "777": ["Can't reach its target", `Clear its path, wipe its sensors. ${DOCK}`],
65881
+ "778": ["Couldn't dock - recovering by itself", "Check the dock's sensor and that the dock hasn't moved"],
65882
+ "779": ["Robot settings changed"],
65883
+ "780": ["Top cover open", "Close the top cover"],
65884
+ "781": ["Dust box not fitted", "Fit the dust box"],
65885
+ "782": ["Waste-water tank not fitted", "Fit the waste-water tank"],
65886
+ "785": ["Position ready"]
65887
+ };
65888
+ var _a;
65889
+ for (const c of [...range(740, 759), 763, 764, 783, 784])
65890
+ T[_a = String(c)] ?? (T[_a] = ["Sensor or camera calibration", "Recalibrate if it failed"]);
65891
+ var _a2;
65892
+ for (const c of range(765, 773))
65893
+ T[_a2 = String(c)] ?? (T[_a2] = ["QR code return-point / position scan", "Rescan if it failed"]);
65894
+ T["774"] ?? (T["774"] = ["Taught route saved"]);
65895
+ function range(a, b) {
65896
+ return Array.from({ length: b - a + 1 }, (_, i) => a + i);
65897
+ }
65898
+ var OUNITECH_CODES = T;
65899
+ function ounitechCodeWords(code) {
65900
+ const c = OUNITECH_CODES[code];
65901
+ return c ? c[1] ? `${c[0]}. ${c[1]}.` : c[0] : null;
65902
+ }
65903
+
65904
+ // src/services/ounitech.adapter.ts
65562
65905
  var OUNITECH_BRAND = "ounitech";
65563
65906
  var OUNITECH_CAPABILITIES = {
65564
65907
  // Pose in the chapter-6 robot list (metres) and the tracking stream.
@@ -65587,8 +65930,9 @@ var OUNITECH_CAPABILITIES = {
65587
65930
  // taskState carries the job and its %, shown in the status words; there is
65588
65931
  // no command id to follow a job WE started, so completion is not claimed.
65589
65932
  processCompletion: false,
65933
+ // The manual: "Combined cleaning" across floors exists in the Ounitech app and needs its
65934
+ // lift-control module (code 500 = floor unreachable); no API call starts one.
65590
65935
  multiFloor: false,
65591
- // plans can name floors; no API starts a cross-floor job
65592
65936
  // `start-spot-cleaning` takes a PIXEL polygon while poses are metres, and
65593
65937
  // whether y is flipped between the two is not documented. Hidden until one
65594
65938
  // real robot confirms the frame.
@@ -65744,9 +66088,9 @@ function ounitechFault(list) {
65744
66088
  }
65745
66089
  function faultWords(a) {
65746
66090
  if (!a.code)
65747
- return { code: "error", message: a.message.slice(0, 200) };
65748
- const words = a.message.startsWith(`Robot alert ${a.code}:`) ? a.message : `${a.message} (fault ${a.code})`;
65749
- return { code: a.code.slice(0, 60), message: words.slice(0, 200) };
66091
+ return { code: "error", message: a.message.slice(0, 300) };
66092
+ const words = alertWords(a.code) ?? a.vendorText ?? a.message;
66093
+ return { code: a.code.slice(0, 60), message: `${a.code} \xB7 ${words}`.slice(0, 300) };
65750
66094
  }
65751
66095
  function ounitechMapShare(map, x, y, clamp4 = false) {
65752
66096
  const w = map?.width, h = map?.height, r = map?.resolution;
@@ -65894,22 +66238,8 @@ function ounitechTaskRecord(t, maps = [], tz = 480) {
65894
66238
  floorNo: Number.isInteger(t.floorNo) ? t.floorNo : map?.floorNo ?? null
65895
66239
  };
65896
66240
  }
65897
- var ALERT_WORDS = {
65898
- "205": "Side brush driver offline",
65899
- "206": "Button panel offline",
65900
- // 306 "接收定点指定区域故障": seen live 30 Sep right after start-area-cleaning on an area with no centre point.
65901
- "306": "Robot couldn't receive the chosen cleaning area",
65902
- "540": "Floor check: cliff sensor switched on",
65903
- "563": "Robot is in a fault state",
65904
- "588": "Switched off with the power button",
65905
- "589": "Switched off by the timed power schedule",
65906
- "593": "Still finding its position - try again shortly",
65907
- "601": "Left wheel driver reset",
65908
- "620": "Right wheel driver offline",
65909
- "621": "Right wheel driver reset",
65910
- "734": "IMU zero offset too large",
65911
- "785": "Localisation ready"
65912
- };
66241
+ var VENDOR_TEXT_EN = { "734": "IMU zero offset too large" };
66242
+ var alertWords = (code) => code ? ounitechCodeWords(code) ?? VENDOR_TEXT_EN[code] ?? null : null;
65913
66243
  var ALERT_LEVEL_WORDS = { \u63D0\u793A: "Notice", \u8B66\u544A: "Warning", \u6545\u969C: "Fault", \u9519\u8BEF: "Error", \u4E25\u91CD: "Critical" };
65914
66244
  function ounitechAlertRecord(a, tz = 480) {
65915
66245
  const raw = String(a.errMsg ?? a.errCode ?? "").trim();
@@ -65925,7 +66255,7 @@ function ounitechAlertRecord(a, tz = 480) {
65925
66255
  code,
65926
66256
  level: lv ? ALERT_LEVEL_WORDS[lv[1].trim()] ?? lv[1].trim() : null,
65927
66257
  // An untranslated Chinese text keeps its code up front, so the row is never only Chinese.
65928
- message: ALERT_WORDS[code] ?? (/[㐀-鿿]/.test(rest || raw) && code ? `Robot alert ${code}: ${rest || raw}` : rest || raw),
66258
+ message: alertWords(code) ?? (/[㐀-鿿]/.test(rest || raw) && code ? `Robot alert ${code}: ${rest || raw}` : rest || raw),
65929
66259
  vendorText: rest || raw,
65930
66260
  resolved,
65931
66261
  at: ounitechTime(a.createTime, tz)
@@ -66303,7 +66633,7 @@ function createOunitechAdapter(credentials, options) {
66303
66633
  const blind = command.areas.filter((n) => centre.has(n) && !centre.get(n));
66304
66634
  if (blind.length) {
66305
66635
  throw new import_node_server_utils164.BadRequestError(
66306
- `${blind.map((n) => `"${n}"`).join(", ")} has no centre point on the robot's map, so the robot can't be sent there (it stops with fault 306). Use Clean all, or redraw the area in the Ounitech app.`
66636
+ `${blind.map((n) => `"${n}"`).join(", ")} has no centre point on the robot's map, so the robot can't be sent there (it stops with fault 306). ` + (blind.includes("default") ? `"default" is the whole map before it is divided into areas. Use Clean all, or divide the map in the Ounitech app (Control -> Divide areas).` : "Use Clean all, or redraw the area in the Ounitech app.")
66307
66637
  );
66308
66638
  }
66309
66639
  await cloud.startAreaCleaning(robotSn, command.areas, options2);
@@ -66566,7 +66896,17 @@ var OUNITECH_BRAND_DEFINITION = {
66566
66896
  { name: "Q3-G", codes: ["Q3G", "Q3-G"] },
66567
66897
  { name: "Q3-W", codes: ["Q3W", "Q3-W"] }
66568
66898
  ],
66569
- info: { makerApp: "the Ounitech app", makerAppOnly: ["pausing or stopping a job", "cleaning across floors", "changing maps, areas and schedules"] },
66899
+ info: {
66900
+ makerApp: "the Ounitech app",
66901
+ makerAppOnly: ["pausing or stopping a job", "cleaning across floors", "changing maps, areas and schedules"],
66902
+ // Q3-G User Manual / Troubleshooting Checklist (2026-09-30). The API has no documented way to clear an Error.
66903
+ clearFault: [
66904
+ 'Press the red emergency stop on top of the robot, then turn it clockwise to release it. It says "emergency stop button bounced" and the fault clears.',
66905
+ "If it has lost its position: lift the handle, push it onto its charging dock until the metal contacts touch, then press and release the emergency stop. It relocates by itself.",
66906
+ "Still stuck: restart it with the power switch at the back. It is ready when only the child-lock light stays on.",
66907
+ "Handle buttons not responding: press Child lock 3 times to unlock them."
66908
+ ]
66909
+ },
66570
66910
  create: (credentials) => createOunitechAdapter(credentials)
66571
66911
  };
66572
66912
  function registerOunitechBrand() {
@@ -71858,7 +72198,7 @@ function toBuildingYaml(name, rmf) {
71858
72198
  }
71859
72199
  const lifts = {};
71860
72200
  for (const l of rmf.lifts) {
71861
- const range = liftFloorRange(l, rmf.levels);
72201
+ const range2 = liftFloorRange(l, rmf.levels);
71862
72202
  lifts[l.name] = {
71863
72203
  x: l.x,
71864
72204
  y: l.y,
@@ -71866,9 +72206,9 @@ function toBuildingYaml(name, rmf) {
71866
72206
  width: l.widthM,
71867
72207
  depth: l.depthM,
71868
72208
  reference_floor_name: l.referenceFloor,
71869
- initial_floor_name: range.initial,
71870
- lowest_floor: range.lowest,
71871
- highest_floor: range.highest,
72209
+ initial_floor_name: range2.initial,
72210
+ lowest_floor: range2.lowest,
72211
+ highest_floor: range2.highest,
71872
72212
  doors: Object.fromEntries(
71873
72213
  l.doors.map((d) => [d.name, { door_type: LIFT_DOOR_TYPE_INT[d.type] ?? 2, x: d.x, y: d.y, width: d.widthM, motion_axis_orientation: d.axisYawRad }])
71874
72214
  ),
@@ -71918,13 +72258,13 @@ function toNavGraphs(name, rmf) {
71918
72258
  const out = {};
71919
72259
  const elevation = new Map(rmf.levels.map((l) => [l.name, l.elevationM]));
71920
72260
  const shafts = rmf.lifts.map((l) => {
71921
- const range = liftFloorRange(l, rmf.levels);
72261
+ const range2 = liftFloorRange(l, rmf.levels);
71922
72262
  return {
71923
72263
  lift: l,
71924
72264
  verts: liftShaft(l),
71925
72265
  // lift.py: no lowest / highest floor = no bound; a lift with no level_doors marks nothing.
71926
- lo: elevation.get(range.lowest) ?? -Infinity,
71927
- hi: elevation.get(range.highest) ?? Infinity,
72266
+ lo: elevation.get(range2.lowest) ?? -Infinity,
72267
+ hi: elevation.get(range2.highest) ?? Infinity,
71928
72268
  hasDoors: l.levelDoors.length > 0
71929
72269
  };
71930
72270
  });