Adds complete initial codebase for the RJ Bulk Supplies fuel delivery backoffice: - Authentication pages and protected route layouts - Customer, supplier, fuel purchase, and delivery note management - Custom reporting and customer statement sending tools - Full UI component library and Tailwind styling - Server actions, API routes, and PocketBase integration - Development scripts and project configuration updates - Updated README and package metadata
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
import { pbList } from "./pocketbase";
|
|
import { DELIVERY_STATUSES } from "./types";
|
|
|
|
export interface DeliveryNoteInput {
|
|
number: string;
|
|
date: string;
|
|
customer: string;
|
|
vehicle_registration?: string;
|
|
qty_litres_supplied: number;
|
|
plant_dispensing?: string;
|
|
status?: string;
|
|
price_per_litre?: number;
|
|
total_value?: number;
|
|
pricing_source?: string;
|
|
}
|
|
|
|
export async function getLatestNoteNumber(token: string): Promise<string> {
|
|
const res = await pbList<any>("delivery_notes", { perPage: 500, fields: "number" }, token);
|
|
if (res.items.length === 0) return "1";
|
|
// Find the highest numeric value across all notes, regardless of any prefix.
|
|
// Preserves prefix (e.g. "DN-001" -> "DN-002") and pads to the same width.
|
|
let bestPrefix = "";
|
|
let bestNum = 0;
|
|
let bestWidth = 1;
|
|
for (const n of res.items) {
|
|
const raw = (n.number || "").trim();
|
|
if (!raw) continue;
|
|
const m = raw.match(/^(\D*)(\d+)$/);
|
|
if (!m) continue;
|
|
const num = parseInt(m[2], 10);
|
|
if (num > bestNum) {
|
|
bestPrefix = m[1];
|
|
bestNum = num;
|
|
bestWidth = m[2].length;
|
|
}
|
|
}
|
|
if (bestNum === 0) return "1";
|
|
const next = bestNum + 1;
|
|
return bestPrefix + String(next).padStart(bestWidth, "0");
|
|
}
|
|
|
|
export async function getDefaultPrice(token: string): Promise<number> {
|
|
try {
|
|
const res = await pbList<any>("fuel_settings", { perPage: 1 }, token);
|
|
if (res.items.length > 0) return Number(res.items[0].default_price_per_litre) || 0;
|
|
} catch {}
|
|
return 0;
|
|
}
|
|
|
|
export async function getCustomerPriceForDate(
|
|
customerId: string,
|
|
date: string,
|
|
token: string,
|
|
): Promise<{ price: number; source: string }> {
|
|
const d = new Date(date);
|
|
if (isNaN(d.getTime())) return { price: await getDefaultPrice(token), source: "default" };
|
|
const y = d.getFullYear();
|
|
const m = d.getMonth() + 1;
|
|
try {
|
|
const filter = `customer = "${customerId}" && year = ${y} && month = ${m}`;
|
|
const res = await pbList<any>("customer_pricing", { perPage: 1, filter }, token);
|
|
if (res.items.length > 0) {
|
|
const row = res.items[0];
|
|
const price = row.special_price ?? row.price_per_litre;
|
|
return { price: Number(price) || 0, source: row.special_price != null ? "special" : "monthly" };
|
|
}
|
|
} catch {}
|
|
return { price: await getDefaultPrice(token), source: "default" };
|
|
}
|
|
|
|
export function cleanEmpty(obj: any) {
|
|
const out: any = {};
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
if (v === "" || v === null || v === undefined) continue;
|
|
out[k] = v;
|
|
}
|
|
return out;
|
|
}
|