@open-mercato/core 0.8.1-develop.7266.1.8e520bbe03 → 0.8.1-develop.7268.1.033c0546d5

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 (128) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities.ids.generated.js +0 -4
  3. package/dist/generated/entities.ids.generated.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +0 -21
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/customers/backend/customers/companies/create/page.js +5 -0
  7. package/dist/modules/customers/backend/customers/companies/create/page.js.map +2 -2
  8. package/dist/modules/wms/di.js +0 -3
  9. package/dist/modules/wms/di.js.map +2 -2
  10. package/generated/entities.ids.generated.ts +0 -4
  11. package/generated/entity-fields-registry.ts +0 -21
  12. package/package.json +7 -7
  13. package/src/modules/auth/i18n/de.json +0 -3
  14. package/src/modules/auth/i18n/en.json +0 -3
  15. package/src/modules/auth/i18n/es.json +0 -3
  16. package/src/modules/auth/i18n/ko.json +0 -3
  17. package/src/modules/auth/i18n/pl.json +0 -3
  18. package/src/modules/customer_accounts/AGENTS.md +1 -1
  19. package/src/modules/customers/backend/customers/companies/create/page.tsx +6 -0
  20. package/src/modules/wms/di.ts +0 -6
  21. package/dist/generated/entities/availability_policy/index.js +0 -41
  22. package/dist/generated/entities/availability_policy/index.js.map +0 -7
  23. package/dist/modules/availability/acl.js +0 -16
  24. package/dist/modules/availability/acl.js.map +0 -7
  25. package/dist/modules/availability/api/check/route.js +0 -95
  26. package/dist/modules/availability/api/check/route.js.map +0 -7
  27. package/dist/modules/availability/api/openapi.js +0 -27
  28. package/dist/modules/availability/api/openapi.js.map +0 -7
  29. package/dist/modules/availability/api/policies/resolve-preview/route.js +0 -58
  30. package/dist/modules/availability/api/policies/resolve-preview/route.js.map +0 -7
  31. package/dist/modules/availability/api/policies/route.js +0 -188
  32. package/dist/modules/availability/api/policies/route.js.map +0 -7
  33. package/dist/modules/availability/backend/availability/check/page.js +0 -185
  34. package/dist/modules/availability/backend/availability/check/page.js.map +0 -7
  35. package/dist/modules/availability/backend/availability/check/page.meta.js +0 -16
  36. package/dist/modules/availability/backend/availability/check/page.meta.js.map +0 -7
  37. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js +0 -79
  38. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js.map +0 -7
  39. package/dist/modules/availability/backend/availability/policies/ScopeFields.js +0 -53
  40. package/dist/modules/availability/backend/availability/policies/ScopeFields.js.map +0 -7
  41. package/dist/modules/availability/backend/availability/policies/[id]/page.js +0 -152
  42. package/dist/modules/availability/backend/availability/policies/[id]/page.js.map +0 -7
  43. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js +0 -12
  44. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js.map +0 -7
  45. package/dist/modules/availability/backend/availability/policies/create/page.js +0 -94
  46. package/dist/modules/availability/backend/availability/policies/create/page.js.map +0 -7
  47. package/dist/modules/availability/backend/availability/policies/create/page.meta.js +0 -12
  48. package/dist/modules/availability/backend/availability/policies/create/page.meta.js.map +0 -7
  49. package/dist/modules/availability/backend/availability/policies/formGroups.js +0 -75
  50. package/dist/modules/availability/backend/availability/policies/formGroups.js.map +0 -7
  51. package/dist/modules/availability/backend/availability/policies/page.js +0 -176
  52. package/dist/modules/availability/backend/availability/policies/page.js.map +0 -7
  53. package/dist/modules/availability/backend/availability/policies/page.meta.js +0 -16
  54. package/dist/modules/availability/backend/availability/policies/page.meta.js.map +0 -7
  55. package/dist/modules/availability/commands/policies.js +0 -312
  56. package/dist/modules/availability/commands/policies.js.map +0 -7
  57. package/dist/modules/availability/commands/scope.js +0 -19
  58. package/dist/modules/availability/commands/scope.js.map +0 -7
  59. package/dist/modules/availability/data/entities.js +0 -95
  60. package/dist/modules/availability/data/entities.js.map +0 -7
  61. package/dist/modules/availability/data/validators.js +0 -53
  62. package/dist/modules/availability/data/validators.js.map +0 -7
  63. package/dist/modules/availability/di.js +0 -41
  64. package/dist/modules/availability/di.js.map +0 -7
  65. package/dist/modules/availability/events.js +0 -18
  66. package/dist/modules/availability/events.js.map +0 -7
  67. package/dist/modules/availability/index.js +0 -15
  68. package/dist/modules/availability/index.js.map +0 -7
  69. package/dist/modules/availability/lib/policyResolution.js +0 -155
  70. package/dist/modules/availability/lib/policyResolution.js.map +0 -7
  71. package/dist/modules/availability/lib/tryResolve.js +0 -11
  72. package/dist/modules/availability/lib/tryResolve.js.map +0 -7
  73. package/dist/modules/availability/migrations/Migration20260922095844_availability.js +0 -16
  74. package/dist/modules/availability/migrations/Migration20260922095844_availability.js.map +0 -7
  75. package/dist/modules/availability/setup.js +0 -12
  76. package/dist/modules/availability/setup.js.map +0 -7
  77. package/dist/modules/wms/lib/availabilityCache.js +0 -85
  78. package/dist/modules/wms/lib/availabilityCache.js.map +0 -7
  79. package/dist/modules/wms/lib/availabilityCalculation.js +0 -178
  80. package/dist/modules/wms/lib/availabilityCalculation.js.map +0 -7
  81. package/dist/modules/wms/lib/availabilityProvider.js +0 -14
  82. package/dist/modules/wms/lib/availabilityProvider.js.map +0 -7
  83. package/dist/modules/wms/lib/sqlInClause.js +0 -9
  84. package/dist/modules/wms/lib/sqlInClause.js.map +0 -7
  85. package/dist/modules/wms/lib/tryResolve.js +0 -11
  86. package/dist/modules/wms/lib/tryResolve.js.map +0 -7
  87. package/dist/modules/wms/subscribers/invalidate-availability-cache.js +0 -16
  88. package/dist/modules/wms/subscribers/invalidate-availability-cache.js.map +0 -7
  89. package/generated/entities/availability_policy/index.ts +0 -19
  90. package/src/modules/availability/acl.ts +0 -12
  91. package/src/modules/availability/api/check/route.ts +0 -114
  92. package/src/modules/availability/api/openapi.ts +0 -28
  93. package/src/modules/availability/api/policies/resolve-preview/route.ts +0 -67
  94. package/src/modules/availability/api/policies/route.ts +0 -224
  95. package/src/modules/availability/backend/availability/check/page.meta.ts +0 -12
  96. package/src/modules/availability/backend/availability/check/page.tsx +0 -223
  97. package/src/modules/availability/backend/availability/policies/ResolutionPreviewPanel.tsx +0 -106
  98. package/src/modules/availability/backend/availability/policies/ScopeFields.tsx +0 -55
  99. package/src/modules/availability/backend/availability/policies/[id]/page.meta.ts +0 -8
  100. package/src/modules/availability/backend/availability/policies/[id]/page.tsx +0 -182
  101. package/src/modules/availability/backend/availability/policies/create/page.meta.ts +0 -8
  102. package/src/modules/availability/backend/availability/policies/create/page.tsx +0 -105
  103. package/src/modules/availability/backend/availability/policies/formGroups.ts +0 -77
  104. package/src/modules/availability/backend/availability/policies/page.meta.ts +0 -12
  105. package/src/modules/availability/backend/availability/policies/page.tsx +0 -196
  106. package/src/modules/availability/commands/policies.ts +0 -358
  107. package/src/modules/availability/commands/scope.ts +0 -26
  108. package/src/modules/availability/data/entities.ts +0 -92
  109. package/src/modules/availability/data/validators.ts +0 -70
  110. package/src/modules/availability/di.ts +0 -54
  111. package/src/modules/availability/events.ts +0 -17
  112. package/src/modules/availability/i18n/de.json +0 -81
  113. package/src/modules/availability/i18n/en.json +0 -81
  114. package/src/modules/availability/i18n/es.json +0 -81
  115. package/src/modules/availability/i18n/ko.json +0 -81
  116. package/src/modules/availability/i18n/pl.json +0 -81
  117. package/src/modules/availability/index.ts +0 -13
  118. package/src/modules/availability/lib/policyResolution.ts +0 -264
  119. package/src/modules/availability/lib/tryResolve.ts +0 -8
  120. package/src/modules/availability/migrations/.snapshot-open-mercato.json +0 -375
  121. package/src/modules/availability/migrations/Migration20260922095844_availability.ts +0 -13
  122. package/src/modules/availability/setup.ts +0 -10
  123. package/src/modules/wms/lib/availabilityCache.ts +0 -136
  124. package/src/modules/wms/lib/availabilityCalculation.ts +0 -289
  125. package/src/modules/wms/lib/availabilityProvider.ts +0 -29
  126. package/src/modules/wms/lib/sqlInClause.ts +0 -21
  127. package/src/modules/wms/lib/tryResolve.ts +0 -8
  128. package/src/modules/wms/subscribers/invalidate-availability-cache.ts +0 -30
@@ -1,182 +0,0 @@
1
- "use client"
2
- import * as React from 'react'
3
- import { useRouter } from 'next/navigation'
4
- import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
5
- import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
6
- import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
7
- import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
8
- import { flash } from '@open-mercato/ui/backend/FlashMessages'
9
- import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
10
- import { useT } from '@open-mercato/shared/lib/i18n/context'
11
- import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'
12
- import { hasFeature } from '@open-mercato/shared/security/features'
13
- import { ScopeFields, type PolicyScope } from '../ScopeFields'
14
- import { ResolutionPreviewPanel } from '../ResolutionPreviewPanel'
15
- import { buildPolicyFieldGroups } from '../formGroups'
16
-
17
- type PolicyRecord = {
18
- id: string
19
- storeId: string | null
20
- productId: string | null
21
- variantId: string | null
22
- isStockManaged: boolean
23
- allowBackorder: boolean
24
- backorderLeadTimeDays: number | null
25
- preorderReleaseAt: string | null
26
- lowStockThreshold: number | null
27
- minOrderQuantity: number | null
28
- maxOrderQuantity: number | null
29
- quantityIncrement: number | null
30
- hideWhenOutOfStock: boolean
31
- isActive: boolean
32
- updatedAt: string | null
33
- }
34
-
35
- type FormValues = {
36
- isStockManaged?: boolean
37
- allowBackorder?: boolean
38
- backorderLeadTimeDays?: number | string | null
39
- preorderReleaseAt?: string | null
40
- lowStockThreshold?: number | string | null
41
- minOrderQuantity?: number | string | null
42
- maxOrderQuantity?: number | string | null
43
- quantityIncrement?: number | string | null
44
- hideWhenOutOfStock?: boolean
45
- isActive?: boolean
46
- }
47
-
48
- function toNullableInt(value: number | string | null | undefined): number | null {
49
- if (value === null || value === undefined || value === '') return null
50
- const parsed = typeof value === 'number' ? value : parseInt(value, 10)
51
- return Number.isFinite(parsed) ? parsed : null
52
- }
53
-
54
- export default function AvailabilityPolicyEditPage({ params }: { params?: { id?: string } }) {
55
- const t = useT()
56
- const router = useRouter()
57
- const id = params?.id ?? ''
58
- const { payload } = useBackendChrome()
59
- const canManage = hasFeature(payload?.grantedFeatures, 'availability.policies.manage')
60
-
61
- const [policy, setPolicy] = React.useState<PolicyRecord | null>(null)
62
- const [isLoading, setIsLoading] = React.useState(true)
63
- const [error, setError] = React.useState<string | null>(null)
64
- const [scope, setScope] = React.useState<PolicyScope>({ productId: '', variantId: '', storeId: '' })
65
-
66
- React.useEffect(() => {
67
- if (!id) return
68
- let cancelled = false
69
- async function load() {
70
- setIsLoading(true)
71
- setError(null)
72
- try {
73
- const call = await apiCall<{ items: PolicyRecord[] }>(`/api/availability/policies?id=${encodeURIComponent(id)}`, undefined, {
74
- fallback: { items: [] },
75
- })
76
- if (cancelled) return
77
- const record = call.ok ? call.result?.items?.[0] ?? null : null
78
- if (!record) {
79
- setError(t('availability.policies.edit.notFound'))
80
- return
81
- }
82
- setPolicy(record)
83
- setScope({ productId: record.productId ?? '', variantId: record.variantId ?? '', storeId: record.storeId ?? '' })
84
- } catch (e) {
85
- if (!cancelled) setError(e instanceof Error ? e.message : t('availability.policies.list.error.loadFailed'))
86
- } finally {
87
- if (!cancelled) setIsLoading(false)
88
- }
89
- }
90
- load()
91
- return () => { cancelled = true }
92
- }, [id, t])
93
-
94
- const groups = React.useMemo(() => buildPolicyFieldGroups(t), [t])
95
-
96
- if (isLoading) return <LoadingMessage label={t('common.loading')} />
97
- if (error || !policy) return <ErrorMessage label={error ?? t('availability.policies.edit.notFound')} />
98
-
99
- return (
100
- <CrudForm<FormValues>
101
- title={t('availability.policies.edit.title')}
102
- titleHeadingLevel={1}
103
- backHref="/backend/availability/policies"
104
- fields={[]}
105
- groups={groups}
106
- readOnly={!canManage}
107
- submitLabel={t('availability.policies.form.action.save')}
108
- cancelHref="/backend/availability/policies"
109
- optimisticLockUpdatedAt={policy.updatedAt ?? null}
110
- initialValues={{
111
- isStockManaged: policy.isStockManaged,
112
- allowBackorder: policy.allowBackorder,
113
- backorderLeadTimeDays: policy.backorderLeadTimeDays,
114
- preorderReleaseAt: policy.preorderReleaseAt,
115
- lowStockThreshold: policy.lowStockThreshold,
116
- minOrderQuantity: policy.minOrderQuantity,
117
- maxOrderQuantity: policy.maxOrderQuantity,
118
- quantityIncrement: policy.quantityIncrement,
119
- hideWhenOutOfStock: policy.hideWhenOutOfStock,
120
- isActive: policy.isActive,
121
- }}
122
- contentHeader={(
123
- <div className="space-y-4">
124
- <ScopeFields value={scope} onChange={setScope} disabled={!canManage} />
125
- <ResolutionPreviewPanel
126
- productId={scope.productId}
127
- variantId={scope.variantId || null}
128
- storeId={scope.storeId || null}
129
- />
130
- </div>
131
- )}
132
- deleteVisible={canManage}
133
- onDelete={async () => {
134
- await deleteCrud('availability/policies', id)
135
- flash(t('availability.policies.flash.deleted'), 'success')
136
- router.push('/backend/availability/policies')
137
- }}
138
- onSubmit={async (values) => {
139
- if (scope.variantId && !scope.productId) {
140
- throw createCrudFormError(
141
- t('availability.policies.errors.variantRequiresProduct'),
142
- { productId: t('availability.policies.errors.variantRequiresProduct') },
143
- )
144
- }
145
- if (values.allowBackorder && toNullableInt(values.backorderLeadTimeDays) == null) {
146
- throw createCrudFormError(
147
- t('availability.policies.errors.backorderRequiresLeadTime'),
148
- { backorderLeadTimeDays: t('availability.policies.errors.backorderRequiresLeadTime') },
149
- )
150
- }
151
- const min = toNullableInt(values.minOrderQuantity)
152
- const max = toNullableInt(values.maxOrderQuantity)
153
- if (min != null && max != null && max < min) {
154
- throw createCrudFormError(
155
- t('availability.policies.errors.maxBelowMin'),
156
- { maxOrderQuantity: t('availability.policies.errors.maxBelowMin') },
157
- )
158
- }
159
-
160
- const payloadBody = {
161
- id,
162
- productId: scope.productId || null,
163
- variantId: scope.variantId || null,
164
- storeId: scope.storeId || null,
165
- isStockManaged: !!values.isStockManaged,
166
- allowBackorder: !!values.allowBackorder,
167
- backorderLeadTimeDays: toNullableInt(values.backorderLeadTimeDays),
168
- preorderReleaseAt: values.preorderReleaseAt || null,
169
- lowStockThreshold: toNullableInt(values.lowStockThreshold),
170
- minOrderQuantity: min,
171
- maxOrderQuantity: max,
172
- quantityIncrement: toNullableInt(values.quantityIncrement),
173
- hideWhenOutOfStock: !!values.hideWhenOutOfStock,
174
- isActive: values.isActive !== false,
175
- }
176
- await updateCrud('availability/policies', payloadBody)
177
- flash(t('availability.policies.flash.updated'), 'success')
178
- router.push('/backend/availability/policies')
179
- }}
180
- />
181
- )
182
- }
@@ -1,8 +0,0 @@
1
- export const metadata = {
2
- requireAuth: true,
3
- requireFeatures: ['availability.policies.manage'],
4
- breadcrumb: [
5
- { label: 'Availability Policies', labelKey: 'availability.policies.nav.title', href: '/backend/availability/policies' },
6
- { label: 'Create', labelKey: 'availability.policies.create.title' },
7
- ],
8
- }
@@ -1,105 +0,0 @@
1
- "use client"
2
- import * as React from 'react'
3
- import { useRouter } from 'next/navigation'
4
- import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
5
- import { createCrud } from '@open-mercato/ui/backend/utils/crud'
6
- import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
7
- import { flash } from '@open-mercato/ui/backend/FlashMessages'
8
- import { useT } from '@open-mercato/shared/lib/i18n/context'
9
- import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
10
- import { ScopeFields, type PolicyScope } from '../ScopeFields'
11
- import { ResolutionPreviewPanel } from '../ResolutionPreviewPanel'
12
- import { buildPolicyFieldGroups } from '../formGroups'
13
-
14
- type FormValues = {
15
- isStockManaged?: boolean
16
- allowBackorder?: boolean
17
- backorderLeadTimeDays?: number | string | null
18
- preorderReleaseAt?: string | null
19
- lowStockThreshold?: number | string | null
20
- minOrderQuantity?: number | string | null
21
- maxOrderQuantity?: number | string | null
22
- quantityIncrement?: number | string | null
23
- hideWhenOutOfStock?: boolean
24
- isActive?: boolean
25
- }
26
-
27
- function toNullableInt(value: number | string | null | undefined): number | null {
28
- if (value === null || value === undefined || value === '') return null
29
- const parsed = typeof value === 'number' ? value : parseInt(value, 10)
30
- return Number.isFinite(parsed) ? parsed : null
31
- }
32
-
33
- export default function AvailabilityPolicyCreatePage() {
34
- const t = useT()
35
- const router = useRouter()
36
- const { organizationId, tenantId } = useOrganizationScopeDetail()
37
- const [scope, setScope] = React.useState<PolicyScope>({ productId: '', variantId: '', storeId: '' })
38
-
39
- const groups = React.useMemo(() => buildPolicyFieldGroups(t), [t])
40
-
41
- return (
42
- <CrudForm<FormValues>
43
- title={t('availability.policies.create.title')}
44
- titleHeadingLevel={1}
45
- backHref="/backend/availability/policies"
46
- fields={[]}
47
- groups={groups}
48
- submitLabel={t('availability.policies.form.action.create')}
49
- cancelHref="/backend/availability/policies"
50
- contentHeader={(
51
- <div className="space-y-4">
52
- <ScopeFields value={scope} onChange={setScope} />
53
- <ResolutionPreviewPanel
54
- productId={scope.productId}
55
- variantId={scope.variantId || null}
56
- storeId={scope.storeId || null}
57
- />
58
- </div>
59
- )}
60
- onSubmit={async (values) => {
61
- if (scope.variantId && !scope.productId) {
62
- throw createCrudFormError(
63
- t('availability.policies.errors.variantRequiresProduct'),
64
- { productId: t('availability.policies.errors.variantRequiresProduct') },
65
- )
66
- }
67
- if (values.allowBackorder && toNullableInt(values.backorderLeadTimeDays) == null) {
68
- throw createCrudFormError(
69
- t('availability.policies.errors.backorderRequiresLeadTime'),
70
- { backorderLeadTimeDays: t('availability.policies.errors.backorderRequiresLeadTime') },
71
- )
72
- }
73
- const min = toNullableInt(values.minOrderQuantity)
74
- const max = toNullableInt(values.maxOrderQuantity)
75
- if (min != null && max != null && max < min) {
76
- throw createCrudFormError(
77
- t('availability.policies.errors.maxBelowMin'),
78
- { maxOrderQuantity: t('availability.policies.errors.maxBelowMin') },
79
- )
80
- }
81
-
82
- const payload = {
83
- organizationId,
84
- tenantId,
85
- productId: scope.productId || null,
86
- variantId: scope.variantId || null,
87
- storeId: scope.storeId || null,
88
- isStockManaged: !!values.isStockManaged,
89
- allowBackorder: !!values.allowBackorder,
90
- backorderLeadTimeDays: toNullableInt(values.backorderLeadTimeDays),
91
- preorderReleaseAt: values.preorderReleaseAt || null,
92
- lowStockThreshold: toNullableInt(values.lowStockThreshold),
93
- minOrderQuantity: min,
94
- maxOrderQuantity: max,
95
- quantityIncrement: toNullableInt(values.quantityIncrement),
96
- hideWhenOutOfStock: !!values.hideWhenOutOfStock,
97
- isActive: values.isActive !== false,
98
- }
99
- await createCrud('availability/policies', payload)
100
- flash(t('availability.policies.flash.created'), 'success')
101
- router.push('/backend/availability/policies')
102
- }}
103
- />
104
- )
105
- }
@@ -1,77 +0,0 @@
1
- import type { CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'
2
-
3
- /**
4
- * Shared field groups for the create and edit policy forms. Scope fields
5
- * (productId/variantId/storeId) live outside this list — see `ScopeFields`.
6
- */
7
- export function buildPolicyFieldGroups(t: (key: string) => string): CrudFormGroup[] {
8
- return [
9
- {
10
- id: 'sell-policy',
11
- column: 1,
12
- title: t('availability.policies.form.group.sellPolicy'),
13
- fields: [
14
- {
15
- id: 'isStockManaged',
16
- type: 'checkbox',
17
- label: t('availability.policies.form.field.isStockManaged'),
18
- description: t('availability.policies.form.field.isStockManaged.help'),
19
- },
20
- {
21
- id: 'allowBackorder',
22
- type: 'checkbox',
23
- label: t('availability.policies.form.field.allowBackorder'),
24
- },
25
- {
26
- id: 'backorderLeadTimeDays',
27
- type: 'number',
28
- label: t('availability.policies.form.field.backorderLeadTimeDays'),
29
- description: t('availability.policies.form.field.backorderLeadTimeDays.help'),
30
- },
31
- {
32
- id: 'preorderReleaseAt',
33
- type: 'datetime-local',
34
- label: t('availability.policies.form.field.preorderReleaseAt'),
35
- },
36
- ],
37
- },
38
- {
39
- id: 'thresholds',
40
- column: 2,
41
- title: t('availability.policies.form.group.thresholds'),
42
- fields: [
43
- {
44
- id: 'lowStockThreshold',
45
- type: 'number',
46
- label: t('availability.policies.form.field.lowStockThreshold'),
47
- },
48
- {
49
- id: 'minOrderQuantity',
50
- type: 'number',
51
- label: t('availability.policies.form.field.minOrderQuantity'),
52
- },
53
- {
54
- id: 'maxOrderQuantity',
55
- type: 'number',
56
- label: t('availability.policies.form.field.maxOrderQuantity'),
57
- },
58
- {
59
- id: 'quantityIncrement',
60
- type: 'number',
61
- label: t('availability.policies.form.field.quantityIncrement'),
62
- },
63
- {
64
- id: 'hideWhenOutOfStock',
65
- type: 'checkbox',
66
- label: t('availability.policies.form.field.hideWhenOutOfStock'),
67
- },
68
- {
69
- id: 'isActive',
70
- type: 'checkbox',
71
- label: t('availability.policies.form.field.isActive'),
72
- defaultValue: true,
73
- },
74
- ],
75
- },
76
- ]
77
- }
@@ -1,12 +0,0 @@
1
- export const metadata = {
2
- requireAuth: true,
3
- requireFeatures: ['availability.policies.view'],
4
- pageTitle: 'Availability Policies',
5
- pageTitleKey: 'availability.policies.nav.title',
6
- pageGroup: 'Availability',
7
- pageGroupKey: 'availability.nav.group',
8
- pageOrder: 10,
9
- pageContext: 'settings' as const,
10
- icon: 'boxes',
11
- breadcrumb: [{ label: 'Availability Policies', labelKey: 'availability.policies.nav.title' }],
12
- }
@@ -1,196 +0,0 @@
1
- "use client"
2
- import * as React from 'react'
3
- import Link from 'next/link'
4
- import { Page, PageBody } from '@open-mercato/ui/backend/Page'
5
- import { DataTable } from '@open-mercato/ui/backend/DataTable'
6
- import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
7
- import { Button } from '@open-mercato/ui/primitives/button'
8
- import { RowActions } from '@open-mercato/ui/backend/RowActions'
9
- import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
10
- import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
11
- import { flash } from '@open-mercato/ui/backend/FlashMessages'
12
- import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
13
- import { useT } from '@open-mercato/shared/lib/i18n/context'
14
- import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
15
- import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
16
-
17
- type Row = {
18
- id: string
19
- storeId: string | null
20
- productId: string | null
21
- variantId: string | null
22
- isStockManaged: boolean
23
- allowBackorder: boolean
24
- isActive: boolean
25
- updatedAt: string | null
26
- }
27
-
28
- type ResponsePayload = {
29
- items: Row[]
30
- total: number
31
- page?: number
32
- pageSize?: number
33
- totalPages: number
34
- totalIsCapped?: boolean
35
- }
36
-
37
- function ScopeCell({ value, allLabel }: { value: string | null; allLabel: string }) {
38
- if (!value) return <span className="text-muted-foreground">{allLabel}</span>
39
- return <code className="text-xs">{value}</code>
40
- }
41
-
42
- export default function AvailabilityPoliciesListPage() {
43
- const [rows, setRows] = React.useState<Row[]>([])
44
- const [page, setPage] = React.useState(1)
45
- const [total, setTotal] = React.useState(0)
46
- const [totalPages, setTotalPages] = React.useState(1)
47
- const [totalIsCapped, setTotalIsCapped] = React.useState(false)
48
- const [isLoading, setIsLoading] = React.useState(true)
49
- const [reloadToken, setReloadToken] = React.useState(0)
50
- const scopeVersion = useOrganizationScopeVersion()
51
- const t = useT()
52
- const { confirm, ConfirmDialogElement } = useConfirmDialog()
53
- const [filterValues, setFilterValues] = React.useState<FilterValues>({})
54
-
55
- const filters = React.useMemo<FilterDef[]>(() => [
56
- { id: 'productId', label: t('availability.policies.list.columns.product'), type: 'text' },
57
- { id: 'storeId', label: t('availability.policies.list.columns.store'), type: 'text' },
58
- {
59
- id: 'isActive',
60
- label: t('availability.policies.list.columns.active'),
61
- type: 'select',
62
- options: [
63
- { value: 'true', label: t('common.yes') },
64
- { value: 'false', label: t('common.no') },
65
- ],
66
- },
67
- ], [t])
68
-
69
- React.useEffect(() => {
70
- let cancelled = false
71
- async function load() {
72
- setIsLoading(true)
73
- try {
74
- const params = new URLSearchParams()
75
- params.set('page', String(page))
76
- params.set('pageSize', '50')
77
- const productId = typeof filterValues.productId === 'string' ? filterValues.productId.trim() : ''
78
- const storeId = typeof filterValues.storeId === 'string' ? filterValues.storeId.trim() : ''
79
- const isActive = typeof filterValues.isActive === 'string' ? filterValues.isActive : ''
80
- if (productId) params.set('productId', productId)
81
- if (storeId) params.set('storeId', storeId)
82
- if (isActive) params.set('isActive', isActive)
83
- const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }
84
- const call = await apiCall<ResponsePayload>(`/api/availability/policies?${params.toString()}`, undefined, { fallback })
85
- if (!call.ok) {
86
- const errorPayload = call.result as { error?: string } | undefined
87
- const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('availability.policies.list.error.loadFailed')
88
- flash(message, 'error')
89
- return
90
- }
91
- const payload = call.result ?? fallback
92
- if (!cancelled) {
93
- setRows(Array.isArray(payload.items) ? payload.items : [])
94
- setTotal(payload.total || 0)
95
- setTotalPages(payload.totalPages || 1)
96
- setTotalIsCapped(payload?.totalIsCapped === true)
97
- }
98
- } catch (error) {
99
- if (!cancelled) {
100
- const message = error instanceof Error ? error.message : t('availability.policies.list.error.loadFailed')
101
- flash(message, 'error')
102
- }
103
- } finally {
104
- if (!cancelled) setIsLoading(false)
105
- }
106
- }
107
- load()
108
- return () => { cancelled = true }
109
- }, [page, reloadToken, scopeVersion, filterValues, t])
110
-
111
- const handleDelete = React.useCallback(async (row: Row) => {
112
- const confirmed = await confirm({ title: t('availability.policies.list.confirmDelete'), variant: 'destructive' })
113
- if (!confirmed) return
114
- try {
115
- const call = await withScopedApiRequestHeaders(buildOptimisticLockHeader(row.updatedAt), () =>
116
- apiCall<{ error?: string }>(`/api/availability/policies?id=${encodeURIComponent(row.id)}`, { method: 'DELETE' }, { fallback: null }),
117
- )
118
- if (!call.ok) {
119
- const errorPayload = call.result as { error?: string } | undefined
120
- const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('availability.policies.list.error.deleteFailed')
121
- flash(message, 'error')
122
- return
123
- }
124
- flash(t('availability.policies.list.success.deleted'), 'success')
125
- setReloadToken((token) => token + 1)
126
- } catch (error) {
127
- const message = error instanceof Error ? error.message : t('availability.policies.list.error.deleteFailed')
128
- flash(message, 'error')
129
- }
130
- }, [confirm, t])
131
-
132
- const columns = React.useMemo<ColumnDef<Row>[]>(() => [
133
- {
134
- accessorKey: 'productId',
135
- header: t('availability.policies.list.columns.product'),
136
- cell: ({ row }) => <ScopeCell value={row.original.productId} allLabel={t('availability.policies.list.storeDefault')} />,
137
- },
138
- {
139
- accessorKey: 'variantId',
140
- header: t('availability.policies.list.columns.variant'),
141
- cell: ({ row }) => <ScopeCell value={row.original.variantId} allLabel={t('availability.policies.list.allVariants')} />,
142
- },
143
- {
144
- accessorKey: 'storeId',
145
- header: t('availability.policies.list.columns.store'),
146
- cell: ({ row }) => <ScopeCell value={row.original.storeId} allLabel={t('availability.policies.list.allStores')} />,
147
- },
148
- {
149
- accessorKey: 'isStockManaged',
150
- header: t('availability.policies.list.columns.stockManaged'),
151
- cell: ({ row }) => (row.original.isStockManaged ? t('common.yes') : t('common.no')),
152
- },
153
- {
154
- accessorKey: 'allowBackorder',
155
- header: t('availability.policies.list.columns.backorder'),
156
- cell: ({ row }) => (row.original.allowBackorder ? t('common.yes') : t('common.no')),
157
- },
158
- {
159
- accessorKey: 'isActive',
160
- header: t('availability.policies.list.columns.active'),
161
- cell: ({ row }) => (row.original.isActive ? t('common.yes') : t('common.no')),
162
- },
163
- ], [t])
164
-
165
- return (
166
- <Page>
167
- <PageBody>
168
- <DataTable
169
- title={t('availability.policies.list.title')}
170
- titleHeadingLevel={1}
171
- actions={(
172
- <Button asChild>
173
- <Link href="/backend/availability/policies/create">{t('availability.policies.list.actions.create')}</Link>
174
- </Button>
175
- )}
176
- columns={columns}
177
- data={rows}
178
- filters={filters}
179
- filterValues={filterValues}
180
- onFiltersApply={(values) => { setFilterValues(values); setPage(1) }}
181
- onFiltersClear={() => { setFilterValues({}); setPage(1) }}
182
- perspective={{ tableId: 'availability.policies.list' }}
183
- rowActions={(row) => (
184
- <RowActions items={[
185
- { id: 'edit', label: t('common.edit'), href: `/backend/availability/policies/${row.id}` },
186
- { id: 'delete', label: t('common.delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
187
- ]} />
188
- )}
189
- pagination={{ page, pageSize: 50, total, totalPages, totalIsCapped, onPageChange: setPage }}
190
- isLoading={isLoading}
191
- />
192
- </PageBody>
193
- {ConfirmDialogElement}
194
- </Page>
195
- )
196
- }