feat(F-177): completed feature

This commit is contained in:
chattie
2026-08-22 18:54:41 +02:00
parent 4081462cc4
commit abdb03b6b5
17 changed files with 326 additions and 23 deletions

View File

@@ -180,6 +180,59 @@ export async function registerOrdersRoutes(
return reply.send({ items: items.map(serializeOrder), total });
});
const staleNotificationsSchema: FastifySchema = {
tags: ['Orders'],
summary: 'List stale order notifications (admin)',
querystring: {
type: 'object',
properties: {
hours: { type: 'integer', minimum: 1, maximum: 168, default: 24 },
limit: { type: 'integer', minimum: 1, maximum: 50, default: 20 },
},
},
response: { 401: errorSchema, 403: errorSchema },
};
app.get('/orders/notifications/stale', { schema: staleNotificationsSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
requireRole(user, 'admin');
const query = request.query as { hours?: string; limit?: string };
const hours = Math.min(Math.max(Number(query.hours ?? 24) || 24, 1), 168);
const limit = Math.min(Math.max(Number(query.limit ?? 20) || 20, 1), 50);
const result = await deps.pool.query<{
id: string;
state: 'PENDING' | 'SHIPPED';
total_cents: number;
customer_email: string | null;
state_changed_at: Date;
age_hours: number;
total_count: number;
}>(
`SELECT o.id, o.state, o.total_cents, u.email AS customer_email,
o.state_changed_at,
FLOOR(EXTRACT(EPOCH FROM (now() - o.state_changed_at)) / 3600)::int AS age_hours,
COUNT(*) OVER()::int AS total_count
FROM orders_orders o
LEFT JOIN identity_users u ON u.id = o.user_id
WHERE o.state IN ('PENDING', 'SHIPPED')
AND o.state_changed_at <= now() - ($1::int * interval '1 hour')
ORDER BY o.state_changed_at ASC
LIMIT $2`,
[hours, limit],
);
return reply.send({
total: result.rows[0]?.total_count ?? 0,
thresholdHours: hours,
items: result.rows.map((row) => ({
id: row.id,
state: row.state,
totalCents: row.total_cents,
customerEmail: row.customer_email,
stateChangedAt: row.state_changed_at.toISOString(),
ageHours: row.age_hours,
})),
});
});
const getOrderAdminSchema: FastifySchema = {
tags: ['Orders'],
summary: 'Get order (admin)',

View File

@@ -232,7 +232,12 @@ export class PgOrderRepository implements OrderRepository {
async updateState(id: string, state: OrderState, trackingNumber?: string, courier?: string): Promise<OrderView | undefined> {
const result = await this.pool.query<OrderRow>(
`UPDATE orders_orders SET state = $2, tracking_number = COALESCE($3, tracking_number), courier = COALESCE($4, courier), updated_at = now()
`UPDATE orders_orders
SET state = $2,
tracking_number = COALESCE($3, tracking_number),
courier = COALESCE($4, courier),
state_changed_at = now(),
updated_at = now()
WHERE id = $1 RETURNING *`,
[id, state, trackingNumber ?? null, courier ?? null],
);

View File

@@ -48,7 +48,7 @@ export async function registerPaymentsRoutes(
if (!row) throw new OrderNotFoundError();
if (!ALLOWED_TRANSITIONS[row.state]?.includes(state)) throw new OrderNotFoundError();
await deps.pool.query(
'UPDATE orders_orders SET state = $2, updated_at = now() WHERE id = $1',
'UPDATE orders_orders SET state = $2, state_changed_at = now(), updated_at = now() WHERE id = $1',
[id, state],
);
const refreshed = await deps.pool.query<{