@open-mercato/core 0.6.8-develop.7054.1.3c75040822 → 0.6.8-develop.7057.1.61440fc3bc
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/customers/api/people/check-phone/route.js +51 -9
- package/dist/modules/customers/api/people/check-phone/route.js.map +2 -2
- package/dist/modules/customers/lib/findPeopleByAddresses.js +1 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +2 -2
- package/dist/modules/eudr/commands/statements.js +0 -3
- package/dist/modules/eudr/commands/statements.js.map +2 -2
- package/dist/modules/eudr/data/validators.js +4 -1
- package/dist/modules/eudr/data/validators.js.map +2 -2
- package/dist/modules/warranty_claims/api/assignees/route.js +3 -3
- package/dist/modules/warranty_claims/api/assignees/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +12 -9
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/api/people/check-phone/route.ts +75 -10
- package/src/modules/customers/lib/findPeopleByAddresses.ts +8 -6
- package/src/modules/eudr/commands/statements.ts +0 -3
- package/src/modules/eudr/data/validators.ts +16 -1
- package/src/modules/warranty_claims/api/assignees/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/route.ts +12 -9
|
@@ -580,9 +580,6 @@ const createStatementCommand: CommandHandler<ScopedStatementCreateInput, Stateme
|
|
|
580
580
|
if (parsed.status !== undefined && parsed.status !== 'draft') {
|
|
581
581
|
throw new CrudHttpError(400, { error: 'eudr.errors.invalidTransition' })
|
|
582
582
|
}
|
|
583
|
-
if (parsed.referenceIssuedAt !== undefined) {
|
|
584
|
-
throw new CrudHttpError(400, { error: 'eudr.errors.referenceIssuedAtImmutable' })
|
|
585
|
-
}
|
|
586
583
|
|
|
587
584
|
const entityManager = (ctx.container.resolve('em') as EntityManager).fork()
|
|
588
585
|
let record!: EudrDueDiligenceStatement
|
|
@@ -46,6 +46,11 @@ export const GEOJSON_TYPES = ['Feature', 'FeatureCollection', 'Point', 'Polygon'
|
|
|
46
46
|
|
|
47
47
|
const uuid = () => z.string().uuid()
|
|
48
48
|
const geoJsonSizeLimit = 1_048_576
|
|
49
|
+
// Two conventions for server-managed fields on these entities: fields listed below
|
|
50
|
+
// are never advertised as create inputs, so a client sending a real value is rejected
|
|
51
|
+
// (`rejectServerComputedFields` tolerates a null/undefined no-op echo). A field that
|
|
52
|
+
// WAS advertised in the create schema — statements' `referenceIssuedAt` — is instead
|
|
53
|
+
// dropped from the create schema, so it is stripped rather than rejected (#5508).
|
|
49
54
|
const serverComputedSubmissionFields = ['completenessScore', 'missingFields'] as const
|
|
50
55
|
const serverComputedPlotFields = ['validationWarnings', 'computedArea'] as const
|
|
51
56
|
const serverComputedRiskAssessmentFields = ['countryRisks', 'overallTier', 'isSimplified', 'assessedByName'] as const
|
|
@@ -64,12 +69,16 @@ const orderSnapshotSchema = z.object({
|
|
|
64
69
|
orderNumber: z.string().max(255).optional().nullable(),
|
|
65
70
|
})
|
|
66
71
|
|
|
72
|
+
// A whole-object POST echoes server-computed fields back with their no-op value
|
|
73
|
+
// (a fresh draft's `submittedAt` is null), so tolerate a null/undefined echo and
|
|
74
|
+
// reject only a meaningful attempt to set server-owned state (#5508).
|
|
67
75
|
function rejectServerComputedFields(fields: readonly string[]): z.ZodType<unknown> {
|
|
68
76
|
return z.unknown().superRefine((input, context) => {
|
|
69
77
|
if (input === null || typeof input !== 'object' || Array.isArray(input)) return
|
|
70
78
|
const record = input as Record<string, unknown>
|
|
71
79
|
for (const field of fields) {
|
|
72
80
|
if (!(field in record)) continue
|
|
81
|
+
if (record[field] === null || record[field] === undefined) continue
|
|
73
82
|
context.addIssue({
|
|
74
83
|
code: z.ZodIssueCode.custom,
|
|
75
84
|
path: [field],
|
|
@@ -333,7 +342,13 @@ const statementBaseSchema = z.object({
|
|
|
333
342
|
notes: z.string().max(5000).optional().nullable(),
|
|
334
343
|
})
|
|
335
344
|
|
|
336
|
-
|
|
345
|
+
// referenceIssuedAt is server-managed (only set at the submitted->available
|
|
346
|
+
// transition) and is never a create input, so it is dropped from the create
|
|
347
|
+
// schema. A whole-object POST that echoes it (null or a value) is accepted and
|
|
348
|
+
// the field ignored, instead of a self-contradictory 400 (#5508).
|
|
349
|
+
export const statementCreateSchema = rejectServerComputedFields(serverComputedStatementFields).pipe(
|
|
350
|
+
statementBaseSchema.omit({ referenceIssuedAt: true }),
|
|
351
|
+
)
|
|
337
352
|
|
|
338
353
|
export const statementUpdateSchema = rejectServerComputedFields(serverComputedStatementFields).pipe(
|
|
339
354
|
z
|
|
@@ -40,12 +40,12 @@ export function parseRequestedIds(raw: string): string[] {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export async function GET(req: Request) {
|
|
43
|
+
const { translate } = await resolveTranslations()
|
|
43
44
|
try {
|
|
44
45
|
const url = new URL(req.url)
|
|
45
46
|
const parsedQuery = querySchema.parse(Object.fromEntries(url.searchParams))
|
|
46
47
|
const container = await createRequestContainer()
|
|
47
48
|
const auth = await getAuthFromRequest(req)
|
|
48
|
-
const { translate } = await resolveTranslations()
|
|
49
49
|
if (!auth || !auth.tenantId) {
|
|
50
50
|
throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
|
|
51
51
|
}
|
|
@@ -75,10 +75,10 @@ export async function GET(req: Request) {
|
|
|
75
75
|
return NextResponse.json(error.body, { status: error.status })
|
|
76
76
|
}
|
|
77
77
|
if (error instanceof z.ZodError) {
|
|
78
|
-
return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
78
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
79
79
|
}
|
|
80
80
|
logger.error('[warranty_claims] Failed to resolve assignee display names', { error })
|
|
81
|
-
return NextResponse.json({ error: 'warranty_claims.errors.load_failed' }, { status: 500 })
|
|
81
|
+
return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data.') }, { status: 500 })
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -153,12 +153,13 @@ function serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[])
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
|
|
156
|
+
const { translate } = await resolveTranslations()
|
|
156
157
|
const auth = await getCustomerAuthFromRequest(req)
|
|
157
158
|
if (!auth) {
|
|
158
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })
|
|
159
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
159
160
|
}
|
|
160
161
|
if (!auth.customerEntityId) {
|
|
161
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
|
|
162
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.customerAccountNotLinked', 'Customer account is not linked to a customer record') }, { status: 403 })
|
|
162
163
|
}
|
|
163
164
|
const container = await createRequestContainer()
|
|
164
165
|
const em = container.resolve('em') as EntityManager
|
|
@@ -264,6 +265,7 @@ async function validatePortalOrderOwnership(
|
|
|
264
265
|
context: PortalContext,
|
|
265
266
|
input: PortalIntakeInput,
|
|
266
267
|
): Promise<OrderValidationResult | Response> {
|
|
268
|
+
const { translate } = await resolveTranslations()
|
|
267
269
|
const orderLineIds = Array.from(new Set(input.lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : [])))
|
|
268
270
|
const orderLinesById = await loadOwnedOrderLines(context, orderLineIds)
|
|
269
271
|
const orderIds = new Set<string>()
|
|
@@ -273,7 +275,7 @@ async function validatePortalOrderOwnership(
|
|
|
273
275
|
let resolvedOrder = input.orderId ? (ordersById.get(input.orderId) ?? null) : null
|
|
274
276
|
if (input.orderId) {
|
|
275
277
|
if (!resolvedOrder) {
|
|
276
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.orderNotOwned' }, { status: 404 })
|
|
278
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderNotOwned', 'Order not found.') }, { status: 404 })
|
|
277
279
|
}
|
|
278
280
|
}
|
|
279
281
|
|
|
@@ -282,10 +284,9 @@ async function validatePortalOrderOwnership(
|
|
|
282
284
|
const resolvedLine = orderLinesById.get(line.orderLineId)
|
|
283
285
|
const lineOrder = resolvedLine ? ordersById.get(resolvedLine.orderId) : null
|
|
284
286
|
if (!resolvedLine || !lineOrder) {
|
|
285
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.orderLineMismatch' }, { status: 404 })
|
|
287
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') }, { status: 404 })
|
|
286
288
|
}
|
|
287
289
|
if (resolvedOrder && lineOrder.id !== resolvedOrder.id) {
|
|
288
|
-
const { translate } = await resolveTranslations()
|
|
289
290
|
return NextResponse.json(
|
|
290
291
|
{ ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') },
|
|
291
292
|
{ status: 400 },
|
|
@@ -337,7 +338,8 @@ export async function GET(req: Request) {
|
|
|
337
338
|
serialNumber: url.searchParams.get('serialNumber') ?? undefined,
|
|
338
339
|
})
|
|
339
340
|
if (!query.success) {
|
|
340
|
-
|
|
341
|
+
const { translate } = await resolveTranslations()
|
|
342
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
341
343
|
}
|
|
342
344
|
const { page, pageSize, search, status, stateGroup, serialNumber } = query.data
|
|
343
345
|
const where = buildPortalOwnedClaimWhere(context)
|
|
@@ -415,15 +417,16 @@ export async function POST(req: Request) {
|
|
|
415
417
|
const contextOrResponse = await resolvePortalContext(req)
|
|
416
418
|
if (contextOrResponse instanceof Response) return contextOrResponse
|
|
417
419
|
const context = contextOrResponse
|
|
420
|
+
const { translate } = await resolveTranslations()
|
|
418
421
|
let body: unknown
|
|
419
422
|
try {
|
|
420
423
|
body = await req.json()
|
|
421
424
|
} catch {
|
|
422
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
425
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
423
426
|
}
|
|
424
427
|
const parsed = portalIntakeInputSchema.safeParse(body)
|
|
425
428
|
if (!parsed.success) {
|
|
426
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
|
|
429
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
|
|
427
430
|
}
|
|
428
431
|
const ownership = await validatePortalOrderOwnership(context, parsed.data)
|
|
429
432
|
if (ownership instanceof Response) return ownership
|
|
@@ -487,7 +490,7 @@ export async function POST(req: Request) {
|
|
|
487
490
|
)
|
|
488
491
|
const claimId = createResult.result?.claimId
|
|
489
492
|
if (typeof claimId !== 'string') {
|
|
490
|
-
return NextResponse.json({ ok: false, error: 'warranty_claims.errors.save_failed' }, { status: 500 })
|
|
493
|
+
return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
|
|
491
494
|
}
|
|
492
495
|
try {
|
|
493
496
|
await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(
|