Files
route-commerce/src/actions/shipping.ts
T
Tyler a6df4c2dd9
Deploy to route.crispygoat.com / deploy (push) Successful in 4m48s
Fix shipping + dashboard + analytics queries against new schema
The /admin/shipping page was 500-ing with 'column c.name does not
exist' because the getShippingOrders query in src/actions/shipping.ts
was still using legacy column names on the new-schema customers
table (name, email, phone) — the actual columns are first_name,
last_name, primary_email, primary_phone.

The dashboard and analytics pages were logging the same kind of
errors but catching them, so the UI just showed zeroed stats instead
of crashing. Same fix:

- customers: name → first_name || ' ' || last_name
- customers: email → primary_email
- customers: phone → primary_phone
- orders: subtotal → total_cents / 100
- orders: created_at → placed_at
- orders: customer_name → join customers and concat first/last

These are the same kind of fixes that landed in migration 0041 for
the command-center RPCs. The application-layer queries just hadn't
been updated.
2026-06-17 11:00:34 -06:00

106 lines
3.3 KiB
TypeScript

"use server";
import { getAdminUser } from "@/lib/admin-permissions";
import { pool } from "@/lib/db";
export type UpdateShippingStatusResult =
| { success: true }
| { success: false; error: string };
// TODO(migration): shipping is dormant in the SaaS rebuild. The legacy
// `shipments` table (with `tracking_number`, `fedex_shipment_id`, etc.),
// the `shipping_status` column on `orders`, and the `update_shipping_order`
// RPC from `supabase/migrations/040_shipping_fulfillment_rpcs.sql` are
// gone. The functions below stub to "not configured" so the admin
// shipping tab renders gracefully. Re-introduce shipping in
// `db/schema/` when the feature is reactivated.
export async function updateShippingStatus(
_orderId: string,
_status: string,
_trackingNumber?: string
): Promise<UpdateShippingStatusResult> {
const adminUser = await getAdminUser();
if (!adminUser) return { success: false, error: "Not authenticated" };
if (!adminUser.can_manage_orders) return { success: false, error: "Not authorized" };
return { success: false, error: "Shipping not configured" };
}
export type GetShippingOrdersResult = {
success: boolean;
orders?: ShippingOrder[];
error?: string;
};
export type ShippingOrder = {
id: string;
customer_name: string;
customer_email: string | null;
customer_phone: string | null;
status: string;
subtotal: number;
shipping_status: string;
tracking_number: string | null;
created_at: string;
brand_id: string | null;
order_items: Array<{
id: string;
product_id: string;
quantity: number;
price: number;
fulfillment: string;
products: { name: string; is_perishable: boolean } | null;
}>;
};
export async function getShippingOrders(): Promise<GetShippingOrdersResult> {
const adminUser = await getAdminUser();
if (!adminUser) return { success: false, error: "Not authenticated" };
// Read shipping-eligible orders from the new schema as a best-effort
// approximation. The legacy shape had `customer_*` columns and a join
// table; we fall back to `customers` for the name and `order_items`
// for line-item info. `subtotal` (legacy) → `total_cents / 100`.
const { rows } = await pool.query<{
id: string;
customer_name: string | null;
customer_email: string | null;
customer_phone: string | null;
status: string;
subtotal: number;
created_at: string;
brand_id: string;
}>(
`SELECT
o.id::text AS id,
TRIM(COALESCE(c.first_name, '') || ' ' || COALESCE(c.last_name, '')) AS customer_name,
c.primary_email AS customer_email,
c.primary_phone AS customer_phone,
o.status,
o.total_cents::float / 100.0 AS subtotal,
o.placed_at::text AS created_at,
o.brand_id::text AS brand_id
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id
WHERE o.fulfillment IN ('ship', 'mixed')
ORDER BY o.placed_at DESC
LIMIT 100`
);
const orders: ShippingOrder[] = rows.map((r) => ({
id: r.id,
customer_name: r.customer_name ?? "Unknown",
customer_email: r.customer_email,
customer_phone: r.customer_phone,
status: r.status,
subtotal: r.subtotal,
shipping_status: "pending",
tracking_number: null,
created_at: r.created_at,
brand_id: r.brand_id,
order_items: [],
}));
return { success: true, orders };
}