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:
Deploy
2026-08-25 23:41:29 +02:00
parent b6adf681d1
commit b6af852b54
29 changed files with 671 additions and 279 deletions

View File

@@ -0,0 +1,20 @@
/**
* F-138: orders_order_history.metadata holds the structured payload for
* machine-readable events (returns, refunds, etc.) so we can keep the
* human-visible `message` column short and friendly while preserving all the
* detail that internal tooling needs.
* @param {import('node-pg-migrate').MigrationBuilder} pgm
*/
export const up = (pgm) => {
pgm.sql(`
ALTER TABLE orders_order_history
ADD COLUMN IF NOT EXISTS metadata jsonb NULL
`);
};
export const down = (pgm) => {
pgm.sql(`
ALTER TABLE orders_order_history
DROP COLUMN IF EXISTS metadata
`);
};

View File

@@ -0,0 +1,65 @@
/**
* F-138 backfill: convert any legacy `orders_order_history` refund entries that
* were stored as raw JSON `message` strings into the new human-readable format
* introduced by `apply-pos-return.ts`. We also move the original payload into
* the new `metadata` column so internal tooling still has the structured data.
* @param {import('node-pg-migrate').MigrationBuilder} pgm
*/
export const up = (pgm) => {
pgm.sql(`
WITH parsed AS (
SELECT id,
(message::jsonb) AS payload,
(
CASE (message::jsonb->>'status')
WHEN 'refund' THEN 'Devolución total'
ELSE 'Devolución parcial'
END
) AS action
FROM orders_order_history
WHERE event_type = 'RETURNED'
AND message ~ '^\\s*\\{'
),
formatted AS (
SELECT id,
action || ' de ' ||
(round(((payload->>'refundedCents')::numeric) / 100)::numeric(12,2)::text) ||
' €' ||
COALESCE(
(SELECT ' — ' || string_agg(
((l->>'returnedQuantity')::int)::text || ' × ' ||
COALESCE(oi.name, 'Artículo'),
', '
ORDER BY ord)
FROM jsonb_array_elements(payload->'lines') WITH ORDINALITY AS x(l, ord)
LEFT JOIN orders_items oi ON oi.id = (l->>'orderItemId')::uuid
WHERE (l->>'returnedQuantity')::int > 0
),
''
) ||
CASE
WHEN NULLIF(payload->>'reason', '') IS NOT NULL
THEN ' (motivo: ' || (payload->>'reason') || ')'
ELSE ''
END AS readable
FROM parsed
)
UPDATE orders_order_history h
SET message = f.readable,
metadata = h.message::jsonb
FROM formatted f
WHERE h.id = f.id
`);
};
export const down = (pgm) => {
// Best-effort: restore the JSON message from metadata. We can't know whether
// a row was originally a JSON message or already human-readable, so the
// down migration just clears the column.
pgm.sql(`
UPDATE orders_order_history
SET message = metadata::text,
metadata = NULL
WHERE event_type = 'RETURNED' AND metadata IS NOT NULL
`);
};