@7365admin1/core 3.64.3 → 3.64.5-staging.292
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/.changeset/customer-sites-one-row-per-site.md +19 -0
- package/.changeset/hid-duplicate-enrollment.md +22 -0
- package/.changeset/hid-facial-error-wording.md +20 -0
- package/.changeset/hid-identity-user-link.md +26 -0
- package/.changeset/hid-objectid-across-driver-copies.md +26 -0
- package/.changeset/hid-profile-qr-backend.md +31 -0
- package/.changeset/hid-reader-user-search.md +22 -0
- package/.changeset/member-lookup-follows-site.md +16 -0
- package/.changeset/module-gate-foundation.md +20 -0
- package/.changeset/org-update-diagnosable-error.md +14 -0
- package/.changeset/patrol-email-scope-restore.md +16 -0
- package/.changeset/people-occupied-structure.md +5 -0
- package/.changeset/resident-app-module-ceiling.md +18 -0
- package/.changeset/role-module-ceiling.md +16 -0
- package/.changeset/role-read-self-arm.md +5 -0
- package/.changeset/role-v2-platform-grant.md +14 -0
- package/.changeset/site-module-allowlist.md +5 -0
- package/.changeset/user-email-allowlist-subscription-authz.md +21 -0
- package/CHANGELOG.md +21 -0
- package/dist/index.d.ts +219 -8
- package/dist/index.js +3066 -987
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3210 -1131
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/anpr-plate.test.mjs +272 -0
- package/test/dahua-protocol.util.test.mjs +137 -0
- package/test/guard-invite-wiring.test.mjs +48 -0
- package/test/member-lookup-site.test.mjs +130 -0
- package/test/module-gate.test.mjs +237 -0
- package/test/occupancy-tree.test.mjs +128 -0
- package/test/org-modules.test.mjs +78 -0
- package/test/resident-login-account.test.mjs +126 -0
- package/test/site-modules.test.mjs +84 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Return one row per site from `/api/customer-sites`, named by the site.
|
|
6
|
+
|
|
7
|
+
`getAll` grouped by `{ name, site }`. A customer-sites row is an ENGAGEMENT and
|
|
8
|
+
one site can carry more than one, so two rows for the same site with different
|
|
9
|
+
names both survived as "distinct" — a provider's site list showed four entries
|
|
10
|
+
for three sites, and the site switcher ticked two at once.
|
|
11
|
+
|
|
12
|
+
`name` is also a snapshot from when the engagement was created and goes stale
|
|
13
|
+
when the site is renamed; recency is no guide, since the newer row held the
|
|
14
|
+
staler name. The site is now looked up and its live name projected over the
|
|
15
|
+
snapshot, with the stored name kept as a fallback.
|
|
16
|
+
|
|
17
|
+
The search and the sort both run after the rename, so a site is found and
|
|
18
|
+
ordered by the name it actually has, and the count is derived from the same
|
|
19
|
+
pipeline.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Refuse to enrol the same person on one HID reader twice.
|
|
6
|
+
|
|
7
|
+
`POST /readers/:readerId/identities` and `PATCH /identities/:identityId` now
|
|
8
|
+
check whether the linked resident, staff member or service provider already
|
|
9
|
+
holds a HID user on that reader, and refuse with a 400 naming the user that is
|
|
10
|
+
in the way. The existing duplicate check in `addIdentity` could not catch this:
|
|
11
|
+
it asks about the reader's own keys, and enrolment allocates a fresh
|
|
12
|
+
`hidUserId` and registration every time, so a second enrolment of one person
|
|
13
|
+
looked entirely new while leaving the reader holding two users for one human.
|
|
14
|
+
|
|
15
|
+
The check is reader-scoped on purpose — the same person enrolled at a different
|
|
16
|
+
gate is normal. Visitors are excluded, because their identity is re-issued
|
|
17
|
+
rather than duplicated and a returning visitor legitimately gets a new
|
|
18
|
+
credential on the same reader.
|
|
19
|
+
|
|
20
|
+
`GET /readers/:readerId/identities` takes an optional `person` filter so the
|
|
21
|
+
enrolment form can ask the same question before it writes anything to the
|
|
22
|
+
device.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Say what a rejected face photo actually means.
|
|
6
|
+
|
|
7
|
+
A facial failure was passed through as the reader wrote it, so an operator was
|
|
8
|
+
shown `HID facial enrollment failed - code 3: Face exists Scores:
|
|
9
|
+
{"bounds_width":240,...}`. The code is the device's, the scores are its quality
|
|
10
|
+
figures for the photo, and neither says what went wrong or what to do.
|
|
11
|
+
|
|
12
|
+
The codes we have seen the reader return are now written as sentences: a face
|
|
13
|
+
already enrolled under another HID user, a face too close to the camera, a face
|
|
14
|
+
not square to it, a face too near the photo's edge. The device's own codes are
|
|
15
|
+
quoted once at the end so support still has them, and a code we have not seen is
|
|
16
|
+
passed through verbatim rather than guessed at.
|
|
17
|
+
|
|
18
|
+
The raw errors and quality scores move off the message and onto the
|
|
19
|
+
`hid-amico-events` row, which is where a rejected photo gets diagnosed later.
|
|
20
|
+
They were the least useful half of a modal and the most useful half of a log.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Store the user account on a HID identity, and recognise one person across every
|
|
6
|
+
id they are filed under.
|
|
7
|
+
|
|
8
|
+
An identity's link fields name a RECORD, and different screens name the same
|
|
9
|
+
human through different records: permission reconciliation writes `user` and
|
|
10
|
+
`member`, the enrolment screen writes `person`, and the enrolment picker itself
|
|
11
|
+
has offered two different things over time, since `loadPermissionSubjects`
|
|
12
|
+
returns a `site.people` id for a resident with an account and a `users` id for
|
|
13
|
+
one without. The duplicate check compared field to matching field, so it saw
|
|
14
|
+
none of it. One reader was found holding three HID users for one resident, keyed
|
|
15
|
+
`user`, `person`-as-user-id and `person`-as-people-id.
|
|
16
|
+
|
|
17
|
+
`createIdentity` and `updateIdentity` now resolve the subject's account through
|
|
18
|
+
`resolvePermissionUserBindings`, the same resolver the permissions screen uses,
|
|
19
|
+
and store it as `user` alongside the record link. The duplicate check matches
|
|
20
|
+
every id that names the subject against every field that can hold one, so a
|
|
21
|
+
resident already enrolled through any screen is refused rather than duplicated.
|
|
22
|
+
|
|
23
|
+
`GET /readers/:readerId/identities` gains a `subject` filter that asks the same
|
|
24
|
+
question, so the enrolment screen can warn before it writes anything to the
|
|
25
|
+
device instead of failing at submit. The existing `person` filter is unchanged
|
|
26
|
+
and remains an exact match on that one field.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Stop the HID repository mistaking every id it reads from Mongo for "not an id".
|
|
6
|
+
|
|
7
|
+
`optionalObjectId` in `hid-amico.repo.ts` identified ObjectIds with
|
|
8
|
+
`instanceof`. That only ever holds for an id this file minted itself: three
|
|
9
|
+
copies of the `mongodb` package are installed across `iservice365-core`,
|
|
10
|
+
`iservice365-utils` and `iservice365-API-core`, and `useAtlas` opens its
|
|
11
|
+
connection with the one under `iservice365-utils`, so every id on every
|
|
12
|
+
document it returns belongs to a different class. `instanceof` was false and
|
|
13
|
+
the value fell through to a `typeof value !== "string"` branch that returned
|
|
14
|
+
null.
|
|
15
|
+
|
|
16
|
+
The visible effect was enrolment. `assertPermissionSubject` treats a resident
|
|
17
|
+
as valid only when their `site.people` record carries a linked user account,
|
|
18
|
+
and it read that link through this helper — so a resident with an account read
|
|
19
|
+
as a resident without one, and `POST /readers/:readerId/identities` answered
|
|
20
|
+
400 "The linked HID identity subject is not active at this site." for residents
|
|
21
|
+
who were perfectly enrollable.
|
|
22
|
+
|
|
23
|
+
Ids are now read structurally, by `toHexString`, which every copy's ObjectId
|
|
24
|
+
has. `toValidatedId` goes through the same helper, so it too stops rejecting
|
|
25
|
+
ids that came from the database. `hid-amico.model.ts` already read ids this
|
|
26
|
+
way; this brings the repository in line with it.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Let a signed-in member hold their own HID QR code.
|
|
6
|
+
|
|
7
|
+
`GET`, `POST` and `DELETE /readers/:readerId/profile-qr` read, issue and revoke
|
|
8
|
+
the caller's own QR credential on one reader. The caller comes from the session,
|
|
9
|
+
so there is no user id in the path to point at somebody else, and each handler
|
|
10
|
+
calls `authorizeReader` for itself rather than trusting the router - the site is
|
|
11
|
+
re-resolved from the stored reader record.
|
|
12
|
+
|
|
13
|
+
The credential is the visitor mechanism: an alphanumeric value in the reader's
|
|
14
|
+
`qrcodes` table, with the reader put into the QR-readable configuration first.
|
|
15
|
+
Unlike a visitor pass it carries no validity window, matching the member's
|
|
16
|
+
facial enrollment, which is standing until removed. Issuing replaces any earlier
|
|
17
|
+
code for that member on that reader, and the replacement is written and verified
|
|
18
|
+
before the old one is removed.
|
|
19
|
+
|
|
20
|
+
It creates no access rule, deliberately. `setProfileFacial` beside it does not
|
|
21
|
+
either: a member's door access comes from the HID permissions screen, and
|
|
22
|
+
minting a rule here would mean anyone who can press Generate admits themselves.
|
|
23
|
+
The read reports whether that screen admits them, resolved through
|
|
24
|
+
`resolvePermissionUserBindings` - the same resolver that writes the rules onto
|
|
25
|
+
the device - so the answer is the one the reader will actually be asked.
|
|
26
|
+
|
|
27
|
+
Two settings are now readable that were already writable: the QR identification
|
|
28
|
+
and legacy-mode flags `configureVisitorQrReader` sets on every issue, plus
|
|
29
|
+
`access_rule_time_zones` alongside its two sibling link tables. Whether a reader
|
|
30
|
+
was set up to accept a QR, and whether an access rule had a schedule, were
|
|
31
|
+
invisible to every screen and every diagnosis.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Make the search on the HID reader user roster work.
|
|
6
|
+
|
|
7
|
+
`listReaderUsers` asked the device to filter. Amico's `where` takes a value or a
|
|
8
|
+
list of values per column and has no operator form, so the substring clause it
|
|
9
|
+
sent for a text term, `{name: {LIKE: "%x%"}}`, was answered
|
|
10
|
+
`400 Invalid value (string or array expected)`. Every search by name threw, and
|
|
11
|
+
the screen showed "Unable to load HID users from reader" with an empty table.
|
|
12
|
+
|
|
13
|
+
A numeric term took a different branch that matched the HID user id exactly.
|
|
14
|
+
That did not fail, it answered wrongly: searching a registration number like
|
|
15
|
+
`000004` resolved to `id = 4` and returned a different person.
|
|
16
|
+
|
|
17
|
+
Rows are already pulled in one page and filtered here for `userType`, so the
|
|
18
|
+
search now joins that filter as a case-insensitive substring over name, id and
|
|
19
|
+
registration - the same three fields, and the same "contains" semantics, the
|
|
20
|
+
identities search uses. Verified against a live reader: a name matches
|
|
21
|
+
partially and case-insensitively, a registration number returns its own user,
|
|
22
|
+
a full UID still works, and no match returns an empty page rather than an error.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A person's role now follows the site they are on.
|
|
6
|
+
|
|
7
|
+
`GET /api/members/user/:id/app/:type` accepts an optional `?site=` (a 24-hex
|
|
8
|
+
site id, like `?org=`). When it is sent, the answer is the person's live
|
|
9
|
+
membership of that app type pinned to that site (inside `?org=` when it is also
|
|
10
|
+
sent); failing that, their organisation-wide membership in `?org=`; failing
|
|
11
|
+
that, any live membership in `?org=`. When none of these exists the route
|
|
12
|
+
answers exactly as before. A removed membership never wins, and a site never
|
|
13
|
+
turns a found membership into "not found". Callers that do not send `site`
|
|
14
|
+
see no change. Login, auth-v2 and verification are unchanged.
|
|
15
|
+
|
|
16
|
+
The rule is the pure `pickSiteMembership` in `utils/member-lookup.util.ts`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Lay the groundwork for module lists becoming a gate, without narrowing anyone.
|
|
6
|
+
|
|
7
|
+
- New `utils/module-gate.util.ts`: the list of modules a client's list can
|
|
8
|
+
govern (generated from layer-common 5a96f95), the spelling groups, the
|
|
9
|
+
always-included set, and pure functions that work out which modules a
|
|
10
|
+
caller's apps must hide (`deniedModules`) and who would lose what if a list
|
|
11
|
+
changed (`moduleImpact`). Nothing calls them yet.
|
|
12
|
+
- The organisation and site module checks now treat every spelling of a module
|
|
13
|
+
as the same module (ticking `workOrder` also allows `work_orders`,
|
|
14
|
+
`work_order` and `work-orders`), and never refuse members, invitations, roles,
|
|
15
|
+
sites, customers, subscriptions, payment methods or site settings. This only
|
|
16
|
+
ever refuses less than before.
|
|
17
|
+
- A Site Settings save (`PATCH /api/sites/id/:id`) now always keeps the site's
|
|
18
|
+
stored module list. Before this, the save replaced `metadata` wholesale and
|
|
19
|
+
could wipe or overwrite that list. `PATCH /api/sites/:id/modules` is the only
|
|
20
|
+
way to change it.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Say what is wrong when an organisation cannot be saved.
|
|
6
|
+
|
|
7
|
+
`PUT /api/organizations/:id` — the write behind onboarding step 1 and the staff
|
|
8
|
+
Client List edit — collapsed every MongoDB write error into a 500 reading
|
|
9
|
+
"Failed to update organization.", with only `error.message` in the log. A
|
|
10
|
+
duplicate-key refusal (the `organizations` collection still carries a unique
|
|
11
|
+
index an earlier version of this file created and nothing ever dropped) is now
|
|
12
|
+
a 400 naming the field the caller has to change, a typed error raised
|
|
13
|
+
underneath is no longer turned into a 500, and the log line carries the error
|
|
14
|
+
name, code, key and the organisation id.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Restore and extend the site scoping on the patrol-log email endpoints.
|
|
6
|
+
|
|
7
|
+
#1967 added a `requireSiteReach` guard to `POST /api/patrol-logs/email`. #1981,
|
|
8
|
+
branched before #1967 landed, rewrote the same controller to add the history
|
|
9
|
+
endpoints and dropped the guard. Neither repo runs its tests in CI, so the e2e
|
|
10
|
+
test #1967 shipped has been failing on `main` unnoticed ever since.
|
|
11
|
+
|
|
12
|
+
All five endpoints now resolve the caller from the session: the two that name a
|
|
13
|
+
site (`send`, `getAll`) check that site, and the three that name a record
|
|
14
|
+
(`resend`, `getById`, `deleteById`) check the site stored on the record. The
|
|
15
|
+
`createdBy` pinning is restored with them, so a send cannot be filed under
|
|
16
|
+
another user's name.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Add `getOccupiedStructure` to the people module: the blocks, levels and units at one site that actually have a person of a given type (`resident` and `tenant` by default), so a block/level/unit picker can offer only locations where the person step has an answer. Site-scoped and authorised with `requireSiteReach` before any person record is read; the status and type filter is `getPeopleByUnit`'s exactly, so a unit is never offered whose person list then comes back empty.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Add the organisation tier over the resident-app modules (R4).
|
|
6
|
+
|
|
7
|
+
`organizations.residentAppModules` is the ceiling over the per-site
|
|
8
|
+
`sites.metadata.residentAppModules` that property-management's Site Settings
|
|
9
|
+
panel already writes. `GET /api/sites/:id` now also returns
|
|
10
|
+
`metadata.residentAppModulesEffective`, the two tiers intersected, because the
|
|
11
|
+
resident app cannot read its own organisation and so cannot apply the ceiling
|
|
12
|
+
itself. The saved per-site field is returned unchanged alongside it, so the
|
|
13
|
+
settings panel still reads back what it wrote.
|
|
14
|
+
|
|
15
|
+
Only an explicit `false` disables, at either tier, so absent keeps meaning
|
|
16
|
+
everything and this is inert for every organisation until Seven365 sets a
|
|
17
|
+
ceiling. A site write that switches ON a module the client was not given is
|
|
18
|
+
refused by name.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Enforce the organisation's module ceiling on every role write (R1 tier three).
|
|
6
|
+
|
|
7
|
+
A role can no longer be granted a permission whose module Seven365 never gave
|
|
8
|
+
the organisation, nor one the organisation never gave the site. Applied to all
|
|
9
|
+
four write paths — `role.controller` create / update / updatePermissionsById and
|
|
10
|
+
`role-v2.controller` create — because `/api/roles/v2` is a separate mount and
|
|
11
|
+
gating one leaves the rule reachable by adding `/v2` to the URL.
|
|
12
|
+
|
|
13
|
+
Only what a write ADDS is tested, so a role that predates the module list can
|
|
14
|
+
still be renamed and can still have a permission removed. Absent and empty
|
|
15
|
+
module lists still mean everything at every tier, so this is inert for all 168
|
|
16
|
+
live organisations until Seven365 sets a list.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope `GET /api/roles/id/:id`: a caller may read a role they themselves hold (resolved from the session's own `members` rows), otherwise the role's organisation rule applies. Closes the cross-tenant read of any organisation's role name and permissions.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Stop `/api/roles/v2` minting a role that holds the console's own resources.
|
|
6
|
+
|
|
7
|
+
`role.controller createRole` has carried `requireNoPlatformGrant` for a while;
|
|
8
|
+
`role-v2.controller createRole` did not. It is a separate controller behind a
|
|
9
|
+
separate route and accepts any permission string, so the hole was reachable by
|
|
10
|
+
adding `/v2` to the URL.
|
|
11
|
+
|
|
12
|
+
Measured on production before adding: no site-scoped role holds a platform-only
|
|
13
|
+
string, and this mount requires a `site`, so the check refuses nothing that
|
|
14
|
+
exists today. It only ever refuses a NEW grant regardless.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Tier two of the module cascade: `sites.metadata.modules`, the modules a site offers, plus `PATCH /sites/:id/modules` for the organisation to set it. The write refuses any key Seven365 never gave the organisation (owner requirement R1); the read fails open at every tier, so absent and empty both mean "everything the organisation has" and no site or role editor changes until somebody sets a list.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Narrow the account-by-e-mail lookup to identity only, and scope the two open subscription reads
|
|
6
|
+
|
|
7
|
+
`GET /api/users/email/:email` and its v2 twin `GET /api/users/v2/email/:email`
|
|
8
|
+
stripped the password hash and session id from the reply but answered every
|
|
9
|
+
other stored field, so any signed-in account could read any other account's
|
|
10
|
+
NRIC, contact number, date of birth, gender, default organisation, status and
|
|
11
|
+
profile just by knowing the e-mail address — across every client on the
|
|
12
|
+
platform. Both now answer `{_id, name, email}` and nothing else. An
|
|
13
|
+
organisation gate would have been the wrong fix: this endpoint exists for the
|
|
14
|
+
invite flow, where the person being looked up is deliberately not in the
|
|
15
|
+
caller's organisation yet.
|
|
16
|
+
|
|
17
|
+
`GET /api/subscriptions/org/:id` answered any organisation's billing record —
|
|
18
|
+
plan, seat count, price, currency, renewal date — to any signed-in account, and
|
|
19
|
+
`GET /api/subscriptions/` answered every subscription on the platform in one
|
|
20
|
+
list. They now carry `requireOrgAccess` and `requirePlatformStaff`, the two
|
|
21
|
+
gates already used elsewhere in the same file.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @iservice365/core
|
|
2
2
|
|
|
3
|
+
## 3.64.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3255256: Lay the groundwork for module lists becoming a gate, without narrowing anyone.
|
|
8
|
+
|
|
9
|
+
- New `utils/module-gate.util.ts`: the list of modules a client's list can
|
|
10
|
+
govern (generated from layer-common 5a96f95), the spelling groups, the
|
|
11
|
+
always-included set, and pure functions that work out which modules a
|
|
12
|
+
caller's apps must hide (`deniedModules`) and who would lose what if a list
|
|
13
|
+
changed (`moduleImpact`). Nothing calls them yet.
|
|
14
|
+
- The organisation and site module checks now treat every spelling of a module
|
|
15
|
+
as the same module (ticking `workOrder` also allows `work_orders`,
|
|
16
|
+
`work_order` and `work-orders`), and never refuse members, invitations, roles,
|
|
17
|
+
sites, customers, subscriptions, payment methods or site settings. This only
|
|
18
|
+
ever refuses less than before.
|
|
19
|
+
- A Site Settings save (`PATCH /api/sites/id/:id`) now always keeps the site's
|
|
20
|
+
stored module list. Before this, the save replaced `metadata` wholesale and
|
|
21
|
+
could wipe or overwrite that list. `PATCH /api/sites/:id/modules` is the only
|
|
22
|
+
way to change it.
|
|
23
|
+
|
|
3
24
|
## 3.64.3
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|