@open-mercato/core 0.4.6-develop-f7d3079656 → 0.4.6-develop-0861f05ea9

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 (107) hide show
  1. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +17 -154
  2. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +3 -3
  3. package/dist/modules/currencies/backend/exchange-rates/create/page.js +14 -152
  4. package/dist/modules/currencies/backend/exchange-rates/create/page.js.map +2 -2
  5. package/dist/modules/currencies/lib/exchangeRateFormConfig.js +167 -0
  6. package/dist/modules/currencies/lib/exchangeRateFormConfig.js.map +7 -0
  7. package/dist/modules/customers/api/dashboard/widgets/utils.js +1 -34
  8. package/dist/modules/customers/api/dashboard/widgets/utils.js.map +2 -2
  9. package/dist/modules/customers/commands/activities.js +3 -8
  10. package/dist/modules/customers/commands/activities.js.map +2 -2
  11. package/dist/modules/customers/commands/comments.js +2 -8
  12. package/dist/modules/customers/commands/comments.js.map +2 -2
  13. package/dist/modules/dashboards/lib/widgetScope.js +38 -0
  14. package/dist/modules/dashboards/lib/widgetScope.js.map +7 -0
  15. package/dist/modules/entities/lib/makeActivityRoute.js +265 -0
  16. package/dist/modules/entities/lib/makeActivityRoute.js.map +7 -0
  17. package/dist/modules/resources/api/activities.js +24 -232
  18. package/dist/modules/resources/api/activities.js.map +2 -2
  19. package/dist/modules/resources/commands/activities.js +3 -8
  20. package/dist/modules/resources/commands/activities.js.map +2 -2
  21. package/dist/modules/resources/commands/comments.js +2 -8
  22. package/dist/modules/resources/commands/comments.js.map +2 -2
  23. package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +27 -182
  24. package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js.map +2 -2
  25. package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js +28 -183
  26. package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +2 -2
  27. package/dist/modules/sales/api/order-line-statuses/route.js +15 -194
  28. package/dist/modules/sales/api/order-line-statuses/route.js.map +2 -2
  29. package/dist/modules/sales/api/order-lines/route.js +15 -281
  30. package/dist/modules/sales/api/order-lines/route.js.map +2 -2
  31. package/dist/modules/sales/api/order-statuses/route.js +15 -194
  32. package/dist/modules/sales/api/order-statuses/route.js.map +2 -2
  33. package/dist/modules/sales/api/payment-statuses/route.js +15 -194
  34. package/dist/modules/sales/api/payment-statuses/route.js.map +2 -2
  35. package/dist/modules/sales/api/quote-lines/route.js +15 -279
  36. package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
  37. package/dist/modules/sales/api/shipment-statuses/route.js +15 -194
  38. package/dist/modules/sales/api/shipment-statuses/route.js.map +2 -2
  39. package/dist/modules/sales/components/PaymentMethodsSettings.js +3 -84
  40. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  41. package/dist/modules/sales/components/ProviderFieldInput.js +86 -0
  42. package/dist/modules/sales/components/ProviderFieldInput.js.map +7 -0
  43. package/dist/modules/sales/components/ShippingMethodsSettings.js +3 -82
  44. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  45. package/dist/modules/sales/lib/makeSalesLineRoute.js +308 -0
  46. package/dist/modules/sales/lib/makeSalesLineRoute.js.map +7 -0
  47. package/dist/modules/sales/lib/makeStatusDictionaryRoute.js +206 -0
  48. package/dist/modules/sales/lib/makeStatusDictionaryRoute.js.map +7 -0
  49. package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js +178 -0
  50. package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js.map +7 -0
  51. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +1 -39
  52. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
  53. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +1 -39
  54. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
  55. package/dist/modules/sales/widgets/dashboard/shared.js +46 -0
  56. package/dist/modules/sales/widgets/dashboard/shared.js.map +7 -0
  57. package/dist/modules/staff/api/activities.js +24 -232
  58. package/dist/modules/staff/api/activities.js.map +2 -2
  59. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +14 -34
  60. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  61. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +15 -34
  62. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  63. package/dist/modules/staff/commands/activities.js +3 -8
  64. package/dist/modules/staff/commands/activities.js.map +2 -2
  65. package/dist/modules/staff/commands/comments.js +2 -8
  66. package/dist/modules/staff/commands/comments.js.map +2 -2
  67. package/dist/modules/staff/lib/leaveRequestHelpers.js +41 -0
  68. package/dist/modules/staff/lib/leaveRequestHelpers.js.map +7 -0
  69. package/package.json +2 -2
  70. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +20 -180
  71. package/src/modules/currencies/backend/exchange-rates/create/page.tsx +16 -175
  72. package/src/modules/currencies/lib/exchangeRateFormConfig.ts +200 -0
  73. package/src/modules/customers/api/dashboard/widgets/utils.ts +1 -53
  74. package/src/modules/customers/commands/activities.ts +2 -8
  75. package/src/modules/customers/commands/comments.ts +2 -8
  76. package/src/modules/dashboards/i18n/de.json +3 -0
  77. package/src/modules/dashboards/i18n/en.json +3 -0
  78. package/src/modules/dashboards/i18n/es.json +3 -0
  79. package/src/modules/dashboards/i18n/pl.json +3 -0
  80. package/src/modules/dashboards/lib/widgetScope.ts +53 -0
  81. package/src/modules/entities/lib/makeActivityRoute.ts +327 -0
  82. package/src/modules/resources/api/activities.ts +25 -269
  83. package/src/modules/resources/commands/activities.ts +2 -7
  84. package/src/modules/resources/commands/comments.ts +2 -8
  85. package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +29 -244
  86. package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +30 -245
  87. package/src/modules/sales/api/order-line-statuses/route.ts +16 -209
  88. package/src/modules/sales/api/order-lines/route.ts +16 -300
  89. package/src/modules/sales/api/order-statuses/route.ts +16 -209
  90. package/src/modules/sales/api/payment-statuses/route.ts +16 -209
  91. package/src/modules/sales/api/quote-lines/route.ts +16 -298
  92. package/src/modules/sales/api/shipment-statuses/route.ts +16 -209
  93. package/src/modules/sales/components/PaymentMethodsSettings.tsx +3 -88
  94. package/src/modules/sales/components/ProviderFieldInput.tsx +85 -0
  95. package/src/modules/sales/components/ShippingMethodsSettings.tsx +3 -86
  96. package/src/modules/sales/lib/makeSalesLineRoute.ts +345 -0
  97. package/src/modules/sales/lib/makeStatusDictionaryRoute.ts +229 -0
  98. package/src/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.ts +247 -0
  99. package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +7 -50
  100. package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +7 -49
  101. package/src/modules/sales/widgets/dashboard/shared.ts +44 -0
  102. package/src/modules/staff/api/activities.ts +25 -269
  103. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +15 -69
  104. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +16 -65
  105. package/src/modules/staff/commands/activities.ts +2 -7
  106. package/src/modules/staff/commands/comments.ts +2 -8
  107. package/src/modules/staff/lib/leaveRequestHelpers.ts +78 -0
@@ -1,224 +1,7 @@
1
- import { createHash } from 'node:crypto'
2
- import { NextResponse } from 'next/server'
3
1
  import { z } from 'zod'
4
- import type { FilterQuery } from '@mikro-orm/core'
5
- import type { CacheStrategy } from '@open-mercato/cache'
6
- import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
7
- import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
8
- import { runWithCacheTenant } from '@open-mercato/cache'
9
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
10
- import { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'
11
- import { resolveDateRange } from '@open-mercato/ui/backend/date-range'
12
2
  import { SalesQuote } from '../../../../data/entities'
13
- import { extractCustomerName, type DatePeriodOption } from '../helpers'
14
- import { resolveWidgetScope, type WidgetScopeContext } from '../../../../../customers/api/dashboard/widgets/utils'
15
-
16
- const WIDGET_CACHE_TTL = 120_000
17
- const WIDGET_CACHE_SEGMENT_TTL = 86_400_000
18
- const WIDGET_CACHE_SEGMENT_KEY = 'widget-data:__segment__'
19
- const WIDGET_CACHE_TAGS = ['widget-data', 'widget-data:sales:quotes']
20
- const WIDGET_CACHE_ID = 'sales:new-quotes'
21
-
22
- const querySchema = z.object({
23
- limit: z.coerce.number().min(1).max(20).default(5),
24
- datePeriod: z.enum(['last24h', 'last7d', 'last30d', 'custom']).default('last24h'),
25
- customFrom: z.string().optional(),
26
- customTo: z.string().optional(),
27
- tenantId: z.string().uuid().optional(),
28
- organizationId: z.string().uuid().optional(),
29
- })
30
-
31
- export const metadata = {
32
- GET: { requireAuth: true, requireFeatures: ['dashboards.view', 'sales.widgets.new-quotes'] },
33
- }
34
-
35
- type WidgetContext = WidgetScopeContext & {
36
- limit: number
37
- datePeriod: DatePeriodOption
38
- customFrom?: string
39
- customTo?: string
40
- }
41
-
42
- type NewQuotesWidgetResponse = {
43
- items: Array<{
44
- id: string
45
- quoteNumber: string
46
- status: string | null
47
- customerName: string | null
48
- customerEntityId: string | null
49
- validFrom: string | null
50
- validUntil: string | null
51
- netAmount: string
52
- grossAmount: string
53
- currency: string | null
54
- createdAt: string
55
- convertedOrderId: string | null
56
- }>
57
- total: number
58
- dateRange: {
59
- from: string
60
- to: string
61
- }
62
- }
63
-
64
- function normalizeOrganizationIds(organizationIds: string[] | null): string[] | null {
65
- if (organizationIds === null) return null
66
- const set = new Set(organizationIds)
67
- return Array.from(set).sort((a, b) => a.localeCompare(b))
68
- }
69
-
70
- function buildCacheKey(params: {
71
- tenantId: string
72
- organizationIds: string[] | null
73
- limit: number
74
- datePeriod: DatePeriodOption
75
- customFrom?: string
76
- customTo?: string
77
- }): string {
78
- const hash = createHash('sha256')
79
- hash.update(
80
- JSON.stringify({
81
- widget: WIDGET_CACHE_ID,
82
- ...params,
83
- organizationIds: normalizeOrganizationIds(params.organizationIds),
84
- })
85
- )
86
- return `widget-data:${hash.digest('hex').slice(0, 16)}`
87
- }
88
-
89
- async function resolveContext(req: Request, translate: (key: string, fallback?: string) => string): Promise<WidgetContext> {
90
- const url = new URL(req.url)
91
- const rawQuery: Record<string, string> = {}
92
- for (const [key, value] of url.searchParams.entries()) rawQuery[key] = value
93
- const parsed = querySchema.safeParse(rawQuery)
94
- if (!parsed.success) {
95
- throw new CrudHttpError(400, { error: translate('sales.errors.invalid_query', 'Invalid query parameters') })
96
- }
97
-
98
- const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {
99
- tenantId: parsed.data.tenantId ?? null,
100
- organizationId: parsed.data.organizationId ?? null,
101
- })
102
-
103
- return {
104
- container,
105
- em,
106
- tenantId,
107
- organizationIds,
108
- limit: parsed.data.limit,
109
- datePeriod: parsed.data.datePeriod,
110
- customFrom: parsed.data.customFrom,
111
- customTo: parsed.data.customTo,
112
- }
113
- }
114
-
115
- export async function GET(req: Request) {
116
- const { translate } = await resolveTranslations()
117
- try {
118
- const { container, em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(
119
- req,
120
- translate
121
- )
122
- const range = (() => {
123
- if (datePeriod === 'custom') {
124
- const from = customFrom ? new Date(customFrom) : new Date(0)
125
- const to = customTo ? new Date(customTo) : new Date()
126
- return { start: from, end: to }
127
- }
128
- const preset = datePeriod === 'last7d' ? 'last_7_days' : datePeriod === 'last30d' ? 'last_30_days' : 'today'
129
- return resolveDateRange(preset)
130
- })()
131
-
132
- let cache: CacheStrategy | null = null
133
- try {
134
- cache = container.resolve<CacheStrategy>('cache')
135
- } catch {
136
- cache = null
137
- }
138
-
139
-
140
-
141
- const cacheKey = buildCacheKey({ tenantId, organizationIds, limit, datePeriod, customFrom, customTo })
142
- const tenantScope = tenantId ?? null
143
-
144
- if (cache) {
145
- try {
146
- const cached = await runWithCacheTenant(tenantScope, () => cache!.get(cacheKey))
147
- if (cached && typeof cached === 'object' && 'items' in (cached as object)) {
148
- return NextResponse.json(cached)
149
- }
150
- } catch {
151
- }
152
- }
153
-
154
- const where: FilterQuery<SalesQuote> = {
155
- tenantId,
156
- deletedAt: null,
157
- createdAt: { $gte: range.start, $lte: range.end },
158
- }
159
-
160
- if (Array.isArray(organizationIds)) {
161
- const unique = Array.from(new Set(organizationIds))
162
- where.organizationId = unique.length === 1 ? unique[0] : { $in: unique }
163
- }
164
-
165
- const organizationIdScope = Array.isArray(organizationIds) && organizationIds.length === 1 ? organizationIds[0] : null
166
- const [quotes, total] = await findAndCountWithDecryption(
167
- em,
168
- SalesQuote,
169
- where,
170
- {
171
- limit,
172
- orderBy: { createdAt: 'desc' as const },
173
- },
174
- { tenantId, organizationId: organizationIdScope },
175
- )
176
-
177
- const items = quotes.map((quote) => ({
178
- id: quote.id,
179
- quoteNumber: quote.quoteNumber,
180
- status: quote.status ?? null,
181
- customerName: extractCustomerName(quote.customerSnapshot) ?? null,
182
- customerEntityId: quote.customerEntityId ?? null,
183
- validFrom: quote.validFrom ? quote.validFrom.toISOString() : null,
184
- validUntil: quote.validUntil ? quote.validUntil.toISOString() : null,
185
- netAmount: quote.grandTotalNetAmount ?? '0',
186
- grossAmount: quote.grandTotalGrossAmount ?? '0',
187
- currency: quote.currencyCode ?? null,
188
- createdAt: quote.createdAt.toISOString(),
189
- convertedOrderId: quote.convertedOrderId ?? null,
190
- }))
191
-
192
- const response: NewQuotesWidgetResponse = {
193
- items,
194
- total,
195
- dateRange: { from: range.start.toISOString(), to: range.end.toISOString() },
196
- }
197
-
198
- if (cache) {
199
- try {
200
- await runWithCacheTenant(tenantScope, () => cache!.set(cacheKey, response, { ttl: WIDGET_CACHE_TTL, tags: WIDGET_CACHE_TAGS }))
201
- await runWithCacheTenant(tenantScope, () => cache!.set(
202
- WIDGET_CACHE_SEGMENT_KEY,
203
- { updatedAt: response.dateRange.to },
204
- { ttl: WIDGET_CACHE_SEGMENT_TTL, tags: ['widget-data'] },
205
- ))
206
- } catch {
207
- }
208
- }
209
-
210
- return NextResponse.json(response)
211
- } catch (err) {
212
- if (err instanceof CrudHttpError) {
213
- return NextResponse.json(err.body, { status: err.status })
214
- }
215
- console.error('sales.widgets.newQuotes failed', err)
216
- return NextResponse.json(
217
- { error: translate('sales.widgets.newQuotes.error', 'Failed to load quotes') },
218
- { status: 500 },
219
- )
220
- }
221
- }
3
+ import { extractCustomerName } from '../helpers'
4
+ import { makeDashboardWidgetRoute } from '../../../../widgets/dashboard/makeDashboardWidgetRoute'
222
5
 
223
6
  const quoteItemSchema = z.object({
224
7
  id: z.string().uuid(),
@@ -235,32 +18,34 @@ const quoteItemSchema = z.object({
235
18
  convertedOrderId: z.string().uuid().nullable(),
236
19
  })
237
20
 
238
- const responseSchema = z.object({
239
- items: z.array(quoteItemSchema),
240
- total: z.number(),
241
- dateRange: z.object({
242
- from: z.string(),
243
- to: z.string(),
21
+ const { GET, metadata, openApi } = makeDashboardWidgetRoute({
22
+ entity: SalesQuote,
23
+ cacheId: 'sales:new-quotes',
24
+ cacheTags: ['widget-data:sales:quotes'],
25
+ feature: 'sales.widgets.new-quotes',
26
+ itemSchema: quoteItemSchema,
27
+ errorPrefix: 'sales.widgets.newQuotes',
28
+ openApi: {
29
+ summary: 'New quotes dashboard widget',
30
+ description: 'Fetches recently created sales quotes for the dashboard widget with a configurable date period.',
31
+ getSummary: 'Fetch recently created sales quotes',
32
+ itemDescription: 'List of recent quotes',
33
+ errorFallback: 'Failed to load quotes',
34
+ },
35
+ mapItem: (quote) => ({
36
+ id: quote.id as string,
37
+ quoteNumber: quote.quoteNumber as string,
38
+ status: (quote.status as string) ?? null,
39
+ customerName: extractCustomerName(quote.customerSnapshot) ?? null,
40
+ customerEntityId: (quote.customerEntityId as string) ?? null,
41
+ validFrom: quote.validFrom ? (quote.validFrom as Date).toISOString() : null,
42
+ validUntil: quote.validUntil ? (quote.validUntil as Date).toISOString() : null,
43
+ netAmount: (quote.grandTotalNetAmount as string) ?? '0',
44
+ grossAmount: (quote.grandTotalGrossAmount as string) ?? '0',
45
+ currency: (quote.currencyCode as string) ?? null,
46
+ createdAt: quote.createdAt ? (quote.createdAt as Date).toISOString() : new Date().toISOString(),
47
+ convertedOrderId: (quote.convertedOrderId as string) ?? null,
244
48
  }),
245
49
  })
246
50
 
247
- const widgetErrorSchema = z.object({ error: z.string() })
248
-
249
- export const openApi: OpenApiRouteDoc = {
250
- tag: 'Sales',
251
- summary: 'New quotes dashboard widget',
252
- description: 'Fetches recently created sales quotes for the dashboard widget with a configurable date period.',
253
- methods: {
254
- GET: {
255
- summary: 'Fetch recently created sales quotes',
256
- query: querySchema,
257
- responses: [{ status: 200, description: 'List of recent quotes', schema: responseSchema }],
258
- errors: [
259
- { status: 400, description: 'Invalid query parameters', schema: widgetErrorSchema },
260
- { status: 401, description: 'Unauthorized', schema: widgetErrorSchema },
261
- { status: 403, description: 'Forbidden', schema: widgetErrorSchema },
262
- { status: 500, description: 'Widget failed to load', schema: widgetErrorSchema },
263
- ],
264
- },
265
- },
266
- }
51
+ export { GET, metadata, openApi }
@@ -1,214 +1,21 @@
1
- import { z } from 'zod'
2
- import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
3
- import type { EntityManager } from '@mikro-orm/postgresql'
4
- import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
- import { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'
7
1
  import { E } from '#generated/entities.ids.generated'
8
2
  import * as F from '#generated/entities/dictionary_entry'
9
- import { statusDictionaryCreateSchema, statusDictionaryUpdateSchema } from '../../data/validators'
10
- import { getSalesDictionaryDefinition, ensureSalesDictionary, type SalesDictionaryKind } from '../../lib/dictionaries'
11
- import { parseScopedCommandInput, resolveCrudRecordId } from '../utils'
12
- import {
13
- createPagedListResponseSchema,
14
- createSalesCrudOpenApi,
15
- defaultDeleteRequestSchema,
16
- } from '../openapi'
17
- import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
18
-
19
- const rawBodySchema = z.object({}).passthrough()
20
-
21
- const listSchema = z
22
- .object({
23
- page: z.coerce.number().min(1).default(1),
24
- pageSize: z.coerce.number().min(1).max(100).default(50),
25
- search: z.string().optional(),
26
- sortField: z.string().optional(),
27
- sortDir: z.enum(['asc', 'desc']).optional(),
28
- })
29
- .passthrough()
30
-
31
- const kind: SalesDictionaryKind = 'order-line-status'
32
- const definition = getSalesDictionaryDefinition(kind)
33
-
34
- export const metadata = {
35
- GET: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
36
- POST: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
37
- PUT: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
38
- DELETE: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
39
- }
40
-
41
- const dictionaryItemSchema = z.object({
42
- id: z.string().uuid(),
43
- value: z.string(),
44
- label: z.string().nullable(),
45
- color: z.string().nullable(),
46
- icon: z.string().nullable(),
47
- organizationId: z.string().uuid().nullable(),
48
- tenantId: z.string().uuid().nullable(),
49
- createdAt: z.string(),
50
- updatedAt: z.string(),
51
- })
52
-
53
- const dictionaryListResponseSchema = createPagedListResponseSchema(dictionaryItemSchema)
54
-
55
- const normalizeId = (value: unknown): string | null => {
56
- if (typeof value !== 'string') return null
57
- const trimmed = value.trim()
58
- return trimmed.length > 0 ? trimmed : null
59
- }
60
-
61
- async function resolveDictionaryContext(ctx: any): Promise<{ dictionaryId: string; organizationId: string | null }> {
62
- if (!ctx.auth || !ctx.auth.tenantId) {
63
- throw new CrudHttpError(401, { error: 'Tenant context is required.' })
64
- }
65
- const em = ctx.container.resolve('em') as EntityManager
66
- const tenantId: string = ctx.auth.tenantId
67
- const candidateOrgIds = new Set<string>()
68
- const pushCandidate = (value: unknown) => {
69
- const normalized = normalizeId(value)
70
- if (normalized) candidateOrgIds.add(normalized)
71
- }
72
- pushCandidate(ctx.selectedOrganizationId)
73
- pushCandidate(ctx.auth.orgId ?? null)
74
- const scope = ctx.organizationScope
75
- if (scope) {
76
- if (Array.isArray(scope.filterIds)) {
77
- for (const id of scope.filterIds) pushCandidate(id)
78
- }
79
- if (Array.isArray(scope.allowedIds)) {
80
- for (const id of scope.allowedIds) pushCandidate(id)
81
- }
82
- }
83
-
84
- for (const orgId of candidateOrgIds) {
85
- const dictionary = await ensureSalesDictionary({
86
- em,
87
- tenantId,
88
- organizationId: orgId,
89
- kind,
90
- })
91
- if (dictionary) {
92
- return { dictionaryId: dictionary.id, organizationId: orgId }
93
- }
94
- }
95
-
96
- const fallback = await em.findOne(
97
- Dictionary,
98
- {
99
- tenantId,
100
- key: definition.key,
101
- deletedAt: null,
102
- },
103
- { orderBy: { createdAt: 'asc' } },
104
- )
105
- if (fallback) {
106
- return { dictionaryId: fallback.id, organizationId: fallback.organizationId }
107
- }
108
- throw new CrudHttpError(400, { error: 'Organization context is required.' })
109
- }
110
-
111
- const crud = makeCrudRoute({
112
- metadata,
113
- orm: {
114
- entity: DictionaryEntry,
115
- idField: 'id',
116
- orgField: 'organizationId',
117
- tenantField: 'tenantId',
118
- softDeleteField: null,
3
+ import { makeStatusDictionaryRoute } from '../../lib/makeStatusDictionaryRoute'
4
+
5
+ const route = makeStatusDictionaryRoute({
6
+ kind: 'order-line-status',
7
+ entityId: E.dictionaries.dictionary_entry,
8
+ fieldConstants: F,
9
+ openApi: {
10
+ resourceName: 'Order line status',
11
+ pluralName: 'Order line statuses',
12
+ description: 'Manage custom order line statuses available for sales documents.',
119
13
  },
120
- list: {
121
- schema: listSchema,
122
- entityId: E.dictionaries.dictionary_entry,
123
- fields: [
124
- F.id,
125
- F.value,
126
- F.label,
127
- F.color,
128
- F.icon,
129
- F.organization_id,
130
- F.tenant_id,
131
- F.created_at,
132
- F.updated_at,
133
- ],
134
- sortFieldMap: {
135
- id: F.id,
136
- value: F.value,
137
- label: F.label,
138
- createdAt: F.created_at,
139
- updatedAt: F.updated_at,
140
- },
141
- buildFilters: async (query, ctx) => {
142
- const { dictionaryId } = await resolveDictionaryContext(ctx)
143
- const filters: Record<string, unknown> = {
144
- dictionary_id: dictionaryId,
145
- }
146
- if (query.search && query.search.trim().length > 0) {
147
- const term = `%${escapeLikePattern(query.search.trim())}%`
148
- filters.$or = [
149
- { [F.value]: { $ilike: term } },
150
- { [F.label]: { $ilike: term } },
151
- ]
152
- }
153
- return filters
154
- },
155
- transformItem: (item: any) => ({
156
- id: item.id,
157
- value: item.value,
158
- label: item.label,
159
- color: item.color ?? null,
160
- icon: item.icon ?? null,
161
- organizationId: item.organization_id ?? null,
162
- tenantId: item.tenant_id ?? null,
163
- createdAt: item.created_at,
164
- updatedAt: item.updated_at,
165
- }),
166
- },
167
- actions: {
168
- create: {
169
- commandId: `${definition.commandPrefix}.create`,
170
- schema: rawBodySchema,
171
- mapInput: async ({ raw, ctx }) => {
172
- const { translate } = await resolveTranslations()
173
- return parseScopedCommandInput(statusDictionaryCreateSchema, raw ?? {}, ctx, translate)
174
- },
175
- response: ({ result }) => ({ id: result?.entryId ?? null }),
176
- status: 201,
177
- },
178
- update: {
179
- commandId: `${definition.commandPrefix}.update`,
180
- schema: rawBodySchema,
181
- mapInput: async ({ raw, ctx }) => {
182
- const { translate } = await resolveTranslations()
183
- return parseScopedCommandInput(statusDictionaryUpdateSchema, raw ?? {}, ctx, translate)
184
- },
185
- response: () => ({ ok: true }),
186
- },
187
- delete: {
188
- commandId: `${definition.commandPrefix}.delete`,
189
- schema: rawBodySchema,
190
- mapInput: async ({ parsed, ctx }) => {
191
- const { translate } = await resolveTranslations()
192
- const id = resolveCrudRecordId(parsed, ctx, translate)
193
- return { id }
194
- },
195
- response: () => ({ ok: true }),
196
- },
197
- },
198
- })
199
-
200
- export const openApi = createSalesCrudOpenApi({
201
- resourceName: 'Order line status',
202
- pluralName: 'Order line statuses',
203
- description: 'Manage custom order line statuses available for sales documents.',
204
- querySchema: listSchema,
205
- listResponseSchema: dictionaryListResponseSchema,
206
- create: { schema: statusDictionaryCreateSchema },
207
- update: { schema: statusDictionaryUpdateSchema },
208
- del: { schema: defaultDeleteRequestSchema },
209
14
  })
210
15
 
211
- export const GET = crud.GET
212
- export const POST = crud.POST
213
- export const PUT = crud.PUT
214
- export const DELETE = crud.DELETE
16
+ export const metadata = route.metadata
17
+ export const openApi = route.openApi
18
+ export const GET = route.GET
19
+ export const POST = route.POST
20
+ export const PUT = route.PUT
21
+ export const DELETE = route.DELETE