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:
@@ -78,6 +78,35 @@ function formatPrice(cents: number) {
|
||||
return `€${(cents / 100).toFixed(2)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* F-138: legacy return events were stored as raw JSON. If the migration
|
||||
* hasn't caught up (or didn't run on a particular row) we still want the UI
|
||||
* to show something readable instead of dumping the payload. Falls back to
|
||||
* the original string when the value isn't JSON.
|
||||
*/
|
||||
function formatRefundMessage(raw: string): string {
|
||||
const trimmed = raw?.trim() ?? '';
|
||||
if (!trimmed.startsWith('{')) return raw;
|
||||
try {
|
||||
const payload = JSON.parse(trimmed) as {
|
||||
refundedCents?: number;
|
||||
status?: 'refund' | 'partial_refund';
|
||||
lines?: Array<{ returnedQuantity?: number }>;
|
||||
reason?: string;
|
||||
};
|
||||
const euros = ((payload.refundedCents ?? 0) / 100).toFixed(2);
|
||||
const action = payload.status === 'refund' ? 'Devolución total' : 'Devolución parcial';
|
||||
const lineCount = Array.isArray(payload.lines)
|
||||
? payload.lines.reduce((sum, line) => sum + (line.returnedQuantity ?? 0), 0)
|
||||
: 0;
|
||||
const reason = payload.reason?.trim();
|
||||
const detail = lineCount > 0 ? ` (${lineCount} uds.)` : '';
|
||||
return `${action} de ${euros} €${detail}${reason ? ` — ${reason}` : ''}`;
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
export default function OrderDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [order, setOrder] = useState<Order | null>(null);
|
||||
@@ -710,6 +739,9 @@ export default function OrderDetailPage() {
|
||||
// ORDERS-FIX: detectar refunds para mostrar de forma más legible
|
||||
const isRefund = /refund|reembolso|devolu/i.test(event.message);
|
||||
const isRefundEvent = event.eventType === 'REFUND' || isRefund;
|
||||
const formattedMessage = isRefundEvent
|
||||
? formatRefundMessage(event.message)
|
||||
: event.message;
|
||||
return (
|
||||
<div key={event.id} className="flex gap-3">
|
||||
<div className={`w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${
|
||||
@@ -718,7 +750,7 @@ export default function OrderDetailPage() {
|
||||
}`} />
|
||||
<div className="min-w-0">
|
||||
<p className={`text-sm break-words ${isRefundEvent ? 'text-pink-700 font-medium' : 'text-gray-800'}`}>
|
||||
{isRefundEvent && <span className="mr-1">💸</span>}{event.message}
|
||||
{isRefundEvent && <span className="mr-1">💸</span>}{formattedMessage}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{new Date(event.createdAt).toLocaleString('es-ES')}
|
||||
|
||||
Reference in New Issue
Block a user