feat(pos): add listActiveStockCatalog
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
af7a74b4fa
commit
a228b02156
2 changed files with 119 additions and 0 deletions
69
src/lib/server/pos-service.test.ts
Normal file
69
src/lib/server/pos-service.test.ts
Normal 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,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
50
src/lib/server/pos-service.ts
Normal file
50
src/lib/server/pos-service.ts
Normal 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),
|
||||
}));
|
||||
}
|
||||
Loading…
Reference in a new issue