fix(release): 0.2.8 harden admin proxy, cart stock caps, selfpay and refund timeline
- admin proxy: 25s timeout, body guards, structured failure logs - cart: addItem enforces stock cap (409 INSUFFICIENT_STOCK), UI clamps qty - tpv selfpay: hide sidebar/discounts/save-pending, rename button, receipt-settings 400 fix - pos admin: quick products slot count aligned to 8 - returns: human-readable history message + metadata jsonb (migrations 064-065) + admin fallback formatter - storefront: product card white background - product page: remove duplicate stock label under add-to-cart button
This commit is contained in:
@@ -57,6 +57,31 @@ async function assertReceiptAccess(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves which store the admin POS settings page should target.
|
||||
*
|
||||
* Accepts the explicit `storeId` query value when present and well-formed;
|
||||
* otherwise falls back to the first active store. This keeps the admin
|
||||
* dashboard recoverable when the user lands on the page before the store
|
||||
* dropdown is hydrated or the dropdown value is somehow invalid (F-139).
|
||||
*/
|
||||
async function resolveStoreIdForReceipt(
|
||||
pool: pg.Pool,
|
||||
rawStoreId: string | undefined,
|
||||
): Promise<string> {
|
||||
if (rawStoreId && storeIdSchema.safeParse(rawStoreId).success) {
|
||||
return rawStoreId;
|
||||
}
|
||||
const fallback = await pool.query<{ id: string }>(
|
||||
`SELECT id FROM pos_stores WHERE active = true ORDER BY created_at ASC LIMIT 1`,
|
||||
);
|
||||
const id = fallback.rows[0]?.id;
|
||||
if (!id) {
|
||||
throw new AppError(404, 'NO_ACTIVE_STORE', 'No hay tiendas activas configuradas');
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps) {
|
||||
const { pool, authenticate } = deps;
|
||||
|
||||
@@ -1208,8 +1233,7 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
|
||||
summary: 'Get company and receipt numbering settings',
|
||||
querystring: {
|
||||
type: 'object',
|
||||
required: ['storeId'],
|
||||
properties: { storeId: { type: 'string', format: 'uuid' } },
|
||||
properties: { storeId: { type: 'string' } },
|
||||
},
|
||||
response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
|
||||
} as FastifySchema,
|
||||
@@ -1217,7 +1241,11 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
|
||||
async (request, reply) => {
|
||||
const user = await authenticate(request);
|
||||
requireRole(user, 'admin');
|
||||
const { storeId } = parseJson(z.object({ storeId: storeIdSchema }), request.query);
|
||||
const rawStoreId = (request.query as { storeId?: string }).storeId;
|
||||
// F-139: the admin POS page can land before the store dropdown has a
|
||||
// selection. Fall back to the first active store so the UI never sees a
|
||||
// 400 caused by an empty querystring.
|
||||
const storeId = await resolveStoreIdForReceipt(pool, rawStoreId);
|
||||
await pool.query(
|
||||
`INSERT INTO pos_receipt_settings (store_id) VALUES ($1)
|
||||
ON CONFLICT (store_id) DO NOTHING`,
|
||||
|
||||
Reference in New Issue
Block a user