feat(invoicing): filter rows to the logged-in rep's own orders

This commit is contained in:
Dave 2026-08-31 23:27:23 +02:00
parent 8faa4437b6
commit 29eb003cd6

View file

@ -12,6 +12,8 @@ import { Switch } from "@/components/ui/switch";
import { ModalShell } from "@/components/ui/modal-shell"; import { ModalShell } from "@/components/ui/modal-shell";
import { StockBookingSection, NEW_BOOKING_EVENT } from "@/components/invoicing/stock-booking-section"; import { StockBookingSection, NEW_BOOKING_EVENT } from "@/components/invoicing/stock-booking-section";
import { getPocketBase } from "@/lib/pocketbase"; import { getPocketBase } from "@/lib/pocketbase";
import { useAuth } from "@/components/auth-provider";
import { isRepUser } from "@/lib/rep-visibility";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useBooleanPreference } from "@/lib/use-boolean-preference"; import { useBooleanPreference } from "@/lib/use-boolean-preference";
import { colorForOrder } from "@/lib/order-color"; import { colorForOrder } from "@/lib/order-color";
@ -159,8 +161,14 @@ async function listAllInvoicingRows(
return [firstPage, ...rest].flatMap((page) => page.items); return [firstPage, ...rest].flatMap((page) => page.items);
} }
async function loadInvoicingRows(pb: ReturnType<typeof getPocketBase>): Promise<RawInvoicingRow[]> { async function loadInvoicingRows(
const seedRows = await listAllInvoicingRows(pb, INVOICING_STATUS_FILTER); pb: ReturnType<typeof getPocketBase>,
repId: string | null,
): Promise<RawInvoicingRow[]> {
const repFilter = repId
? ` && order.customer.rep = "${escapeFilterValue(repId)}"`
: "";
const seedRows = await listAllInvoicingRows(pb, INVOICING_STATUS_FILTER + repFilter);
const readyOrderIds = Array.from( const readyOrderIds = Array.from(
new Set( new Set(
seedRows seedRows
@ -174,6 +182,9 @@ async function loadInvoicingRows(pb: ReturnType<typeof getPocketBase>): Promise<
return seedRows; return seedRows;
} }
// No need to re-apply repFilter here — readyOrderIds is already
// derived from the rep-scoped seedRows above, so the sibling fetch
// (by explicit order id) is implicitly scoped too.
const siblingFilter = readyOrderIds const siblingFilter = readyOrderIds
.map((orderId) => `order = "${escapeFilterValue(orderId)}"`) .map((orderId) => `order = "${escapeFilterValue(orderId)}"`)
.join(" || "); .join(" || ");
@ -1404,18 +1415,22 @@ export default function InvoicingPage() {
// card list can render without a second round-trip. // card list can render without a second round-trip.
const [stockBookings, setStockBookings] = useState<StockBookingRow[] | null>(null); const [stockBookings, setStockBookings] = useState<StockBookingRow[] | null>(null);
const { user } = useAuth();
const load = useCallback(async () => { const load = useCallback(async () => {
setRows(null); setRows(null);
try { try {
const pb = getPocketBase(); const pb = getPocketBase();
const items = await loadInvoicingRows(pb); const userType = (user as { user_type?: string[] } | null)?.user_type;
const repId = user && isRepUser(userType) ? user.id : null;
const items = await loadInvoicingRows(pb, repId);
setRows(items); setRows(items);
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : String(err); const message = err instanceof Error ? err.message : String(err);
toast.error(message); toast.error(message);
setRows([]); setRows([]);
} }
}, []); }, [user]);
/** /**
* Lazy load of the booking list that surfaces on the Invoiced tab. * Lazy load of the booking list that surfaces on the Invoiced tab.