- B.1: removed running_cost from PB monthly_income schema - B.2: 40 of 40 machines now have income (was 32/40) - B.3: removed syntheticCode() + auto-create branch; deleted 2 ghost Z* assets; seed fails loudly on unmatched - C.1: collapsed 13 cost-component rows into 5 machines + new cost_lines collection; Finance/Insurance/Tracking categories removed from assets - C.2: type field on assets derived from code prefix - C.3: sequential codes SC01-07, OC01-02, PC01-02; fixed SITE CONTANER spelling - C.4: flat sortable plant register table with type filter pills - C.5: wired filter input on plant register - D: verified term 2 (insurance) reconciles for all 5 financed machines
110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
import { serverAuth } from "./pb";
|
|
import type { RecordModel } from "pocketbase";
|
|
|
|
export type Asset = {
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
category: string;
|
|
type: string;
|
|
gl_account: string;
|
|
supplier: string;
|
|
monthly_excl: number;
|
|
monthly_incl: number;
|
|
purchase_value: number;
|
|
is_provisional: boolean;
|
|
} & RecordModel;
|
|
|
|
export type CostLine = {
|
|
id: string;
|
|
asset: string;
|
|
year: number;
|
|
month: number;
|
|
kind: "instalment" | "insurance" | "tracking" | "operator" | "running";
|
|
amount: number;
|
|
} & RecordModel;
|
|
|
|
export type MonthlyIncome = {
|
|
id: string;
|
|
asset: string;
|
|
year: number;
|
|
month: number;
|
|
income: number;
|
|
fixed_expenses: number;
|
|
running_cost: number;
|
|
} & RecordModel;
|
|
|
|
export type MonthlyRunningCost = {
|
|
id: string;
|
|
asset: string;
|
|
year: number;
|
|
month: number;
|
|
amount: number;
|
|
} & RecordModel;
|
|
|
|
export type Overhead = {
|
|
id: string;
|
|
section: string;
|
|
description: string;
|
|
gl_account: string;
|
|
supplier: string;
|
|
qty: number;
|
|
unit_price: number;
|
|
amount_excl: number;
|
|
vat: number;
|
|
amount_incl: number;
|
|
} & RecordModel;
|
|
|
|
export async function getAssets(): Promise<Asset[]> {
|
|
const pb = await serverAuth();
|
|
return pb.collection("assets").getFullList<Asset>({ sort: "category,name" });
|
|
}
|
|
|
|
export async function getAssetsByCategory(category: string): Promise<Asset[]> {
|
|
const all = await getAssets();
|
|
return all.filter((a) => a.category === category);
|
|
}
|
|
|
|
export async function getMonthlyIncome(year = 2026): Promise<MonthlyIncome[]> {
|
|
const pb = await serverAuth();
|
|
return pb.collection("monthly_income").getFullList<MonthlyIncome>({
|
|
filter: `year = ${year}`,
|
|
sort: "month",
|
|
});
|
|
}
|
|
|
|
// Part 1.2: YTD aggregations operate on actuals only by default.
|
|
// `is_forecast` is a boolean field on monthly_income; rows where it's true are excluded.
|
|
export async function getActualMonthlyIncome(year = 2026): Promise<MonthlyIncome[]> {
|
|
const all = await getMonthlyIncome(year);
|
|
return all.filter((r) => !(r as any).is_forecast);
|
|
}
|
|
|
|
// Returns the last month that has any actual income — used to label forecast ranges.
|
|
export function lastActualMonth(rows: MonthlyIncome[]): number {
|
|
let m = 0;
|
|
for (const r of rows) if (r.income > 0 && r.month > m) m = r.month;
|
|
return m;
|
|
}
|
|
|
|
export async function getRunningCosts(year = 2026): Promise<MonthlyRunningCost[]> {
|
|
const pb = await serverAuth();
|
|
return pb.collection("monthly_running_costs").getFullList<MonthlyRunningCost>({
|
|
filter: `year = ${year}`,
|
|
sort: "month",
|
|
});
|
|
}
|
|
|
|
export async function getOverheads(): Promise<Overhead[]> {
|
|
const pb = await serverAuth();
|
|
return pb.collection("overheads").getFullList<Overhead>({ sort: "section,description" });
|
|
}
|
|
|
|
export async function getOverheadsBySection(section: string): Promise<Overhead[]> {
|
|
const all = await getOverheads();
|
|
return all.filter((o) => o.section === section);
|
|
}
|
|
|
|
export const MONTH_NAMES = [
|
|
"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",
|
|
];
|