feat(stock): missing-pricing-only filter on /stock
This commit is contained in:
parent
f63fb7d8fc
commit
104cf4cc6c
2 changed files with 54 additions and 1 deletions
|
|
@ -337,4 +337,30 @@ describe("StockListPage", () => {
|
|||
expect(screen.getByText("Selling (R)")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Stock list — missing-pricing-only filter", () => {
|
||||
beforeEach(() => {
|
||||
_resetState();
|
||||
_state.items = [
|
||||
{ id: "aaaaaaaaaaaaaaa", sku: "B-001", name: "Has both", selling_price: 3.25, cost_price: 2.5, current_qty: 1, min_qty: 0, is_active: true, supplier: "" },
|
||||
{ id: "bbbbbbbbbbbbbbb", sku: "B-002", name: "Missing both", selling_price: 0, cost_price: 0, current_qty: 1, min_qty: 0, is_active: true, supplier: "" },
|
||||
];
|
||||
});
|
||||
afterEach(() => cleanup());
|
||||
|
||||
it("only shows rows where selling_price=0 AND cost_price=0", async () => {
|
||||
render(<Page />);
|
||||
expect(await screen.findByText("Has both")).toBeInTheDocument();
|
||||
|
||||
// Simulate the PB server filtering by the missing-pricing clause:
|
||||
// only "Missing both" comes back from getList when the toggle is on.
|
||||
mockListResponse([
|
||||
{ id: "bbbbbbbbbbbbbbb", sku: "B-002", name: "Missing both", selling_price: 0, cost_price: 0, current_qty: 1, min_qty: 0, is_active: true, supplier: "" },
|
||||
]);
|
||||
|
||||
fireEvent.click(screen.getByRole("switch", { name: /Missing pricing only/i }));
|
||||
await waitFor(() => expect(screen.queryByText("Has both")).toBeNull());
|
||||
expect(await screen.findByText("Missing both")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ export default function StockListPage() {
|
|||
const [search, setSearch] = useState("");
|
||||
const [lowStockOnly, setLowStockOnly] = useState(false);
|
||||
const [showPricingColumns, setShowPricingColumns] = useState(false);
|
||||
const [missingPricingOnly, setMissingPricingOnly] = useState(false);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
|
||||
const fetchItems = useCallback(async () => {
|
||||
|
|
@ -104,6 +105,9 @@ export default function StockListPage() {
|
|||
if (lowStockOnly) {
|
||||
filters.push("(current_qty <= min_qty && is_active = true)");
|
||||
}
|
||||
if (missingPricingOnly) {
|
||||
filters.push("(selling_price = 0 && cost_price = 0 && is_active = true)");
|
||||
}
|
||||
|
||||
const result = await pb.collection("stock_items").getList<StockItem>(
|
||||
1,
|
||||
|
|
@ -125,7 +129,7 @@ export default function StockListPage() {
|
|||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [search, lowStockOnly]);
|
||||
}, [search, lowStockOnly, missingPricingOnly]);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- data fetch on dep change
|
||||
|
|
@ -195,6 +199,11 @@ export default function StockListPage() {
|
|||
[items],
|
||||
);
|
||||
|
||||
const missingPricingCount = useMemo(
|
||||
() => items.filter((it) => (it.selling_price ?? 0) === 0 && (it.cost_price ?? 0) === 0).length,
|
||||
[items],
|
||||
);
|
||||
|
||||
const handleSearchChange = (v: string) => setSearchInput(v);
|
||||
const clearSearch = () => setSearchInput("");
|
||||
|
||||
|
|
@ -215,6 +224,15 @@ export default function StockListPage() {
|
|||
) : (
|
||||
"Inventory levels across all stock items."
|
||||
)}
|
||||
{missingPricingCount > 0 && (
|
||||
<span className="ml-2 inline-flex items-center">
|
||||
·{" "}
|
||||
<span className="font-semibold text-rose-700 dark:text-rose-400">
|
||||
{missingPricingCount}
|
||||
</span>{" "}
|
||||
need pricing
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
|
|
@ -276,6 +294,15 @@ export default function StockListPage() {
|
|||
<Label htmlFor="show-pricing-toggle" className="cursor-pointer">
|
||||
Show pricing columns
|
||||
</Label>
|
||||
<Switch
|
||||
id="missing-pricing-toggle"
|
||||
checked={missingPricingOnly}
|
||||
onCheckedChange={(checked) => setMissingPricingOnly(checked === true)}
|
||||
aria-label="Missing pricing only"
|
||||
/>
|
||||
<Label htmlFor="missing-pricing-toggle" className="cursor-pointer">
|
||||
Missing pricing only
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue