feat(pos): add listSalesInRange for history/cashup

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Dave 2026-08-31 01:34:27 +02:00
parent b177aca35c
commit 739ef8f846
2 changed files with 114 additions and 1 deletions

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi } from "vitest";
import { checkoutSale, listActiveStockCatalog } from "./pos-service";
import { checkoutSale, listActiveStockCatalog, listSalesInRange } from "./pos-service";
function stubPb(items: unknown[]) {
return {
@ -279,3 +279,89 @@ describe("checkoutSale", () => {
expect(updates).toEqual([]);
});
});
describe("listSalesInRange", () => {
it("fetches sales in the range and their line items in one follow-up call", async () => {
const salesGetList = vi.fn().mockResolvedValue({
items: [
{ id: "sale-1", total: 100 },
{ id: "sale-2", total: 200 },
],
});
const itemsGetList = vi.fn().mockResolvedValue({
items: [
{ id: "si-1", sale: "sale-1", qty: 2 },
{ id: "si-2", sale: "sale-2", qty: 1 },
],
});
const pb = {
collection: (name: string) => {
if (name === "pos_sales") {
return {
getFullList: vi.fn(),
getOne: vi.fn(),
create: vi.fn(),
update: vi.fn(),
getList: salesGetList,
};
}
if (name === "pos_sale_items") {
return {
getFullList: vi.fn(),
getOne: vi.fn(),
create: vi.fn(),
update: vi.fn(),
getList: itemsGetList,
};
}
throw new Error(`unexpected collection ${name}`);
},
};
const result = await listSalesInRange(pb, {
from: "2026-08-30 00:00:00",
to: "2026-08-30 23:59:59",
});
expect(result.sales).toHaveLength(2);
expect(result.items).toHaveLength(2);
expect(salesGetList).toHaveBeenCalledWith(1, 500, {
filter: "created >= '2026-08-30 00:00:00' && created <= '2026-08-30 23:59:59'",
sort: "-created",
});
expect(itemsGetList).toHaveBeenCalledWith(1, 2000, {
filter: "sale = 'sale-1' || sale = 'sale-2'",
});
});
it("skips the items query when there are no sales in range", async () => {
const salesGetList = vi.fn().mockResolvedValue({ items: [] });
const itemsGetList = vi.fn();
const pb = {
collection: (name: string) =>
name === "pos_sales"
? {
getFullList: vi.fn(),
getOne: vi.fn(),
create: vi.fn(),
update: vi.fn(),
getList: salesGetList,
}
: {
getFullList: vi.fn(),
getOne: vi.fn(),
create: vi.fn(),
update: vi.fn(),
getList: itemsGetList,
},
};
const result = await listSalesInRange(pb, {
from: "2026-08-30 00:00:00",
to: "2026-08-30 23:59:59",
});
expect(result).toEqual({ sales: [], items: [] });
expect(itemsGetList).not.toHaveBeenCalled();
});
});

View file

@ -264,3 +264,30 @@ export async function checkoutSale(
return { ok: true, sale, items, stockErrors, duplicate: false };
}
// ─── listSalesInRange ───────────────────────────────────────────────
export type PosSalesRangeResult = {
sales: PosSale[];
items: PosSaleItem[];
};
/**
* Sales + their line items for history.html / cashup.html. `from`/`to`
* are PB datetime filter strings (e.g. "2026-08-30 00:00:00").
*/
export async function listSalesInRange(
pb: PosPb,
range: { from: string; to: string },
): Promise<PosSalesRangeResult> {
const salesPage = await pb.collection("pos_sales").getList<PosSale>(1, 500, {
filter: `created >= '${escapePbValue(range.from)}' && created <= '${escapePbValue(range.to)}'`,
sort: "-created",
});
const sales = salesPage.items;
if (sales.length === 0) return { sales: [], items: [] };
const filter = sales.map((s) => `sale = '${escapePbValue(s.id)}'`).join(" || ");
const itemsPage = await pb.collection("pos_sale_items").getList<PosSaleItem>(1, 2000, { filter });
return { sales, items: itemsPage.items };
}