@open-mercato/core 0.6.6-develop.6460.1.f94c74174c → 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 (50) 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/sales/api/document-addresses/route.js +96 -6
  5. package/dist/modules/sales/api/document-addresses/route.js.map +2 -2
  6. package/dist/modules/sales/commands/documentAddresses.js +7 -0
  7. package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
  8. package/dist/modules/staff/commands/activities.js +23 -12
  9. package/dist/modules/staff/commands/activities.js.map +2 -2
  10. package/dist/modules/staff/commands/addresses.js +20 -12
  11. package/dist/modules/staff/commands/addresses.js.map +2 -2
  12. package/dist/modules/staff/commands/comments.js +18 -11
  13. package/dist/modules/staff/commands/comments.js.map +2 -2
  14. package/dist/modules/staff/commands/job-histories.js +23 -12
  15. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  16. package/dist/modules/staff/commands/leave-requests.js +37 -19
  17. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  18. package/dist/modules/staff/commands/shared.js +28 -1
  19. package/dist/modules/staff/commands/shared.js.map +2 -2
  20. package/dist/modules/staff/commands/tag-assignments.js +20 -3
  21. package/dist/modules/staff/commands/tag-assignments.js.map +2 -2
  22. package/dist/modules/staff/commands/team-members.js +21 -11
  23. package/dist/modules/staff/commands/team-members.js.map +2 -2
  24. package/dist/modules/staff/commands/team-roles.js +21 -11
  25. package/dist/modules/staff/commands/team-roles.js.map +2 -2
  26. package/dist/modules/staff/commands/teams.js +21 -11
  27. package/dist/modules/staff/commands/teams.js.map +2 -2
  28. package/dist/modules/staff/commands/timesheets-entries.js +24 -14
  29. package/dist/modules/staff/commands/timesheets-entries.js.map +2 -2
  30. package/dist/modules/staff/commands/timesheets-projects.js +34 -18
  31. package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
  32. package/dist/modules/workflows/api/instances/route.js +1 -1
  33. package/dist/modules/workflows/api/instances/route.js.map +2 -2
  34. package/package.json +7 -7
  35. package/src/helpers/integration/auth.ts +178 -86
  36. package/src/modules/sales/api/document-addresses/route.ts +140 -7
  37. package/src/modules/sales/commands/documentAddresses.ts +8 -0
  38. package/src/modules/staff/commands/activities.ts +23 -11
  39. package/src/modules/staff/commands/addresses.ts +20 -11
  40. package/src/modules/staff/commands/comments.ts +18 -10
  41. package/src/modules/staff/commands/job-histories.ts +27 -11
  42. package/src/modules/staff/commands/leave-requests.ts +38 -19
  43. package/src/modules/staff/commands/shared.ts +38 -0
  44. package/src/modules/staff/commands/tag-assignments.ts +19 -2
  45. package/src/modules/staff/commands/team-members.ts +21 -10
  46. package/src/modules/staff/commands/team-roles.ts +21 -10
  47. package/src/modules/staff/commands/teams.ts +21 -10
  48. package/src/modules/staff/commands/timesheets-entries.ts +24 -13
  49. package/src/modules/staff/commands/timesheets-projects.ts +34 -17
  50. package/src/modules/workflows/api/instances/route.ts +2 -2
@@ -74,6 +74,44 @@ export function scopeForDecryption(scope: StaffCommandScope): { tenantId: string
74
74
  return { tenantId: scope.tenantId, organizationId: scope.organizationId }
75
75
  }
76
76
 
77
+ export type StaffSnapshotScope = {
78
+ tenantId?: string | null
79
+ organizationId?: string | null
80
+ }
81
+
82
+ type StaffSnapshotScopeSource = {
83
+ tenantId?: string | null
84
+ organizationId?: string | null
85
+ }
86
+
87
+ const NULL_DECRYPTION_SCOPE = { tenantId: null, organizationId: null } as const
88
+
89
+ export function staffSnapshotScopeFromContext(ctx: CommandRuntimeContext): StaffSnapshotScope | null {
90
+ const tenantId = ctx.auth?.tenantId ?? null
91
+ if (!tenantId) return null
92
+ return { tenantId }
93
+ }
94
+
95
+ export function staffSnapshotScopeFromSnapshot(source: StaffSnapshotScopeSource | null | undefined): StaffSnapshotScope | null {
96
+ if (!source?.tenantId || !source.organizationId) return null
97
+ return { tenantId: source.tenantId, organizationId: source.organizationId }
98
+ }
99
+
100
+ export function scopedStaffSnapshotWhere(id: string, scope?: StaffSnapshotScope | null) {
101
+ const where: { id: string; tenantId?: string; organizationId?: string } = { id }
102
+ if (scope?.tenantId) where.tenantId = scope.tenantId
103
+ if (scope?.organizationId) where.organizationId = scope.organizationId
104
+ return where
105
+ }
106
+
107
+ export function staffSnapshotDecryptionScope(scope?: StaffSnapshotScope | null) {
108
+ if (!scope) return NULL_DECRYPTION_SCOPE
109
+ return {
110
+ tenantId: scope.tenantId ?? null,
111
+ organizationId: scope.organizationId ?? null,
112
+ }
113
+ }
114
+
77
115
  export async function requireTeamMember(
78
116
  em: EntityManager,
79
117
  memberId: string,
@@ -20,6 +20,9 @@ import {
20
20
  explicitStaffCommandScope,
21
21
  extractUndoPayload,
22
22
  scopeForDecryption,
23
+ scopedStaffSnapshotWhere,
24
+ staffSnapshotDecryptionScope,
25
+ staffSnapshotScopeFromSnapshot,
23
26
  } from './shared'
24
27
 
25
28
  type TeamMemberTagAssignmentSnapshot = {
@@ -113,7 +116,14 @@ const assignTeamMemberTagCommand: CommandHandler<StaffTeamMemberTagAssignmentInp
113
116
  const before = payload?.before
114
117
  if (!before) return
115
118
  const em = (ctx.container.resolve('em') as EntityManager).fork()
116
- const member = await em.findOne(StaffTeamMember, { id: before.memberId })
119
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
120
+ const member = await findOneWithDecryption(
121
+ em,
122
+ StaffTeamMember,
123
+ scopedStaffSnapshotWhere(before.memberId, snapshotScope),
124
+ undefined,
125
+ staffSnapshotDecryptionScope(snapshotScope),
126
+ )
117
127
  if (!member) return
118
128
  const nextTags = normalizeTagList(
119
129
  Array.isArray(member.tags) ? member.tags.filter((tag) => tag !== before.tag) : [],
@@ -243,7 +253,14 @@ const unassignTeamMemberTagCommand: CommandHandler<StaffTeamMemberTagAssignmentI
243
253
  const before = payload?.before
244
254
  if (!before) return
245
255
  const em = (ctx.container.resolve('em') as EntityManager).fork()
246
- const member = await em.findOne(StaffTeamMember, { id: before.memberId })
256
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
257
+ const member = await findOneWithDecryption(
258
+ em,
259
+ StaffTeamMember,
260
+ scopedStaffSnapshotWhere(before.memberId, snapshotScope),
261
+ undefined,
262
+ staffSnapshotDecryptionScope(snapshotScope),
263
+ )
247
264
  if (!member) return
248
265
  const currentTags = Array.isArray(member.tags) ? member.tags : []
249
266
  if (!currentTags.includes(before.tag)) {
@@ -27,6 +27,11 @@ import {
27
27
  ensureTenantScope,
28
28
  extractUndoPayload,
29
29
  scopeForDecryption,
30
+ scopedStaffSnapshotWhere,
31
+ staffSnapshotDecryptionScope,
32
+ staffSnapshotScopeFromContext,
33
+ staffSnapshotScopeFromSnapshot,
34
+ type StaffSnapshotScope,
30
35
  } from './shared'
31
36
  import { E } from '#generated/entities.ids.generated'
32
37
 
@@ -67,8 +72,14 @@ function normalizeStringList(value: unknown): string[] {
67
72
  return Array.from(set)
68
73
  }
69
74
 
70
- async function loadTeamMemberSnapshot(em: EntityManager, id: string): Promise<TeamMemberSnapshot | null> {
71
- const member = await findOneWithDecryption(em, StaffTeamMember, { id }, undefined, { tenantId: null, organizationId: null })
75
+ async function loadTeamMemberSnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<TeamMemberSnapshot | null> {
76
+ const member = await findOneWithDecryption(
77
+ em,
78
+ StaffTeamMember,
79
+ scopedStaffSnapshotWhere(id, scope),
80
+ undefined,
81
+ staffSnapshotDecryptionScope(scope),
82
+ )
72
83
  if (!member) return null
73
84
  return {
74
85
  id: member.id,
@@ -232,14 +243,14 @@ const createTeamMemberCommand: CommandHandler<StaffTeamMemberCreateInput, { memb
232
243
  },
233
244
  captureAfter: async (_input, result, ctx) => {
234
245
  const em = (ctx.container.resolve('em') as EntityManager).fork()
235
- const snapshot = await loadTeamMemberSnapshot(em, result.memberId)
246
+ const snapshot = await loadTeamMemberSnapshot(em, result.memberId, staffSnapshotScopeFromContext(ctx))
236
247
  if (!snapshot) return null
237
248
  const custom = await loadTeamMemberCustomSnapshot(em, snapshot)
238
249
  return { snapshot, custom }
239
250
  },
240
251
  buildLog: async ({ result, ctx }) => {
241
252
  const em = (ctx.container.resolve('em') as EntityManager).fork()
242
- const snapshot = await loadTeamMemberSnapshot(em, result.memberId)
253
+ const snapshot = await loadTeamMemberSnapshot(em, result.memberId, staffSnapshotScopeFromContext(ctx))
243
254
  if (!snapshot) return null
244
255
  const custom = await loadTeamMemberCustomSnapshot(em, snapshot)
245
256
  const { translate } = await resolveTranslations()
@@ -263,7 +274,7 @@ const createTeamMemberCommand: CommandHandler<StaffTeamMemberCreateInput, { memb
263
274
  const after = payload?.after
264
275
  if (!after) return
265
276
  const em = (ctx.container.resolve('em') as EntityManager).fork()
266
- const member = await em.findOne(StaffTeamMember, { id: after.id })
277
+ const member = await em.findOne(StaffTeamMember, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
267
278
  if (member) {
268
279
  member.deletedAt = new Date()
269
280
  await em.flush()
@@ -306,7 +317,7 @@ const updateTeamMemberCommand: CommandHandler<StaffTeamMemberUpdateInput, { memb
306
317
  async prepare(rawInput, ctx) {
307
318
  const { parsed } = parseWithCustomFields(staffTeamMemberUpdateSchema, rawInput)
308
319
  const em = (ctx.container.resolve('em') as EntityManager)
309
- const snapshot = await loadTeamMemberSnapshot(em, parsed.id)
320
+ const snapshot = await loadTeamMemberSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
310
321
  if (!snapshot) return {}
311
322
  const custom = await loadTeamMemberCustomSnapshot(em, snapshot)
312
323
  return { before: snapshot, customBefore: custom }
@@ -387,7 +398,7 @@ const updateTeamMemberCommand: CommandHandler<StaffTeamMemberUpdateInput, { memb
387
398
  const before = snapshots.before as TeamMemberSnapshot | undefined
388
399
  if (!before) return null
389
400
  const em = (ctx.container.resolve('em') as EntityManager).fork()
390
- const after = await loadTeamMemberSnapshot(em, before.id)
401
+ const after = await loadTeamMemberSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
391
402
  if (!after) return null
392
403
  const customBefore = (snapshots as { customBefore?: CustomFieldSnapshot | null }).customBefore ?? undefined
393
404
  const customAfter = await loadTeamMemberCustomSnapshot(em, after)
@@ -430,7 +441,7 @@ const updateTeamMemberCommand: CommandHandler<StaffTeamMemberUpdateInput, { memb
430
441
  const before = payload?.before
431
442
  if (!before) return
432
443
  const em = (ctx.container.resolve('em') as EntityManager).fork()
433
- const member = await em.findOne(StaffTeamMember, { id: before.id })
444
+ const member = await em.findOne(StaffTeamMember, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
434
445
  if (!member) return
435
446
  member.teamId = before.teamId ?? null
436
447
  member.displayName = before.displayName
@@ -477,7 +488,7 @@ const deleteTeamMemberCommand: CommandHandler<{ id?: string }, { memberId: strin
477
488
  const id = input?.id
478
489
  if (!id) throw new CrudHttpError(400, { error: 'Member id is required.' })
479
490
  const em = (ctx.container.resolve('em') as EntityManager)
480
- const snapshot = await loadTeamMemberSnapshot(em, id)
491
+ const snapshot = await loadTeamMemberSnapshot(em, id, staffSnapshotScopeFromContext(ctx))
481
492
  if (!snapshot) return {}
482
493
  const custom = await loadTeamMemberCustomSnapshot(em, snapshot)
483
494
  return { before: snapshot, customBefore: custom }
@@ -542,7 +553,7 @@ const deleteTeamMemberCommand: CommandHandler<{ id?: string }, { memberId: strin
542
553
  const before = payload?.before
543
554
  if (!before) return
544
555
  const em = (ctx.container.resolve('em') as EntityManager).fork()
545
- let member = await em.findOne(StaffTeamMember, { id: before.id })
556
+ let member = await em.findOne(StaffTeamMember, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
546
557
  if (!member) {
547
558
  member = em.create(StaffTeamMember, {
548
559
  id: before.id,
@@ -25,6 +25,11 @@ import {
25
25
  ensureTenantScope,
26
26
  extractUndoPayload,
27
27
  scopeForDecryption,
28
+ scopedStaffSnapshotWhere,
29
+ staffSnapshotDecryptionScope,
30
+ staffSnapshotScopeFromContext,
31
+ staffSnapshotScopeFromSnapshot,
32
+ type StaffSnapshotScope,
28
33
  } from './shared'
29
34
  import { E } from '#generated/entities.ids.generated'
30
35
 
@@ -51,8 +56,14 @@ type TeamRoleUndoPayload = {
51
56
  customAfter?: CustomFieldSnapshot | null
52
57
  }
53
58
 
54
- async function loadTeamRoleSnapshot(em: EntityManager, id: string): Promise<TeamRoleSnapshot | null> {
55
- const role = await findOneWithDecryption(em, StaffTeamRole, { id }, undefined, { tenantId: null, organizationId: null })
59
+ async function loadTeamRoleSnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<TeamRoleSnapshot | null> {
60
+ const role = await findOneWithDecryption(
61
+ em,
62
+ StaffTeamRole,
63
+ scopedStaffSnapshotWhere(id, scope),
64
+ undefined,
65
+ staffSnapshotDecryptionScope(scope),
66
+ )
56
67
  if (!role) return null
57
68
  return {
58
69
  id: role.id,
@@ -157,14 +168,14 @@ const createTeamRoleCommand: CommandHandler<StaffTeamRoleCreateInput, { roleId:
157
168
  },
158
169
  captureAfter: async (_input, result, ctx) => {
159
170
  const em = (ctx.container.resolve('em') as EntityManager).fork()
160
- const snapshot = await loadTeamRoleSnapshot(em, result.roleId)
171
+ const snapshot = await loadTeamRoleSnapshot(em, result.roleId, staffSnapshotScopeFromContext(ctx))
161
172
  if (!snapshot) return null
162
173
  const custom = await loadTeamRoleCustomSnapshot(em, snapshot)
163
174
  return { snapshot, custom }
164
175
  },
165
176
  buildLog: async ({ result, ctx }) => {
166
177
  const em = (ctx.container.resolve('em') as EntityManager).fork()
167
- const snapshot = await loadTeamRoleSnapshot(em, result.roleId)
178
+ const snapshot = await loadTeamRoleSnapshot(em, result.roleId, staffSnapshotScopeFromContext(ctx))
168
179
  if (!snapshot) return null
169
180
  const custom = await loadTeamRoleCustomSnapshot(em, snapshot)
170
181
  const { translate } = await resolveTranslations()
@@ -188,7 +199,7 @@ const createTeamRoleCommand: CommandHandler<StaffTeamRoleCreateInput, { roleId:
188
199
  const after = payload?.after
189
200
  if (!after) return
190
201
  const em = (ctx.container.resolve('em') as EntityManager).fork()
191
- const role = await em.findOne(StaffTeamRole, { id: after.id })
202
+ const role = await em.findOne(StaffTeamRole, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
192
203
  if (role) {
193
204
  role.deletedAt = new Date()
194
205
  await em.flush()
@@ -231,7 +242,7 @@ const updateTeamRoleCommand: CommandHandler<StaffTeamRoleUpdateInput, { roleId:
231
242
  async prepare(rawInput, ctx) {
232
243
  const { parsed } = parseWithCustomFields(staffTeamRoleUpdateSchema, rawInput)
233
244
  const em = (ctx.container.resolve('em') as EntityManager)
234
- const snapshot = await loadTeamRoleSnapshot(em, parsed.id)
245
+ const snapshot = await loadTeamRoleSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
235
246
  if (!snapshot) return {}
236
247
  const custom = await loadTeamRoleCustomSnapshot(em, snapshot)
237
248
  return { before: snapshot, customBefore: custom }
@@ -293,7 +304,7 @@ const updateTeamRoleCommand: CommandHandler<StaffTeamRoleUpdateInput, { roleId:
293
304
  const before = snapshots.before as TeamRoleSnapshot | undefined
294
305
  if (!before) return null
295
306
  const em = (ctx.container.resolve('em') as EntityManager).fork()
296
- const after = await loadTeamRoleSnapshot(em, before.id)
307
+ const after = await loadTeamRoleSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
297
308
  if (!after) return null
298
309
  const customBefore = (snapshots as { customBefore?: CustomFieldSnapshot | null }).customBefore ?? undefined
299
310
  const customAfter = await loadTeamRoleCustomSnapshot(em, after)
@@ -334,7 +345,7 @@ const updateTeamRoleCommand: CommandHandler<StaffTeamRoleUpdateInput, { roleId:
334
345
  const before = payload?.before
335
346
  if (!before) return
336
347
  const em = (ctx.container.resolve('em') as EntityManager).fork()
337
- const role = await em.findOne(StaffTeamRole, { id: before.id })
348
+ const role = await em.findOne(StaffTeamRole, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
338
349
  if (!role) return
339
350
  role.teamId = before.teamId ?? null
340
351
  role.name = before.name
@@ -379,7 +390,7 @@ const deleteTeamRoleCommand: CommandHandler<{ id?: string }, { roleId: string }>
379
390
  const id = input?.id
380
391
  if (!id) throw new CrudHttpError(400, { error: 'Role id is required.' })
381
392
  const em = (ctx.container.resolve('em') as EntityManager)
382
- const snapshot = await loadTeamRoleSnapshot(em, id)
393
+ const snapshot = await loadTeamRoleSnapshot(em, id, staffSnapshotScopeFromContext(ctx))
383
394
  if (!snapshot) return {}
384
395
  const custom = await loadTeamRoleCustomSnapshot(em, snapshot)
385
396
  return { before: snapshot, customBefore: custom }
@@ -464,7 +475,7 @@ const deleteTeamRoleCommand: CommandHandler<{ id?: string }, { roleId: string }>
464
475
  const before = payload?.before
465
476
  if (!before) return
466
477
  const em = (ctx.container.resolve('em') as EntityManager).fork()
467
- let role = await em.findOne(StaffTeamRole, { id: before.id })
478
+ let role = await em.findOne(StaffTeamRole, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
468
479
  if (!role) {
469
480
  role = em.create(StaffTeamRole, {
470
481
  id: before.id,
@@ -25,6 +25,11 @@ import {
25
25
  ensureTenantScope,
26
26
  extractUndoPayload,
27
27
  scopeForDecryption,
28
+ scopedStaffSnapshotWhere,
29
+ staffSnapshotDecryptionScope,
30
+ staffSnapshotScopeFromContext,
31
+ staffSnapshotScopeFromSnapshot,
32
+ type StaffSnapshotScope,
28
33
  } from './shared'
29
34
  import { E } from '#generated/entities.ids.generated'
30
35
 
@@ -49,8 +54,14 @@ type TeamUndoPayload = {
49
54
  customAfter?: CustomFieldSnapshot | null
50
55
  }
51
56
 
52
- async function loadTeamSnapshot(em: EntityManager, id: string): Promise<TeamSnapshot | null> {
53
- const team = await findOneWithDecryption(em, StaffTeam, { id }, undefined, { tenantId: null, organizationId: null })
57
+ async function loadTeamSnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<TeamSnapshot | null> {
58
+ const team = await findOneWithDecryption(
59
+ em,
60
+ StaffTeam,
61
+ scopedStaffSnapshotWhere(id, scope),
62
+ undefined,
63
+ staffSnapshotDecryptionScope(scope),
64
+ )
54
65
  if (!team) return null
55
66
  return {
56
67
  id: team.id,
@@ -129,14 +140,14 @@ const createTeamCommand: CommandHandler<StaffTeamCreateInput, { teamId: string }
129
140
  },
130
141
  captureAfter: async (_input, result, ctx) => {
131
142
  const em = (ctx.container.resolve('em') as EntityManager).fork()
132
- const snapshot = await loadTeamSnapshot(em, result.teamId)
143
+ const snapshot = await loadTeamSnapshot(em, result.teamId, staffSnapshotScopeFromContext(ctx))
133
144
  if (!snapshot) return null
134
145
  const custom = await loadTeamCustomSnapshot(em, snapshot)
135
146
  return { snapshot, custom }
136
147
  },
137
148
  buildLog: async ({ result, ctx }) => {
138
149
  const em = (ctx.container.resolve('em') as EntityManager).fork()
139
- const snapshot = await loadTeamSnapshot(em, result.teamId)
150
+ const snapshot = await loadTeamSnapshot(em, result.teamId, staffSnapshotScopeFromContext(ctx))
140
151
  if (!snapshot) return null
141
152
  const custom = await loadTeamCustomSnapshot(em, snapshot)
142
153
  const { translate } = await resolveTranslations()
@@ -160,7 +171,7 @@ const createTeamCommand: CommandHandler<StaffTeamCreateInput, { teamId: string }
160
171
  const after = payload?.after
161
172
  if (!after) return
162
173
  const em = (ctx.container.resolve('em') as EntityManager).fork()
163
- const team = await em.findOne(StaffTeam, { id: after.id })
174
+ const team = await em.findOne(StaffTeam, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
164
175
  if (team) {
165
176
  team.deletedAt = new Date()
166
177
  await em.flush()
@@ -203,7 +214,7 @@ const updateTeamCommand: CommandHandler<StaffTeamUpdateInput, { teamId: string }
203
214
  async prepare(rawInput, ctx) {
204
215
  const { parsed } = parseWithCustomFields(staffTeamUpdateSchema, rawInput)
205
216
  const em = (ctx.container.resolve('em') as EntityManager)
206
- const snapshot = await loadTeamSnapshot(em, parsed.id)
217
+ const snapshot = await loadTeamSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
207
218
  if (!snapshot) return {}
208
219
  const custom = await loadTeamCustomSnapshot(em, snapshot)
209
220
  return { before: snapshot, customBefore: custom }
@@ -258,7 +269,7 @@ const updateTeamCommand: CommandHandler<StaffTeamUpdateInput, { teamId: string }
258
269
  const before = snapshots.before as TeamSnapshot | undefined
259
270
  if (!before) return null
260
271
  const em = (ctx.container.resolve('em') as EntityManager).fork()
261
- const after = await loadTeamSnapshot(em, before.id)
272
+ const after = await loadTeamSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
262
273
  if (!after) return null
263
274
  const customBefore = (snapshots as { customBefore?: CustomFieldSnapshot | null }).customBefore ?? undefined
264
275
  const customAfter = await loadTeamCustomSnapshot(em, after)
@@ -297,7 +308,7 @@ const updateTeamCommand: CommandHandler<StaffTeamUpdateInput, { teamId: string }
297
308
  const before = payload?.before
298
309
  if (!before) return
299
310
  const em = (ctx.container.resolve('em') as EntityManager).fork()
300
- const team = await em.findOne(StaffTeam, { id: before.id })
311
+ const team = await em.findOne(StaffTeam, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
301
312
  if (!team) return
302
313
  team.name = before.name
303
314
  team.description = before.description ?? null
@@ -340,7 +351,7 @@ const deleteTeamCommand: CommandHandler<{ id?: string }, { teamId: string }> = {
340
351
  const id = input?.id
341
352
  if (!id) throw new CrudHttpError(400, { error: 'Team id is required.' })
342
353
  const em = (ctx.container.resolve('em') as EntityManager)
343
- const snapshot = await loadTeamSnapshot(em, id)
354
+ const snapshot = await loadTeamSnapshot(em, id, staffSnapshotScopeFromContext(ctx))
344
355
  if (!snapshot) return {}
345
356
  const custom = await loadTeamCustomSnapshot(em, snapshot)
346
357
  return { before: snapshot, customBefore: custom }
@@ -418,7 +429,7 @@ const deleteTeamCommand: CommandHandler<{ id?: string }, { teamId: string }> = {
418
429
  const before = payload?.before
419
430
  if (!before) return
420
431
  const em = (ctx.container.resolve('em') as EntityManager).fork()
421
- let team = await em.findOne(StaffTeam, { id: before.id })
432
+ let team = await em.findOne(StaffTeam, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
422
433
  if (!team) {
423
434
  team = em.create(StaffTeam, {
424
435
  id: before.id,
@@ -30,6 +30,11 @@ import {
30
30
  ensureTenantScope,
31
31
  extractUndoPayload,
32
32
  scopeForDecryption,
33
+ scopedStaffSnapshotWhere,
34
+ staffSnapshotDecryptionScope,
35
+ staffSnapshotScopeFromContext,
36
+ staffSnapshotScopeFromSnapshot,
37
+ type StaffSnapshotScope,
33
38
  } from './shared'
34
39
  import { getStaffMemberByUserId } from '../lib/staffMemberResolver'
35
40
 
@@ -132,8 +137,14 @@ type TimeEntryUndoPayload = {
132
137
  after?: TimeEntrySnapshot | null
133
138
  }
134
139
 
135
- async function loadTimeEntrySnapshot(em: EntityManager, id: string): Promise<TimeEntrySnapshot | null> {
136
- const entry = await findOneWithDecryption(em, StaffTimeEntry, { id }, undefined, { tenantId: null, organizationId: null })
140
+ async function loadTimeEntrySnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<TimeEntrySnapshot | null> {
141
+ const entry = await findOneWithDecryption(
142
+ em,
143
+ StaffTimeEntry,
144
+ scopedStaffSnapshotWhere(id, scope),
145
+ undefined,
146
+ staffSnapshotDecryptionScope(scope),
147
+ )
137
148
  if (!entry) return null
138
149
  return {
139
150
  id: entry.id,
@@ -241,13 +252,13 @@ const createTimeEntryCommand: CommandHandler<StaffTimeEntryCreateInput, { timeEn
241
252
  },
242
253
  captureAfter: async (_input, result, ctx) => {
243
254
  const em = (ctx.container.resolve('em') as EntityManager).fork()
244
- const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId)
255
+ const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId, staffSnapshotScopeFromContext(ctx))
245
256
  if (!snapshot) return null
246
257
  return { snapshot }
247
258
  },
248
259
  buildLog: async ({ result, ctx }) => {
249
260
  const em = (ctx.container.resolve('em') as EntityManager).fork()
250
- const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId)
261
+ const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId, staffSnapshotScopeFromContext(ctx))
251
262
  if (!snapshot) return null
252
263
  const { translate } = await resolveTranslations()
253
264
  return {
@@ -269,7 +280,7 @@ const createTimeEntryCommand: CommandHandler<StaffTimeEntryCreateInput, { timeEn
269
280
  const after = payload?.after
270
281
  if (!after) return
271
282
  const em = (ctx.container.resolve('em') as EntityManager).fork()
272
- const entry = await em.findOne(StaffTimeEntry, { id: after.id })
283
+ const entry = await em.findOne(StaffTimeEntry, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
273
284
  if (entry) {
274
285
  entry.deletedAt = new Date()
275
286
  await em.flush()
@@ -411,13 +422,13 @@ const startTimerCommand: CommandHandler<StaffTimeEntryStartTimerInput, { timeEnt
411
422
  },
412
423
  captureAfter: async (_input, result, ctx) => {
413
424
  const em = (ctx.container.resolve('em') as EntityManager).fork()
414
- const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId)
425
+ const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId, staffSnapshotScopeFromContext(ctx))
415
426
  if (!snapshot) return null
416
427
  return { snapshot }
417
428
  },
418
429
  buildLog: async ({ result, ctx }) => {
419
430
  const em = (ctx.container.resolve('em') as EntityManager).fork()
420
- const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId)
431
+ const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId, staffSnapshotScopeFromContext(ctx))
421
432
  if (!snapshot) return null
422
433
  const { translate } = await resolveTranslations()
423
434
  return {
@@ -439,7 +450,7 @@ const startTimerCommand: CommandHandler<StaffTimeEntryStartTimerInput, { timeEnt
439
450
  const after = payload?.after
440
451
  if (!after) return
441
452
  const em = (ctx.container.resolve('em') as EntityManager).fork()
442
- const entry = await em.findOne(StaffTimeEntry, { id: after.id })
453
+ const entry = await em.findOne(StaffTimeEntry, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
443
454
  if (entry) {
444
455
  entry.deletedAt = new Date()
445
456
  await em.flush()
@@ -460,7 +471,7 @@ const updateTimeEntryCommand: CommandHandler<StaffTimeEntryUpdateInput, { timeEn
460
471
  async prepare(rawInput, ctx) {
461
472
  const parsed = staffTimeEntryUpdateSchema.parse(rawInput)
462
473
  const em = (ctx.container.resolve('em') as EntityManager)
463
- const snapshot = await loadTimeEntrySnapshot(em, parsed.id)
474
+ const snapshot = await loadTimeEntrySnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
464
475
  if (!snapshot) return {}
465
476
  return { before: snapshot }
466
477
  },
@@ -521,7 +532,7 @@ const updateTimeEntryCommand: CommandHandler<StaffTimeEntryUpdateInput, { timeEn
521
532
  const before = snapshots.before as TimeEntrySnapshot | undefined
522
533
  if (!before) return null
523
534
  const em = (ctx.container.resolve('em') as EntityManager).fork()
524
- const after = await loadTimeEntrySnapshot(em, before.id)
535
+ const after = await loadTimeEntrySnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
525
536
  if (!after) return null
526
537
  const changes = buildChanges(before as unknown as Record<string, unknown>, after as unknown as Record<string, unknown>, [
527
538
  'date',
@@ -556,7 +567,7 @@ const updateTimeEntryCommand: CommandHandler<StaffTimeEntryUpdateInput, { timeEn
556
567
  const before = payload?.before
557
568
  if (!before) return
558
569
  const em = (ctx.container.resolve('em') as EntityManager).fork()
559
- const entry = await em.findOne(StaffTimeEntry, { id: before.id })
570
+ const entry = await em.findOne(StaffTimeEntry, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
560
571
  if (!entry) return
561
572
  entry.date = before.date as unknown as Date
562
573
  entry.durationMinutes = before.durationMinutes
@@ -586,7 +597,7 @@ const deleteTimeEntryCommand: CommandHandler<{ id?: string }, { timeEntryId: str
586
597
  const id = input?.id
587
598
  if (!id) throw new CrudHttpError(400, { error: 'Time entry id is required.' })
588
599
  const em = (ctx.container.resolve('em') as EntityManager)
589
- const snapshot = await loadTimeEntrySnapshot(em, id)
600
+ const snapshot = await loadTimeEntrySnapshot(em, id, staffSnapshotScopeFromContext(ctx))
590
601
  if (!snapshot) return {}
591
602
  return { before: snapshot }
592
603
  },
@@ -656,7 +667,7 @@ const deleteTimeEntryCommand: CommandHandler<{ id?: string }, { timeEntryId: str
656
667
  const before = payload?.before
657
668
  if (!before) return
658
669
  const em = (ctx.container.resolve('em') as EntityManager).fork()
659
- let entry = await em.findOne(StaffTimeEntry, { id: before.id })
670
+ let entry = await em.findOne(StaffTimeEntry, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
660
671
  if (!entry) {
661
672
  entry = em.create(StaffTimeEntry, {
662
673
  id: before.id,
@@ -35,6 +35,11 @@ import {
35
35
  ensureTenantScope,
36
36
  extractUndoPayload,
37
37
  scopeForDecryption,
38
+ scopedStaffSnapshotWhere,
39
+ staffSnapshotDecryptionScope,
40
+ staffSnapshotScopeFromContext,
41
+ staffSnapshotScopeFromSnapshot,
42
+ type StaffSnapshotScope,
38
43
  } from './shared'
39
44
 
40
45
  function isUniqueViolation(error: unknown): boolean {
@@ -87,8 +92,14 @@ type TimeProjectMemberUndoPayload = {
87
92
  after?: TimeProjectMemberSnapshot | null
88
93
  }
89
94
 
90
- async function loadTimeProjectSnapshot(em: EntityManager, id: string): Promise<TimeProjectSnapshot | null> {
91
- const project = await findOneWithDecryption(em, StaffTimeProject, { id }, undefined, { tenantId: null, organizationId: null })
95
+ async function loadTimeProjectSnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<TimeProjectSnapshot | null> {
96
+ const project = await findOneWithDecryption(
97
+ em,
98
+ StaffTimeProject,
99
+ scopedStaffSnapshotWhere(id, scope),
100
+ undefined,
101
+ staffSnapshotDecryptionScope(scope),
102
+ )
92
103
  if (!project) return null
93
104
  return {
94
105
  id: project.id,
@@ -108,8 +119,14 @@ async function loadTimeProjectSnapshot(em: EntityManager, id: string): Promise<T
108
119
  }
109
120
  }
110
121
 
111
- async function loadTimeProjectMemberSnapshot(em: EntityManager, id: string): Promise<TimeProjectMemberSnapshot | null> {
112
- const member = await findOneWithDecryption(em, StaffTimeProjectMember, { id }, undefined, { tenantId: null, organizationId: null })
122
+ async function loadTimeProjectMemberSnapshot(em: EntityManager, id: string, scope?: StaffSnapshotScope | null): Promise<TimeProjectMemberSnapshot | null> {
123
+ const member = await findOneWithDecryption(
124
+ em,
125
+ StaffTimeProjectMember,
126
+ scopedStaffSnapshotWhere(id, scope),
127
+ undefined,
128
+ staffSnapshotDecryptionScope(scope),
129
+ )
113
130
  if (!member) return null
114
131
  return {
115
132
  id: member.id,
@@ -223,13 +240,13 @@ const createTimeProjectCommand: CommandHandler<StaffTimeProjectCreateInput, { ti
223
240
  },
224
241
  captureAfter: async (_input, result, ctx) => {
225
242
  const em = (ctx.container.resolve('em') as EntityManager).fork()
226
- const snapshot = await loadTimeProjectSnapshot(em, result.timeProjectId)
243
+ const snapshot = await loadTimeProjectSnapshot(em, result.timeProjectId, staffSnapshotScopeFromContext(ctx))
227
244
  if (!snapshot) return null
228
245
  return { snapshot }
229
246
  },
230
247
  buildLog: async ({ result, ctx }) => {
231
248
  const em = (ctx.container.resolve('em') as EntityManager).fork()
232
- const snapshot = await loadTimeProjectSnapshot(em, result.timeProjectId)
249
+ const snapshot = await loadTimeProjectSnapshot(em, result.timeProjectId, staffSnapshotScopeFromContext(ctx))
233
250
  if (!snapshot) return null
234
251
  const { translate } = await resolveTranslations()
235
252
  return {
@@ -251,7 +268,7 @@ const createTimeProjectCommand: CommandHandler<StaffTimeProjectCreateInput, { ti
251
268
  const after = payload?.after
252
269
  if (!after) return
253
270
  const em = (ctx.container.resolve('em') as EntityManager).fork()
254
- const project = await em.findOne(StaffTimeProject, { id: after.id })
271
+ const project = await em.findOne(StaffTimeProject, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
255
272
  if (project) {
256
273
  project.deletedAt = new Date()
257
274
  await em.flush()
@@ -284,7 +301,7 @@ const updateTimeProjectCommand: CommandHandler<StaffTimeProjectUpdateInput, { ti
284
301
  async prepare(rawInput, ctx) {
285
302
  const parsed = staffTimeProjectUpdateSchema.parse(rawInput)
286
303
  const em = (ctx.container.resolve('em') as EntityManager)
287
- const snapshot = await loadTimeProjectSnapshot(em, parsed.id)
304
+ const snapshot = await loadTimeProjectSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx))
288
305
  if (!snapshot) return {}
289
306
  return { before: snapshot }
290
307
  },
@@ -346,7 +363,7 @@ const updateTimeProjectCommand: CommandHandler<StaffTimeProjectUpdateInput, { ti
346
363
  const before = snapshots.before as TimeProjectSnapshot | undefined
347
364
  if (!before) return null
348
365
  const em = (ctx.container.resolve('em') as EntityManager).fork()
349
- const after = await loadTimeProjectSnapshot(em, before.id)
366
+ const after = await loadTimeProjectSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
350
367
  if (!after) return null
351
368
  const changes = buildChanges(before as unknown as Record<string, unknown>, after as unknown as Record<string, unknown>, [
352
369
  'name',
@@ -384,7 +401,7 @@ const updateTimeProjectCommand: CommandHandler<StaffTimeProjectUpdateInput, { ti
384
401
  const before = payload?.before
385
402
  if (!before) return
386
403
  const em = (ctx.container.resolve('em') as EntityManager).fork()
387
- const project = await em.findOne(StaffTimeProject, { id: before.id })
404
+ const project = await em.findOne(StaffTimeProject, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
388
405
  if (!project) return
389
406
  project.name = before.name
390
407
  project.customerId = before.customerId ?? null
@@ -421,7 +438,7 @@ const deleteTimeProjectCommand: CommandHandler<{ id?: string }, { timeProjectId:
421
438
  const id = input?.id
422
439
  if (!id) throw new CrudHttpError(400, { error: 'Time project id is required.' })
423
440
  const em = (ctx.container.resolve('em') as EntityManager)
424
- const snapshot = await loadTimeProjectSnapshot(em, id)
441
+ const snapshot = await loadTimeProjectSnapshot(em, id, staffSnapshotScopeFromContext(ctx))
425
442
  if (!snapshot) return {}
426
443
  return { before: snapshot }
427
444
  },
@@ -482,7 +499,7 @@ const deleteTimeProjectCommand: CommandHandler<{ id?: string }, { timeProjectId:
482
499
  const before = payload?.before
483
500
  if (!before) return
484
501
  const em = (ctx.container.resolve('em') as EntityManager).fork()
485
- let project = await em.findOne(StaffTimeProject, { id: before.id })
502
+ let project = await em.findOne(StaffTimeProject, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
486
503
  if (!project) {
487
504
  project = em.create(StaffTimeProject, {
488
505
  id: before.id,
@@ -604,13 +621,13 @@ const assignTimeProjectMemberCommand: CommandHandler<StaffTimeProjectMemberAssig
604
621
  },
605
622
  captureAfter: async (_input, result, ctx) => {
606
623
  const em = (ctx.container.resolve('em') as EntityManager).fork()
607
- const snapshot = await loadTimeProjectMemberSnapshot(em, result.timeProjectMemberId)
624
+ const snapshot = await loadTimeProjectMemberSnapshot(em, result.timeProjectMemberId, staffSnapshotScopeFromContext(ctx))
608
625
  if (!snapshot) return null
609
626
  return { snapshot }
610
627
  },
611
628
  buildLog: async ({ result, ctx }) => {
612
629
  const em = (ctx.container.resolve('em') as EntityManager).fork()
613
- const snapshot = await loadTimeProjectMemberSnapshot(em, result.timeProjectMemberId)
630
+ const snapshot = await loadTimeProjectMemberSnapshot(em, result.timeProjectMemberId, staffSnapshotScopeFromContext(ctx))
614
631
  if (!snapshot) return null
615
632
  const { translate } = await resolveTranslations()
616
633
  return {
@@ -632,7 +649,7 @@ const assignTimeProjectMemberCommand: CommandHandler<StaffTimeProjectMemberAssig
632
649
  const after = payload?.after
633
650
  if (!after) return
634
651
  const em = (ctx.container.resolve('em') as EntityManager).fork()
635
- const member = await em.findOne(StaffTimeProjectMember, { id: after.id })
652
+ const member = await em.findOne(StaffTimeProjectMember, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
636
653
  if (member) {
637
654
  member.deletedAt = new Date()
638
655
  await em.flush()
@@ -662,7 +679,7 @@ const unassignTimeProjectMemberCommand: CommandHandler<{ id?: string }, { timePr
662
679
  const id = input?.id
663
680
  if (!id) throw new CrudHttpError(400, { error: 'Time project member id is required.' })
664
681
  const em = (ctx.container.resolve('em') as EntityManager)
665
- const snapshot = await loadTimeProjectMemberSnapshot(em, id)
682
+ const snapshot = await loadTimeProjectMemberSnapshot(em, id, staffSnapshotScopeFromContext(ctx))
666
683
  if (!snapshot) return {}
667
684
  return { before: snapshot }
668
685
  },
@@ -720,7 +737,7 @@ const unassignTimeProjectMemberCommand: CommandHandler<{ id?: string }, { timePr
720
737
  const before = payload?.before
721
738
  if (!before) return
722
739
  const em = (ctx.container.resolve('em') as EntityManager).fork()
723
- let member = await em.findOne(StaffTimeProjectMember, { id: before.id })
740
+ let member = await em.findOne(StaffTimeProjectMember, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
724
741
  if (!member) {
725
742
  member = em.create(StaffTimeProjectMember, {
726
743
  id: before.id,