// Sales-order detail screen. // // **The headline demo flow.** Confirm a DRAFT, ship a CONFIRMED, // or cancel either. Each action updates the order in place, // reloads the journal entry list to show the AR row appearing // (POSTED → SETTLED), and reloads stock movements to show the // SALES_SHIPMENT ledger entry appearing. // // **Why ship asks for a location.** The framework requires every // shipment to name the warehouse the goods came from — the // inventory ledger tags the row with that location and the audit // trail must always answer "which warehouse shipped this". The UI // proposes the first WAREHOUSE-typed location it finds; an // operator can pick another from the dropdown. import { useCallback, useEffect, useState } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import { finance, inventory, salesOrders } from '@/api/client' import type { JournalEntry, Location, SalesOrder, StockMovement } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { StatusBadge } from '@/components/StatusBadge' export function SalesOrderDetailPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() const [order, setOrder] = useState(null) const [locations, setLocations] = useState([]) const [shippingLocation, setShippingLocation] = useState('') const [movements, setMovements] = useState([]) const [journalEntries, setJournalEntries] = useState([]) const [loading, setLoading] = useState(true) const [acting, setActing] = useState(false) const [error, setError] = useState(null) const [actionMessage, setActionMessage] = useState(null) const reloadSideEffects = useCallback(async (orderCode: string) => { const [ms, js] = await Promise.all([ inventory.listMovements(), finance.listJournalEntries(), ]) setMovements(ms.filter((m) => m.reference?.includes(orderCode) ?? false)) setJournalEntries(js.filter((j) => j.orderCode === orderCode)) }, []) useEffect(() => { let active = true setLoading(true) Promise.all([salesOrders.get(id), inventory.listLocations()]) .then(async ([o, locs]: [SalesOrder, Location[]]) => { if (!active) return setOrder(o) setLocations(locs.filter((l) => l.active)) const firstWarehouse = locs.find((l) => l.active && l.type === 'WAREHOUSE') setShippingLocation(firstWarehouse?.code ?? locs[0]?.code ?? '') await reloadSideEffects(o.code) }) .catch((e: unknown) => { if (active) setError(e instanceof Error ? e : new Error(String(e))) }) .finally(() => active && setLoading(false)) return () => { active = false } }, [id, reloadSideEffects]) if (loading) return if (error) return if (!order) return const onConfirm = async () => { setActing(true) setError(null) setActionMessage(null) try { const updated = await salesOrders.confirm(order.id) setOrder(updated) await reloadSideEffects(updated.code) setActionMessage('Confirmed. pbc-finance has posted an AR journal entry.') } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { setActing(false) } } const onShip = async () => { if (!shippingLocation) { setError(new Error('Pick a shipping location first.')) return } setActing(true) setError(null) setActionMessage(null) try { const updated = await salesOrders.ship(order.id, shippingLocation) setOrder(updated) await reloadSideEffects(updated.code) setActionMessage( `Shipped from ${shippingLocation}. Stock debited, journal entry settled.`, ) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { setActing(false) } } const onCancel = async () => { setActing(true) setError(null) setActionMessage(null) try { const updated = await salesOrders.cancel(order.id) setOrder(updated) await reloadSideEffects(updated.code) setActionMessage('Cancelled. pbc-finance has reversed any open journal entry.') } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { setActing(false) } } const canConfirm = order.status === 'DRAFT' const canShip = order.status === 'CONFIRMED' const canCancel = order.status === 'DRAFT' || order.status === 'CONFIRMED' return (
navigate('/sales-orders')}> ← Back } />
Status:
Total
{Number(order.totalAmount).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2, })}{' '} {order.currencyCode}
{actionMessage && (
{actionMessage}
)}

Actions

Lines

{order.lines.map((l) => ( ))}
# Item Qty Unit price Line total
{l.lineNo} {l.itemCode} {String(l.quantity)} {Number(l.unitPrice).toLocaleString(undefined, { minimumFractionDigits: 2, })}{' '} {l.currencyCode} {Number(l.lineTotal).toLocaleString(undefined, { minimumFractionDigits: 2, })}

Inventory movements

{movements.length === 0 ? (
No movements yet.
) : ( {movements.map((m) => ( ))}
Item Δ Reason Reference
{m.itemCode} {String(m.delta)} {m.reason} {m.reference ?? '—'}
)}

Journal entries

{journalEntries.length === 0 ? (
No entries yet.
) : ( {journalEntries.map((j) => ( ))}
Code Type Status Amount
{j.code} {j.type} {Number(j.amount).toLocaleString(undefined, { minimumFractionDigits: 2 })}{' '} {j.currencyCode}
)}
View all journal entries →
) }