feat(pos): add listSalesInRange for history/cashup
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b177aca35c
commit
739ef8f846
2 changed files with 114 additions and 1 deletions
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue