@open-mercato/core 0.6.6-develop.6429.1.2fba7258d1 → 0.6.6-develop.6450.1.b493fb430c

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 (87) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/api/people/[id]/route.js +73 -2
  3. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  4. package/dist/modules/planner/commands/shared.js +1 -8
  5. package/dist/modules/planner/commands/shared.js.map +2 -2
  6. package/dist/modules/resources/commands/shared.js +1 -7
  7. package/dist/modules/resources/commands/shared.js.map +2 -2
  8. package/dist/modules/shipping_carriers/api/cancel/route.js +2 -2
  9. package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
  10. package/dist/modules/shipping_carriers/api/points/route.js +2 -2
  11. package/dist/modules/shipping_carriers/api/points/route.js.map +2 -2
  12. package/dist/modules/shipping_carriers/api/rates/route.js +2 -2
  13. package/dist/modules/shipping_carriers/api/rates/route.js.map +2 -2
  14. package/dist/modules/shipping_carriers/api/shipments/route.js +2 -2
  15. package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
  16. package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +2 -2
  17. package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +2 -2
  18. package/dist/modules/shipping_carriers/api/tracking/route.js +2 -2
  19. package/dist/modules/shipping_carriers/api/tracking/route.js.map +2 -2
  20. package/dist/modules/shipping_carriers/lib/upstream-error-response.js +13 -0
  21. package/dist/modules/shipping_carriers/lib/upstream-error-response.js.map +7 -0
  22. package/dist/modules/staff/commands/activities.js +46 -8
  23. package/dist/modules/staff/commands/activities.js.map +2 -2
  24. package/dist/modules/staff/commands/addresses.js +40 -7
  25. package/dist/modules/staff/commands/addresses.js.map +2 -2
  26. package/dist/modules/staff/commands/comments.js +46 -8
  27. package/dist/modules/staff/commands/comments.js.map +2 -2
  28. package/dist/modules/staff/commands/job-histories.js +40 -7
  29. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  30. package/dist/modules/staff/commands/leave-requests.js +31 -9
  31. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  32. package/dist/modules/staff/commands/shared.js +56 -7
  33. package/dist/modules/staff/commands/shared.js.map +2 -2
  34. package/dist/modules/staff/commands/tag-assignments.js +18 -7
  35. package/dist/modules/staff/commands/tag-assignments.js.map +2 -2
  36. package/dist/modules/staff/commands/team-members.js +16 -5
  37. package/dist/modules/staff/commands/team-members.js.map +2 -2
  38. package/dist/modules/staff/commands/team-roles.js +16 -5
  39. package/dist/modules/staff/commands/team-roles.js.map +2 -2
  40. package/dist/modules/staff/commands/teams.js +16 -5
  41. package/dist/modules/staff/commands/teams.js.map +2 -2
  42. package/dist/modules/staff/commands/timesheets-entries.js +17 -5
  43. package/dist/modules/staff/commands/timesheets-entries.js.map +2 -2
  44. package/dist/modules/staff/commands/timesheets-projects.js +22 -9
  45. package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
  46. package/dist/modules/sync_excel/lib/upload-storage.js +9 -6
  47. package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
  48. package/dist/modules/translations/api/get/locales.js +2 -1
  49. package/dist/modules/translations/api/get/locales.js.map +2 -2
  50. package/dist/modules/translations/api/put/locales.js +1 -1
  51. package/dist/modules/translations/api/put/locales.js.map +2 -2
  52. package/dist/modules/workflows/examples/sales-pipeline-definition.json +8 -8
  53. package/dist/modules/workflows/lib/activity-executor.js +39 -3
  54. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  55. package/package.json +7 -7
  56. package/src/modules/customers/api/people/[id]/route.ts +107 -2
  57. package/src/modules/planner/commands/shared.ts +2 -11
  58. package/src/modules/resources/commands/shared.ts +2 -10
  59. package/src/modules/shipping_carriers/api/cancel/route.ts +2 -2
  60. package/src/modules/shipping_carriers/api/points/route.ts +2 -2
  61. package/src/modules/shipping_carriers/api/rates/route.ts +2 -2
  62. package/src/modules/shipping_carriers/api/shipments/route.ts +2 -2
  63. package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +2 -2
  64. package/src/modules/shipping_carriers/api/tracking/route.ts +2 -2
  65. package/src/modules/shipping_carriers/i18n/de.json +1 -0
  66. package/src/modules/shipping_carriers/i18n/en.json +1 -0
  67. package/src/modules/shipping_carriers/i18n/es.json +1 -0
  68. package/src/modules/shipping_carriers/i18n/pl.json +1 -0
  69. package/src/modules/shipping_carriers/lib/upstream-error-response.ts +11 -0
  70. package/src/modules/staff/commands/activities.ts +46 -8
  71. package/src/modules/staff/commands/addresses.ts +40 -7
  72. package/src/modules/staff/commands/comments.ts +46 -8
  73. package/src/modules/staff/commands/job-histories.ts +40 -7
  74. package/src/modules/staff/commands/leave-requests.ts +36 -9
  75. package/src/modules/staff/commands/shared.ts +71 -8
  76. package/src/modules/staff/commands/tag-assignments.ts +18 -7
  77. package/src/modules/staff/commands/team-members.ts +16 -5
  78. package/src/modules/staff/commands/team-roles.ts +16 -5
  79. package/src/modules/staff/commands/teams.ts +16 -5
  80. package/src/modules/staff/commands/timesheets-entries.ts +17 -5
  81. package/src/modules/staff/commands/timesheets-projects.ts +22 -9
  82. package/src/modules/sync_excel/lib/upload-storage.ts +8 -6
  83. package/src/modules/translations/api/get/locales.ts +1 -0
  84. package/src/modules/translations/api/put/locales.ts +1 -1
  85. package/src/modules/workflows/AGENTS.md +2 -1
  86. package/src/modules/workflows/examples/sales-pipeline-definition.json +8 -8
  87. package/src/modules/workflows/lib/activity-executor.ts +49 -5
@@ -14,7 +14,16 @@ import {
14
14
  type StaffTeamMemberCommentUpdateInput,
15
15
  } from '../data/validators'
16
16
  import { staffTeamMemberCommentCrudEvents } from '../lib/crud'
17
- import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload, requireTeamMember } from './shared'
17
+ import {
18
+ applyScopeToWhere,
19
+ commandActorScope,
20
+ commandInputScope,
21
+ ensureOrganizationScope,
22
+ ensureTenantScope,
23
+ explicitStaffCommandScope,
24
+ extractUndoPayload,
25
+ requireTeamMember,
26
+ } from './shared'
18
27
  import { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'
19
28
  import { E } from '#generated/entities.ids.generated'
20
29
 
@@ -62,10 +71,16 @@ const createCommentCommand: CommandHandler<
62
71
  const parsed = staffTeamMemberCommentCreateSchema.parse(rawInput)
63
72
  ensureTenantScope(ctx, parsed.tenantId)
64
73
  ensureOrganizationScope(ctx, parsed.organizationId)
74
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
65
75
  const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId, ctx.auth)
66
76
 
67
77
  const em = (ctx.container.resolve('em') as EntityManager).fork()
68
- const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
78
+ const member = await requireTeamMember(
79
+ em,
80
+ parsed.entityId,
81
+ scope,
82
+ 'Team member not found',
83
+ )
69
84
  ensureTenantScope(ctx, member.tenantId)
70
85
  ensureOrganizationScope(ctx, member.organizationId)
71
86
 
@@ -151,7 +166,12 @@ const createCommentCommand: CommandHandler<
151
166
  appearanceColor: snapshot.appearanceColor,
152
167
  }),
153
168
  beforeRestore: async ({ em, snapshot }) => {
154
- const member = await requireTeamMember(em, snapshot.memberId, 'Team member not found')
169
+ const member = await requireTeamMember(
170
+ em,
171
+ snapshot.memberId,
172
+ explicitStaffCommandScope(snapshot.tenantId, snapshot.organizationId),
173
+ 'Team member not found',
174
+ )
155
175
  return { member }
156
176
  },
157
177
  buildResult: (entity) => ({ commentId: entity.id, authorUserId: entity.authorUserId ?? null }),
@@ -169,13 +189,17 @@ const updateCommentCommand: CommandHandler<StaffTeamMemberCommentUpdateInput, {
169
189
  async execute(rawInput, ctx) {
170
190
  const parsed = staffTeamMemberCommentUpdateSchema.parse(rawInput)
171
191
  const em = (ctx.container.resolve('em') as EntityManager).fork()
172
- const comment = await em.findOne(StaffTeamMemberComment, { id: parsed.id })
192
+ const scope = commandActorScope(ctx)
193
+ const comment = await em.findOne(
194
+ StaffTeamMemberComment,
195
+ applyScopeToWhere<StaffTeamMemberComment>({ id: parsed.id }, scope),
196
+ )
173
197
  if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })
174
198
  ensureTenantScope(ctx, comment.tenantId)
175
199
  ensureOrganizationScope(ctx, comment.organizationId)
176
200
 
177
201
  if (parsed.entityId !== undefined) {
178
- const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
202
+ const member = await requireTeamMember(em, parsed.entityId, scope, 'Team member not found')
179
203
  ensureTenantScope(ctx, member.tenantId)
180
204
  ensureOrganizationScope(ctx, member.organizationId)
181
205
  comment.member = member
@@ -245,7 +269,12 @@ const updateCommentCommand: CommandHandler<StaffTeamMemberCommentUpdateInput, {
245
269
  if (!before) return
246
270
  const em = (ctx.container.resolve('em') as EntityManager).fork()
247
271
  let comment = await em.findOne(StaffTeamMemberComment, { id: before.id })
248
- const member = await requireTeamMember(em, before.memberId, 'Team member not found')
272
+ const member = await requireTeamMember(
273
+ em,
274
+ before.memberId,
275
+ explicitStaffCommandScope(before.tenantId, before.organizationId),
276
+ 'Team member not found',
277
+ )
249
278
 
250
279
  if (!comment) {
251
280
  comment = em.create(StaffTeamMemberComment, {
@@ -298,7 +327,11 @@ const deleteCommentCommand: CommandHandler<{ body?: Record<string, unknown>; que
298
327
  async execute(input, ctx) {
299
328
  const id = requireId(input, 'Comment id required')
300
329
  const em = (ctx.container.resolve('em') as EntityManager).fork()
301
- const comment = await em.findOne(StaffTeamMemberComment, { id })
330
+ const scope = commandActorScope(ctx)
331
+ const comment = await em.findOne(
332
+ StaffTeamMemberComment,
333
+ applyScopeToWhere<StaffTeamMemberComment>({ id }, scope),
334
+ )
302
335
  if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })
303
336
  ensureTenantScope(ctx, comment.tenantId)
304
337
  ensureOrganizationScope(ctx, comment.organizationId)
@@ -345,7 +378,12 @@ const deleteCommentCommand: CommandHandler<{ body?: Record<string, unknown>; que
345
378
  const before = payload?.before
346
379
  if (!before) return
347
380
  const em = (ctx.container.resolve('em') as EntityManager).fork()
348
- const member = await requireTeamMember(em, before.memberId, 'Team member not found')
381
+ const member = await requireTeamMember(
382
+ em,
383
+ before.memberId,
384
+ explicitStaffCommandScope(before.tenantId, before.organizationId),
385
+ 'Team member not found',
386
+ )
349
387
  let comment = await em.findOne(StaffTeamMemberComment, { id: before.id })
350
388
  if (!comment) {
351
389
  comment = em.create(StaffTeamMemberComment, {
@@ -12,8 +12,12 @@ import {
12
12
  } from '../data/validators'
13
13
  import { staffTeamMemberJobHistoryCrudEvents } from '../lib/crud'
14
14
  import {
15
+ applyScopeToWhere,
16
+ commandActorScope,
17
+ commandInputScope,
15
18
  ensureOrganizationScope,
16
19
  ensureTenantScope,
20
+ explicitStaffCommandScope,
17
21
  extractUndoPayload,
18
22
  requireTeamMember,
19
23
  } from './shared'
@@ -74,9 +78,15 @@ const createJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryCreateInp
74
78
  const parsed = staffTeamMemberJobHistoryCreateSchema.parse(rawInput)
75
79
  ensureTenantScope(ctx, parsed.tenantId)
76
80
  ensureOrganizationScope(ctx, parsed.organizationId)
81
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
77
82
 
78
83
  const em = (ctx.container.resolve('em') as EntityManager).fork()
79
- const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
84
+ const member = await requireTeamMember(
85
+ em,
86
+ parsed.entityId,
87
+ scope,
88
+ 'Team member not found',
89
+ )
80
90
  ensureTenantScope(ctx, member.tenantId)
81
91
  ensureOrganizationScope(ctx, member.organizationId)
82
92
 
@@ -150,7 +160,12 @@ const createJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryCreateInp
150
160
  throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for job history create' })
151
161
  }
152
162
  const em = (ctx.container.resolve('em') as EntityManager).fork()
153
- const member = await requireTeamMember(em, after.memberId, 'Team member not found')
163
+ const member = await requireTeamMember(
164
+ em,
165
+ after.memberId,
166
+ explicitStaffCommandScope(after.tenantId, after.organizationId),
167
+ 'Team member not found',
168
+ )
154
169
  let record = await em.findOne(StaffTeamMemberJobHistory, { id: after.id })
155
170
  if (!record) {
156
171
  record = em.create(StaffTeamMemberJobHistory, {
@@ -206,7 +221,11 @@ const updateJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryUpdateInp
206
221
  async execute(rawInput, ctx) {
207
222
  const parsed = staffTeamMemberJobHistoryUpdateSchema.parse(rawInput)
208
223
  const em = (ctx.container.resolve('em') as EntityManager).fork()
209
- const record = await em.findOne(StaffTeamMemberJobHistory, { id: parsed.id })
224
+ const scope = commandActorScope(ctx)
225
+ const record = await em.findOne(
226
+ StaffTeamMemberJobHistory,
227
+ applyScopeToWhere<StaffTeamMemberJobHistory>({ id: parsed.id }, scope),
228
+ )
210
229
  if (!record) {
211
230
  enforceRecordGoneIsConflict({
212
231
  resourceKind: JOB_HISTORY_LOCK_RESOURCE_KIND,
@@ -227,7 +246,7 @@ const updateJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryUpdateInp
227
246
  })
228
247
 
229
248
  if (parsed.entityId !== undefined) {
230
- const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
249
+ const member = await requireTeamMember(em, parsed.entityId, scope, 'Team member not found')
231
250
  ensureTenantScope(ctx, member.tenantId)
232
251
  ensureOrganizationScope(ctx, member.organizationId)
233
252
  record.member = member
@@ -298,7 +317,12 @@ const updateJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryUpdateInp
298
317
  if (!before) return
299
318
  const em = (ctx.container.resolve('em') as EntityManager).fork()
300
319
  let record = await em.findOne(StaffTeamMemberJobHistory, { id: before.id })
301
- const member = await requireTeamMember(em, before.memberId, 'Team member not found')
320
+ const member = await requireTeamMember(
321
+ em,
322
+ before.memberId,
323
+ explicitStaffCommandScope(before.tenantId, before.organizationId),
324
+ 'Team member not found',
325
+ )
302
326
 
303
327
  if (!record) {
304
328
  record = em.create(StaffTeamMemberJobHistory, {
@@ -354,7 +378,11 @@ const deleteJobHistoryCommand: CommandHandler<{ id?: string; updatedAt?: string;
354
378
  const id = requireId(input, 'Job history id required')
355
379
  const expectedUpdatedAt = typeof input.updatedAt === 'string' ? input.updatedAt : undefined
356
380
  const em = (ctx.container.resolve('em') as EntityManager).fork()
357
- const record = await em.findOne(StaffTeamMemberJobHistory, { id })
381
+ const scope = commandActorScope(ctx)
382
+ const record = await em.findOne(
383
+ StaffTeamMemberJobHistory,
384
+ applyScopeToWhere<StaffTeamMemberJobHistory>({ id }, scope),
385
+ )
358
386
  if (!record) {
359
387
  enforceRecordGoneIsConflict({
360
388
  resourceKind: JOB_HISTORY_LOCK_RESOURCE_KIND,
@@ -416,7 +444,12 @@ const deleteJobHistoryCommand: CommandHandler<{ id?: string; updatedAt?: string;
416
444
  const before = payload?.before
417
445
  if (!before) return
418
446
  const em = (ctx.container.resolve('em') as EntityManager).fork()
419
- const member = await requireTeamMember(em, before.memberId, 'Team member not found')
447
+ const member = await requireTeamMember(
448
+ em,
449
+ before.memberId,
450
+ explicitStaffCommandScope(before.tenantId, before.organizationId),
451
+ 'Team member not found',
452
+ )
420
453
  let record = await em.findOne(StaffTeamMemberJobHistory, { id: before.id })
421
454
  if (!record) {
422
455
  record = em.create(StaffTeamMemberJobHistory, {
@@ -21,7 +21,17 @@ import {
21
21
  type StaffLeaveRequestDecisionInput,
22
22
  type StaffLeaveRequestUpdateInput,
23
23
  } from '../data/validators'
24
- import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload, requireTeamMember } from './shared'
24
+ import {
25
+ commandActorScope,
26
+ commandInputScope,
27
+ ensureOrganizationScope,
28
+ ensureTenantScope,
29
+ explicitStaffCommandScope,
30
+ extractUndoPayload,
31
+ requireTeamMember,
32
+ scopeForDecryption,
33
+ applyScopeToWhere,
34
+ } from './shared'
25
35
  import { E } from '#generated/entities.ids.generated'
26
36
  import { resolveNotificationService } from '../../notifications/lib/notificationService'
27
37
  import { buildFeatureNotificationFromType, buildNotificationFromType } from '../../notifications/lib/notificationBuilder'
@@ -167,8 +177,18 @@ function leaveRequestSeedFromSnapshot(snapshot: LeaveRequestSnapshot): Record<st
167
177
  }
168
178
  }
169
179
 
170
- async function requireLeaveRequest(em: EntityManager, id: string): Promise<StaffLeaveRequest> {
171
- const request = await findOneWithDecryption(em, StaffLeaveRequest, { id, deletedAt: null }, undefined, { tenantId: null, organizationId: null })
180
+ async function requireLeaveRequest(
181
+ em: EntityManager,
182
+ id: string,
183
+ scope: ReturnType<typeof explicitStaffCommandScope>,
184
+ ): Promise<StaffLeaveRequest> {
185
+ const request = await findOneWithDecryption(
186
+ em,
187
+ StaffLeaveRequest,
188
+ applyScopeToWhere<StaffLeaveRequest>({ id, deletedAt: null }, scope),
189
+ undefined,
190
+ scopeForDecryption(scope),
191
+ )
172
192
  if (!request) throw new CrudHttpError(404, { error: 'Leave request not found.' })
173
193
  return request
174
194
  }
@@ -245,9 +265,15 @@ const createLeaveRequestCommand: CommandHandler<StaffLeaveRequestCreateInput, {
245
265
  const parsed = staffLeaveRequestCreateSchema.parse(rawInput)
246
266
  ensureTenantScope(ctx, parsed.tenantId)
247
267
  ensureOrganizationScope(ctx, parsed.organizationId)
268
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
248
269
 
249
270
  const em = (ctx.container.resolve('em') as EntityManager).fork()
250
- const member = await requireTeamMember(em, parsed.memberId, 'Team member not found')
271
+ const member = await requireTeamMember(
272
+ em,
273
+ parsed.memberId,
274
+ scope,
275
+ 'Team member not found',
276
+ )
251
277
  ensureTenantScope(ctx, member.tenantId)
252
278
  ensureOrganizationScope(ctx, member.organizationId)
253
279
 
@@ -400,13 +426,14 @@ const updateLeaveRequestCommand: CommandHandler<StaffLeaveRequestUpdateInput, {
400
426
  async execute(rawInput, ctx) {
401
427
  const parsed = staffLeaveRequestUpdateSchema.parse(rawInput)
402
428
  const em = (ctx.container.resolve('em') as EntityManager).fork()
403
- const request = await requireLeaveRequest(em, parsed.id)
429
+ const scope = commandActorScope(ctx)
430
+ const request = await requireLeaveRequest(em, parsed.id, scope)
404
431
  ensureTenantScope(ctx, request.tenantId)
405
432
  ensureOrganizationScope(ctx, request.organizationId)
406
433
  ensurePendingStatus(request)
407
434
 
408
435
  if (parsed.memberId !== undefined) {
409
- const member = await requireTeamMember(em, parsed.memberId, 'Team member not found')
436
+ const member = await requireTeamMember(em, parsed.memberId, scope, 'Team member not found')
410
437
  ensureTenantScope(ctx, member.tenantId)
411
438
  ensureOrganizationScope(ctx, member.organizationId)
412
439
  request.member = member
@@ -514,7 +541,7 @@ const deleteLeaveRequestCommand: CommandHandler<{ id: string }, { requestId: str
514
541
  async execute(rawInput, ctx) {
515
542
  const parsed = staffLeaveRequestDecisionSchema.pick({ id: true }).parse(rawInput)
516
543
  const em = (ctx.container.resolve('em') as EntityManager).fork()
517
- const request = await requireLeaveRequest(em, parsed.id)
544
+ const request = await requireLeaveRequest(em, parsed.id, commandActorScope(ctx))
518
545
  ensureTenantScope(ctx, request.tenantId)
519
546
  ensureOrganizationScope(ctx, request.organizationId)
520
547
  ensurePendingStatus(request)
@@ -595,7 +622,7 @@ const acceptLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
595
622
  async execute(rawInput, ctx) {
596
623
  const parsed = staffLeaveRequestDecisionSchema.parse(rawInput)
597
624
  const em = (ctx.container.resolve('em') as EntityManager).fork()
598
- const request = await requireLeaveRequest(em, parsed.id)
625
+ const request = await requireLeaveRequest(em, parsed.id, commandActorScope(ctx))
599
626
  ensureTenantScope(ctx, request.tenantId)
600
627
  ensureOrganizationScope(ctx, request.organizationId)
601
628
  ensurePendingStatus(request)
@@ -802,7 +829,7 @@ const rejectLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
802
829
  async execute(rawInput, ctx) {
803
830
  const parsed = staffLeaveRequestDecisionSchema.parse(rawInput)
804
831
  const em = (ctx.container.resolve('em') as EntityManager).fork()
805
- const request = await requireLeaveRequest(em, parsed.id)
832
+ const request = await requireLeaveRequest(em, parsed.id, commandActorScope(ctx))
806
833
  ensureTenantScope(ctx, request.tenantId)
807
834
  ensureOrganizationScope(ctx, request.organizationId)
808
835
  ensurePendingStatus(request)
@@ -1,25 +1,88 @@
1
- import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
2
1
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
- import { ensureOrganizationScope } from '@open-mercato/shared/lib/commands/scope'
2
+ import { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
4
3
  import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
5
- import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
6
5
  import { StaffTeamMember } from '../data/entities'
7
6
 
8
- export function ensureTenantScope(ctx: CommandRuntimeContext, tenantId: string): void {
9
- const currentTenant = ctx.auth?.tenantId ?? null
10
- if (currentTenant && currentTenant !== tenantId) {
7
+ export { ensureOrganizationScope, ensureTenantScope, extractUndoPayload }
8
+
9
+ export type StaffCommandScope = {
10
+ tenantId: string | null
11
+ organizationId: string | null
12
+ requireTenant: boolean
13
+ requireOrganization: boolean
14
+ }
15
+
16
+ export function commandActorScope(ctx: CommandRuntimeContext): StaffCommandScope {
17
+ const isPrivilegedActor = ctx.auth?.isSuperAdmin === true || ctx.systemActor === true
18
+ const tenantId = isPrivilegedActor ? null : (ctx.auth?.tenantId ?? ctx.organizationScope?.tenantId ?? null)
19
+ const organizationId = isPrivilegedActor ? null : (ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)
20
+ const organizationUnrestricted =
21
+ isPrivilegedActor || (organizationId === null && tenantId !== null && ctx.organizationScope?.allowedIds === null)
22
+ return {
23
+ tenantId,
24
+ organizationId: organizationUnrestricted ? null : organizationId,
25
+ requireTenant: !isPrivilegedActor,
26
+ requireOrganization: !organizationUnrestricted,
27
+ }
28
+ }
29
+
30
+ export function explicitStaffCommandScope(tenantId: string | null, organizationId: string | null): StaffCommandScope {
31
+ return {
32
+ tenantId,
33
+ organizationId,
34
+ requireTenant: true,
35
+ requireOrganization: true,
36
+ }
37
+ }
38
+
39
+ export function commandInputScope(ctx: CommandRuntimeContext, tenantId: string, organizationId: string): StaffCommandScope {
40
+ if (ctx.auth?.isSuperAdmin === true || ctx.systemActor === true) {
41
+ return explicitStaffCommandScope(tenantId, organizationId)
42
+ }
43
+
44
+ ensureTenantScope(ctx, tenantId)
45
+ ensureOrganizationScope(ctx, organizationId)
46
+
47
+ const actorTenantId = ctx.auth?.tenantId ?? ctx.organizationScope?.tenantId ?? null
48
+ if (!actorTenantId || actorTenantId !== tenantId) {
11
49
  throw new CrudHttpError(403, { error: 'Forbidden' })
12
50
  }
51
+
52
+ if (!ctx.organizationScope) {
53
+ const currentOrganizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
54
+ if (!currentOrganizationId || currentOrganizationId !== organizationId) {
55
+ throw new CrudHttpError(403, { error: 'Forbidden' })
56
+ }
57
+ }
58
+
59
+ return explicitStaffCommandScope(tenantId, organizationId)
60
+ }
61
+
62
+ export function applyScopeToWhere<TEntity extends object>(
63
+ where: FilterQuery<TEntity>,
64
+ scope: StaffCommandScope,
65
+ ): FilterQuery<TEntity> {
66
+ const scoped = { ...(where as Record<string, unknown>) }
67
+ if (scope.requireTenant || scope.tenantId !== null) scoped.tenantId = scope.tenantId
68
+ if (scope.requireOrganization || scope.organizationId !== null) scoped.organizationId = scope.organizationId
69
+ return scoped as FilterQuery<TEntity>
13
70
  }
14
71
 
15
- export { ensureOrganizationScope, extractUndoPayload }
72
+ export function scopeForDecryption(scope: StaffCommandScope): { tenantId: string | null; organizationId: string | null } {
73
+ return { tenantId: scope.tenantId, organizationId: scope.organizationId }
74
+ }
16
75
 
17
76
  export async function requireTeamMember(
18
77
  em: EntityManager,
19
78
  memberId: string,
79
+ scope: StaffCommandScope,
20
80
  message = 'Team member not found',
21
81
  ): Promise<StaffTeamMember> {
22
- const member = await em.findOne(StaffTeamMember, { id: memberId })
82
+ const member = await em.findOne(
83
+ StaffTeamMember,
84
+ applyScopeToWhere<StaffTeamMember>({ id: memberId, deletedAt: null }, scope),
85
+ )
23
86
  if (!member) throw new CrudHttpError(404, { error: message })
24
87
  return member
25
88
  }
@@ -12,7 +12,15 @@ import {
12
12
  type StaffTeamMemberTagAssignmentInput,
13
13
  } from '../data/validators'
14
14
  import { staffTeamMemberCrudEvents } from '../lib/crud'
15
- import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload } from './shared'
15
+ import {
16
+ applyScopeToWhere,
17
+ commandInputScope,
18
+ ensureOrganizationScope,
19
+ ensureTenantScope,
20
+ explicitStaffCommandScope,
21
+ extractUndoPayload,
22
+ scopeForDecryption,
23
+ } from './shared'
16
24
 
17
25
  type TeamMemberTagAssignmentSnapshot = {
18
26
  tag: string
@@ -43,12 +51,13 @@ const assignTeamMemberTagCommand: CommandHandler<StaffTeamMemberTagAssignmentInp
43
51
  ensureTenantScope(ctx, parsed.tenantId)
44
52
  ensureOrganizationScope(ctx, parsed.organizationId)
45
53
  const em = (ctx.container.resolve('em') as EntityManager).fork()
54
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
46
55
  const member = await findOneWithDecryption(
47
56
  em,
48
57
  StaffTeamMember,
49
- { id: parsed.memberId, deletedAt: null },
58
+ applyScopeToWhere<StaffTeamMember>({ id: parsed.memberId, deletedAt: null }, scope),
50
59
  undefined,
51
- { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
60
+ scopeForDecryption(scope),
52
61
  )
53
62
  if (!member) throw new CrudHttpError(404, { error: 'Team member not found.' })
54
63
  ensureTenantScope(ctx, member.tenantId)
@@ -131,12 +140,13 @@ const assignTeamMemberTagCommand: CommandHandler<StaffTeamMemberTagAssignmentInp
131
140
  const before = payload?.before
132
141
  if (!before) return { memberId: logEntry.resourceId ?? '' }
133
142
  const em = (ctx.container.resolve('em') as EntityManager).fork()
143
+ const scope = explicitStaffCommandScope(before.tenantId, before.organizationId)
134
144
  const member = await findOneWithDecryption(
135
145
  em,
136
146
  StaffTeamMember,
137
- { id: before.memberId, deletedAt: null },
147
+ applyScopeToWhere<StaffTeamMember>({ id: before.memberId, deletedAt: null }, scope),
138
148
  undefined,
139
- { tenantId: before.tenantId, organizationId: before.organizationId },
149
+ scopeForDecryption(scope),
140
150
  )
141
151
  if (!member) throw new CrudHttpError(404, { error: 'Team member not found.' })
142
152
  const currentTags = normalizeTagList(Array.isArray(member.tags) ? member.tags : [])
@@ -171,12 +181,13 @@ const unassignTeamMemberTagCommand: CommandHandler<StaffTeamMemberTagAssignmentI
171
181
  ensureTenantScope(ctx, parsed.tenantId)
172
182
  ensureOrganizationScope(ctx, parsed.organizationId)
173
183
  const em = (ctx.container.resolve('em') as EntityManager).fork()
184
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
174
185
  const member = await findOneWithDecryption(
175
186
  em,
176
187
  StaffTeamMember,
177
- { id: parsed.memberId, deletedAt: null },
188
+ applyScopeToWhere<StaffTeamMember>({ id: parsed.memberId, deletedAt: null }, scope),
178
189
  undefined,
179
- { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
190
+ scopeForDecryption(scope),
180
191
  )
181
192
  if (!member) throw new CrudHttpError(404, { error: 'Team member not found.' })
182
193
  ensureTenantScope(ctx, member.tenantId)
@@ -19,7 +19,15 @@ import {
19
19
  type StaffTeamMemberUpdateInput,
20
20
  } from '../data/validators'
21
21
  import { staffTeamMemberCrudEvents } from '../lib/crud'
22
- import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload } from './shared'
22
+ import {
23
+ applyScopeToWhere,
24
+ commandActorScope,
25
+ commandInputScope,
26
+ ensureOrganizationScope,
27
+ ensureTenantScope,
28
+ extractUndoPayload,
29
+ scopeForDecryption,
30
+ } from './shared'
23
31
  import { E } from '#generated/entities.ids.generated'
24
32
 
25
33
  const teamMemberCrudIndexer: CrudIndexerConfig<StaffTeamMember> = {
@@ -165,6 +173,7 @@ const createTeamMemberCommand: CommandHandler<StaffTeamMemberCreateInput, { memb
165
173
  const { parsed, custom } = parseWithCustomFields(staffTeamMemberCreateSchema, rawInput)
166
174
  ensureTenantScope(ctx, parsed.tenantId)
167
175
  ensureOrganizationScope(ctx, parsed.organizationId)
176
+ commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
168
177
 
169
178
  const em = (ctx.container.resolve('em') as EntityManager).fork()
170
179
  const roleIds = normalizeStringList(parsed.roleIds)
@@ -305,12 +314,13 @@ const updateTeamMemberCommand: CommandHandler<StaffTeamMemberUpdateInput, { memb
305
314
  async execute(rawInput, ctx) {
306
315
  const { parsed, custom } = parseWithCustomFields(staffTeamMemberUpdateSchema, rawInput)
307
316
  const em = (ctx.container.resolve('em') as EntityManager).fork()
317
+ const scope = commandActorScope(ctx)
308
318
  const member = await findOneWithDecryption(
309
319
  em,
310
320
  StaffTeamMember,
311
- { id: parsed.id, deletedAt: null },
321
+ applyScopeToWhere<StaffTeamMember>({ id: parsed.id, deletedAt: null }, scope),
312
322
  undefined,
313
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
323
+ scopeForDecryption(scope),
314
324
  )
315
325
  if (!member) throw new CrudHttpError(404, { error: 'Team member not found.' })
316
326
  ensureTenantScope(ctx, member.tenantId)
@@ -476,12 +486,13 @@ const deleteTeamMemberCommand: CommandHandler<{ id?: string }, { memberId: strin
476
486
  const id = input?.id
477
487
  if (!id) throw new CrudHttpError(400, { error: 'Member id is required.' })
478
488
  const em = (ctx.container.resolve('em') as EntityManager).fork()
489
+ const scope = commandActorScope(ctx)
479
490
  const member = await findOneWithDecryption(
480
491
  em,
481
492
  StaffTeamMember,
482
- { id, deletedAt: null },
493
+ applyScopeToWhere<StaffTeamMember>({ id, deletedAt: null }, scope),
483
494
  undefined,
484
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
495
+ scopeForDecryption(scope),
485
496
  )
486
497
  if (!member) throw new CrudHttpError(404, { error: 'Team member not found.' })
487
498
  ensureTenantScope(ctx, member.tenantId)
@@ -17,7 +17,15 @@ import {
17
17
  type StaffTeamRoleUpdateInput,
18
18
  } from '../data/validators'
19
19
  import { staffTeamRoleCrudEvents } from '../lib/crud'
20
- import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload } from './shared'
20
+ import {
21
+ applyScopeToWhere,
22
+ commandActorScope,
23
+ commandInputScope,
24
+ ensureOrganizationScope,
25
+ ensureTenantScope,
26
+ extractUndoPayload,
27
+ scopeForDecryption,
28
+ } from './shared'
21
29
  import { E } from '#generated/entities.ids.generated'
22
30
 
23
31
  const teamRoleCrudIndexer: CrudIndexerConfig<StaffTeamRole> = {
@@ -100,6 +108,7 @@ const createTeamRoleCommand: CommandHandler<StaffTeamRoleCreateInput, { roleId:
100
108
  const { parsed, custom } = parseWithCustomFields(staffTeamRoleCreateSchema, rawInput)
101
109
  ensureTenantScope(ctx, parsed.tenantId)
102
110
  ensureOrganizationScope(ctx, parsed.organizationId)
111
+ commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
103
112
 
104
113
  const em = (ctx.container.resolve('em') as EntityManager).fork()
105
114
  if (parsed.teamId) {
@@ -230,12 +239,13 @@ const updateTeamRoleCommand: CommandHandler<StaffTeamRoleUpdateInput, { roleId:
230
239
  async execute(rawInput, ctx) {
231
240
  const { parsed, custom } = parseWithCustomFields(staffTeamRoleUpdateSchema, rawInput)
232
241
  const em = (ctx.container.resolve('em') as EntityManager).fork()
242
+ const scope = commandActorScope(ctx)
233
243
  const role = await findOneWithDecryption(
234
244
  em,
235
245
  StaffTeamRole,
236
- { id: parsed.id, deletedAt: null },
246
+ applyScopeToWhere<StaffTeamRole>({ id: parsed.id, deletedAt: null }, scope),
237
247
  undefined,
238
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
248
+ scopeForDecryption(scope),
239
249
  )
240
250
  if (!role) throw new CrudHttpError(404, { error: 'Team role not found.' })
241
251
  ensureTenantScope(ctx, role.tenantId)
@@ -378,12 +388,13 @@ const deleteTeamRoleCommand: CommandHandler<{ id?: string }, { roleId: string }>
378
388
  const id = input?.id
379
389
  if (!id) throw new CrudHttpError(400, { error: 'Role id is required.' })
380
390
  const em = (ctx.container.resolve('em') as EntityManager).fork()
391
+ const scope = commandActorScope(ctx)
381
392
  const role = await findOneWithDecryption(
382
393
  em,
383
394
  StaffTeamRole,
384
- { id, deletedAt: null },
395
+ applyScopeToWhere<StaffTeamRole>({ id, deletedAt: null }, scope),
385
396
  undefined,
386
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
397
+ scopeForDecryption(scope),
387
398
  )
388
399
  if (!role) throw new CrudHttpError(404, { error: 'Team role not found.' })
389
400
  ensureTenantScope(ctx, role.tenantId)
@@ -17,7 +17,15 @@ import {
17
17
  type StaffTeamUpdateInput,
18
18
  } from '../data/validators'
19
19
  import { staffTeamCrudEvents } from '../lib/crud'
20
- import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload } from './shared'
20
+ import {
21
+ applyScopeToWhere,
22
+ commandActorScope,
23
+ commandInputScope,
24
+ ensureOrganizationScope,
25
+ ensureTenantScope,
26
+ extractUndoPayload,
27
+ scopeForDecryption,
28
+ } from './shared'
21
29
  import { E } from '#generated/entities.ids.generated'
22
30
 
23
31
  const teamCrudIndexer: CrudIndexerConfig<StaffTeam> = {
@@ -77,6 +85,7 @@ const createTeamCommand: CommandHandler<StaffTeamCreateInput, { teamId: string }
77
85
  const { parsed, custom } = parseWithCustomFields(staffTeamCreateSchema, rawInput)
78
86
  ensureTenantScope(ctx, parsed.tenantId)
79
87
  ensureOrganizationScope(ctx, parsed.organizationId)
88
+ commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
80
89
 
81
90
  const em = (ctx.container.resolve('em') as EntityManager).fork()
82
91
  const now = new Date()
@@ -202,12 +211,13 @@ const updateTeamCommand: CommandHandler<StaffTeamUpdateInput, { teamId: string }
202
211
  async execute(rawInput, ctx) {
203
212
  const { parsed, custom } = parseWithCustomFields(staffTeamUpdateSchema, rawInput)
204
213
  const em = (ctx.container.resolve('em') as EntityManager).fork()
214
+ const scope = commandActorScope(ctx)
205
215
  const team = await findOneWithDecryption(
206
216
  em,
207
217
  StaffTeam,
208
- { id: parsed.id, deletedAt: null },
218
+ applyScopeToWhere<StaffTeam>({ id: parsed.id, deletedAt: null }, scope),
209
219
  undefined,
210
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
220
+ scopeForDecryption(scope),
211
221
  )
212
222
  if (!team) throw new CrudHttpError(404, { error: 'Team not found.' })
213
223
  ensureTenantScope(ctx, team.tenantId)
@@ -339,12 +349,13 @@ const deleteTeamCommand: CommandHandler<{ id?: string }, { teamId: string }> = {
339
349
  const id = input?.id
340
350
  if (!id) throw new CrudHttpError(400, { error: 'Team id is required.' })
341
351
  const em = (ctx.container.resolve('em') as EntityManager).fork()
352
+ const scope = commandActorScope(ctx)
342
353
  const team = await findOneWithDecryption(
343
354
  em,
344
355
  StaffTeam,
345
- { id, deletedAt: null },
356
+ applyScopeToWhere<StaffTeam>({ id, deletedAt: null }, scope),
346
357
  undefined,
347
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
358
+ scopeForDecryption(scope),
348
359
  )
349
360
  if (!team) throw new CrudHttpError(404, { error: 'Team not found.' })
350
361
  ensureTenantScope(ctx, team.tenantId)