@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.
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +17 -154
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +3 -3
- package/dist/modules/currencies/backend/exchange-rates/create/page.js +14 -152
- package/dist/modules/currencies/backend/exchange-rates/create/page.js.map +2 -2
- package/dist/modules/currencies/lib/exchangeRateFormConfig.js +167 -0
- package/dist/modules/currencies/lib/exchangeRateFormConfig.js.map +7 -0
- package/dist/modules/customers/api/dashboard/widgets/utils.js +1 -34
- package/dist/modules/customers/api/dashboard/widgets/utils.js.map +2 -2
- package/dist/modules/customers/commands/activities.js +3 -8
- package/dist/modules/customers/commands/activities.js.map +2 -2
- package/dist/modules/customers/commands/comments.js +2 -8
- package/dist/modules/customers/commands/comments.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetScope.js +38 -0
- package/dist/modules/dashboards/lib/widgetScope.js.map +7 -0
- package/dist/modules/entities/lib/makeActivityRoute.js +265 -0
- package/dist/modules/entities/lib/makeActivityRoute.js.map +7 -0
- package/dist/modules/resources/api/activities.js +24 -232
- package/dist/modules/resources/api/activities.js.map +2 -2
- package/dist/modules/resources/commands/activities.js +3 -8
- package/dist/modules/resources/commands/activities.js.map +2 -2
- package/dist/modules/resources/commands/comments.js +2 -8
- package/dist/modules/resources/commands/comments.js.map +2 -2
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +27 -182
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js.map +2 -2
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js +28 -183
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +2 -2
- package/dist/modules/sales/api/order-line-statuses/route.js +15 -194
- package/dist/modules/sales/api/order-line-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +15 -281
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/api/order-statuses/route.js +15 -194
- package/dist/modules/sales/api/order-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/payment-statuses/route.js +15 -194
- package/dist/modules/sales/api/payment-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/quote-lines/route.js +15 -279
- package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
- package/dist/modules/sales/api/shipment-statuses/route.js +15 -194
- package/dist/modules/sales/api/shipment-statuses/route.js.map +2 -2
- package/dist/modules/sales/components/PaymentMethodsSettings.js +3 -84
- package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/ProviderFieldInput.js +86 -0
- package/dist/modules/sales/components/ProviderFieldInput.js.map +7 -0
- package/dist/modules/sales/components/ShippingMethodsSettings.js +3 -82
- package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js +308 -0
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +7 -0
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js +206 -0
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js +178 -0
- package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +1 -39
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +1 -39
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/shared.js +46 -0
- package/dist/modules/sales/widgets/dashboard/shared.js.map +7 -0
- package/dist/modules/staff/api/activities.js +24 -232
- package/dist/modules/staff/api/activities.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +14 -34
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +15 -34
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/commands/activities.js +3 -8
- package/dist/modules/staff/commands/activities.js.map +2 -2
- package/dist/modules/staff/commands/comments.js +2 -8
- package/dist/modules/staff/commands/comments.js.map +2 -2
- package/dist/modules/staff/lib/leaveRequestHelpers.js +41 -0
- package/dist/modules/staff/lib/leaveRequestHelpers.js.map +7 -0
- package/package.json +2 -2
- package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +20 -180
- package/src/modules/currencies/backend/exchange-rates/create/page.tsx +16 -175
- package/src/modules/currencies/lib/exchangeRateFormConfig.ts +200 -0
- package/src/modules/customers/api/dashboard/widgets/utils.ts +1 -53
- package/src/modules/customers/commands/activities.ts +2 -8
- package/src/modules/customers/commands/comments.ts +2 -8
- package/src/modules/dashboards/i18n/de.json +3 -0
- package/src/modules/dashboards/i18n/en.json +3 -0
- package/src/modules/dashboards/i18n/es.json +3 -0
- package/src/modules/dashboards/i18n/pl.json +3 -0
- package/src/modules/dashboards/lib/widgetScope.ts +53 -0
- package/src/modules/entities/lib/makeActivityRoute.ts +327 -0
- package/src/modules/resources/api/activities.ts +25 -269
- package/src/modules/resources/commands/activities.ts +2 -7
- package/src/modules/resources/commands/comments.ts +2 -8
- package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +29 -244
- package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +30 -245
- package/src/modules/sales/api/order-line-statuses/route.ts +16 -209
- package/src/modules/sales/api/order-lines/route.ts +16 -300
- package/src/modules/sales/api/order-statuses/route.ts +16 -209
- package/src/modules/sales/api/payment-statuses/route.ts +16 -209
- package/src/modules/sales/api/quote-lines/route.ts +16 -298
- package/src/modules/sales/api/shipment-statuses/route.ts +16 -209
- package/src/modules/sales/components/PaymentMethodsSettings.tsx +3 -88
- package/src/modules/sales/components/ProviderFieldInput.tsx +85 -0
- package/src/modules/sales/components/ShippingMethodsSettings.tsx +3 -86
- package/src/modules/sales/lib/makeSalesLineRoute.ts +345 -0
- package/src/modules/sales/lib/makeStatusDictionaryRoute.ts +229 -0
- package/src/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.ts +247 -0
- package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +7 -50
- package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +7 -49
- package/src/modules/sales/widgets/dashboard/shared.ts +44 -0
- package/src/modules/staff/api/activities.ts +25 -269
- package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +15 -69
- package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +16 -65
- package/src/modules/staff/commands/activities.ts +2 -7
- package/src/modules/staff/commands/comments.ts +2 -8
- 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
|
|
14
|
-
import {
|
|
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
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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 {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
212
|
-
export const
|
|
213
|
-
export const
|
|
214
|
-
export const
|
|
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
|