@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
@@ -42,6 +42,8 @@ import { loadPersonCompanyLinks, summarizePersonCompanies } from '../../../lib/p
42
42
  import { normalizeCustomerDetailCustomFields } from '../../detailCustomFields'
43
43
  import { buildEmailVisibilityMikroFilter } from '../../../lib/visibilityFilter'
44
44
  import { resolveCustomerDetailTenantScope } from '../../../lib/detailTenantScope'
45
+ import { runWithCacheTenant } from '@open-mercato/cache'
46
+ import { buildCollectionTags, canonicalizeResourceTag, isCrudCacheEnabled, resolveCrudCache } from '@open-mercato/shared/lib/crud/cache'
45
47
 
46
48
  export const metadata = {
47
49
  GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },
@@ -51,6 +53,63 @@ const paramsSchema = z.object({
51
53
  id: z.string().uuid(),
52
54
  })
53
55
 
56
+ // Person detail is one of the heaviest custom GETs (decryption sweeps across
57
+ // addresses, tags, labels, interactions, custom fields, plus per-link query
58
+ // engine enrichment). Mirror the gated get-then-set cache from the roles list
59
+ // (#3143) so a hit skips every sweep below. Writes flow through the customers
60
+ // command bus, which already flushes the matching crud:<resourceKind> collection
61
+ // tags post-commit — these set tags reuse the exact same shapes (see
62
+ // invalidateCrudCache), so no new invalidation wiring is needed (#3663).
63
+ // The resource ids below are canonicalized with canonicalizeResourceTag (just
64
+ // like invalidateCrudCache does via expandResourceAliases) before building the
65
+ // collection tags, so camelCase ids (tagAssignment, labelAssignment,
66
+ // personCompanyLink) produce the SAME tag the command bus deletes on
67
+ // write/undo/redo — otherwise the cache would never be invalidated for them.
68
+ const PERSON_DETAIL_TTL_MS = 60_000
69
+ const PERSON_DETAIL_TAG_RESOURCES = [
70
+ 'customers.person',
71
+ 'customers.address',
72
+ 'customers.tagAssignment',
73
+ 'customers.labelAssignment',
74
+ 'customers.personCompanyLink',
75
+ 'customers.interaction',
76
+ 'customers.activity',
77
+ ] as const
78
+
79
+ function buildPersonDetailCacheKey(params: {
80
+ personId: string
81
+ tenantId: string | null
82
+ organizationId: string | null
83
+ callerId: string | null
84
+ selectedOrganizationId: string | null
85
+ scopedOrganizationIds: string[]
86
+ interactionMode: 'canonical' | 'legacy'
87
+ includeTokens: string[]
88
+ }): string {
89
+ const include = [...params.includeTokens].sort((a, b) => a.localeCompare(b)).join('|') || 'none'
90
+ const scoped = [...params.scopedOrganizationIds].sort((a, b) => a.localeCompare(b)).join('|') || 'null'
91
+ return [
92
+ 'customers:person-detail',
93
+ params.personId,
94
+ `tenant=${params.tenantId ?? 'null'}`,
95
+ `org=${params.organizationId ?? 'null'}`,
96
+ `caller=${params.callerId ?? 'null'}`,
97
+ `selOrg=${params.selectedOrganizationId ?? 'null'}`,
98
+ `scope=${scoped}`,
99
+ `mode=${params.interactionMode}`,
100
+ `include=${include}`,
101
+ ].join(':')
102
+ }
103
+
104
+ function buildPersonDetailCacheTags(tenantId: string | null, organizationId: string | null): string[] {
105
+ const tags: string[] = []
106
+ for (const resource of PERSON_DETAIL_TAG_RESOURCES) {
107
+ const canonical = canonicalizeResourceTag(resource) ?? resource
108
+ tags.push(...buildCollectionTags(canonical, tenantId, [organizationId]))
109
+ }
110
+ return tags
111
+ }
112
+
54
113
  function parseIncludeParams(request: Request): Set<string> {
55
114
  const url = new URL(request.url)
56
115
  const raw = url.searchParams.getAll('include')
@@ -485,6 +544,39 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
485
544
  return forbidden('Access denied')
486
545
  }
487
546
 
547
+ // Gated get-then-set cache. Key on the resolved person plus the effective
548
+ // RBAC scope (caller identity, selected/visible orgs) and the payload-shaping
549
+ // inputs (interaction mode + include set). The lookup sits before the
550
+ // expensive sweeps below so a hit returns without touching the DB.
551
+ const personDetailTenantId = person.tenantId ?? auth.tenantId ?? null
552
+ const personDetailOrganizationId = person.organizationId ?? null
553
+ const personDetailCache = isCrudCacheEnabled() ? resolveCrudCache(container) : null
554
+ const personDetailCacheKey = personDetailCache
555
+ ? buildPersonDetailCacheKey({
556
+ personId: person.id,
557
+ tenantId: personDetailTenantId,
558
+ organizationId: personDetailOrganizationId,
559
+ callerId: auth.sub ?? null,
560
+ selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,
561
+ scopedOrganizationIds: Array.isArray(scope?.filterIds) ? scope.filterIds : [],
562
+ interactionMode,
563
+ includeTokens: Array.from(includeTokens),
564
+ })
565
+ : null
566
+ const personDetailCacheTags = personDetailCache
567
+ ? buildPersonDetailCacheTags(personDetailTenantId, personDetailOrganizationId)
568
+ : []
569
+
570
+ if (personDetailCache && personDetailCacheKey) {
571
+ const cached = await runWithCacheTenant(personDetailTenantId, () => personDetailCache.get(personDetailCacheKey))
572
+ if (cached) {
573
+ statusCode = 200
574
+ profileMeta = { cacheHit: true, include: Array.from(includeTokens), interactionMode }
575
+ profiler.mark('cache_hit')
576
+ return NextResponse.json(cached)
577
+ }
578
+ }
579
+
488
580
  const [profileResult, personCompanyLinks] = await Promise.all([
489
581
  findOneWithDecryption(em, CustomerPersonProfile, { entity: person }, { populate: ['company'] }, { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null }),
490
582
  loadPersonCompanyLinks(em, person),
@@ -801,7 +893,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
801
893
  companies: companies.length,
802
894
  }
803
895
 
804
- const response = NextResponse.json({
896
+ const payload = {
805
897
  interactionMode,
806
898
  person: {
807
899
  id: person.id,
@@ -1011,7 +1103,20 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
1011
1103
  name: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.name ?? null : null,
1012
1104
  email: viewerUserIdFinal ? userMap.get(viewerUserIdFinal)?.email ?? auth.email ?? null : auth.email ?? null,
1013
1105
  },
1014
- })
1106
+ }
1107
+
1108
+ if (personDetailCache && personDetailCacheKey) {
1109
+ try {
1110
+ await runWithCacheTenant(personDetailTenantId, () =>
1111
+ personDetailCache.set(personDetailCacheKey, payload, {
1112
+ ttl: PERSON_DETAIL_TTL_MS,
1113
+ tags: personDetailCacheTags,
1114
+ }),
1115
+ )
1116
+ } catch {}
1117
+ }
1118
+
1119
+ const response = NextResponse.json(payload)
1015
1120
  statusCode = 200
1016
1121
  profileMeta = {
1017
1122
  include: Array.from(includeTokens),
@@ -1,13 +1,4 @@
1
- import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
2
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
- import { ensureOrganizationScope } from '@open-mercato/shared/lib/commands/scope'
1
+ import { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
4
2
  import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
5
3
 
6
- export function ensureTenantScope(ctx: CommandRuntimeContext, tenantId: string): void {
7
- const currentTenant = ctx.auth?.tenantId ?? null
8
- if (currentTenant && currentTenant !== tenantId) {
9
- throw new CrudHttpError(403, { error: 'Forbidden' })
10
- }
11
- }
12
-
13
- export { ensureOrganizationScope, extractUndoPayload }
4
+ export { ensureOrganizationScope, ensureTenantScope, extractUndoPayload }
@@ -1,18 +1,10 @@
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
4
  import type { EntityManager } from '@mikro-orm/postgresql'
6
5
  import { ResourcesResource } 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) {
11
- throw new CrudHttpError(403, { error: 'Forbidden' })
12
- }
13
- }
14
-
15
- export { ensureOrganizationScope, extractUndoPayload }
7
+ export { ensureOrganizationScope, ensureTenantScope, extractUndoPayload }
16
8
 
17
9
  export async function requireResource(
18
10
  em: EntityManager,
@@ -8,6 +8,7 @@ import {
8
8
  } from '@open-mercato/shared/lib/crud/mutation-guard'
9
9
  import type { ShippingCarrierService } from '../../lib/shipping-service'
10
10
  import { isShipmentCancelNotAllowedError } from '../../lib/status-sync'
11
+ import { shippingCarrierUpstreamErrorResponse } from '../../lib/upstream-error-response'
11
12
  import { cancelShipmentSchema } from '../../data/validators'
12
13
  import { shippingCarriersTag } from '../openapi'
13
14
 
@@ -81,8 +82,7 @@ export async function POST(req: Request) {
81
82
  if (isShipmentCancelNotAllowedError(error)) {
82
83
  return NextResponse.json({ error: error.message }, { status: 422 })
83
84
  }
84
- const message = error instanceof Error ? error.message : 'Failed to cancel shipment'
85
- return NextResponse.json({ error: message }, { status: 502 })
85
+ return shippingCarrierUpstreamErrorResponse('cancel.post', error)
86
86
  }
87
87
  }
88
88
 
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import type { ShippingCarrierService } from '../../lib/shipping-service'
5
+ import { shippingCarrierUpstreamErrorResponse } from '../../lib/upstream-error-response'
5
6
  import { searchDropOffPointsQuerySchema } from '../../data/validators'
6
7
  import { shippingCarriersTag } from '../openapi'
7
8
 
@@ -35,8 +36,7 @@ export async function GET(req: Request) {
35
36
  })
36
37
  return NextResponse.json({ points })
37
38
  } catch (error: unknown) {
38
- const message = error instanceof Error ? error.message : 'Failed to search drop-off points'
39
- return NextResponse.json({ error: message }, { status: 502 })
39
+ return shippingCarrierUpstreamErrorResponse('points.search', error)
40
40
  }
41
41
  }
42
42
 
@@ -3,6 +3,7 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
5
5
  import type { ShippingCarrierService } from '../../lib/shipping-service'
6
+ import { shippingCarrierUpstreamErrorResponse } from '../../lib/upstream-error-response'
6
7
  import { calculateRatesSchema } from '../../data/validators'
7
8
  import { shippingCarriersTag } from '../openapi'
8
9
 
@@ -31,8 +32,7 @@ export async function POST(req: Request) {
31
32
  })
32
33
  return NextResponse.json({ rates })
33
34
  } catch (error: unknown) {
34
- const message = error instanceof Error ? error.message : 'Failed to calculate rates'
35
- return NextResponse.json({ error: message }, { status: 502 })
35
+ return shippingCarrierUpstreamErrorResponse('rates.calculate', error)
36
36
  }
37
37
  }
38
38
 
@@ -8,6 +8,7 @@ import {
8
8
  } from '@open-mercato/shared/lib/crud/mutation-guard'
9
9
  import type { ShippingCarrierService } from '../../lib/shipping-service'
10
10
  import { isShipmentIdempotencyConflictError } from '../../lib/shipment-idempotency'
11
+ import { shippingCarrierUpstreamErrorResponse } from '../../lib/upstream-error-response'
11
12
  import { createShipmentSchema } from '../../data/validators'
12
13
  import { shippingCarriersTag } from '../openapi'
13
14
 
@@ -90,8 +91,7 @@ export async function POST(req: Request) {
90
91
  { status: 409 },
91
92
  )
92
93
  }
93
- const message = error instanceof Error ? error.message : 'Failed to create shipment'
94
- return NextResponse.json({ error: message }, { status: 502 })
94
+ return shippingCarrierUpstreamErrorResponse('shipments.create', error)
95
95
  }
96
96
  }
97
97
 
@@ -3,6 +3,7 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
5
5
  import type { ShippingCarrierService } from '../../../lib/shipping-service'
6
+ import { shippingCarrierUpstreamErrorResponse } from '../../../lib/upstream-error-response'
6
7
  import { trackingRefreshSchema } from '../../../data/validators'
7
8
  import { shippingCarriersTag } from '../../openapi'
8
9
 
@@ -31,8 +32,7 @@ export async function POST(req: Request) {
31
32
  })
32
33
  return NextResponse.json(tracking)
33
34
  } catch (error: unknown) {
34
- const message = error instanceof Error ? error.message : 'Failed to refresh tracking'
35
- return NextResponse.json({ error: message }, { status: 502 })
35
+ return shippingCarrierUpstreamErrorResponse('tracking.refresh', error)
36
36
  }
37
37
  }
38
38
 
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import type { ShippingCarrierService } from '../../lib/shipping-service'
5
+ import { shippingCarrierUpstreamErrorResponse } from '../../lib/upstream-error-response'
5
6
  import { trackingQuerySchema } from '../../data/validators'
6
7
  import { shippingCarriersTag } from '../openapi'
7
8
 
@@ -34,8 +35,7 @@ export async function GET(req: Request) {
34
35
  })
35
36
  return NextResponse.json(tracking)
36
37
  } catch (error: unknown) {
37
- const message = error instanceof Error ? error.message : 'Failed to fetch tracking'
38
- return NextResponse.json({ error: message }, { status: 502 })
38
+ return shippingCarrierUpstreamErrorResponse('tracking.get', error)
39
39
  }
40
40
  }
41
41
 
@@ -69,6 +69,7 @@
69
69
  "shipping_carriers.create.summary.targetPoint": "Paketbox",
70
70
  "shipping_carriers.create.summary.to": "Nach",
71
71
  "shipping_carriers.create.title": "Sendung beim Transporteur erstellen",
72
+ "shipping_carriers.errors.upstreamFailed": "Anfrage an den Transporteur fehlgeschlagen. Versuchen Sie es spaeter erneut.",
72
73
  "shipping_carriers.feature.manage": "Versanddienstleister-Vorgaenge verwalten",
73
74
  "shipping_carriers.feature.view": "Sendungen der Versanddienstleister anzeigen",
74
75
  "shipping_carriers.status.cancelled": "Storniert",
@@ -69,6 +69,7 @@
69
69
  "shipping_carriers.create.summary.targetPoint": "Locker point",
70
70
  "shipping_carriers.create.summary.to": "To",
71
71
  "shipping_carriers.create.title": "Create carrier shipment",
72
+ "shipping_carriers.errors.upstreamFailed": "Carrier provider request failed. Try again later.",
72
73
  "shipping_carriers.feature.manage": "Manage shipping carrier operations",
73
74
  "shipping_carriers.feature.view": "View shipping carrier shipments",
74
75
  "shipping_carriers.status.cancelled": "Cancelled",
@@ -69,6 +69,7 @@
69
69
  "shipping_carriers.create.summary.targetPoint": "Casillero",
70
70
  "shipping_carriers.create.summary.to": "Hasta",
71
71
  "shipping_carriers.create.title": "Crear envio con transportista",
72
+ "shipping_carriers.errors.upstreamFailed": "Error en la solicitud al transportista. Intentalo de nuevo mas tarde.",
72
73
  "shipping_carriers.feature.manage": "Gestionar operaciones de transportistas",
73
74
  "shipping_carriers.feature.view": "Ver envios de transportistas",
74
75
  "shipping_carriers.status.cancelled": "Cancelado",
@@ -69,6 +69,7 @@
69
69
  "shipping_carriers.create.summary.targetPoint": "Paczkomat",
70
70
  "shipping_carriers.create.summary.to": "Odbiór",
71
71
  "shipping_carriers.create.title": "Utwórz przesyłkę u przewoźnika",
72
+ "shipping_carriers.errors.upstreamFailed": "Żądanie do przewoźnika nie powiodło się. Spróbuj ponownie później.",
72
73
  "shipping_carriers.feature.manage": "Zarzadzanie operacjami przewoznikow",
73
74
  "shipping_carriers.feature.view": "Podglad przesylek przewoznikow",
74
75
  "shipping_carriers.status.cancelled": "Anulowano",
@@ -0,0 +1,11 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
3
+
4
+ const upstreamFailedKey = 'shipping_carriers.errors.upstreamFailed'
5
+ const upstreamFailedFallback = 'Carrier provider request failed. Try again later.'
6
+
7
+ export async function shippingCarrierUpstreamErrorResponse(routeId: string, error: unknown) {
8
+ console.error(`[shipping_carriers.${routeId}] provider upstream error`, error)
9
+ const { translate } = await resolveTranslations()
10
+ return NextResponse.json({ error: translate(upstreamFailedKey, upstreamFailedFallback) }, { status: 502 })
11
+ }
@@ -22,7 +22,16 @@ import {
22
22
  type StaffTeamMemberActivityUpdateInput,
23
23
  } from '../data/validators'
24
24
  import { staffTeamMemberActivityCrudEvents } from '../lib/crud'
25
- import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload, requireTeamMember } from './shared'
25
+ import {
26
+ applyScopeToWhere,
27
+ commandActorScope,
28
+ commandInputScope,
29
+ ensureOrganizationScope,
30
+ ensureTenantScope,
31
+ explicitStaffCommandScope,
32
+ extractUndoPayload,
33
+ requireTeamMember,
34
+ } from './shared'
26
35
  import { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'
27
36
  import { E } from '#generated/entities.ids.generated'
28
37
  import {
@@ -118,10 +127,16 @@ const createActivityCommand: CommandHandler<
118
127
  const { parsed, custom } = parseWithCustomFields(staffTeamMemberActivityCreateSchema, rawInput)
119
128
  ensureTenantScope(ctx, parsed.tenantId)
120
129
  ensureOrganizationScope(ctx, parsed.organizationId)
130
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
121
131
  const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId, ctx.auth)
122
132
 
123
133
  const em = (ctx.container.resolve('em') as EntityManager).fork()
124
- const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
134
+ const member = await requireTeamMember(
135
+ em,
136
+ parsed.entityId,
137
+ scope,
138
+ 'Team member not found',
139
+ )
125
140
  ensureTenantScope(ctx, member.tenantId)
126
141
  ensureOrganizationScope(ctx, member.organizationId)
127
142
 
@@ -199,7 +214,12 @@ const createActivityCommand: CommandHandler<
199
214
  throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for activity create' })
200
215
  }
201
216
  const em = (ctx.container.resolve('em') as EntityManager).fork()
202
- const member = await requireTeamMember(em, after.activity.memberId, 'Team member not found')
217
+ const member = await requireTeamMember(
218
+ em,
219
+ after.activity.memberId,
220
+ explicitStaffCommandScope(after.activity.tenantId, after.activity.organizationId),
221
+ 'Team member not found',
222
+ )
203
223
  let activity = await em.findOne(StaffTeamMemberActivity, { id: after.activity.id })
204
224
  if (!activity) {
205
225
  activity = em.create(StaffTeamMemberActivity, {
@@ -261,13 +281,17 @@ const updateActivityCommand: CommandHandler<StaffTeamMemberActivityUpdateInput,
261
281
  async execute(rawInput, ctx) {
262
282
  const { parsed, custom } = parseWithCustomFields(staffTeamMemberActivityUpdateSchema, rawInput)
263
283
  const em = (ctx.container.resolve('em') as EntityManager).fork()
264
- const activity = await em.findOne(StaffTeamMemberActivity, { id: parsed.id })
284
+ const scope = commandActorScope(ctx)
285
+ const activity = await em.findOne(
286
+ StaffTeamMemberActivity,
287
+ applyScopeToWhere<StaffTeamMemberActivity>({ id: parsed.id }, scope),
288
+ )
265
289
  if (!activity) throw new CrudHttpError(404, { error: 'Activity not found' })
266
290
  ensureTenantScope(ctx, activity.tenantId)
267
291
  ensureOrganizationScope(ctx, activity.organizationId)
268
292
 
269
293
  if (parsed.entityId !== undefined) {
270
- const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
294
+ const member = await requireTeamMember(em, parsed.entityId, scope, 'Team member not found')
271
295
  ensureTenantScope(ctx, member.tenantId)
272
296
  ensureOrganizationScope(ctx, member.organizationId)
273
297
  activity.member = member
@@ -354,7 +378,12 @@ const updateActivityCommand: CommandHandler<StaffTeamMemberActivityUpdateInput,
354
378
  if (!before) return
355
379
  const em = (ctx.container.resolve('em') as EntityManager).fork()
356
380
  let activity = await em.findOne(StaffTeamMemberActivity, { id: before.activity.id })
357
- const member = await requireTeamMember(em, before.activity.memberId, 'Team member not found')
381
+ const member = await requireTeamMember(
382
+ em,
383
+ before.activity.memberId,
384
+ explicitStaffCommandScope(before.activity.tenantId, before.activity.organizationId),
385
+ 'Team member not found',
386
+ )
358
387
 
359
388
  if (!activity) {
360
389
  activity = em.create(StaffTeamMemberActivity, {
@@ -426,7 +455,11 @@ const deleteActivityCommand: CommandHandler<{ body?: Record<string, unknown>; qu
426
455
  async execute(input, ctx) {
427
456
  const id = requireId(input, 'Activity id required')
428
457
  const em = (ctx.container.resolve('em') as EntityManager).fork()
429
- const activity = await em.findOne(StaffTeamMemberActivity, { id })
458
+ const scope = commandActorScope(ctx)
459
+ const activity = await em.findOne(
460
+ StaffTeamMemberActivity,
461
+ applyScopeToWhere<StaffTeamMemberActivity>({ id }, scope),
462
+ )
430
463
  if (!activity) throw new CrudHttpError(404, { error: 'Activity not found' })
431
464
  ensureTenantScope(ctx, activity.tenantId)
432
465
  ensureOrganizationScope(ctx, activity.organizationId)
@@ -473,7 +506,12 @@ const deleteActivityCommand: CommandHandler<{ body?: Record<string, unknown>; qu
473
506
  const before = payload?.before
474
507
  if (!before) return
475
508
  const em = (ctx.container.resolve('em') as EntityManager).fork()
476
- const member = await requireTeamMember(em, before.activity.memberId, 'Team member not found')
509
+ const member = await requireTeamMember(
510
+ em,
511
+ before.activity.memberId,
512
+ explicitStaffCommandScope(before.activity.tenantId, before.activity.organizationId),
513
+ 'Team member not found',
514
+ )
477
515
  let activity = await em.findOne(StaffTeamMemberActivity, { id: before.activity.id })
478
516
  if (!activity) {
479
517
  activity = em.create(StaffTeamMemberActivity, {
@@ -12,8 +12,12 @@ import {
12
12
  } from '../data/validators'
13
13
  import { staffTeamMemberAddressCrudEvents } 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'
@@ -92,9 +96,15 @@ const createAddressCommand: CommandHandler<StaffTeamMemberAddressCreateInput, {
92
96
  const parsed = staffTeamMemberAddressCreateSchema.parse(rawInput)
93
97
  ensureTenantScope(ctx, parsed.tenantId)
94
98
  ensureOrganizationScope(ctx, parsed.organizationId)
99
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
95
100
 
96
101
  const em = (ctx.container.resolve('em') as EntityManager).fork()
97
- const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
102
+ const member = await requireTeamMember(
103
+ em,
104
+ parsed.entityId,
105
+ scope,
106
+ 'Team member not found',
107
+ )
98
108
  ensureTenantScope(ctx, member.tenantId)
99
109
  ensureOrganizationScope(ctx, member.organizationId)
100
110
 
@@ -182,7 +192,12 @@ const createAddressCommand: CommandHandler<StaffTeamMemberAddressCreateInput, {
182
192
  throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for address create' })
183
193
  }
184
194
  const em = (ctx.container.resolve('em') as EntityManager).fork()
185
- const member = await requireTeamMember(em, after.memberId, 'Team member not found')
195
+ const member = await requireTeamMember(
196
+ em,
197
+ after.memberId,
198
+ explicitStaffCommandScope(after.tenantId, after.organizationId),
199
+ 'Team member not found',
200
+ )
186
201
  let address = await em.findOne(StaffTeamMemberAddress, { id: after.id })
187
202
  if (!address) {
188
203
  address = em.create(StaffTeamMemberAddress, {
@@ -260,13 +275,17 @@ const updateAddressCommand: CommandHandler<StaffTeamMemberAddressUpdateInput, {
260
275
  async execute(rawInput, ctx) {
261
276
  const parsed = staffTeamMemberAddressUpdateSchema.parse(rawInput)
262
277
  const em = (ctx.container.resolve('em') as EntityManager).fork()
263
- const address = await em.findOne(StaffTeamMemberAddress, { id: parsed.id })
278
+ const scope = commandActorScope(ctx)
279
+ const address = await em.findOne(
280
+ StaffTeamMemberAddress,
281
+ applyScopeToWhere<StaffTeamMemberAddress>({ id: parsed.id }, scope),
282
+ )
264
283
  if (!address) throw new CrudHttpError(404, { error: 'Address not found' })
265
284
  ensureTenantScope(ctx, address.tenantId)
266
285
  ensureOrganizationScope(ctx, address.organizationId)
267
286
 
268
287
  if (parsed.entityId !== undefined) {
269
- const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
288
+ const member = await requireTeamMember(em, parsed.entityId, scope, 'Team member not found')
270
289
  ensureTenantScope(ctx, member.tenantId)
271
290
  ensureOrganizationScope(ctx, member.organizationId)
272
291
  address.member = member
@@ -367,7 +386,12 @@ const updateAddressCommand: CommandHandler<StaffTeamMemberAddressUpdateInput, {
367
386
  if (!before) return
368
387
  const em = (ctx.container.resolve('em') as EntityManager).fork()
369
388
  let address = await em.findOne(StaffTeamMemberAddress, { id: before.id })
370
- const member = await requireTeamMember(em, before.memberId, 'Team member not found')
389
+ const member = await requireTeamMember(
390
+ em,
391
+ before.memberId,
392
+ explicitStaffCommandScope(before.tenantId, before.organizationId),
393
+ 'Team member not found',
394
+ )
371
395
 
372
396
  if (!address) {
373
397
  address = em.create(StaffTeamMemberAddress, {
@@ -444,7 +468,11 @@ const deleteAddressCommand: CommandHandler<{ body?: Record<string, unknown>; que
444
468
  async execute(input, ctx) {
445
469
  const id = requireId(input, 'Address id required')
446
470
  const em = (ctx.container.resolve('em') as EntityManager).fork()
447
- const address = await em.findOne(StaffTeamMemberAddress, { id })
471
+ const scope = commandActorScope(ctx)
472
+ const address = await em.findOne(
473
+ StaffTeamMemberAddress,
474
+ applyScopeToWhere<StaffTeamMemberAddress>({ id }, scope),
475
+ )
448
476
  if (!address) throw new CrudHttpError(404, { error: 'Address not found' })
449
477
  ensureTenantScope(ctx, address.tenantId)
450
478
  ensureOrganizationScope(ctx, address.organizationId)
@@ -491,7 +519,12 @@ const deleteAddressCommand: CommandHandler<{ body?: Record<string, unknown>; que
491
519
  const before = payload?.before
492
520
  if (!before) return
493
521
  const em = (ctx.container.resolve('em') as EntityManager).fork()
494
- const member = await requireTeamMember(em, before.memberId, 'Team member not found')
522
+ const member = await requireTeamMember(
523
+ em,
524
+ before.memberId,
525
+ explicitStaffCommandScope(before.tenantId, before.organizationId),
526
+ 'Team member not found',
527
+ )
495
528
  let address = await em.findOne(StaffTeamMemberAddress, { id: before.id })
496
529
  if (!address) {
497
530
  address = em.create(StaffTeamMemberAddress, {