@open-mercato/core 0.10.1-develop.7841.1.af87ac614d → 0.10.1-develop.7857.1.259928eb85

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 (98) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/api_keys/api/keys/route.js +15 -1
  3. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  4. package/dist/modules/attachments/api/file/[id]/route.js +6 -1
  5. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  6. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +6 -1
  7. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  8. package/dist/modules/attachments/api/library/[id]/route.js +16 -1
  9. package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
  10. package/dist/modules/attachments/api/route.js +11 -3
  11. package/dist/modules/attachments/api/route.js.map +2 -2
  12. package/dist/modules/attachments/cli.js +2 -0
  13. package/dist/modules/attachments/cli.js.map +2 -2
  14. package/dist/modules/attachments/data/validators.js +11 -0
  15. package/dist/modules/attachments/data/validators.js.map +2 -2
  16. package/dist/modules/attachments/index.js +10 -0
  17. package/dist/modules/attachments/index.js.map +2 -2
  18. package/dist/modules/attachments/lib/access-query.js +5 -1
  19. package/dist/modules/attachments/lib/access-query.js.map +2 -2
  20. package/dist/modules/attachments/lib/createFromBuffer.js +2 -0
  21. package/dist/modules/attachments/lib/createFromBuffer.js.map +2 -2
  22. package/dist/modules/attachments/lib/drivers/driverFactory.js +54 -33
  23. package/dist/modules/attachments/lib/drivers/driverFactory.js.map +2 -2
  24. package/dist/modules/attachments/lib/drivers/index.js +11 -1
  25. package/dist/modules/attachments/lib/drivers/index.js.map +2 -2
  26. package/dist/modules/attachments/lib/drivers/storageValidation.js +85 -0
  27. package/dist/modules/attachments/lib/drivers/storageValidation.js.map +7 -0
  28. package/dist/modules/attachments/lib/scoped-upload-service.js +3 -1
  29. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  30. package/dist/modules/attachments/lib/storageErrors.js +22 -0
  31. package/dist/modules/attachments/lib/storageErrors.js.map +7 -0
  32. package/dist/modules/auth/api/users/route.js +16 -1
  33. package/dist/modules/auth/api/users/route.js.map +2 -2
  34. package/dist/modules/auth/backend/users/page.js +13 -2
  35. package/dist/modules/auth/backend/users/page.js.map +2 -2
  36. package/dist/modules/catalog/commands/products.js +4 -2
  37. package/dist/modules/catalog/commands/products.js.map +2 -2
  38. package/dist/modules/customers/api/companies/[id]/route.js +4 -9
  39. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  40. package/dist/modules/customers/api/people/[id]/route.js +4 -9
  41. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  42. package/dist/modules/customers/components/detail/TaskDialog.js +2 -0
  43. package/dist/modules/customers/components/detail/TaskDialog.js.map +2 -2
  44. package/dist/modules/customers/lib/todoCompatibility.js +23 -0
  45. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  46. package/dist/modules/directory/api/organization-branding/route.js +6 -9
  47. package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
  48. package/dist/modules/directory/backend/directory/branding/page.js +15 -3
  49. package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
  50. package/dist/modules/wms/api/listEnrichers.js +3 -1
  51. package/dist/modules/wms/api/listEnrichers.js.map +2 -2
  52. package/dist/modules/wms/index.js +1 -1
  53. package/dist/modules/wms/index.js.map +2 -2
  54. package/jest.config.cjs +1 -1
  55. package/package.json +8 -8
  56. package/src/modules/api_keys/api/keys/route.ts +15 -1
  57. package/src/modules/api_keys/i18n/de.json +1 -0
  58. package/src/modules/api_keys/i18n/en.json +1 -0
  59. package/src/modules/api_keys/i18n/es.json +1 -0
  60. package/src/modules/api_keys/i18n/ko.json +1 -0
  61. package/src/modules/api_keys/i18n/pl.json +1 -0
  62. package/src/modules/attachments/api/file/[id]/route.ts +7 -1
  63. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -1
  64. package/src/modules/attachments/api/library/[id]/route.ts +14 -1
  65. package/src/modules/attachments/api/route.ts +11 -3
  66. package/src/modules/attachments/cli.ts +2 -0
  67. package/src/modules/attachments/data/validators.ts +11 -0
  68. package/src/modules/attachments/i18n/de.json +1 -0
  69. package/src/modules/attachments/i18n/en.json +1 -0
  70. package/src/modules/attachments/i18n/es.json +1 -0
  71. package/src/modules/attachments/i18n/ko.json +1 -0
  72. package/src/modules/attachments/i18n/pl.json +1 -0
  73. package/src/modules/attachments/index.ts +12 -0
  74. package/src/modules/attachments/lib/access-query.ts +5 -1
  75. package/src/modules/attachments/lib/createFromBuffer.ts +2 -0
  76. package/src/modules/attachments/lib/drivers/driverFactory.ts +79 -55
  77. package/src/modules/attachments/lib/drivers/index.ts +13 -0
  78. package/src/modules/attachments/lib/drivers/storageValidation.ts +114 -0
  79. package/src/modules/attachments/lib/scoped-upload-service.ts +3 -1
  80. package/src/modules/attachments/lib/storageErrors.ts +21 -0
  81. package/src/modules/auth/api/users/route.ts +18 -1
  82. package/src/modules/auth/backend/users/page.tsx +14 -2
  83. package/src/modules/auth/i18n/de.json +1 -0
  84. package/src/modules/auth/i18n/en.json +1 -0
  85. package/src/modules/auth/i18n/es.json +1 -0
  86. package/src/modules/auth/i18n/ko.json +1 -0
  87. package/src/modules/auth/i18n/pl.json +1 -0
  88. package/src/modules/catalog/commands/products.ts +4 -1
  89. package/src/modules/customer_accounts/AGENTS.md +1 -1
  90. package/src/modules/customers/api/companies/[id]/route.ts +6 -13
  91. package/src/modules/customers/api/people/[id]/route.ts +6 -13
  92. package/src/modules/customers/components/detail/TaskDialog.tsx +2 -0
  93. package/src/modules/customers/lib/todoCompatibility.ts +28 -0
  94. package/src/modules/directory/api/organization-branding/route.ts +5 -8
  95. package/src/modules/directory/backend/directory/branding/page.tsx +15 -3
  96. package/src/modules/sales/AGENTS.md +3 -3
  97. package/src/modules/wms/api/listEnrichers.ts +5 -2
  98. package/src/modules/wms/index.ts +1 -1
@@ -17,7 +17,7 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
17
17
  import { Organization } from '@open-mercato/core/modules/directory/data/entities'
18
18
  import { organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'
19
19
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
20
- import { resolveSingleOrganizationIdOrDeny } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
20
+ import { isExplicitlyEmptyOrganizationScope } from '@open-mercato/shared/lib/auth/organizationScope'
21
21
  import '@open-mercato/core/modules/directory/commands/organizations'
22
22
  import { createLogger } from '@open-mercato/shared/lib/logger'
23
23
  import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
@@ -82,15 +82,12 @@ async function resolveCurrentOrganization(req: Request) {
82
82
 
83
83
  const container = await createRequestContainer()
84
84
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
85
- let organizationId: string | null
86
- try {
87
- organizationId = resolveSingleOrganizationIdOrDeny(scope, auth)
88
- } catch (error) {
89
- if (isCrudHttpError(error)) {
90
- return { response: NextResponse.json(error.body, { status: error.status }) }
85
+ if (isExplicitlyEmptyOrganizationScope(scope)) {
86
+ return {
87
+ response: NextResponse.json({ error: translate('api.errors.forbidden', 'Forbidden') }, { status: 403 }),
91
88
  }
92
- throw error
93
89
  }
90
+ const organizationId = scope.selectedId
94
91
  const tenantId = scope.tenantId ?? auth.tenantId ?? null
95
92
  if (!organizationId || !tenantId) {
96
93
  return {
@@ -15,7 +15,8 @@ import { Button } from '@open-mercato/ui/primitives/button'
15
15
  import { Input } from '@open-mercato/ui/primitives/input'
16
16
  import { SwitchField } from '@open-mercato/ui/primitives/switch-field'
17
17
  import { useT } from '@open-mercato/shared/lib/i18n/context'
18
- import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
18
+ import { useOrganizationScopeDetail, useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
19
+ import { getCurrentOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/organizationEvents'
19
20
 
20
21
  type BrandingPayload = {
21
22
  organizationId: string
@@ -42,6 +43,7 @@ export default function OrganizationBrandingPage() {
42
43
  const t = useT()
43
44
  const queryClient = useQueryClient()
44
45
  const { organizationId, tenantId } = useOrganizationScopeDetail()
46
+ const scopeVersion = useOrganizationScopeVersion()
45
47
  const [logoUrl, setLogoUrl] = React.useState('')
46
48
  const [logoPreserveAspectRatio, setLogoPreserveAspectRatio] = React.useState(false)
47
49
  const [selectedFile, setSelectedFile] = React.useState<File | null>(null)
@@ -67,7 +69,8 @@ export default function OrganizationBrandingPage() {
67
69
  setLogoUrl(data?.logoUrl ?? '')
68
70
  setLogoPreserveAspectRatio(data?.logoPreserveAspectRatio ?? false)
69
71
  setSelectedFile(null)
70
- }, [data?.logoPreserveAspectRatio, data?.logoUrl, organizationId, tenantId])
72
+ if (fileInputRef.current) fileInputRef.current.value = ''
73
+ }, [scopeVersion, organizationId, tenantId, data?.organizationId, data?.logoPreserveAspectRatio, data?.logoUrl])
71
74
 
72
75
  React.useEffect(() => {
73
76
  if (!selectedFile || typeof URL === 'undefined') {
@@ -111,7 +114,13 @@ export default function OrganizationBrandingPage() {
111
114
  try {
112
115
  await runMutation({
113
116
  operation: async () => {
117
+ if (getCurrentOrganizationScopeVersion() !== scopeVersion) {
118
+ throw new Error('[internal] Organization scope changed during branding save')
119
+ }
114
120
  const uploadedLogoUrl = shouldUpload ? await uploadLogo(data.organizationId) : null
121
+ if (getCurrentOrganizationScopeVersion() !== scopeVersion) {
122
+ throw new Error('[internal] Organization scope changed during branding save')
123
+ }
115
124
  const resolvedLogoUrl = uploadedLogoUrl ?? nextLogoUrl ?? logoUrl.trim()
116
125
  const response = await withScopedApiRequestHeaders(
117
126
  buildOptimisticLockHeader(data.updatedAt),
@@ -142,12 +151,15 @@ export default function OrganizationBrandingPage() {
142
151
  hasUpload: shouldUpload,
143
152
  },
144
153
  })
154
+ if (getCurrentOrganizationScopeVersion() !== scopeVersion) return
145
155
  await queryClient.invalidateQueries({ queryKey: ['directory-organization-branding'] })
156
+ if (getCurrentOrganizationScopeVersion() !== scopeVersion) return
146
157
  window.dispatchEvent(new Event('om:refresh-sidebar'))
147
158
  setSelectedFile(null)
148
159
  if (fileInputRef.current) fileInputRef.current.value = ''
149
160
  flash(t('directory.branding.flash.saved', 'Organization branding updated'), 'success')
150
161
  } catch (err: unknown) {
162
+ if (getCurrentOrganizationScopeVersion() !== scopeVersion) return
151
163
  if (surfaceRecordConflict(err, t)) return
152
164
  const fallback = t('directory.branding.errors.save', 'Failed to update organization branding')
153
165
  const message = err instanceof Error ? err.message : fallback
@@ -155,7 +167,7 @@ export default function OrganizationBrandingPage() {
155
167
  } finally {
156
168
  setSaving(false)
157
169
  }
158
- }, [data, logoPreserveAspectRatio, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo])
170
+ }, [data, logoPreserveAspectRatio, logoUrl, queryClient, runMutation, scopeVersion, selectedFile, t, uploadLogo])
159
171
 
160
172
  const handleSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {
161
173
  event.preventDefault()
@@ -5,7 +5,7 @@ Use the sales module for orders, quotes, invoices, shipments, and payments. This
5
5
  ## Always
6
6
 
7
7
  1. **MUST use `salesCalculationService` from DI** for document math.
8
- 2. **MUST follow document flow**: Quote → Order → Invoice — no skipping steps
8
+ 2. **MUST follow document flow**: Quote → Order → Invoice (quote optional)
9
9
  3. **MUST scope all documents to a channel** — channel selection affects pricing, numbering, and visibility
10
10
 
11
11
  ## Ask First
@@ -36,7 +36,7 @@ Quote → Order → Invoice
36
36
  Shipments + Payments
37
37
  ```
38
38
 
39
- - Quotes convert to orders — MUST NOT create orders without a source quote (unless configured)
39
+ - Quotes convert via `sales.quotes.convert_to_order`; orders MAY also be created without a quote
40
40
  - Orders track shipments and payments independently
41
41
  - Each entity has its own status workflow — MUST NOT skip workflow states
42
42
 
@@ -56,7 +56,7 @@ const calcService = container.resolve('salesCalculationService')
56
56
 
57
57
  ### Core Entities
58
58
  - **Sales Orders** — confirmed customer orders. MUST have a channel and at least one line
59
- - **Sales Quotes** — proposed orders. MUST track conversion status
59
+ - **Sales Quotes** — proposed orders; deleted on conversion
60
60
  - **Order/Quote Lines** — individual items. MUST reference valid products
61
61
  - **Adjustments** — discounts/surcharges. MUST use registered `AdjustmentKind`
62
62
 
@@ -277,7 +277,8 @@ const CUSTOMER_FIELDS = ['id', 'display_name']
277
277
  /**
278
278
  * Decorate list items with `vendor_name` when they carry a `vendor_id` column
279
279
  * referencing `customers.customer_entity` by UUID only (no ORM cross-module
280
- * relations). Mutates `payload.items` in place.
280
+ * relations). No-op when the optional `customers` module is not enabled.
281
+ * Mutates `payload.items` in place.
281
282
  */
282
283
  export async function attachVendorLabelsToListItems(
283
284
  payload: AnyListPayload,
@@ -285,11 +286,13 @@ export async function attachVendorLabelsToListItems(
285
286
  ): Promise<void> {
286
287
  const items = Array.isArray(payload?.items) ? payload.items : []
287
288
  if (items.length === 0) return
289
+ const customerEntityId = E.customers?.customer_entity
290
+ if (!customerEntityId) return
288
291
  const ids = uniqueIdsFromKeys(items, ['vendor_id'])
289
292
  if (ids.length === 0) return
290
293
  const map = (await batchLoadById(
291
294
  ctx,
292
- E.customers.customer_entity,
295
+ customerEntityId,
293
296
  ids,
294
297
  CUSTOMER_FIELDS,
295
298
  'vendor',
@@ -9,7 +9,7 @@ export const metadata: ModuleInfo = {
9
9
  author: 'Open Mercato Team',
10
10
  license: 'MIT',
11
11
  ejectable: true,
12
- requires: ['catalog', 'sales', 'feature_toggles'],
12
+ requires: ['catalog', 'customers', 'sales', 'feature_toggles'],
13
13
  }
14
14
 
15
15
  export { features } from './acl'