@open-mercato/core 0.8.1-develop.7266.1.8e520bbe03 → 0.8.1-develop.7267.1.2e95af80a6

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 (124) 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/wms/di.js +0 -3
  7. package/dist/modules/wms/di.js.map +2 -2
  8. package/generated/entities.ids.generated.ts +0 -4
  9. package/generated/entity-fields-registry.ts +0 -21
  10. package/package.json +7 -7
  11. package/src/modules/auth/i18n/de.json +0 -3
  12. package/src/modules/auth/i18n/en.json +0 -3
  13. package/src/modules/auth/i18n/es.json +0 -3
  14. package/src/modules/auth/i18n/ko.json +0 -3
  15. package/src/modules/auth/i18n/pl.json +0 -3
  16. package/src/modules/wms/di.ts +0 -6
  17. package/dist/generated/entities/availability_policy/index.js +0 -41
  18. package/dist/generated/entities/availability_policy/index.js.map +0 -7
  19. package/dist/modules/availability/acl.js +0 -16
  20. package/dist/modules/availability/acl.js.map +0 -7
  21. package/dist/modules/availability/api/check/route.js +0 -95
  22. package/dist/modules/availability/api/check/route.js.map +0 -7
  23. package/dist/modules/availability/api/openapi.js +0 -27
  24. package/dist/modules/availability/api/openapi.js.map +0 -7
  25. package/dist/modules/availability/api/policies/resolve-preview/route.js +0 -58
  26. package/dist/modules/availability/api/policies/resolve-preview/route.js.map +0 -7
  27. package/dist/modules/availability/api/policies/route.js +0 -188
  28. package/dist/modules/availability/api/policies/route.js.map +0 -7
  29. package/dist/modules/availability/backend/availability/check/page.js +0 -185
  30. package/dist/modules/availability/backend/availability/check/page.js.map +0 -7
  31. package/dist/modules/availability/backend/availability/check/page.meta.js +0 -16
  32. package/dist/modules/availability/backend/availability/check/page.meta.js.map +0 -7
  33. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js +0 -79
  34. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js.map +0 -7
  35. package/dist/modules/availability/backend/availability/policies/ScopeFields.js +0 -53
  36. package/dist/modules/availability/backend/availability/policies/ScopeFields.js.map +0 -7
  37. package/dist/modules/availability/backend/availability/policies/[id]/page.js +0 -152
  38. package/dist/modules/availability/backend/availability/policies/[id]/page.js.map +0 -7
  39. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js +0 -12
  40. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js.map +0 -7
  41. package/dist/modules/availability/backend/availability/policies/create/page.js +0 -94
  42. package/dist/modules/availability/backend/availability/policies/create/page.js.map +0 -7
  43. package/dist/modules/availability/backend/availability/policies/create/page.meta.js +0 -12
  44. package/dist/modules/availability/backend/availability/policies/create/page.meta.js.map +0 -7
  45. package/dist/modules/availability/backend/availability/policies/formGroups.js +0 -75
  46. package/dist/modules/availability/backend/availability/policies/formGroups.js.map +0 -7
  47. package/dist/modules/availability/backend/availability/policies/page.js +0 -176
  48. package/dist/modules/availability/backend/availability/policies/page.js.map +0 -7
  49. package/dist/modules/availability/backend/availability/policies/page.meta.js +0 -16
  50. package/dist/modules/availability/backend/availability/policies/page.meta.js.map +0 -7
  51. package/dist/modules/availability/commands/policies.js +0 -312
  52. package/dist/modules/availability/commands/policies.js.map +0 -7
  53. package/dist/modules/availability/commands/scope.js +0 -19
  54. package/dist/modules/availability/commands/scope.js.map +0 -7
  55. package/dist/modules/availability/data/entities.js +0 -95
  56. package/dist/modules/availability/data/entities.js.map +0 -7
  57. package/dist/modules/availability/data/validators.js +0 -53
  58. package/dist/modules/availability/data/validators.js.map +0 -7
  59. package/dist/modules/availability/di.js +0 -41
  60. package/dist/modules/availability/di.js.map +0 -7
  61. package/dist/modules/availability/events.js +0 -18
  62. package/dist/modules/availability/events.js.map +0 -7
  63. package/dist/modules/availability/index.js +0 -15
  64. package/dist/modules/availability/index.js.map +0 -7
  65. package/dist/modules/availability/lib/policyResolution.js +0 -155
  66. package/dist/modules/availability/lib/policyResolution.js.map +0 -7
  67. package/dist/modules/availability/lib/tryResolve.js +0 -11
  68. package/dist/modules/availability/lib/tryResolve.js.map +0 -7
  69. package/dist/modules/availability/migrations/Migration20260922095844_availability.js +0 -16
  70. package/dist/modules/availability/migrations/Migration20260922095844_availability.js.map +0 -7
  71. package/dist/modules/availability/setup.js +0 -12
  72. package/dist/modules/availability/setup.js.map +0 -7
  73. package/dist/modules/wms/lib/availabilityCache.js +0 -85
  74. package/dist/modules/wms/lib/availabilityCache.js.map +0 -7
  75. package/dist/modules/wms/lib/availabilityCalculation.js +0 -178
  76. package/dist/modules/wms/lib/availabilityCalculation.js.map +0 -7
  77. package/dist/modules/wms/lib/availabilityProvider.js +0 -14
  78. package/dist/modules/wms/lib/availabilityProvider.js.map +0 -7
  79. package/dist/modules/wms/lib/sqlInClause.js +0 -9
  80. package/dist/modules/wms/lib/sqlInClause.js.map +0 -7
  81. package/dist/modules/wms/lib/tryResolve.js +0 -11
  82. package/dist/modules/wms/lib/tryResolve.js.map +0 -7
  83. package/dist/modules/wms/subscribers/invalidate-availability-cache.js +0 -16
  84. package/dist/modules/wms/subscribers/invalidate-availability-cache.js.map +0 -7
  85. package/generated/entities/availability_policy/index.ts +0 -19
  86. package/src/modules/availability/acl.ts +0 -12
  87. package/src/modules/availability/api/check/route.ts +0 -114
  88. package/src/modules/availability/api/openapi.ts +0 -28
  89. package/src/modules/availability/api/policies/resolve-preview/route.ts +0 -67
  90. package/src/modules/availability/api/policies/route.ts +0 -224
  91. package/src/modules/availability/backend/availability/check/page.meta.ts +0 -12
  92. package/src/modules/availability/backend/availability/check/page.tsx +0 -223
  93. package/src/modules/availability/backend/availability/policies/ResolutionPreviewPanel.tsx +0 -106
  94. package/src/modules/availability/backend/availability/policies/ScopeFields.tsx +0 -55
  95. package/src/modules/availability/backend/availability/policies/[id]/page.meta.ts +0 -8
  96. package/src/modules/availability/backend/availability/policies/[id]/page.tsx +0 -182
  97. package/src/modules/availability/backend/availability/policies/create/page.meta.ts +0 -8
  98. package/src/modules/availability/backend/availability/policies/create/page.tsx +0 -105
  99. package/src/modules/availability/backend/availability/policies/formGroups.ts +0 -77
  100. package/src/modules/availability/backend/availability/policies/page.meta.ts +0 -12
  101. package/src/modules/availability/backend/availability/policies/page.tsx +0 -196
  102. package/src/modules/availability/commands/policies.ts +0 -358
  103. package/src/modules/availability/commands/scope.ts +0 -26
  104. package/src/modules/availability/data/entities.ts +0 -92
  105. package/src/modules/availability/data/validators.ts +0 -70
  106. package/src/modules/availability/di.ts +0 -54
  107. package/src/modules/availability/events.ts +0 -17
  108. package/src/modules/availability/i18n/de.json +0 -81
  109. package/src/modules/availability/i18n/en.json +0 -81
  110. package/src/modules/availability/i18n/es.json +0 -81
  111. package/src/modules/availability/i18n/ko.json +0 -81
  112. package/src/modules/availability/i18n/pl.json +0 -81
  113. package/src/modules/availability/index.ts +0 -13
  114. package/src/modules/availability/lib/policyResolution.ts +0 -264
  115. package/src/modules/availability/lib/tryResolve.ts +0 -8
  116. package/src/modules/availability/migrations/.snapshot-open-mercato.json +0 -375
  117. package/src/modules/availability/migrations/Migration20260922095844_availability.ts +0 -13
  118. package/src/modules/availability/setup.ts +0 -10
  119. package/src/modules/wms/lib/availabilityCache.ts +0 -136
  120. package/src/modules/wms/lib/availabilityCalculation.ts +0 -289
  121. package/src/modules/wms/lib/availabilityProvider.ts +0 -29
  122. package/src/modules/wms/lib/sqlInClause.ts +0 -21
  123. package/src/modules/wms/lib/tryResolve.ts +0 -8
  124. 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
- }