feat(F-113): completed feature

This commit is contained in:
chattie
2026-08-21 12:27:13 +02:00
parent 027cacd871
commit c05c0b0582
25 changed files with 581 additions and 74 deletions

View File

@@ -227,6 +227,7 @@ export async function registerOrdersRoutes(
],
},
trackingNumber: { type: 'string', maxLength: 120, description: 'Número de seguimiento del envío (obligatorio al marcar como Enviado)' },
courier: { type: 'string', maxLength: 120, description: 'Transportista del envío, de la lista editable en Ajustes (obligatorio al marcar como Enviado)' },
},
},
response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
@@ -238,16 +239,22 @@ export async function registerOrdersRoutes(
const user = await deps.authenticate(request);
requireRole(user, 'admin');
const { id } = parseJson(orderIdParamSchema, request.params);
const { state, trackingNumber } = parseJson(
transitionSchema.extend({ trackingNumber: z.string().min(1).max(120).optional() }),
const { state, trackingNumber, courier } = parseJson(
transitionSchema.extend({
trackingNumber: z.string().min(1).max(120).optional(),
courier: z.string().min(1).max(120).optional(),
}),
request.body,
);
if (state === 'SHIPPED' && !trackingNumber?.trim()) {
throw new AppError(422, 'TRACKING_NUMBER_REQUIRED', 'Indica el número de seguimiento para marcar el pedido como enviado');
}
if (state === 'SHIPPED' && !courier?.trim()) {
throw new AppError(422, 'COURIER_REQUIRED', 'Indica el transportista para marcar el pedido como enviado');
}
let order;
try {
order = await service.transitionAdmin(id, state as OrderState, trackingNumber?.trim() || undefined);
order = await service.transitionAdmin(id, state as OrderState, trackingNumber?.trim() || undefined, courier?.trim() || undefined);
} catch (error) {
throw mapOrderError(error);
}
@@ -266,6 +273,7 @@ export async function registerOrdersRoutes(
orderId: order.id,
state: order.state,
trackingNumber: order.trackingNumber,
courier: order.courier,
});
notified = true;
} else {
@@ -279,7 +287,7 @@ export async function registerOrdersRoutes(
deps.pool,
order.id,
'STATE_CHANGE',
`Estado cambiado a ${ORDER_STATE_LABELS[order.state] ?? order.state}${order.trackingNumber ? ` · Tracking: ${order.trackingNumber}` : ''}`,
`Estado cambiado a ${ORDER_STATE_LABELS[order.state] ?? order.state}${order.courier ? ` · Transportista: ${order.courier}` : ''}${order.trackingNumber ? ` · Tracking: ${order.trackingNumber}` : ''}`,
order.trackingNumber ?? null,
user.email,
);
@@ -304,9 +312,10 @@ export async function registerOrdersRoutes(
const user = await deps.authenticate(request);
requireRole(user, 'admin');
const { id } = parseJson(orderIdParamSchema, request.params);
const { trackingNumber, note } = parseJson(
const { trackingNumber, courier, note } = parseJson(
z.object({
trackingNumber: z.string().trim().min(1).max(120),
courier: z.string().trim().min(1).max(120).optional(),
note: z.string().trim().max(500).optional(),
}),
request.body,
@@ -314,13 +323,13 @@ export async function registerOrdersRoutes(
const existing = await service.getOrderAdmin(id);
if (!existing) throw new AppError(404, 'ORDER_NOT_FOUND', 'Order not found');
await deps.pool.query(
`UPDATE orders_orders SET tracking_number = $2, updated_at = now() WHERE id = $1`,
[id, trackingNumber],
`UPDATE orders_orders SET tracking_number = $2, courier = COALESCE($3, courier), updated_at = now() WHERE id = $1`,
[id, trackingNumber, courier ?? null],
);
const message = `Seguimiento actualizado: ${trackingNumber}${note ? ` · Nota: ${note}` : ''}`;
const message = `Seguimiento actualizado: ${trackingNumber}${courier ? ` · Transportista: ${courier}` : ''}${note ? ` · Nota: ${note}` : ''}`;
await recordOrderHistory(deps.pool, id, 'SHIPPING_UPDATE', message, trackingNumber, user.email);
const updated = await service.getOrderAdmin(id);
return reply.send(serializeOrder(updated ?? { ...existing, trackingNumber }));
return reply.send(serializeOrder(updated ?? { ...existing, trackingNumber, courier: courier ?? existing.courier }));
});
const historySchema: FastifySchema = {
@@ -490,6 +499,7 @@ function serializeOrder(order: {
taxCents: number;
totalCents: number;
trackingNumber?: string | null;
courier?: string | null;
createdAt: Date;
updatedAt: Date;
idempotencyKey: string | null;
@@ -518,6 +528,7 @@ function serializeOrder(order: {
taxCents: order.taxCents,
totalCents: order.totalCents,
trackingNumber: order.trackingNumber ?? null,
courier: order.courier ?? null,
idempotencyKey: order.idempotencyKey,
items: order.items.map((item) => ({
id: item.id,