fix(orders): incoming payload — optional fields use .nullish(), accept ISO date OR datetime
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,27 +10,33 @@ import { verifySiteKey } from '../sites/auth-verify.js';
|
|||||||
const STATUSES = ['new', 'accepted', 'invoiced', 'paid', 'fulfilled', 'cancelled'] as const;
|
const STATUSES = ['new', 'accepted', 'invoiced', 'paid', 'fulfilled', 'cancelled'] as const;
|
||||||
const VAT_VALUES = ['none', 'vat_0', 'vat_5', 'vat_7', 'vat_10', 'vat_20'] as const;
|
const VAT_VALUES = ['none', 'vat_0', 'vat_5', 'vat_7', 'vat_10', 'vat_20'] as const;
|
||||||
|
|
||||||
// Внешний (S2S) payload от сайта — толерантный
|
// Внешний (S2S) payload от сайта — толерантный. Optional поля = .nullish()
|
||||||
|
// (можно опустить в JSON, прислать null или валидное значение).
|
||||||
|
const isoDateOrDateTime = z.string().regex(
|
||||||
|
/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?)?$/,
|
||||||
|
'нужна дата ISO (YYYY-MM-DD) или datetime',
|
||||||
|
);
|
||||||
|
|
||||||
const IncomingItem = z.object({
|
const IncomingItem = z.object({
|
||||||
name: z.string().min(1).max(500),
|
name: z.string().min(1).max(500),
|
||||||
qty: z.coerce.number().positive().default(1),
|
qty: z.coerce.number().positive().default(1),
|
||||||
unit: z.string().max(50).default('шт'),
|
unit: z.string().max(50).default('шт'),
|
||||||
priceRub: z.coerce.number().nonnegative(), // в рублях, чтобы сайту удобней
|
priceRub: z.coerce.number().nonnegative(),
|
||||||
vat: z.enum(VAT_VALUES).default('none'),
|
vat: z.enum(VAT_VALUES).default('none'),
|
||||||
eventDate: z.string().datetime().nullable().optional(),
|
eventDate: isoDateOrDateTime.nullish(),
|
||||||
serviceSlug: z.string().optional(), // если сайт знает наш каталог
|
serviceSlug: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const IncomingOrder = z.object({
|
const IncomingOrder = z.object({
|
||||||
customerName: z.string().min(1).max(500),
|
customerName: z.string().min(1).max(500),
|
||||||
customerEmail: optionalText(200),
|
customerEmail: z.string().email().nullish(),
|
||||||
customerPhone: optionalText(50),
|
customerPhone: z.string().max(50).nullish(),
|
||||||
customerInn: optionalText(20),
|
customerInn: z.string().max(20).nullish(),
|
||||||
customerKpp: optionalText(20),
|
customerKpp: z.string().max(20).nullish(),
|
||||||
customerAddress: optionalText(1000),
|
customerAddress: z.string().max(1000).nullish(),
|
||||||
customerKind: z.enum(['ul', 'ip', 'fl']).default('ul'),
|
customerKind: z.enum(['ul', 'ip', 'fl']).default('ul'),
|
||||||
notes: optionalText(2000),
|
notes: z.string().max(2000).nullish(),
|
||||||
acceptedOfferAt: z.string().datetime().nullable().optional(),
|
acceptedOfferAt: isoDateOrDateTime.nullish(),
|
||||||
items: z.array(IncomingItem).min(1),
|
items: z.array(IncomingItem).min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,17 +167,17 @@ export async function ordersRoutes(app: FastifyInstance) {
|
|||||||
siteId: localSite?.id ?? null,
|
siteId: localSite?.id ?? null,
|
||||||
status: 'new',
|
status: 'new',
|
||||||
customerName: data.customerName,
|
customerName: data.customerName,
|
||||||
customerEmail: data.customerEmail,
|
customerEmail: data.customerEmail ?? null,
|
||||||
customerPhone: data.customerPhone,
|
customerPhone: data.customerPhone ?? null,
|
||||||
customerInn: data.customerInn,
|
customerInn: data.customerInn ?? null,
|
||||||
customerKpp: data.customerKpp,
|
customerKpp: data.customerKpp ?? null,
|
||||||
customerAddress: data.customerAddress,
|
customerAddress: data.customerAddress ?? null,
|
||||||
customerKind: data.customerKind,
|
customerKind: data.customerKind,
|
||||||
totalCents: sums.totalCents,
|
totalCents: sums.totalCents,
|
||||||
vatCents: sums.vatCents,
|
vatCents: sums.vatCents,
|
||||||
currency: 'RUB',
|
currency: 'RUB',
|
||||||
acceptedOfferAt: data.acceptedOfferAt ? new Date(data.acceptedOfferAt) : null,
|
acceptedOfferAt: data.acceptedOfferAt ? new Date(data.acceptedOfferAt) : null,
|
||||||
notes: data.notes,
|
notes: data.notes ?? null,
|
||||||
rawPayload: req.body as Prisma.InputJsonValue,
|
rawPayload: req.body as Prisma.InputJsonValue,
|
||||||
items: {
|
items: {
|
||||||
create: data.items.map((it, i) => {
|
create: data.items.map((it, i) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user