migrate: replace Supabase REST with Drizzle/pg in 28 more files (wave 5 final)
- admin components: TaxDashboard, OrderTableBody, TaxQuarterlySummary, StopProductAssignment, StopTableClient, ProductAssignmentForm, StopMessagingForm - api routes: wholesale/checkout, wholesale/price-sheet, api/supabase (DELETED), api/reports/export, route-trace/* (stubbed - feature retired) - lib: src/lib/supabase.ts (mock-only shim, no @supabase imports), src/lib/supabase/server.ts (DELETED) - actions: wholesale-auth (stubbed - awaiting Auth.js migration), shipping/settings, water-log/*, tax (added getTaxSummaryAction/getTaxableOrdersAction), time-tracking/* (stubbed) - new actions: stops/manage-stop-products (assign/unassign), stops/get-stop-customers (pending pickup list), orders.toggleOrderPickupComplete (legacy column) All Supabase REST calls replaced with Drizzle ORM, raw pg.Pool queries, or existing server actions. Typecheck: clean. Tests: 22/22 pass. Build: OK.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"use server";
|
||||
|
||||
/**
|
||||
* List the customers with pending pickups for a given stop.
|
||||
*
|
||||
* TODO(migration): the SaaS rebuild's `orders` table
|
||||
* (db/schema/orders.ts) doesn't carry a `stop_id` column, so this
|
||||
* helper queries the legacy `orders` table directly via `pool.query`
|
||||
* for the customer-name / email / phone. When the new schema grows a
|
||||
* `stop_id` reference, switch this read to a Drizzle query.
|
||||
*/
|
||||
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { assertBrandAccess } from "@/lib/brand-scope";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
export type StopCustomer = {
|
||||
id: string;
|
||||
customer_name: string;
|
||||
customer_email: string | null;
|
||||
customer_phone: string | null;
|
||||
pickup_complete: boolean;
|
||||
};
|
||||
|
||||
export type GetStopPendingCustomersResult =
|
||||
| { success: true; customers: StopCustomer[] }
|
||||
| { success: false; error: string };
|
||||
|
||||
export async function getStopPendingCustomers(
|
||||
stopId: string
|
||||
): Promise<GetStopPendingCustomersResult> {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) return { success: false, error: "Not authenticated" };
|
||||
if (!adminUser.can_manage_stops) return { success: false, error: "Not authorized" };
|
||||
|
||||
// Resolve the stop's brand so we can scope-check.
|
||||
const { rows: stopRows } = await pool.query<{ brand_id: string | null }>(
|
||||
"SELECT brand_id::text AS brand_id FROM stops WHERE id = $1 LIMIT 1",
|
||||
[stopId]
|
||||
);
|
||||
const brandId = stopRows[0]?.brand_id ?? null;
|
||||
if (brandId) {
|
||||
try { assertBrandAccess(adminUser, brandId); } catch {
|
||||
return { success: false, error: "Brand access denied" };
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows } = await pool.query<StopCustomer>(
|
||||
`SELECT id::text AS id,
|
||||
COALESCE(customer_name, '') AS customer_name,
|
||||
customer_email,
|
||||
customer_phone,
|
||||
COALESCE(pickup_complete, false) AS pickup_complete
|
||||
FROM orders
|
||||
WHERE stop_id = $1
|
||||
AND COALESCE(pickup_complete, false) = false
|
||||
ORDER BY created_at DESC`,
|
||||
[stopId]
|
||||
);
|
||||
return { success: true, customers: rows };
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
error: err instanceof Error ? err.message : "Failed to load customers",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
"use server";
|
||||
|
||||
/**
|
||||
* Assign / unassign a product to a stop.
|
||||
*
|
||||
* TODO(migration): the `assign_product_to_stop` and
|
||||
* `unassign_product_from_stop` SECURITY DEFINER RPCs live in
|
||||
* supabase/migrations/030. The RPCs still exist in the database and
|
||||
* are called via `pool.query` rather than the Supabase REST gateway.
|
||||
* When stops/products are re-platformed on the SaaS rebuild, replace
|
||||
* these with direct inserts/deletes on the new `db/schema/stops.ts`
|
||||
* `stopProducts` table.
|
||||
*/
|
||||
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
export type AssignProductResult =
|
||||
| { success: true; id: string }
|
||||
| { success: false; error: string };
|
||||
|
||||
export type UnassignProductResult =
|
||||
| { success: true }
|
||||
| { success: false; error: string };
|
||||
|
||||
export async function assignProductToStop(params: {
|
||||
stopId: string;
|
||||
productId: string;
|
||||
}): Promise<AssignProductResult> {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) return { success: false, error: "Not authenticated" };
|
||||
if (!adminUser.can_manage_products && !adminUser.can_manage_orders) {
|
||||
return { success: false, error: "Not authorized" };
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows } = await pool.query<{ id: string; success?: boolean; error?: string }>(
|
||||
"SELECT * FROM assign_product_to_stop($1::uuid, $2::uuid, $3::text)",
|
||||
[params.stopId, params.productId, adminUser.user_id]
|
||||
);
|
||||
const data = rows[0];
|
||||
if (!data?.id) {
|
||||
return { success: false, error: data?.error ?? "Failed to assign product to stop" };
|
||||
}
|
||||
return { success: true, id: data.id };
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
error: err instanceof Error ? err.message : "Failed to assign product to stop",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function unassignProductFromStop(params: {
|
||||
stopId: string;
|
||||
productId: string;
|
||||
}): Promise<UnassignProductResult> {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) return { success: false, error: "Not authenticated" };
|
||||
if (!adminUser.can_manage_products && !adminUser.can_manage_orders) {
|
||||
return { success: false, error: "Not authorized" };
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query(
|
||||
"SELECT * FROM unassign_product_from_stop($1::uuid, $2::uuid, $3::text)",
|
||||
[params.stopId, params.productId, adminUser.user_id]
|
||||
);
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
error: err instanceof Error ? err.message : "Failed to unassign product from stop",
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user