feat(pos): add listActiveStockCatalog

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Dave 2026-08-31 01:31:53 +02:00
parent af7a74b4fa
commit a228b02156
2 changed files with 119 additions and 0 deletions

View file

@ -0,0 +1,69 @@
import { describe, it, expect, vi } from "vitest";
import { listActiveStockCatalog } from "./pos-service";
function stubPb(items: unknown[]) {
return {
collection: () => ({
getFullList: vi.fn().mockResolvedValue(items),
getList: vi.fn(),
getOne: vi.fn(),
create: vi.fn(),
update: vi.fn(),
}),
};
}
describe("listActiveStockCatalog", () => {
it("flattens stock_items rows, pulling the expanded category name", async () => {
const pb = stubPb([
{
id: "si1",
sku: "SKU-1",
name: "Widget",
description: "A widget",
unit: "ea",
current_qty: 12,
selling_price: 99.5,
cost_price: 50,
expand: { category: { name: "Hardware" } },
},
{
id: "si2",
sku: "SKU-2",
name: "Uncategorised thing",
description: "",
unit: "ea",
current_qty: 0,
selling_price: 10,
cost_price: 5,
},
]);
const result = await listActiveStockCatalog(pb);
expect(result).toEqual([
{
id: "si1",
sku: "SKU-1",
name: "Widget",
description: "A widget",
unit: "ea",
category: "Hardware",
current_qty: 12,
selling_price: 99.5,
cost_price: 50,
},
{
id: "si2",
sku: "SKU-2",
name: "Uncategorised thing",
description: "",
unit: "ea",
category: "",
current_qty: 0,
selling_price: 10,
cost_price: 5,
},
]);
});
});

View file

@ -0,0 +1,50 @@
import type { PosCatalogItem, PosSale, PosSaleItem, StockItem } from "@/lib/types";
/**
* PB client type kept loose so both the real `PocketBase` instance
* and test stubs satisfy the contract — same approach as
* `stock-items-service.ts`'s `StockItemsPb`.
*/
export type PosPb = {
collection: (name: string) => {
getFullList: <T = unknown>(opts?: Record<string, unknown>) => Promise<T[]>;
getList: <T = unknown>(
page: number,
perPage: number,
opts?: Record<string, unknown>,
) => Promise<{ items: T[] }>;
getOne: <T = unknown>(id: string, opts?: Record<string, unknown>) => Promise<T>;
create: <T = unknown>(data: Record<string, unknown>) => Promise<T>;
update: <T = unknown>(id: string, data: Record<string, unknown>) => Promise<T>;
};
};
// ─── listActiveStockCatalog ────────────────────────────────────────
type StockItemWithCategory = StockItem & {
expand?: { category?: { name?: string } };
};
/**
* Catalog source for the POS: active stock_items only, flattened so
* the frontend doesn't need to know about PB's relation/expand shape.
*/
export async function listActiveStockCatalog(pb: PosPb): Promise<PosCatalogItem[]> {
const items = await pb.collection("stock_items").getFullList<StockItemWithCategory>({
filter: "is_active = true",
sort: "name",
expand: "category",
});
return items.map((it) => ({
id: it.id,
sku: it.sku,
name: it.name,
description: it.description,
unit: it.unit,
category: it.expand?.category?.name || "",
current_qty: Number(it.current_qty ?? 0),
selling_price: Number(it.selling_price ?? 0),
cost_price: Number(it.cost_price ?? 0),
}));
}