orders: fix the 42703 that broke EVERY order detail page
getOrderDetail joined order_merchant_cadence on merchant_entity_id. Migration 021 rekeyed that view to merchant_key (merchant_entity_id is NULL on ~9% of the feed, and duplicate merchant entities split one shop's history); 027 and 029 moved order_feed and order_spend across, and this query was missed. Every /orders/<key> request has since failed with column rec.merchant_entity_id does not exist The reason nobody saw a 500 is the second half of this commit: the page collapsed every failure into 'That order could not be found.' A server fault wearing the costume of a data condition reads as an empty spine and gets investigated in the wrong repo. The hook now carries the status and only a genuine 404 says the order is missing.
This commit is contained in:
@@ -63,10 +63,22 @@ export default function OrderDetailPage({ params }: { params: Promise<{ entityKe
|
||||
|
||||
if (isLoading) return <div className="p-6 text-zinc-500 text-sm">Loading order…</div>;
|
||||
if (error || !o) {
|
||||
// A missing order and a broken query are different problems and must not
|
||||
// share a sentence. Only a 404 means "no such order"; anything else is this
|
||||
// page failing, and saying so is what sends the next person to the server
|
||||
// log instead of to the spine.
|
||||
const status = (error as (Error & { status?: number }) | null)?.status;
|
||||
return (
|
||||
<div className="max-w-[1180px] mx-auto">
|
||||
<Link href="/orders" className="font-mono text-[11.5px] text-indigo-400">← All orders</Link>
|
||||
{status === 404 ? (
|
||||
<p className="mt-6 text-zinc-400 text-sm">That order could not be found.</p>
|
||||
) : (
|
||||
<p className="mt-6 text-zinc-400 text-sm">
|
||||
This order could not be loaded{status ? ` (server error ${status})` : ""}. The order exists —
|
||||
something in this page failed. Check the finance-app log.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+10
-1
@@ -1243,7 +1243,16 @@ export function useOrderDetail(entityKey: string | null) {
|
||||
staleTime: 60_000,
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`/api/orders/${encodeURIComponent(entityKey!)}`);
|
||||
if (!res.ok) throw new Error("Failed to load order");
|
||||
if (!res.ok) {
|
||||
// Carry the status. Collapsing every failure into one Error is how a
|
||||
// 500 on this route rendered as "that order could not be found" on
|
||||
// every single order for weeks — a server fault wearing the costume of
|
||||
// a data condition, which reads as "the spine is empty" and gets
|
||||
// investigated nowhere near the actual bug.
|
||||
const err = new Error(res.status === 404 ? "Order not found" : "Failed to load order");
|
||||
(err as Error & { status?: number }).status = res.status;
|
||||
throw err;
|
||||
}
|
||||
return res.json();
|
||||
},
|
||||
});
|
||||
|
||||
+17
-1
@@ -73,6 +73,12 @@ export interface OrderRow {
|
||||
/** Merchant orders on a regular cadence: the subscription signal, derived
|
||||
* not extracted. NULL unless the gaps are tight relative to their mean. */
|
||||
cadence_days: number | null;
|
||||
/** Worst sender-authentication verdict across the order's source documents.
|
||||
* 93% are 'pass' — the badge exists for the 7% that are not. Do NOT badge
|
||||
* source_trust instead: it is 'untrusted_external' on 100% of rows, because
|
||||
* every order here came from email. */
|
||||
auth_verdict: string | null;
|
||||
injection_flagged: boolean | null;
|
||||
txn_count: number;
|
||||
first_txn_id: number | null;
|
||||
}
|
||||
@@ -220,6 +226,7 @@ export async function getOrderFeed(filters: OrderFilters) {
|
||||
f.ordered_at, f.eta_date, f.delivered_at,
|
||||
f.currency, f.order_total, f.line_item_count,
|
||||
f.content_class, f.display_name, f.cadence_days,
|
||||
f.auth_verdict, f.injection_flagged,
|
||||
m.canonical_name AS merchant_name,
|
||||
link.txn_count, link.first_txn_id,
|
||||
(SELECT sp.refunded_amount FROM order_spend sp
|
||||
@@ -399,7 +406,16 @@ export async function getOrderDetail(entityKey: string): Promise<OrderDetail | n
|
||||
LEFT JOIN entities me2 ON me2.id = o.merchant_entity_id
|
||||
LEFT JOIN order_platforms p ON p.slug = o.platform
|
||||
LEFT JOIN order_spend sp ON sp.entity_id = o.entity_id
|
||||
LEFT JOIN order_merchant_cadence rec ON rec.merchant_entity_id = o.merchant_entity_id
|
||||
-- Cadence is keyed on the NORMALISED MERCHANT NAME, not merchant_entity_id.
|
||||
-- Migration 021 rekeyed the view (that column is NULL on ~9% of the feed
|
||||
-- and duplicate merchant entities split the same shop's history); 027 and
|
||||
-- 029 moved order_feed/order_spend onto merchant_key, and this query was
|
||||
-- missed. The result was a 42703 on EVERY order detail page — the join
|
||||
-- referenced a column the view no longer had. Keep this expression
|
||||
-- identical to the one in migration 027.
|
||||
LEFT JOIN order_merchant_cadence rec
|
||||
ON rec.merchant_key = regexp_replace(
|
||||
lower(COALESCE(me2.canonical_name, o.platform)), '[^a-z0-9]', '', 'g')
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT di.content_class, di.counterparty
|
||||
FROM extracted_facts ef
|
||||
|
||||
Reference in New Issue
Block a user