feat(orders): show the receipt in the transaction detail panel
ci / lint-test (push) Failing after 47s
ci / lint-test (push) Failing after 47s
`expense_metadata` has held the itemised receipt since ingestion started and nothing in the UI ever read it. A transaction that came from a DoorDash or Uber Eats receipt showed a merchant and an amount, with the item list and the delivery addresses sitting unread in the row behind it (user, 2026-07-27). Adds GET /api/transactions/[id]/order and an "Order details" section in the edit modal: line items with their options, pick-up/delivery stops with times and addresses, the card tail when one was involved, and the provider's own order reference. Two details that matter: - The lookup resolves from **both** sides — `transaction_id` OR `matched_transaction_id`. A card-settled order creates no transaction of its own (I5); the receipt points at the statement line instead. Matching only on transaction_id would have left the panel blank on exactly the card-paid orders, which are the ones whose detail is hardest to find elsewhere. - An empty item list says so in words rather than rendering nothing. Uber itemises groceries but not restaurant orders, and orders ingested before the Uber item parser existed have none either — a blank section reads as a bug when it is usually the receipt. Read-only. This is what a provider sent; editing it would make provenance mean nothing.
This commit is contained in:
@@ -250,6 +250,33 @@ export function useTransactionSplits(transactionId: number) {
|
||||
});
|
||||
}
|
||||
|
||||
export interface OrderReceipt {
|
||||
platform: "doordash" | "ubereats" | "uber" | null;
|
||||
order_reference: string | null;
|
||||
line_items: { qty: number; description: string; amount: number; options?: string[] }[];
|
||||
route: { label: string; time: string | null; address: string }[];
|
||||
subtotal: string | null;
|
||||
amount: string | null;
|
||||
currency: string | null;
|
||||
card_last4: string | null;
|
||||
flags: string[];
|
||||
source_email_subject: string | null;
|
||||
transaction_date: string | null;
|
||||
}
|
||||
|
||||
/** The receipt behind a transaction, or null when it did not come from one. */
|
||||
export function useOrderReceipt(transactionId: number) {
|
||||
return useQuery<OrderReceipt | null>({
|
||||
queryKey: ["order-receipt", transactionId],
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`/api/transactions/${transactionId}/order`);
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
},
|
||||
staleTime: Infinity, // a receipt never changes
|
||||
});
|
||||
}
|
||||
|
||||
export function useSetSplits() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
|
||||
Reference in New Issue
Block a user