@open-mercato/core 0.6.6-develop.6458.1.113a54fb91 → 0.6.6-develop.6465.1.019f0fb26f

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 (53) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/auth.js +148 -82
  3. package/dist/helpers/integration/auth.js.map +2 -2
  4. package/dist/modules/query_index/api/status.js +21 -1
  5. package/dist/modules/query_index/api/status.js.map +2 -2
  6. package/dist/modules/sales/api/document-addresses/route.js +96 -6
  7. package/dist/modules/sales/api/document-addresses/route.js.map +2 -2
  8. package/dist/modules/sales/commands/documentAddresses.js +7 -0
  9. package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
  10. package/dist/modules/staff/commands/activities.js +23 -12
  11. package/dist/modules/staff/commands/activities.js.map +2 -2
  12. package/dist/modules/staff/commands/addresses.js +20 -12
  13. package/dist/modules/staff/commands/addresses.js.map +2 -2
  14. package/dist/modules/staff/commands/comments.js +18 -11
  15. package/dist/modules/staff/commands/comments.js.map +2 -2
  16. package/dist/modules/staff/commands/job-histories.js +23 -12
  17. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  18. package/dist/modules/staff/commands/leave-requests.js +37 -19
  19. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  20. package/dist/modules/staff/commands/shared.js +28 -1
  21. package/dist/modules/staff/commands/shared.js.map +2 -2
  22. package/dist/modules/staff/commands/tag-assignments.js +20 -3
  23. package/dist/modules/staff/commands/tag-assignments.js.map +2 -2
  24. package/dist/modules/staff/commands/team-members.js +21 -11
  25. package/dist/modules/staff/commands/team-members.js.map +2 -2
  26. package/dist/modules/staff/commands/team-roles.js +21 -11
  27. package/dist/modules/staff/commands/team-roles.js.map +2 -2
  28. package/dist/modules/staff/commands/teams.js +21 -11
  29. package/dist/modules/staff/commands/teams.js.map +2 -2
  30. package/dist/modules/staff/commands/timesheets-entries.js +24 -14
  31. package/dist/modules/staff/commands/timesheets-entries.js.map +2 -2
  32. package/dist/modules/staff/commands/timesheets-projects.js +34 -18
  33. package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
  34. package/dist/modules/workflows/api/instances/route.js +1 -1
  35. package/dist/modules/workflows/api/instances/route.js.map +2 -2
  36. package/package.json +7 -7
  37. package/src/helpers/integration/auth.ts +178 -86
  38. package/src/modules/query_index/api/status.ts +32 -4
  39. package/src/modules/sales/api/document-addresses/route.ts +140 -7
  40. package/src/modules/sales/commands/documentAddresses.ts +8 -0
  41. package/src/modules/staff/commands/activities.ts +23 -11
  42. package/src/modules/staff/commands/addresses.ts +20 -11
  43. package/src/modules/staff/commands/comments.ts +18 -10
  44. package/src/modules/staff/commands/job-histories.ts +27 -11
  45. package/src/modules/staff/commands/leave-requests.ts +38 -19
  46. package/src/modules/staff/commands/shared.ts +38 -0
  47. package/src/modules/staff/commands/tag-assignments.ts +19 -2
  48. package/src/modules/staff/commands/team-members.ts +21 -10
  49. package/src/modules/staff/commands/team-roles.ts +21 -10
  50. package/src/modules/staff/commands/teams.ts +21 -10
  51. package/src/modules/staff/commands/timesheets-entries.ts +24 -13
  52. package/src/modules/staff/commands/timesheets-projects.ts +34 -17
  53. package/src/modules/workflows/api/instances/route.ts +2 -2
@@ -31,6 +31,10 @@ import {
31
31
  explicitStaffCommandScope,
32
32
  extractUndoPayload,
33
33
  requireTeamMember,
34
+ scopedStaffSnapshotWhere,
35
+ staffSnapshotScopeFromContext,
36
+ staffSnapshotScopeFromSnapshot,
37
+ type StaffSnapshotScope,
34
38
  } from './shared'
35
39
  import { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'
36
40
  import { E } from '#generated/entities.ids.generated'
@@ -71,8 +75,8 @@ type ActivityChangeMap = Record<string, { from: unknown; to: unknown }> & {
71
75
  custom?: CustomFieldChangeSet
72
76
  }
73
77
 
74
- async function loadActivitySnapshot(em: EntityManager, id: string): Promise<ActivitySnapshot | null> {
75
- const activity = await em.findOne(StaffTeamMemberActivity, { id })
78
+ async function loadActivitySnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<ActivitySnapshot | null> {
79
+ const activity = await em.findOne(StaffTeamMemberActivity, scopedStaffSnapshotWhere(id, scope))
76
80
  if (!activity) return null
77
81
  const custom = await loadCustomFieldSnapshot(em, {
78
82
  entityId: E.staff.staff_team_member_activity,
@@ -177,7 +181,7 @@ const createActivityCommand: CommandHandler<
177
181
  },
178
182
  captureAfter: async (_input, result, ctx) => {
179
183
  const em = (ctx.container.resolve('em') as EntityManager).fork()
180
- return await loadActivitySnapshot(em, result.activityId)
184
+ return await loadActivitySnapshot(em, result.activityId, staffSnapshotScopeFromContext(ctx))
181
185
  },
182
186
  buildLog: async ({ result, snapshots }) => {
183
187
  const { translate } = await resolveTranslations()
@@ -199,10 +203,15 @@ const createActivityCommand: CommandHandler<
199
203
  }
200
204
  },
201
205
  undo: async ({ logEntry, ctx }) => {
202
- const activityId = logEntry?.resourceId ?? null
206
+ const payload = extractUndoPayload<ActivityUndoPayload>(logEntry)
207
+ const after = payload?.after
208
+ const activityId = after?.activity.id ?? logEntry?.resourceId ?? null
203
209
  if (!activityId) return
204
210
  const em = (ctx.container.resolve('em') as EntityManager).fork()
205
- const existing = await em.findOne(StaffTeamMemberActivity, { id: activityId })
211
+ const existing = await em.findOne(
212
+ StaffTeamMemberActivity,
213
+ scopedStaffSnapshotWhere(activityId, staffSnapshotScopeFromSnapshot(after?.activity)),
214
+ )
206
215
  if (existing) {
207
216
  em.remove(existing)
208
217
  await em.flush()
@@ -214,13 +223,14 @@ const createActivityCommand: CommandHandler<
214
223
  throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for activity create' })
215
224
  }
216
225
  const em = (ctx.container.resolve('em') as EntityManager).fork()
226
+ const snapshotScope = staffSnapshotScopeFromSnapshot(after.activity)
217
227
  const member = await requireTeamMember(
218
228
  em,
219
229
  after.activity.memberId,
220
230
  explicitStaffCommandScope(after.activity.tenantId, after.activity.organizationId),
221
231
  'Team member not found',
222
232
  )
223
- let activity = await em.findOne(StaffTeamMemberActivity, { id: after.activity.id })
233
+ let activity = await em.findOne(StaffTeamMemberActivity, scopedStaffSnapshotWhere(after.activity.id, snapshotScope))
224
234
  if (!activity) {
225
235
  activity = em.create(StaffTeamMemberActivity, {
226
236
  id: after.activity.id,
@@ -275,7 +285,7 @@ const updateActivityCommand: CommandHandler<StaffTeamMemberActivityUpdateInput,
275
285
  async prepare(rawInput, ctx) {
276
286
  const parsed = staffTeamMemberActivityUpdateSchema.parse(rawInput)
277
287
  const em = (ctx.container.resolve('em') as EntityManager)
278
- const snapshot = await loadActivitySnapshot(em, parsed.id)
288
+ const snapshot = await loadActivitySnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
279
289
  return snapshot ? { before: snapshot } : {}
280
290
  },
281
291
  async execute(rawInput, ctx) {
@@ -326,7 +336,7 @@ const updateActivityCommand: CommandHandler<StaffTeamMemberActivityUpdateInput,
326
336
  },
327
337
  captureAfter: async (_input, result, ctx) => {
328
338
  const em = (ctx.container.resolve('em') as EntityManager).fork()
329
- return await loadActivitySnapshot(em, result.activityId)
339
+ return await loadActivitySnapshot(em, result.activityId, staffSnapshotScopeFromContext(ctx))
330
340
  },
331
341
  buildLog: async ({ snapshots }) => {
332
342
  const { translate } = await resolveTranslations()
@@ -377,7 +387,8 @@ const updateActivityCommand: CommandHandler<StaffTeamMemberActivityUpdateInput,
377
387
  const before = payload?.before
378
388
  if (!before) return
379
389
  const em = (ctx.container.resolve('em') as EntityManager).fork()
380
- let activity = await em.findOne(StaffTeamMemberActivity, { id: before.activity.id })
390
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before.activity)
391
+ let activity = await em.findOne(StaffTeamMemberActivity, scopedStaffSnapshotWhere(before.activity.id, snapshotScope))
381
392
  const member = await requireTeamMember(
382
393
  em,
383
394
  before.activity.memberId,
@@ -449,7 +460,7 @@ const deleteActivityCommand: CommandHandler<{ body?: Record<string, unknown>; qu
449
460
  async prepare(input, ctx) {
450
461
  const id = requireId(input, 'Activity id required')
451
462
  const em = (ctx.container.resolve('em') as EntityManager)
452
- const snapshot = await loadActivitySnapshot(em, id)
463
+ const snapshot = await loadActivitySnapshot(em, id, staffSnapshotScopeFromContext(ctx))
453
464
  return snapshot ? { before: snapshot } : {}
454
465
  },
455
466
  async execute(input, ctx) {
@@ -506,13 +517,14 @@ const deleteActivityCommand: CommandHandler<{ body?: Record<string, unknown>; qu
506
517
  const before = payload?.before
507
518
  if (!before) return
508
519
  const em = (ctx.container.resolve('em') as EntityManager).fork()
520
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before.activity)
509
521
  const member = await requireTeamMember(
510
522
  em,
511
523
  before.activity.memberId,
512
524
  explicitStaffCommandScope(before.activity.tenantId, before.activity.organizationId),
513
525
  'Team member not found',
514
526
  )
515
- let activity = await em.findOne(StaffTeamMemberActivity, { id: before.activity.id })
527
+ let activity = await em.findOne(StaffTeamMemberActivity, scopedStaffSnapshotWhere(before.activity.id, snapshotScope))
516
528
  if (!activity) {
517
529
  activity = em.create(StaffTeamMemberActivity, {
518
530
  id: before.activity.id,
@@ -20,6 +20,10 @@ import {
20
20
  explicitStaffCommandScope,
21
21
  extractUndoPayload,
22
22
  requireTeamMember,
23
+ scopedStaffSnapshotWhere,
24
+ staffSnapshotScopeFromContext,
25
+ staffSnapshotScopeFromSnapshot,
26
+ type StaffSnapshotScope,
23
27
  } from './shared'
24
28
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
25
29
  import { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'
@@ -57,8 +61,8 @@ type AddressUndoPayload = {
57
61
  after?: AddressSnapshot | null
58
62
  }
59
63
 
60
- async function loadAddressSnapshot(em: EntityManager, id: string): Promise<AddressSnapshot | null> {
61
- const address = await em.findOne(StaffTeamMemberAddress, { id })
64
+ async function loadAddressSnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<AddressSnapshot | null> {
65
+ const address = await em.findOne(StaffTeamMemberAddress, scopedStaffSnapshotWhere(id, scope))
62
66
  if (!address) return null
63
67
  return {
64
68
  id: address.id,
@@ -155,7 +159,7 @@ const createAddressCommand: CommandHandler<StaffTeamMemberAddressCreateInput, {
155
159
  },
156
160
  captureAfter: async (_input, result, ctx) => {
157
161
  const em = (ctx.container.resolve('em') as EntityManager).fork()
158
- return await loadAddressSnapshot(em, result.addressId)
162
+ return await loadAddressSnapshot(em, result.addressId, staffSnapshotScopeFromContext(ctx))
159
163
  },
160
164
  buildLog: async ({ result, snapshots }) => {
161
165
  const { translate } = await resolveTranslations()
@@ -177,10 +181,12 @@ const createAddressCommand: CommandHandler<StaffTeamMemberAddressCreateInput, {
177
181
  }
178
182
  },
179
183
  undo: async ({ logEntry, ctx }) => {
180
- const addressId = logEntry?.resourceId ?? null
184
+ const payload = extractUndoPayload<AddressUndoPayload>(logEntry)
185
+ const after = payload?.after
186
+ const addressId = after?.id ?? logEntry?.resourceId ?? null
181
187
  if (!addressId) return
182
188
  const em = (ctx.container.resolve('em') as EntityManager).fork()
183
- const address = await em.findOne(StaffTeamMemberAddress, { id: addressId })
189
+ const address = await em.findOne(StaffTeamMemberAddress, scopedStaffSnapshotWhere(addressId, staffSnapshotScopeFromSnapshot(after)))
184
190
  if (address) {
185
191
  em.remove(address)
186
192
  await em.flush()
@@ -192,13 +198,14 @@ const createAddressCommand: CommandHandler<StaffTeamMemberAddressCreateInput, {
192
198
  throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for address create' })
193
199
  }
194
200
  const em = (ctx.container.resolve('em') as EntityManager).fork()
201
+ const snapshotScope = staffSnapshotScopeFromSnapshot(after)
195
202
  const member = await requireTeamMember(
196
203
  em,
197
204
  after.memberId,
198
205
  explicitStaffCommandScope(after.tenantId, after.organizationId),
199
206
  'Team member not found',
200
207
  )
201
- let address = await em.findOne(StaffTeamMemberAddress, { id: after.id })
208
+ let address = await em.findOne(StaffTeamMemberAddress, scopedStaffSnapshotWhere(after.id, snapshotScope))
202
209
  if (!address) {
203
210
  address = em.create(StaffTeamMemberAddress, {
204
211
  id: after.id,
@@ -269,7 +276,7 @@ const updateAddressCommand: CommandHandler<StaffTeamMemberAddressUpdateInput, {
269
276
  async prepare(rawInput, ctx) {
270
277
  const parsed = staffTeamMemberAddressUpdateSchema.parse(rawInput)
271
278
  const em = (ctx.container.resolve('em') as EntityManager)
272
- const snapshot = await loadAddressSnapshot(em, parsed.id)
279
+ const snapshot = await loadAddressSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
273
280
  return snapshot ? { before: snapshot } : {}
274
281
  },
275
282
  async execute(rawInput, ctx) {
@@ -330,7 +337,7 @@ const updateAddressCommand: CommandHandler<StaffTeamMemberAddressUpdateInput, {
330
337
  },
331
338
  captureAfter: async (_input, result, ctx) => {
332
339
  const em = (ctx.container.resolve('em') as EntityManager).fork()
333
- return await loadAddressSnapshot(em, result.addressId)
340
+ return await loadAddressSnapshot(em, result.addressId, staffSnapshotScopeFromContext(ctx))
334
341
  },
335
342
  buildLog: async ({ snapshots }) => {
336
343
  const { translate } = await resolveTranslations()
@@ -385,7 +392,8 @@ const updateAddressCommand: CommandHandler<StaffTeamMemberAddressUpdateInput, {
385
392
  const before = payload?.before
386
393
  if (!before) return
387
394
  const em = (ctx.container.resolve('em') as EntityManager).fork()
388
- let address = await em.findOne(StaffTeamMemberAddress, { id: before.id })
395
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
396
+ let address = await em.findOne(StaffTeamMemberAddress, scopedStaffSnapshotWhere(before.id, snapshotScope))
389
397
  const member = await requireTeamMember(
390
398
  em,
391
399
  before.memberId,
@@ -462,7 +470,7 @@ const deleteAddressCommand: CommandHandler<{ body?: Record<string, unknown>; que
462
470
  async prepare(input, ctx) {
463
471
  const id = requireId(input, 'Address id required')
464
472
  const em = (ctx.container.resolve('em') as EntityManager)
465
- const snapshot = await loadAddressSnapshot(em, id)
473
+ const snapshot = await loadAddressSnapshot(em, id, staffSnapshotScopeFromContext(ctx))
466
474
  return snapshot ? { before: snapshot } : {}
467
475
  },
468
476
  async execute(input, ctx) {
@@ -519,13 +527,14 @@ const deleteAddressCommand: CommandHandler<{ body?: Record<string, unknown>; que
519
527
  const before = payload?.before
520
528
  if (!before) return
521
529
  const em = (ctx.container.resolve('em') as EntityManager).fork()
530
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
522
531
  const member = await requireTeamMember(
523
532
  em,
524
533
  before.memberId,
525
534
  explicitStaffCommandScope(before.tenantId, before.organizationId),
526
535
  'Team member not found',
527
536
  )
528
- let address = await em.findOne(StaffTeamMemberAddress, { id: before.id })
537
+ let address = await em.findOne(StaffTeamMemberAddress, scopedStaffSnapshotWhere(before.id, snapshotScope))
529
538
  if (!address) {
530
539
  address = em.create(StaffTeamMemberAddress, {
531
540
  id: before.id,
@@ -23,6 +23,10 @@ import {
23
23
  explicitStaffCommandScope,
24
24
  extractUndoPayload,
25
25
  requireTeamMember,
26
+ scopedStaffSnapshotWhere,
27
+ staffSnapshotScopeFromContext,
28
+ staffSnapshotScopeFromSnapshot,
29
+ type StaffSnapshotScope,
26
30
  } from './shared'
27
31
  import { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'
28
32
  import { E } from '#generated/entities.ids.generated'
@@ -47,8 +51,8 @@ type CommentUndoPayload = {
47
51
  after?: CommentSnapshot | null
48
52
  }
49
53
 
50
- async function loadCommentSnapshot(em: EntityManager, id: string): Promise<CommentSnapshot | null> {
51
- const comment = await em.findOne(StaffTeamMemberComment, { id })
54
+ async function loadCommentSnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<CommentSnapshot | null> {
55
+ const comment = await em.findOne(StaffTeamMemberComment, scopedStaffSnapshotWhere(id, scope))
52
56
  if (!comment) return null
53
57
  return {
54
58
  id: comment.id,
@@ -116,7 +120,7 @@ const createCommentCommand: CommandHandler<
116
120
  },
117
121
  captureAfter: async (_input, result, ctx) => {
118
122
  const em = (ctx.container.resolve('em') as EntityManager).fork()
119
- return await loadCommentSnapshot(em, result.commentId)
123
+ return await loadCommentSnapshot(em, result.commentId, staffSnapshotScopeFromContext(ctx))
120
124
  },
121
125
  buildLog: async ({ result, snapshots }) => {
122
126
  const { translate } = await resolveTranslations()
@@ -138,10 +142,12 @@ const createCommentCommand: CommandHandler<
138
142
  }
139
143
  },
140
144
  undo: async ({ logEntry, ctx }) => {
141
- const commentId = logEntry?.resourceId ?? null
145
+ const payload = extractUndoPayload<CommentUndoPayload>(logEntry)
146
+ const after = payload?.after
147
+ const commentId = after?.id ?? logEntry?.resourceId ?? null
142
148
  if (!commentId) return
143
149
  const em = (ctx.container.resolve('em') as EntityManager).fork()
144
- const existing = await em.findOne(StaffTeamMemberComment, { id: commentId })
150
+ const existing = await em.findOne(StaffTeamMemberComment, scopedStaffSnapshotWhere(commentId, staffSnapshotScopeFromSnapshot(after)))
145
151
  if (existing) {
146
152
  em.remove(existing)
147
153
  await em.flush()
@@ -183,7 +189,7 @@ const updateCommentCommand: CommandHandler<StaffTeamMemberCommentUpdateInput, {
183
189
  async prepare(rawInput, ctx) {
184
190
  const parsed = staffTeamMemberCommentUpdateSchema.parse(rawInput)
185
191
  const em = (ctx.container.resolve('em') as EntityManager)
186
- const snapshot = await loadCommentSnapshot(em, parsed.id)
192
+ const snapshot = await loadCommentSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
187
193
  return snapshot ? { before: snapshot } : {}
188
194
  },
189
195
  async execute(rawInput, ctx) {
@@ -229,7 +235,7 @@ const updateCommentCommand: CommandHandler<StaffTeamMemberCommentUpdateInput, {
229
235
  },
230
236
  captureAfter: async (_input, result, ctx) => {
231
237
  const em = (ctx.container.resolve('em') as EntityManager).fork()
232
- return await loadCommentSnapshot(em, result.commentId)
238
+ return await loadCommentSnapshot(em, result.commentId, staffSnapshotScopeFromContext(ctx))
233
239
  },
234
240
  buildLog: async ({ snapshots }) => {
235
241
  const { translate } = await resolveTranslations()
@@ -268,7 +274,8 @@ const updateCommentCommand: CommandHandler<StaffTeamMemberCommentUpdateInput, {
268
274
  const before = payload?.before
269
275
  if (!before) return
270
276
  const em = (ctx.container.resolve('em') as EntityManager).fork()
271
- let comment = await em.findOne(StaffTeamMemberComment, { id: before.id })
277
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
278
+ let comment = await em.findOne(StaffTeamMemberComment, scopedStaffSnapshotWhere(before.id, snapshotScope))
272
279
  const member = await requireTeamMember(
273
280
  em,
274
281
  before.memberId,
@@ -321,7 +328,7 @@ const deleteCommentCommand: CommandHandler<{ body?: Record<string, unknown>; que
321
328
  async prepare(input, ctx) {
322
329
  const id = requireId(input, 'Comment id required')
323
330
  const em = (ctx.container.resolve('em') as EntityManager)
324
- const snapshot = await loadCommentSnapshot(em, id)
331
+ const snapshot = await loadCommentSnapshot(em, id, staffSnapshotScopeFromContext(ctx))
325
332
  return snapshot ? { before: snapshot } : {}
326
333
  },
327
334
  async execute(input, ctx) {
@@ -378,13 +385,14 @@ const deleteCommentCommand: CommandHandler<{ body?: Record<string, unknown>; que
378
385
  const before = payload?.before
379
386
  if (!before) return
380
387
  const em = (ctx.container.resolve('em') as EntityManager).fork()
388
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
381
389
  const member = await requireTeamMember(
382
390
  em,
383
391
  before.memberId,
384
392
  explicitStaffCommandScope(before.tenantId, before.organizationId),
385
393
  'Team member not found',
386
394
  )
387
- let comment = await em.findOne(StaffTeamMemberComment, { id: before.id })
395
+ let comment = await em.findOne(StaffTeamMemberComment, scopedStaffSnapshotWhere(before.id, snapshotScope))
388
396
  if (!comment) {
389
397
  comment = em.create(StaffTeamMemberComment, {
390
398
  id: before.id,
@@ -20,6 +20,10 @@ import {
20
20
  explicitStaffCommandScope,
21
21
  extractUndoPayload,
22
22
  requireTeamMember,
23
+ scopedStaffSnapshotWhere,
24
+ staffSnapshotScopeFromContext,
25
+ staffSnapshotScopeFromSnapshot,
26
+ type StaffSnapshotScope,
23
27
  } from './shared'
24
28
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
25
29
  import { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'
@@ -55,8 +59,12 @@ type JobHistoryUndoPayload = {
55
59
  after?: JobHistorySnapshot | null
56
60
  }
57
61
 
58
- async function loadJobHistorySnapshot(em: EntityManager, id: string): Promise<JobHistorySnapshot | null> {
59
- const record = await em.findOne(StaffTeamMemberJobHistory, { id })
62
+ async function loadJobHistorySnapshot(
63
+ em: EntityManager,
64
+ id: string,
65
+ scope?: StaffSnapshotScope | null,
66
+ ): Promise<JobHistorySnapshot | null> {
67
+ const record = await em.findOne(StaffTeamMemberJobHistory, scopedStaffSnapshotWhere(id, scope))
60
68
  if (!record) return null
61
69
  return {
62
70
  id: record.id,
@@ -123,7 +131,7 @@ const createJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryCreateInp
123
131
  },
124
132
  captureAfter: async (_input, result, ctx) => {
125
133
  const em = (ctx.container.resolve('em') as EntityManager).fork()
126
- return await loadJobHistorySnapshot(em, result.jobHistoryId)
134
+ return await loadJobHistorySnapshot(em, result.jobHistoryId, staffSnapshotScopeFromContext(ctx))
127
135
  },
128
136
  buildLog: async ({ result, snapshots }) => {
129
137
  const { translate } = await resolveTranslations()
@@ -145,10 +153,15 @@ const createJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryCreateInp
145
153
  }
146
154
  },
147
155
  undo: async ({ logEntry, ctx }) => {
148
- const jobHistoryId = logEntry?.resourceId ?? null
156
+ const payload = extractUndoPayload<JobHistoryUndoPayload>(logEntry)
157
+ const after = payload?.after ?? null
158
+ const jobHistoryId = after?.id ?? logEntry?.resourceId ?? null
149
159
  if (!jobHistoryId) return
150
160
  const em = (ctx.container.resolve('em') as EntityManager).fork()
151
- const record = await em.findOne(StaffTeamMemberJobHistory, { id: jobHistoryId })
161
+ const record = await em.findOne(
162
+ StaffTeamMemberJobHistory,
163
+ scopedStaffSnapshotWhere(jobHistoryId, staffSnapshotScopeFromSnapshot(after)),
164
+ )
152
165
  if (record) {
153
166
  em.remove(record)
154
167
  await em.flush()
@@ -160,13 +173,14 @@ const createJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryCreateInp
160
173
  throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for job history create' })
161
174
  }
162
175
  const em = (ctx.container.resolve('em') as EntityManager).fork()
176
+ const snapshotScope = staffSnapshotScopeFromSnapshot(after)
163
177
  const member = await requireTeamMember(
164
178
  em,
165
179
  after.memberId,
166
180
  explicitStaffCommandScope(after.tenantId, after.organizationId),
167
181
  'Team member not found',
168
182
  )
169
- let record = await em.findOne(StaffTeamMemberJobHistory, { id: after.id })
183
+ let record = await em.findOne(StaffTeamMemberJobHistory, scopedStaffSnapshotWhere(after.id, snapshotScope))
170
184
  if (!record) {
171
185
  record = em.create(StaffTeamMemberJobHistory, {
172
186
  id: after.id,
@@ -215,7 +229,7 @@ const updateJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryUpdateInp
215
229
  async prepare(rawInput, ctx) {
216
230
  const parsed = staffTeamMemberJobHistoryUpdateSchema.parse(rawInput)
217
231
  const em = (ctx.container.resolve('em') as EntityManager)
218
- const snapshot = await loadJobHistorySnapshot(em, parsed.id)
232
+ const snapshot = await loadJobHistorySnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
219
233
  return snapshot ? { before: snapshot } : {}
220
234
  },
221
235
  async execute(rawInput, ctx) {
@@ -277,7 +291,7 @@ const updateJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryUpdateInp
277
291
  },
278
292
  captureAfter: async (_input, result, ctx) => {
279
293
  const em = (ctx.container.resolve('em') as EntityManager).fork()
280
- return await loadJobHistorySnapshot(em, result.jobHistoryId)
294
+ return await loadJobHistorySnapshot(em, result.jobHistoryId, staffSnapshotScopeFromContext(ctx))
281
295
  },
282
296
  buildLog: async ({ snapshots }) => {
283
297
  const { translate } = await resolveTranslations()
@@ -316,7 +330,8 @@ const updateJobHistoryCommand: CommandHandler<StaffTeamMemberJobHistoryUpdateInp
316
330
  const before = payload?.before
317
331
  if (!before) return
318
332
  const em = (ctx.container.resolve('em') as EntityManager).fork()
319
- let record = await em.findOne(StaffTeamMemberJobHistory, { id: before.id })
333
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
334
+ let record = await em.findOne(StaffTeamMemberJobHistory, scopedStaffSnapshotWhere(before.id, snapshotScope))
320
335
  const member = await requireTeamMember(
321
336
  em,
322
337
  before.memberId,
@@ -371,7 +386,7 @@ const deleteJobHistoryCommand: CommandHandler<{ id?: string; updatedAt?: string;
371
386
  async prepare(input, ctx) {
372
387
  const id = requireId(input, 'Job history id required')
373
388
  const em = (ctx.container.resolve('em') as EntityManager)
374
- const snapshot = await loadJobHistorySnapshot(em, id)
389
+ const snapshot = await loadJobHistorySnapshot(em, id, staffSnapshotScopeFromContext(ctx))
375
390
  return snapshot ? { before: snapshot } : {}
376
391
  },
377
392
  async execute(input, ctx) {
@@ -444,13 +459,14 @@ const deleteJobHistoryCommand: CommandHandler<{ id?: string; updatedAt?: string;
444
459
  const before = payload?.before
445
460
  if (!before) return
446
461
  const em = (ctx.container.resolve('em') as EntityManager).fork()
462
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
447
463
  const member = await requireTeamMember(
448
464
  em,
449
465
  before.memberId,
450
466
  explicitStaffCommandScope(before.tenantId, before.organizationId),
451
467
  'Team member not found',
452
468
  )
453
- let record = await em.findOne(StaffTeamMemberJobHistory, { id: before.id })
469
+ let record = await em.findOne(StaffTeamMemberJobHistory, scopedStaffSnapshotWhere(before.id, snapshotScope))
454
470
  if (!record) {
455
471
  record = em.create(StaffTeamMemberJobHistory, {
456
472
  id: before.id,
@@ -22,6 +22,7 @@ import {
22
22
  type StaffLeaveRequestUpdateInput,
23
23
  } from '../data/validators'
24
24
  import {
25
+ applyScopeToWhere,
25
26
  commandActorScope,
26
27
  commandInputScope,
27
28
  ensureOrganizationScope,
@@ -30,7 +31,11 @@ import {
30
31
  extractUndoPayload,
31
32
  requireTeamMember,
32
33
  scopeForDecryption,
33
- applyScopeToWhere,
34
+ scopedStaffSnapshotWhere,
35
+ staffSnapshotDecryptionScope,
36
+ staffSnapshotScopeFromContext,
37
+ staffSnapshotScopeFromSnapshot,
38
+ type StaffSnapshotScope,
34
39
  } from './shared'
35
40
  import { E } from '#generated/entities.ids.generated'
36
41
  import { resolveNotificationService } from '../../notifications/lib/notificationService'
@@ -128,8 +133,14 @@ function buildFullDayRrule(date: string): string | null {
128
133
  return buildAvailabilityRrule(start, end)
129
134
  }
130
135
 
131
- async function loadLeaveRequestSnapshot(em: EntityManager, id: string): Promise<LeaveRequestSnapshot | null> {
132
- const request = await findOneWithDecryption(em, StaffLeaveRequest, { id }, undefined, { tenantId: null, organizationId: null })
136
+ async function loadLeaveRequestSnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<LeaveRequestSnapshot | null> {
137
+ const request = await findOneWithDecryption(
138
+ em,
139
+ StaffLeaveRequest,
140
+ scopedStaffSnapshotWhere(id, scope),
141
+ undefined,
142
+ staffSnapshotDecryptionScope(scope),
143
+ )
133
144
  if (!request) return null
134
145
  const memberId = typeof request.member === 'string' ? request.member : request.member.id
135
146
  return {
@@ -349,12 +360,12 @@ const createLeaveRequestCommand: CommandHandler<StaffLeaveRequestCreateInput, {
349
360
  },
350
361
  captureAfter: async (_input, result, ctx) => {
351
362
  const em = (ctx.container.resolve('em') as EntityManager).fork()
352
- return await loadLeaveRequestSnapshot(em, result.requestId)
363
+ return await loadLeaveRequestSnapshot(em, result.requestId, staffSnapshotScopeFromContext(ctx))
353
364
  },
354
365
  buildLog: async ({ result, ctx }) => {
355
366
  const { translate } = await resolveTranslations()
356
367
  const em = (ctx.container.resolve('em') as EntityManager).fork()
357
- const snapshot = await loadLeaveRequestSnapshot(em, result.requestId)
368
+ const snapshot = await loadLeaveRequestSnapshot(em, result.requestId, staffSnapshotScopeFromContext(ctx))
358
369
  return {
359
370
  actionLabel: translate('staff.audit.leaveRequests.create', 'Create leave request'),
360
371
  resourceKind: 'staff.leave_request',
@@ -376,7 +387,7 @@ const createLeaveRequestCommand: CommandHandler<StaffLeaveRequestCreateInput, {
376
387
  const after = payload?.after
377
388
  if (!after) return
378
389
  const em = (ctx.container.resolve('em') as EntityManager).fork()
379
- const request = await em.findOne(StaffLeaveRequest, { id: after.id })
390
+ const request = await em.findOne(StaffLeaveRequest, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
380
391
  if (request) {
381
392
  request.deletedAt = new Date()
382
393
  request.updatedAt = new Date()
@@ -405,7 +416,7 @@ const createLeaveRequestCommand: CommandHandler<StaffLeaveRequestCreateInput, {
405
416
  findOneWithDecryption(
406
417
  em,
407
418
  StaffLeaveRequest,
408
- { id },
419
+ scopedStaffSnapshotWhere(id, staffSnapshotScopeFromSnapshot(snapshot)),
409
420
  undefined,
410
421
  { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId },
411
422
  ),
@@ -420,7 +431,7 @@ const updateLeaveRequestCommand: CommandHandler<StaffLeaveRequestUpdateInput, {
420
431
  async prepare(rawInput, ctx) {
421
432
  const parsed = staffLeaveRequestUpdateSchema.parse(rawInput)
422
433
  const em = (ctx.container.resolve('em') as EntityManager)
423
- const snapshot = await loadLeaveRequestSnapshot(em, parsed.id)
434
+ const snapshot = await loadLeaveRequestSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
424
435
  return snapshot ? { before: snapshot } : {}
425
436
  },
426
437
  async execute(rawInput, ctx) {
@@ -468,7 +479,7 @@ const updateLeaveRequestCommand: CommandHandler<StaffLeaveRequestUpdateInput, {
468
479
  const before = snapshots.before as LeaveRequestSnapshot | undefined
469
480
  if (!before) return null
470
481
  const em = (ctx.container.resolve('em') as EntityManager).fork()
471
- const after = await loadLeaveRequestSnapshot(em, before.id)
482
+ const after = await loadLeaveRequestSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
472
483
  const changes = after
473
484
  ? buildChanges(before as unknown as Record<string, unknown>, after as unknown as Record<string, unknown>, [
474
485
  'memberId',
@@ -505,7 +516,7 @@ const updateLeaveRequestCommand: CommandHandler<StaffLeaveRequestUpdateInput, {
505
516
  const after = payload?.after
506
517
  if (!before || !after) return
507
518
  const em = (ctx.container.resolve('em') as EntityManager).fork()
508
- const request = await em.findOne(StaffLeaveRequest, { id: after.id })
519
+ const request = await em.findOne(StaffLeaveRequest, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
509
520
  if (!request) return
510
521
  request.startDate = new Date(before.startDate)
511
522
  request.endDate = new Date(before.endDate)
@@ -568,12 +579,12 @@ const deleteLeaveRequestCommand: CommandHandler<{ id: string }, { requestId: str
568
579
  },
569
580
  captureAfter: async (_input, result, ctx) => {
570
581
  const em = (ctx.container.resolve('em') as EntityManager).fork()
571
- return await loadLeaveRequestSnapshot(em, result.requestId)
582
+ return await loadLeaveRequestSnapshot(em, result.requestId, staffSnapshotScopeFromContext(ctx))
572
583
  },
573
584
  buildLog: async ({ result, ctx }) => {
574
585
  const { translate } = await resolveTranslations()
575
586
  const em = (ctx.container.resolve('em') as EntityManager).fork()
576
- const snapshot = await loadLeaveRequestSnapshot(em, result.requestId)
587
+ const snapshot = await loadLeaveRequestSnapshot(em, result.requestId, staffSnapshotScopeFromContext(ctx))
577
588
  return {
578
589
  actionLabel: translate('staff.audit.leaveRequests.delete', 'Delete leave request'),
579
590
  resourceKind: 'staff.leave_request',
@@ -595,7 +606,7 @@ const deleteLeaveRequestCommand: CommandHandler<{ id: string }, { requestId: str
595
606
  const after = payload?.after
596
607
  if (!after) return
597
608
  const em = (ctx.container.resolve('em') as EntityManager).fork()
598
- const request = await em.findOne(StaffLeaveRequest, { id: after.id })
609
+ const request = await em.findOne(StaffLeaveRequest, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
599
610
  if (!request) return
600
611
  request.deletedAt = null
601
612
  request.updatedAt = new Date()
@@ -728,7 +739,11 @@ const acceptLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
728
739
  const { translate } = await resolveTranslations()
729
740
  const before = snapshots.before as LeaveRequestSnapshot | undefined
730
741
  const em = (ctx.container.resolve('em') as EntityManager).fork()
731
- const after = await loadLeaveRequestSnapshot(em, result.requestId)
742
+ const after = await loadLeaveRequestSnapshot(
743
+ em,
744
+ result.requestId,
745
+ staffSnapshotScopeFromSnapshot(before) ?? staffSnapshotScopeFromContext(ctx),
746
+ )
732
747
  return {
733
748
  actionLabel: translate('staff.audit.leaveRequests.accept', 'Approve leave request'),
734
749
  resourceKind: 'staff.leave_request',
@@ -751,7 +766,7 @@ const acceptLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
751
766
  async prepare(rawInput, ctx) {
752
767
  const parsed = staffLeaveRequestDecisionSchema.parse(rawInput)
753
768
  const em = (ctx.container.resolve('em') as EntityManager)
754
- const snapshot = await loadLeaveRequestSnapshot(em, parsed.id)
769
+ const snapshot = await loadLeaveRequestSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
755
770
  return snapshot ? { before: snapshot } : {}
756
771
  },
757
772
  undo: async ({ logEntry, ctx }) => {
@@ -760,7 +775,7 @@ const acceptLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
760
775
  const availabilityRuleIds = payload?.availabilityRuleIds ?? []
761
776
  if (!before) return
762
777
  const em = (ctx.container.resolve('em') as EntityManager).fork()
763
- const request = await em.findOne(StaffLeaveRequest, { id: before.id })
778
+ const request = await em.findOne(StaffLeaveRequest, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
764
779
  if (!request) return
765
780
 
766
781
  // Fetch rules before mutating so the query cannot reset unit-of-work tracking
@@ -891,14 +906,18 @@ const rejectLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
891
906
  async prepare(rawInput, ctx) {
892
907
  const parsed = staffLeaveRequestDecisionSchema.parse(rawInput)
893
908
  const em = (ctx.container.resolve('em') as EntityManager)
894
- const snapshot = await loadLeaveRequestSnapshot(em, parsed.id)
909
+ const snapshot = await loadLeaveRequestSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
895
910
  return snapshot ? { before: snapshot } : {}
896
911
  },
897
912
  buildLog: async ({ result, ctx, snapshots }) => {
898
913
  const { translate } = await resolveTranslations()
899
914
  const before = snapshots.before as LeaveRequestSnapshot | undefined
900
915
  const em = (ctx.container.resolve('em') as EntityManager).fork()
901
- const after = await loadLeaveRequestSnapshot(em, result.requestId)
916
+ const after = await loadLeaveRequestSnapshot(
917
+ em,
918
+ result.requestId,
919
+ staffSnapshotScopeFromSnapshot(before) ?? staffSnapshotScopeFromContext(ctx),
920
+ )
902
921
  return {
903
922
  actionLabel: translate('staff.audit.leaveRequests.reject', 'Reject leave request'),
904
923
  resourceKind: 'staff.leave_request',
@@ -922,7 +941,7 @@ const rejectLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
922
941
  const before = payload?.before
923
942
  if (!before) return
924
943
  const em = (ctx.container.resolve('em') as EntityManager).fork()
925
- const request = await em.findOne(StaffLeaveRequest, { id: before.id })
944
+ const request = await em.findOne(StaffLeaveRequest, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
926
945
  if (!request) return
927
946
  request.status = before.status
928
947
  request.decisionComment = before.decisionComment