- Supplier invoices on purchase orders (several per PO), global Upload invoice with search and drag-drop - PO invoice status line (red/orange/teal), week/month filter, group by supplier, drag-drop PO upload - Suppliers 2: spend, payable by terms, cash hidden by default, manual payments with surplus/shortfall - Fix multi-page PO parsing (per-page rows, header filter direction) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
63 lines
3.2 KiB
TypeScript
63 lines
3.2 KiB
TypeScript
import { Store } from "lucide-react";
|
|
import { serverAuth } from "@/lib/pb";
|
|
import { buildSupplierSpend } from "@/lib/supplier-spend";
|
|
import { Suppliers2View } from "@/components/Suppliers2View";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* Suppliers 2 — the Income Overview treatment for suppliers: one card per company with a
|
|
* 12-month spend chart, this month's spend, and what is payable. Cash suppliers are hidden
|
|
* by default. Same data as /expenses/suppliers; nothing here is written.
|
|
*/
|
|
export default async function Suppliers2Page() {
|
|
const pb = await serverAuth();
|
|
const [suppliers, purchases, invoices, slips, payments] = await Promise.all([
|
|
pb.collection("suppliers").getFullList({ sort: "name" }),
|
|
pb.collection("purchases").getFullList({ fields: "id,po_no,po_date,total,supplier_ref" }),
|
|
pb.collection("purchase_invoices").getFullList({ fields: "purchase,invoice_no,invoice_date,amount_incl" }).catch(() => [] as any[]),
|
|
pb.collection("cash_float_entries").getFullList({ filter: 'kind = "expense"', fields: "amount,occurred_on,created,supplier_ref" }),
|
|
// Created by scripts/setup-supplier-payments-collection.mjs; absent on a fresh database.
|
|
pb.collection("supplier_payments").getFullList().catch(() => [] as any[]),
|
|
]);
|
|
const invByPo = new Map<string, any[]>();
|
|
for (const v of invoices) invByPo.set(v.purchase, [...(invByPo.get(v.purchase) ?? []), v]);
|
|
|
|
const now = new Date();
|
|
const rows = buildSupplierSpend({
|
|
year: now.getFullYear(),
|
|
month: now.getMonth() + 1,
|
|
suppliers: suppliers.map((s) => ({ id: s.id, name: String(s.name), code: String(s.code || ""), terms: String(s.terms || "") })),
|
|
purchases: purchases.map((p) => ({
|
|
id: p.id, po_no: String(p.po_no || ""), po_date: String(p.po_date || ""), total: Number(p.total || 0), supplier_ref: String(p.supplier_ref || ""),
|
|
invoices: (invByPo.get(p.id) ?? []).map((v) => ({
|
|
invoice_no: String(v.invoice_no || ""), invoice_date: String(v.invoice_date || ""),
|
|
amount_incl: typeof v.amount_incl === "number" && v.amount_incl > 0 ? v.amount_incl : null,
|
|
})),
|
|
})),
|
|
payments: payments.map((x) => ({
|
|
id: x.id, supplier_ref: String(x.supplier || ""), paid_on: String(x.paid_on || ""),
|
|
description: String(x.description || ""), amount: Number(x.amount || 0),
|
|
})),
|
|
slips: slips.map((s) => ({ amount: Number(s.amount || 0), at: String(s.occurred_on || s.created || ""), supplier_ref: String(s.supplier_ref || "") })),
|
|
});
|
|
|
|
return (
|
|
<div className="p-4 sm:p-8 max-w-[1400px]">
|
|
<Suppliers2View
|
|
rows={rows}
|
|
year={now.getFullYear()}
|
|
month={now.getMonth() + 1}
|
|
title={
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 shrink-0 rounded-xl bg-gradient-to-br from-[#30638E]/15 to-[#00798C]/15 grid place-items-center text-[#30638E]"><Store className="w-5 h-5" /></div>
|
|
<div>
|
|
<h1 className="text-2xl font-semibold text-[#003D5B] dark:text-white">Suppliers 2</h1>
|
|
<p className="text-gray-500 dark:text-gray-400 text-sm">Spend and payables per supplier</p>
|
|
</div>
|
|
</div>
|
|
}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|