@open-mercato/core 0.6.3-develop.3810.1.ad92c339f5 → 0.6.3-develop.3820.1.636677865b

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.
Files changed (32) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +37 -1
  3. package/dist/modules/customers/api/assignable-staff/route.js +14 -153
  4. package/dist/modules/customers/api/assignable-staff/route.js.map +2 -2
  5. package/dist/modules/customers/components/detail/assignableStaff.js +1 -1
  6. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  7. package/dist/modules/planner/api/access.js +20 -53
  8. package/dist/modules/planner/api/access.js.map +2 -2
  9. package/dist/modules/staff/api/team-members/assignable/route.js +214 -0
  10. package/dist/modules/staff/api/team-members/assignable/route.js.map +7 -0
  11. package/dist/modules/staff/di.js +14 -0
  12. package/dist/modules/staff/di.js.map +7 -0
  13. package/dist/modules/staff/lib/availabilityAccess.js +73 -0
  14. package/dist/modules/staff/lib/availabilityAccess.js.map +7 -0
  15. package/package.json +7 -7
  16. package/src/modules/auth/AGENTS.md +27 -0
  17. package/src/modules/catalog/AGENTS.md +21 -2
  18. package/src/modules/currencies/AGENTS.md +21 -2
  19. package/src/modules/customer_accounts/AGENTS.md +26 -6
  20. package/src/modules/customers/AGENTS.md +20 -1
  21. package/src/modules/customers/api/assignable-staff/route.ts +14 -185
  22. package/src/modules/customers/components/detail/assignableStaff.ts +1 -1
  23. package/src/modules/data_sync/AGENTS.md +30 -11
  24. package/src/modules/integrations/AGENTS.md +31 -13
  25. package/src/modules/planner/api/access.ts +29 -55
  26. package/src/modules/progress/AGENTS.md +20 -2
  27. package/src/modules/sales/AGENTS.md +24 -5
  28. package/src/modules/staff/AGENTS.md +78 -0
  29. package/src/modules/staff/api/team-members/assignable/route.ts +257 -0
  30. package/src/modules/staff/di.ts +20 -0
  31. package/src/modules/staff/lib/availabilityAccess.ts +90 -0
  32. package/src/modules/workflows/AGENTS.md +25 -3
@@ -1,4 +1,4 @@
1
- [build:core] found 2603 entry points
1
+ [build:core] found 2607 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 172 entry points
4
4
  [build:core:generated] built successfully
package/AGENTS.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  `@open-mercato/core` contains all core business modules (auth, catalog, customers, sales, etc.). This guide covers the full extensibility contract and module development patterns.
4
4
 
5
+ ## Always
6
+
7
+ - Preserve auto-discovery contracts for module files, API routes, pages, subscribers, workers, widgets, and generated registries.
8
+ - Export `openApi` from every API route file.
9
+ - Use `makeCrudRoute` with `indexer: { entityType }` for CRUD routes that should participate in query indexing.
10
+ - Wire custom write routes through the mutation guard contract.
11
+ - Use declarative feature guards and add new `acl.ts` features to `setup.ts` `defaultRoleFeatures`.
12
+ - Use `findWithDecryption` / `findOneWithDecryption` for encrypted entities.
13
+ - Implement domain writes through commands so audit, undo, cache, events, and indexing stay consistent.
14
+ - Run `yarn generate` after changing module files discovered by the generator.
15
+
16
+ ## Ask First
17
+
18
+ - Ask before changing any contract surface from `BACKWARD_COMPATIBILITY.md`: auto-discovery, public types, import paths, event IDs, widget spot IDs, API URLs, DB schema, DI names, ACL features, notification IDs, CLI commands, or generated file contracts.
19
+ - Ask before moving versioned generated files or changing where generated registries live.
20
+ - Ask before applying migrations with `yarn db:migrate`; normal PRs should include migration files and snapshots.
21
+
22
+ ## Never
23
+
24
+ - Never create direct ORM relationships between modules; use foreign key IDs and fetch separately.
25
+ - Never expose cross-tenant data or omit tenant/organization scoping.
26
+ - Never hand-edit generated files.
27
+ - Never import generated app bootstrap files from packages.
28
+ - Never run raw `em.find` / `em.findOne` between scalar mutations and `em.flush()` on the same `EntityManager` without `withAtomicFlush`.
29
+ - Never hand-roll AES/KMS encryption or bypass `TenantDataEncryptionService`.
30
+ - Never compare raw feature arrays with exact string checks when wildcard grants apply.
31
+
32
+ ## Validation Commands
33
+
34
+ ```bash
35
+ yarn db:generate
36
+ yarn generate
37
+ yarn workspace @open-mercato/core build
38
+ yarn workspace @open-mercato/core test
39
+ ```
40
+
5
41
  ## Core Modules
6
42
 
7
43
  | Module | Path | Description |
@@ -593,7 +629,7 @@ const crud = makeCrudRoute({
593
629
  })
594
630
  ```
595
631
 
596
- ### Key Rules
632
+ ### Response Enricher Rules
597
633
 
598
634
  - MUST implement `enrichMany()` for batch endpoints (prevents N+1 queries)
599
635
  - MUST namespace enriched fields with `_moduleName` prefix (e.g. `_example.todoCount`)
@@ -1,12 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
- import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
4
- import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
5
- import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
6
- import { User } from "@open-mercato/core/modules/auth/data/entities";
7
- import { StaffTeam, StaffTeamMember } from "@open-mercato/core/modules/staff/data/entities";
8
3
  import { createPagedListResponseSchema } from "../openapi.js";
9
- import { resolveAuthActorId, resolveCustomersRequestContext } from "../../lib/interactionRequestContext.js";
10
4
  const querySchema = z.object({
11
5
  page: z.coerce.number().min(1).default(1),
12
6
  pageSize: z.coerce.number().min(1).max(100).default(24),
@@ -32,164 +26,31 @@ const errorSchema = z.object({ error: z.string() });
32
26
  const metadata = {
33
27
  GET: { requireAuth: true, requireFeatures: ["customers.roles.view"] }
34
28
  };
35
- async function canAccessAssignableStaff(rbac, userId, scope) {
36
- if (!rbac) return false;
37
- if (await rbac.userHasAllFeatures(userId, ["customers.roles.manage"], scope)) {
38
- return true;
39
- }
40
- return rbac.userHasAllFeatures(userId, ["customers.activities.manage"], scope);
41
- }
42
29
  async function GET(request) {
43
- const { translate } = await resolveTranslations();
44
- try {
45
- const query = querySchema.parse(Object.fromEntries(new URL(request.url).searchParams));
46
- const { container, em, auth, selectedOrganizationId } = await resolveCustomersRequestContext(request);
47
- if (!selectedOrganizationId) {
48
- throw new CrudHttpError(
49
- 400,
50
- { error: translate("customers.errors.organization_required", "Organization context is required") }
51
- );
52
- }
53
- const actorId = resolveAuthActorId(auth);
54
- const rbacService = container.resolve("rbacService");
55
- const scope = { tenantId: auth.tenantId, organizationId: selectedOrganizationId };
56
- const hasAccess = await canAccessAssignableStaff(rbacService, actorId, scope);
57
- if (!hasAccess) {
58
- throw new CrudHttpError(
59
- 403,
60
- {
61
- error: translate(
62
- "customers.assignableStaff.forbidden",
63
- "Insufficient permissions to load assignable staff."
64
- )
65
- }
66
- );
67
- }
68
- const normalizedSearch = query.search?.trim().toLowerCase() ?? "";
69
- const members = await findWithDecryption(
70
- em,
71
- StaffTeamMember,
72
- {
73
- tenantId: auth.tenantId,
74
- organizationId: selectedOrganizationId,
75
- deletedAt: null,
76
- isActive: true
77
- },
78
- { orderBy: { displayName: "asc" } },
79
- scope
80
- );
81
- const userIds = Array.from(
82
- new Set(
83
- members.map((member) => typeof member.userId === "string" && member.userId.trim().length > 0 ? member.userId : null).filter((value) => typeof value === "string")
84
- )
85
- );
86
- const teamIds = Array.from(
87
- new Set(
88
- members.map((member) => typeof member.teamId === "string" && member.teamId.trim().length > 0 ? member.teamId : null).filter((value) => typeof value === "string")
89
- )
90
- );
91
- const [users, teams] = await Promise.all([
92
- userIds.length > 0 ? findWithDecryption(
93
- em,
94
- User,
95
- {
96
- id: { $in: userIds },
97
- deletedAt: null,
98
- tenantId: auth.tenantId,
99
- organizationId: selectedOrganizationId
100
- },
101
- void 0,
102
- scope
103
- ) : Promise.resolve([]),
104
- teamIds.length > 0 ? findWithDecryption(
105
- em,
106
- StaffTeam,
107
- {
108
- id: { $in: teamIds },
109
- deletedAt: null,
110
- tenantId: auth.tenantId,
111
- organizationId: selectedOrganizationId
112
- },
113
- void 0,
114
- scope
115
- ) : Promise.resolve([])
116
- ]);
117
- const userById = new Map(
118
- users.map((user) => [
119
- user.id,
120
- {
121
- id: user.id,
122
- email: user.email ?? null
123
- }
124
- ])
125
- );
126
- const teamById = new Map(
127
- teams.map((team) => [
128
- team.id,
129
- {
130
- id: team.id,
131
- name: team.name ?? null
132
- }
133
- ])
134
- );
135
- const items = members.filter((member) => typeof member.userId === "string" && member.userId.trim().length > 0).map((member) => {
136
- const userId = member.userId;
137
- const user = userById.get(userId) ?? { id: userId, email: null };
138
- const team = member.teamId ? teamById.get(member.teamId) ?? null : null;
139
- return {
140
- id: member.id,
141
- teamMemberId: member.id,
142
- userId,
143
- displayName: member.displayName?.trim() || user.email || userId,
144
- email: user.email,
145
- teamName: team?.name ?? null,
146
- user,
147
- team
148
- };
149
- }).filter((item) => {
150
- if (!normalizedSearch) return true;
151
- const haystack = [item.displayName, item.email, item.teamName].filter((value) => typeof value === "string" && value.length > 0).join(" ").toLowerCase();
152
- return haystack.includes(normalizedSearch);
153
- });
154
- const deduped = Array.from(
155
- items.reduce((acc, item) => {
156
- if (!acc.has(item.userId)) {
157
- acc.set(item.userId, item);
158
- }
159
- return acc;
160
- }, /* @__PURE__ */ new Map())
161
- ).map(([, item]) => item);
162
- const start = (query.page - 1) * query.pageSize;
163
- return NextResponse.json({
164
- items: deduped.slice(start, start + query.pageSize),
165
- total: deduped.length,
166
- page: query.page,
167
- pageSize: query.pageSize
168
- });
169
- } catch (error) {
170
- if (isCrudHttpError(error)) {
171
- return NextResponse.json(error.body, { status: error.status });
172
- }
173
- if (error instanceof z.ZodError) {
174
- return NextResponse.json({ error: translate("customers.errors.validationFailed", "Validation failed") }, { status: 400 });
175
- }
176
- console.error("customers.assignable-staff.get failed", error);
177
- return NextResponse.json({ error: translate("customers.errors.assignable_staff_load_failed", "Failed to load assignable staff") }, { status: 500 });
178
- }
30
+ const url = new URL(request.url);
31
+ const target = new URL("/api/staff/team-members/assignable", url.origin);
32
+ target.search = url.search;
33
+ return NextResponse.redirect(target, 308);
179
34
  }
180
35
  const openApi = {
181
36
  tag: "Customers",
182
- summary: "Assignable staff candidates",
37
+ summary: "Assignable staff candidates (DEPRECATED \u2014 redirects to /api/staff/team-members/assignable)",
183
38
  methods: {
184
39
  GET: {
185
- summary: "List staff members that can be assigned from customer flows",
40
+ deprecated: true,
41
+ summary: "DEPRECATED: use GET /api/staff/team-members/assignable instead.",
186
42
  query: querySchema,
187
- description: "Returns active staff members linked to auth users. Access requires either customers.roles.manage or customers.activities.manage.",
43
+ description: "Deprecated. Returns 308 Permanent Redirect to /api/staff/team-members/assignable preserving the query string. Will be removed no earlier than the next major release.",
188
44
  responses: [
189
45
  {
190
46
  status: 200,
191
- description: "Assignable staff members",
47
+ description: "Assignable staff members (only reachable by following the redirect).",
192
48
  schema: createPagedListResponseSchema(itemSchema)
49
+ },
50
+ {
51
+ status: 308,
52
+ description: "Permanent redirect to /api/staff/team-members/assignable.",
53
+ schema: errorSchema
193
54
  }
194
55
  ],
195
56
  errors: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/api/assignable-staff/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { StaffTeam, StaffTeamMember } from '@open-mercato/core/modules/staff/data/entities'\nimport { createPagedListResponseSchema } from '../openapi'\nimport { resolveAuthActorId, resolveCustomersRequestContext } from '../../lib/interactionRequestContext'\n\nconst querySchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(24),\n search: z.string().optional(),\n })\n .passthrough()\n\nconst itemSchema = z.object({\n id: z.string().uuid(),\n teamMemberId: z.string().uuid(),\n userId: z.string().uuid(),\n displayName: z.string(),\n email: z.string().nullable().optional(),\n teamName: z.string().nullable().optional(),\n user: z\n .object({\n id: z.string().uuid(),\n email: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n team: z\n .object({\n id: z.string().uuid(),\n name: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.roles.view'] },\n}\n\nasync function canAccessAssignableStaff(\n rbac: RbacService | undefined,\n userId: string,\n scope: { tenantId: string; organizationId: string },\n): Promise<boolean> {\n if (!rbac) return false\n if (\n await rbac.userHasAllFeatures(userId, ['customers.roles.manage'], scope)\n ) {\n return true\n }\n return rbac.userHasAllFeatures(userId, ['customers.activities.manage'], scope)\n}\n\nexport async function GET(request: Request) {\n const { translate } = await resolveTranslations()\n try {\n const query = querySchema.parse(Object.fromEntries(new URL(request.url).searchParams))\n const { container, em, auth, selectedOrganizationId } = await resolveCustomersRequestContext(request)\n\n if (!selectedOrganizationId) {\n throw new CrudHttpError(\n 400,\n { error: translate('customers.errors.organization_required', 'Organization context is required') },\n )\n }\n\n const actorId = resolveAuthActorId(auth)\n const rbacService = container.resolve('rbacService') as RbacService | undefined\n const scope = { tenantId: auth.tenantId, organizationId: selectedOrganizationId }\n const hasAccess = await canAccessAssignableStaff(rbacService, actorId, scope)\n if (!hasAccess) {\n throw new CrudHttpError(\n 403,\n {\n error: translate(\n 'customers.assignableStaff.forbidden',\n 'Insufficient permissions to load assignable staff.',\n ),\n },\n )\n }\n\n const normalizedSearch = query.search?.trim().toLowerCase() ?? ''\n\n const members = await findWithDecryption(\n em,\n StaffTeamMember,\n {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n deletedAt: null,\n isActive: true,\n },\n { orderBy: { displayName: 'asc' } },\n scope,\n )\n\n const userIds = Array.from(\n new Set(\n members\n .map((member) => (typeof member.userId === 'string' && member.userId.trim().length > 0 ? member.userId : null))\n .filter((value): value is string => typeof value === 'string'),\n ),\n )\n const teamIds = Array.from(\n new Set(\n members\n .map((member) => (typeof member.teamId === 'string' && member.teamId.trim().length > 0 ? member.teamId : null))\n .filter((value): value is string => typeof value === 'string'),\n ),\n )\n\n const [users, teams] = await Promise.all([\n userIds.length > 0\n ? findWithDecryption(\n em,\n User,\n {\n id: { $in: userIds },\n deletedAt: null,\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n },\n undefined,\n scope,\n )\n : Promise.resolve([]),\n teamIds.length > 0\n ? findWithDecryption(\n em,\n StaffTeam,\n {\n id: { $in: teamIds },\n deletedAt: null,\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n },\n undefined,\n scope,\n )\n : Promise.resolve([]),\n ])\n\n const userById = new Map(\n users.map((user) => [\n user.id,\n {\n id: user.id,\n email: user.email ?? null,\n },\n ]),\n )\n const teamById = new Map(\n teams.map((team) => [\n team.id,\n {\n id: team.id,\n name: team.name ?? null,\n },\n ]),\n )\n\n const items = members\n .filter((member) => typeof member.userId === 'string' && member.userId.trim().length > 0)\n .map((member) => {\n const userId = member.userId as string\n const user = userById.get(userId) ?? { id: userId, email: null }\n const team = member.teamId ? teamById.get(member.teamId) ?? null : null\n return {\n id: member.id,\n teamMemberId: member.id,\n userId,\n displayName: member.displayName?.trim() || user.email || userId,\n email: user.email,\n teamName: team?.name ?? null,\n user,\n team,\n }\n })\n .filter((item) => {\n if (!normalizedSearch) return true\n const haystack = [item.displayName, item.email, item.teamName]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(normalizedSearch)\n })\n\n const deduped = Array.from(\n items.reduce((acc, item) => {\n if (!acc.has(item.userId)) {\n acc.set(item.userId, item)\n }\n return acc\n }, new Map<string, (typeof items)[number]>()),\n ).map(([, item]) => item)\n\n const start = (query.page - 1) * query.pageSize\n return NextResponse.json({\n items: deduped.slice(start, start + query.pageSize),\n total: deduped.length,\n page: query.page,\n pageSize: query.pageSize,\n })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n if (error instanceof z.ZodError) {\n return NextResponse.json({ error: translate('customers.errors.validationFailed', 'Validation failed') }, { status: 400 })\n }\n console.error('customers.assignable-staff.get failed', error)\n return NextResponse.json({ error: translate('customers.errors.assignable_staff_load_failed', 'Failed to load assignable staff') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Assignable staff candidates',\n methods: {\n GET: {\n summary: 'List staff members that can be assigned from customer flows',\n query: querySchema,\n description:\n 'Returns active staff members linked to auth users. Access requires either customers.roles.manage or customers.activities.manage.',\n responses: [\n {\n status: 200,\n description: 'Assignable staff members',\n schema: createPagedListResponseSchema(itemSchema),\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createPagedListResponseSchema } from '../openapi'\n\nconst querySchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(24),\n search: z.string().optional(),\n })\n .passthrough()\n\nconst itemSchema = z.object({\n id: z.string().uuid(),\n teamMemberId: z.string().uuid(),\n userId: z.string().uuid(),\n displayName: z.string(),\n email: z.string().nullable().optional(),\n teamName: z.string().nullable().optional(),\n user: z\n .object({\n id: z.string().uuid(),\n email: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n team: z\n .object({\n id: z.string().uuid(),\n name: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.roles.view'] },\n}\n\nexport async function GET(request: Request) {\n const url = new URL(request.url)\n const target = new URL('/api/staff/team-members/assignable', url.origin)\n target.search = url.search\n return NextResponse.redirect(target, 308)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Assignable staff candidates (DEPRECATED \u2014 redirects to /api/staff/team-members/assignable)',\n methods: {\n GET: {\n deprecated: true,\n summary: 'DEPRECATED: use GET /api/staff/team-members/assignable instead.',\n query: querySchema,\n description:\n 'Deprecated. Returns 308 Permanent Redirect to /api/staff/team-members/assignable preserving the query string. Will be removed no earlier than the next major release.',\n responses: [\n {\n status: 200,\n description: 'Assignable staff members (only reachable by following the redirect).',\n schema: createPagedListResponseSchema(itemSchema),\n },\n {\n status: 308,\n description: 'Permanent redirect to /api/staff/team-members/assignable.',\n schema: errorSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,qCAAqC;AAE9C,MAAM,cAAc,EACjB,OAAO;AAAA,EACN,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EACxC,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EACtD,QAAQ,EAAE,OAAO,EAAE,SAAS;AAC9B,CAAC,EACA,YAAY;AAEf,MAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,cAAc,EAAE,OAAO,EAAE,KAAK;AAAA,EAC9B,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,EACxB,aAAa,EAAE,OAAO;AAAA,EACtB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACzC,MAAM,EACH,OAAO;AAAA,IACN,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACxC,CAAC,EACA,SAAS,EACT,SAAS;AAAA,EACZ,MAAM,EACH,OAAO;AAAA,IACN,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACvC,CAAC,EACA,SAAS,EACT,SAAS;AACd,CAAC;AAED,MAAM,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAE3C,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACtE;AAEA,eAAsB,IAAI,SAAkB;AAC1C,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,SAAS,IAAI,IAAI,sCAAsC,IAAI,MAAM;AACvE,SAAO,SAAS,IAAI;AACpB,SAAO,aAAa,SAAS,QAAQ,GAAG;AAC1C;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,OAAO;AAAA,MACP,aACE;AAAA,MACF,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,8BAA8B,UAAU;AAAA,QAClD;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,YAAY;AAAA,QACnE,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,YAAY;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -8,7 +8,7 @@ async function fetchAssignableStaffMembersPage(query, options) {
8
8
  params.set("search", normalizedQuery);
9
9
  }
10
10
  const data = await readApiResultOrThrow(
11
- `/api/customers/assignable-staff?${params.toString()}`,
11
+ `/api/staff/team-members/assignable?${params.toString()}`,
12
12
  options?.signal ? { signal: options.signal } : void 0
13
13
  );
14
14
  const rawItems = Array.isArray(data?.items) ? data.items : [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/assignableStaff.ts"],
4
- "sourcesContent": ["import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { FilterOption } from '@open-mercato/shared/lib/query/advanced-filter'\n\nexport type AssignableStaffMember = {\n teamMemberId: string\n userId: string\n displayName: string\n email: string | null\n teamName: string | null\n}\n\ntype AssignableStaffResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n page?: number\n pageSize?: number\n}\n\nexport type AssignableStaffMembersPage = {\n items: AssignableStaffMember[]\n total: number\n page: number\n pageSize: number\n}\n\nexport async function fetchAssignableStaffMembersPage(\n query: string,\n options?: { page?: number; pageSize?: number; signal?: AbortSignal },\n): Promise<AssignableStaffMembersPage> {\n const params = new URLSearchParams()\n params.set('page', String(options?.page ?? 1))\n params.set('pageSize', String(options?.pageSize ?? 24))\n const normalizedQuery = query.trim()\n if (normalizedQuery.length > 0) {\n params.set('search', normalizedQuery)\n }\n\n const data = await readApiResultOrThrow<AssignableStaffResponse>(\n `/api/customers/assignable-staff?${params.toString()}`,\n options?.signal ? { signal: options.signal } : undefined,\n )\n\n const rawItems = Array.isArray(data?.items) ? data.items : []\n const deduped = new Map<string, AssignableStaffMember>()\n\n for (const item of rawItems) {\n const userId =\n typeof item?.userId === 'string'\n ? item.userId\n : typeof item?.user_id === 'string'\n ? item.user_id\n : null\n if (!userId || deduped.has(userId)) continue\n\n const user =\n item?.user && typeof item.user === 'object'\n ? (item.user as Record<string, unknown>)\n : null\n const team =\n item?.team && typeof item.team === 'object'\n ? (item.team as Record<string, unknown>)\n : null\n\n const displayName =\n typeof item?.displayName === 'string' && item.displayName.trim().length > 0\n ? item.displayName.trim()\n : typeof item?.display_name === 'string' && item.display_name.trim().length > 0\n ? item.display_name.trim()\n : null\n const email =\n user && typeof user.email === 'string' && user.email.trim().length > 0\n ? user.email.trim()\n : typeof item?.email === 'string' && item.email.trim().length > 0\n ? item.email.trim()\n : null\n const teamName =\n typeof item?.teamName === 'string' && item.teamName.trim().length > 0\n ? item.teamName.trim()\n : typeof item?.team_name === 'string' && item.team_name.trim().length > 0\n ? item.team_name.trim()\n : team && typeof team.name === 'string' && team.name.trim().length > 0\n ? team.name.trim()\n : null\n const teamMemberId =\n typeof item?.teamMemberId === 'string'\n ? item.teamMemberId\n : typeof item?.team_member_id === 'string'\n ? item.team_member_id\n : typeof item?.id === 'string'\n ? item.id\n : userId\n\n deduped.set(userId, {\n teamMemberId,\n userId,\n displayName: displayName ?? email ?? userId,\n email,\n teamName,\n })\n }\n\n return {\n items: Array.from(deduped.values()),\n total:\n typeof data?.total === 'number' && Number.isFinite(data.total)\n ? data.total\n : deduped.size,\n page:\n typeof data?.page === 'number' && Number.isFinite(data.page)\n ? data.page\n : options?.page ?? 1,\n pageSize:\n typeof data?.pageSize === 'number' && Number.isFinite(data.pageSize)\n ? data.pageSize\n : options?.pageSize ?? 24,\n }\n}\n\nexport async function fetchAssignableStaffMembers(\n query: string,\n options?: { pageSize?: number; signal?: AbortSignal },\n): Promise<AssignableStaffMember[]> {\n const result = await fetchAssignableStaffMembersPage(query, options)\n return result.items\n}\n\nexport function mapAssignableStaffToFilterOptions(items: AssignableStaffMember[]): FilterOption[] {\n return items.map((item) => ({\n value: item.userId,\n label: item.email && item.email !== item.displayName\n ? `${item.displayName} (${item.email})`\n : item.displayName,\n tone: 'neutral',\n }))\n}\n\nexport function ensureCurrentUserFilterOption(\n options: FilterOption[],\n currentUserId: string,\n fallbackLabel: string,\n): FilterOption[] {\n const trimmed = currentUserId.trim()\n if (!trimmed || options.some((option) => option.value === trimmed)) return options\n return [{ value: trimmed, label: fallbackLabel, tone: 'neutral' }, ...options]\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { FilterOption } from '@open-mercato/shared/lib/query/advanced-filter'\n\nexport type AssignableStaffMember = {\n teamMemberId: string\n userId: string\n displayName: string\n email: string | null\n teamName: string | null\n}\n\ntype AssignableStaffResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n page?: number\n pageSize?: number\n}\n\nexport type AssignableStaffMembersPage = {\n items: AssignableStaffMember[]\n total: number\n page: number\n pageSize: number\n}\n\nexport async function fetchAssignableStaffMembersPage(\n query: string,\n options?: { page?: number; pageSize?: number; signal?: AbortSignal },\n): Promise<AssignableStaffMembersPage> {\n const params = new URLSearchParams()\n params.set('page', String(options?.page ?? 1))\n params.set('pageSize', String(options?.pageSize ?? 24))\n const normalizedQuery = query.trim()\n if (normalizedQuery.length > 0) {\n params.set('search', normalizedQuery)\n }\n\n const data = await readApiResultOrThrow<AssignableStaffResponse>(\n `/api/staff/team-members/assignable?${params.toString()}`,\n options?.signal ? { signal: options.signal } : undefined,\n )\n\n const rawItems = Array.isArray(data?.items) ? data.items : []\n const deduped = new Map<string, AssignableStaffMember>()\n\n for (const item of rawItems) {\n const userId =\n typeof item?.userId === 'string'\n ? item.userId\n : typeof item?.user_id === 'string'\n ? item.user_id\n : null\n if (!userId || deduped.has(userId)) continue\n\n const user =\n item?.user && typeof item.user === 'object'\n ? (item.user as Record<string, unknown>)\n : null\n const team =\n item?.team && typeof item.team === 'object'\n ? (item.team as Record<string, unknown>)\n : null\n\n const displayName =\n typeof item?.displayName === 'string' && item.displayName.trim().length > 0\n ? item.displayName.trim()\n : typeof item?.display_name === 'string' && item.display_name.trim().length > 0\n ? item.display_name.trim()\n : null\n const email =\n user && typeof user.email === 'string' && user.email.trim().length > 0\n ? user.email.trim()\n : typeof item?.email === 'string' && item.email.trim().length > 0\n ? item.email.trim()\n : null\n const teamName =\n typeof item?.teamName === 'string' && item.teamName.trim().length > 0\n ? item.teamName.trim()\n : typeof item?.team_name === 'string' && item.team_name.trim().length > 0\n ? item.team_name.trim()\n : team && typeof team.name === 'string' && team.name.trim().length > 0\n ? team.name.trim()\n : null\n const teamMemberId =\n typeof item?.teamMemberId === 'string'\n ? item.teamMemberId\n : typeof item?.team_member_id === 'string'\n ? item.team_member_id\n : typeof item?.id === 'string'\n ? item.id\n : userId\n\n deduped.set(userId, {\n teamMemberId,\n userId,\n displayName: displayName ?? email ?? userId,\n email,\n teamName,\n })\n }\n\n return {\n items: Array.from(deduped.values()),\n total:\n typeof data?.total === 'number' && Number.isFinite(data.total)\n ? data.total\n : deduped.size,\n page:\n typeof data?.page === 'number' && Number.isFinite(data.page)\n ? data.page\n : options?.page ?? 1,\n pageSize:\n typeof data?.pageSize === 'number' && Number.isFinite(data.pageSize)\n ? data.pageSize\n : options?.pageSize ?? 24,\n }\n}\n\nexport async function fetchAssignableStaffMembers(\n query: string,\n options?: { pageSize?: number; signal?: AbortSignal },\n): Promise<AssignableStaffMember[]> {\n const result = await fetchAssignableStaffMembersPage(query, options)\n return result.items\n}\n\nexport function mapAssignableStaffToFilterOptions(items: AssignableStaffMember[]): FilterOption[] {\n return items.map((item) => ({\n value: item.userId,\n label: item.email && item.email !== item.displayName\n ? `${item.displayName} (${item.email})`\n : item.displayName,\n tone: 'neutral',\n }))\n}\n\nexport function ensureCurrentUserFilterOption(\n options: FilterOption[],\n currentUserId: string,\n fallbackLabel: string,\n): FilterOption[] {\n const trimmed = currentUserId.trim()\n if (!trimmed || options.some((option) => option.value === trimmed)) return options\n return [{ value: trimmed, label: fallbackLabel, tone: 'neutral' }, ...options]\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,71 +1,38 @@
1
1
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
2
- import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
- import { StaffTeamMember } from "@open-mercato/core/modules/staff/data/entities";
4
- const MANAGE_AVAILABILITY_FEATURE = "planner.manage_availability";
5
- const SELF_MANAGE_FEATURE = "staff.my_availability.manage";
6
- const SELF_UNAVAILABILITY_FEATURE = "staff.my_availability.unavailability";
7
2
  function buildForbiddenError(translate) {
8
- return new CrudHttpError(403, { error: translate("planner.availability.errors.unauthorized", "Unauthorized") });
3
+ return new CrudHttpError(403, {
4
+ error: translate("planner.availability.errors.unauthorized", "Unauthorized")
5
+ });
6
+ }
7
+ function buildStaffModuleNotLoadedError() {
8
+ return new CrudHttpError(403, { error: "staff_module_not_loaded" });
9
9
  }
10
10
  async function resolveAvailabilityWriteAccess(ctx) {
11
- const auth = ctx.auth;
12
- const tenantId = auth?.tenantId ?? null;
13
- const organizationId = ctx.selectedOrganizationId ?? auth?.orgId ?? null;
14
- if (!auth || !auth.sub || auth.isApiKey) {
15
- return {
16
- canManageAll: false,
17
- canManageSelf: false,
18
- canManageUnavailability: false,
19
- memberId: null,
20
- tenantId,
21
- organizationId
22
- };
23
- }
24
- const rbac = ctx.container.resolve("rbacService");
25
- const canManageAll = await rbac.userHasAllFeatures(auth.sub, [MANAGE_AVAILABILITY_FEATURE], { tenantId, organizationId });
26
- if (canManageAll) {
27
- return {
28
- canManageAll: true,
29
- canManageSelf: true,
30
- canManageUnavailability: true,
31
- memberId: null,
32
- tenantId,
33
- organizationId
34
- };
35
- }
36
- const [canManageSelf, canManageUnavailability] = await Promise.all([
37
- rbac.userHasAllFeatures(auth.sub, [SELF_MANAGE_FEATURE], { tenantId, organizationId }),
38
- rbac.userHasAllFeatures(auth.sub, [SELF_UNAVAILABILITY_FEATURE], { tenantId, organizationId })
39
- ]);
40
- if (!canManageSelf) {
11
+ const tenantId = ctx.auth?.tenantId ?? null;
12
+ const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
13
+ const resolver = ctx.container.resolve(
14
+ "availabilityAccessResolver",
15
+ { allowUnregistered: true }
16
+ );
17
+ if (!resolver) {
18
+ console.warn(
19
+ "[planner] staff_module_not_loaded \u2014 availabilityAccessResolver unregistered; denying availability write access"
20
+ );
41
21
  return {
42
22
  canManageAll: false,
43
23
  canManageSelf: false,
44
24
  canManageUnavailability: false,
45
25
  memberId: null,
46
26
  tenantId,
47
- organizationId
27
+ organizationId,
28
+ unregistered: true
48
29
  };
49
30
  }
50
- const em = ctx.container.resolve("em");
51
- const member = await findOneWithDecryption(
52
- em,
53
- StaffTeamMember,
54
- { userId: auth.sub, deletedAt: null },
55
- void 0,
56
- { tenantId, organizationId }
57
- );
58
- return {
59
- canManageAll: false,
60
- canManageSelf,
61
- canManageUnavailability,
62
- memberId: member?.id ?? null,
63
- tenantId,
64
- organizationId
65
- };
31
+ return resolver.resolveAvailabilityWriteAccess(ctx);
66
32
  }
67
33
  async function assertAvailabilityWriteAccess(ctx, params, translate) {
68
34
  const access = await resolveAvailabilityWriteAccess(ctx);
35
+ if (access.unregistered) throw buildStaffModuleNotLoadedError();
69
36
  if (access.canManageAll) return access;
70
37
  if (!access.canManageSelf) throw buildForbiddenError(translate);
71
38
  if (!access.memberId || params.subjectType !== "member" || params.subjectId !== access.memberId) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/planner/api/access.ts"],
4
- "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { StaffTeamMember } from '@open-mercato/core/modules/staff/data/entities'\n\ntype TranslateFn = (key: string, fallback?: string) => string\n\nexport type AvailabilityAccessContext = {\n container: AwilixContainer\n auth: AuthContext | null\n selectedOrganizationId?: string | null\n}\n\nexport type AvailabilityWriteAccess = {\n canManageAll: boolean\n canManageSelf: boolean\n canManageUnavailability: boolean\n memberId: string | null\n tenantId: string | null\n organizationId: string | null\n}\n\nconst MANAGE_AVAILABILITY_FEATURE = 'planner.manage_availability'\nconst SELF_MANAGE_FEATURE = 'staff.my_availability.manage'\nconst SELF_UNAVAILABILITY_FEATURE = 'staff.my_availability.unavailability'\n\nfunction buildForbiddenError(translate: TranslateFn) {\n return new CrudHttpError(403, { error: translate('planner.availability.errors.unauthorized', 'Unauthorized') })\n}\n\nexport async function resolveAvailabilityWriteAccess(ctx: AvailabilityAccessContext): Promise<AvailabilityWriteAccess> {\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? auth?.orgId ?? null\n if (!auth || !auth.sub || auth.isApiKey) {\n return {\n canManageAll: false,\n canManageSelf: false,\n canManageUnavailability: false,\n memberId: null,\n tenantId,\n organizationId,\n }\n }\n const rbac = ctx.container.resolve('rbacService') as RbacService\n const canManageAll = await rbac.userHasAllFeatures(auth.sub, [MANAGE_AVAILABILITY_FEATURE], { tenantId, organizationId })\n if (canManageAll) {\n return {\n canManageAll: true,\n canManageSelf: true,\n canManageUnavailability: true,\n memberId: null,\n tenantId,\n organizationId,\n }\n }\n const [canManageSelf, canManageUnavailability] = await Promise.all([\n rbac.userHasAllFeatures(auth.sub, [SELF_MANAGE_FEATURE], { tenantId, organizationId }),\n rbac.userHasAllFeatures(auth.sub, [SELF_UNAVAILABILITY_FEATURE], { tenantId, organizationId }),\n ])\n if (!canManageSelf) {\n return {\n canManageAll: false,\n canManageSelf: false,\n canManageUnavailability: false,\n memberId: null,\n tenantId,\n organizationId,\n }\n }\n const em = ctx.container.resolve('em') as EntityManager\n const member = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, deletedAt: null },\n undefined,\n { tenantId, organizationId },\n )\n return {\n canManageAll: false,\n canManageSelf,\n canManageUnavailability,\n memberId: member?.id ?? null,\n tenantId,\n organizationId,\n }\n}\n\nexport async function assertAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n params: { subjectType: string; subjectId: string; requiresUnavailability?: boolean },\n translate: TranslateFn,\n): Promise<AvailabilityWriteAccess> {\n const access = await resolveAvailabilityWriteAccess(ctx)\n if (access.canManageAll) return access\n if (!access.canManageSelf) throw buildForbiddenError(translate)\n if (!access.memberId || params.subjectType !== 'member' || params.subjectId !== access.memberId) {\n throw buildForbiddenError(translate)\n }\n if (params.requiresUnavailability && !access.canManageUnavailability) {\n throw buildForbiddenError(translate)\n }\n return access\n}\n"],
5
- "mappings": "AAIA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,uBAAuB;AAmBhC,MAAM,8BAA8B;AACpC,MAAM,sBAAsB;AAC5B,MAAM,8BAA8B;AAEpC,SAAS,oBAAoB,WAAwB;AACnD,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,4CAA4C,cAAc,EAAE,CAAC;AAChH;AAEA,eAAsB,+BAA+B,KAAkE;AACrH,QAAM,OAAO,IAAI;AACjB,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,iBAAiB,IAAI,0BAA0B,MAAM,SAAS;AACpE,MAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,KAAK,UAAU;AACvC,WAAO;AAAA,MACL,cAAc;AAAA,MACd,eAAe;AAAA,MACf,yBAAyB;AAAA,MACzB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,OAAO,IAAI,UAAU,QAAQ,aAAa;AAChD,QAAM,eAAe,MAAM,KAAK,mBAAmB,KAAK,KAAK,CAAC,2BAA2B,GAAG,EAAE,UAAU,eAAe,CAAC;AACxH,MAAI,cAAc;AAChB,WAAO;AAAA,MACL,cAAc;AAAA,MACd,eAAe;AAAA,MACf,yBAAyB;AAAA,MACzB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,CAAC,eAAe,uBAAuB,IAAI,MAAM,QAAQ,IAAI;AAAA,IACjE,KAAK,mBAAmB,KAAK,KAAK,CAAC,mBAAmB,GAAG,EAAE,UAAU,eAAe,CAAC;AAAA,IACrF,KAAK,mBAAmB,KAAK,KAAK,CAAC,2BAA2B,GAAG,EAAE,UAAU,eAAe,CAAC;AAAA,EAC/F,CAAC;AACD,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,MACL,cAAc;AAAA,MACd,eAAe;AAAA,MACf,yBAAyB;AAAA,MACzB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AACrC,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA,EAAE,QAAQ,KAAK,KAAK,WAAW,KAAK;AAAA,IACpC;AAAA,IACA,EAAE,UAAU,eAAe;AAAA,EAC7B;AACA,SAAO;AAAA,IACL,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,MAAM;AAAA,IACxB;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,8BACpB,KACA,QACA,WACkC;AAClC,QAAM,SAAS,MAAM,+BAA+B,GAAG;AACvD,MAAI,OAAO,aAAc,QAAO;AAChC,MAAI,CAAC,OAAO,cAAe,OAAM,oBAAoB,SAAS;AAC9D,MAAI,CAAC,OAAO,YAAY,OAAO,gBAAgB,YAAY,OAAO,cAAc,OAAO,UAAU;AAC/F,UAAM,oBAAoB,SAAS;AAAA,EACrC;AACA,MAAI,OAAO,0BAA0B,CAAC,OAAO,yBAAyB;AACpE,UAAM,oBAAoB,SAAS;AAAA,EACrC;AACA,SAAO;AACT;",
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\ntype TranslateFn = (key: string, fallback?: string) => string\n\nexport type AvailabilityAccessContext = {\n container: AwilixContainer\n auth: AuthContext | null\n selectedOrganizationId?: string | null\n}\n\nexport type AvailabilityWriteAccess = {\n canManageAll: boolean\n canManageSelf: boolean\n canManageUnavailability: boolean\n memberId: string | null\n tenantId: string | null\n organizationId: string | null\n unregistered?: boolean\n}\n\ntype AvailabilityAccessResolver = {\n resolveAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n ): Promise<AvailabilityWriteAccess>\n}\n\nfunction buildForbiddenError(translate: TranslateFn) {\n return new CrudHttpError(403, {\n error: translate('planner.availability.errors.unauthorized', 'Unauthorized'),\n })\n}\n\nfunction buildStaffModuleNotLoadedError() {\n return new CrudHttpError(403, { error: 'staff_module_not_loaded' })\n}\n\nexport async function resolveAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n): Promise<AvailabilityWriteAccess> {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n const resolver = ctx.container.resolve<AvailabilityAccessResolver | undefined>(\n 'availabilityAccessResolver',\n { allowUnregistered: true },\n )\n if (!resolver) {\n console.warn(\n '[planner] staff_module_not_loaded \u2014 availabilityAccessResolver unregistered; denying availability write access',\n )\n return {\n canManageAll: false,\n canManageSelf: false,\n canManageUnavailability: false,\n memberId: null,\n tenantId,\n organizationId,\n unregistered: true,\n }\n }\n return resolver.resolveAvailabilityWriteAccess(ctx)\n}\n\nexport async function assertAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n params: { subjectType: string; subjectId: string; requiresUnavailability?: boolean },\n translate: TranslateFn,\n): Promise<AvailabilityWriteAccess> {\n const access = await resolveAvailabilityWriteAccess(ctx)\n if (access.unregistered) throw buildStaffModuleNotLoadedError()\n if (access.canManageAll) return access\n if (!access.canManageSelf) throw buildForbiddenError(translate)\n if (!access.memberId || params.subjectType !== 'member' || params.subjectId !== access.memberId) {\n throw buildForbiddenError(translate)\n }\n if (params.requiresUnavailability && !access.canManageUnavailability) {\n throw buildForbiddenError(translate)\n }\n return access\n}\n"],
5
+ "mappings": "AAEA,SAAS,qBAAqB;AA0B9B,SAAS,oBAAoB,WAAwB;AACnD,SAAO,IAAI,cAAc,KAAK;AAAA,IAC5B,OAAO,UAAU,4CAA4C,cAAc;AAAA,EAC7E,CAAC;AACH;AAEA,SAAS,iCAAiC;AACxC,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,0BAA0B,CAAC;AACpE;AAEA,eAAsB,+BACpB,KACkC;AAClC,QAAM,WAAW,IAAI,MAAM,YAAY;AACvC,QAAM,iBAAiB,IAAI,0BAA0B,IAAI,MAAM,SAAS;AACxE,QAAM,WAAW,IAAI,UAAU;AAAA,IAC7B;AAAA,IACA,EAAE,mBAAmB,KAAK;AAAA,EAC5B;AACA,MAAI,CAAC,UAAU;AACb,YAAQ;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,MACL,cAAc;AAAA,MACd,eAAe;AAAA,MACf,yBAAyB;AAAA,MACzB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF;AACA,SAAO,SAAS,+BAA+B,GAAG;AACpD;AAEA,eAAsB,8BACpB,KACA,QACA,WACkC;AAClC,QAAM,SAAS,MAAM,+BAA+B,GAAG;AACvD,MAAI,OAAO,aAAc,OAAM,+BAA+B;AAC9D,MAAI,OAAO,aAAc,QAAO;AAChC,MAAI,CAAC,OAAO,cAAe,OAAM,oBAAoB,SAAS;AAC9D,MAAI,CAAC,OAAO,YAAY,OAAO,gBAAgB,YAAY,OAAO,cAAc,OAAO,UAAU;AAC/F,UAAM,oBAAoB,SAAS;AAAA,EACrC;AACA,MAAI,OAAO,0BAA0B,CAAC,OAAO,yBAAyB;AACpE,UAAM,oBAAoB,SAAS;AAAA,EACrC;AACA,SAAO;AACT;",
6
6
  "names": []
7
7
  }