RJBULK-APP/actions/pricing.ts
SJCoetz 1efa979443 feat: initial setup of rj bulk supplies backoffice app
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
2026-07-19 20:38:16 +02:00

157 lines
6.1 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { pbCreate, pbDelete, pbList, pbUpdate } from "@/lib/pocketbase";
import { getAuth } from "@/lib/auth";
import { pbId } from "@/lib/pb";
export type ActionResult = { ok: true } | { ok: false; error: string };
const ruleSchema = z.object({
customer: z.string().min(1, "Customer is required"),
year: z.coerce.number().int().min(2000).max(2100),
month: z.coerce.number().int().min(1).max(12),
price_per_litre: z.coerce.number().positive("Price must be > 0"),
special_price: z.coerce.number().optional(),
});
export async function createPricingRule(_: unknown, formData: FormData): Promise<ActionResult> {
const { token, user } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
const parsed = ruleSchema.safeParse({
customer: formData.get("customer"),
year: formData.get("year"),
month: formData.get("month"),
price_per_litre: formData.get("price_per_litre"),
special_price: formData.get("special_price") || undefined,
});
if (!parsed.success) return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
try {
await pbCreate(
"customer_pricing",
{
id: pbId(),
...parsed.data,
...(user ? { created_by: user.id } : {}),
},
token,
);
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to create rule" };
}
revalidatePath("/pricing");
return { ok: true };
}
export async function updatePricingRule(id: string, _: unknown, formData: FormData): Promise<ActionResult> {
const { token } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
const parsed = ruleSchema.safeParse({
customer: formData.get("customer"),
year: formData.get("year"),
month: formData.get("month"),
price_per_litre: formData.get("price_per_litre"),
special_price: formData.get("special_price") || undefined,
});
if (!parsed.success) return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
try {
await pbUpdate("customer_pricing", id, parsed.data, token);
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to update rule" };
}
revalidatePath("/pricing");
return { ok: true };
}
export async function deletePricingRule(id: string): Promise<ActionResult> {
const { token } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
try {
await pbDelete("customer_pricing", id, token);
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to delete" };
}
revalidatePath("/pricing");
return { ok: true };
}
export async function updateDefaultPrice(price: number): Promise<ActionResult> {
const { token } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
if (isNaN(price) || price <= 0) return { ok: false, error: "Price must be > 0" };
try {
const settings = await pbList<any>("fuel_settings", { perPage: 1 }, token);
if (settings.items.length === 0) {
return { ok: false, error: "Settings not configured" };
}
await pbUpdate("fuel_settings", settings.items[0].id, { default_price_per_litre: price }, token);
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to update" };
}
revalidatePath("/pricing");
revalidatePath("/settings");
return { ok: true };
}
export async function applyMonthlyIncrease(year: number, month: number, deltaCents: number): Promise<ActionResult> {
const { token, user } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
if (isNaN(deltaCents) || deltaCents === 0) return { ok: false, error: "Delta must be non-zero" };
try {
// 1. Log the increase
const existing = await pbList<any>("monthly_price_increases", { perPage: 1, filter: `year = ${year} && month = ${month}` }, token);
if (existing.items.length > 0) {
await pbUpdate("monthly_price_increases", existing.items[0].id, { percentage_increase: deltaCents, applied_by: user?.id }, token);
} else {
await pbCreate("monthly_price_increases", { id: pbId(), year, month, percentage_increase: deltaCents, applied_by: user?.id }, token);
}
// 2. Get all active customers
const customers = (await pbList<any>("customers", { perPage: 500 }, token)).items;
// 3. For each customer, create a new pricing rule with the increased price (if no special price exists)
for (const c of customers) {
// Check existing rule for this month
const filter = `customer = "${c.id}" && year = ${year} && month = ${month}`;
const existingRule = await pbList<any>("customer_pricing", { perPage: 1, filter }, token);
// Get the previous month's price
const prev = new Date(year, month - 2, 1);
const prevFilter = `customer = "${c.id}" && year = ${prev.getFullYear()} && month = ${prev.getMonth() + 1}`;
const prevRule = await pbList<any>("customer_pricing", { perPage: 1, filter: prevFilter, sort: "-id" }, token);
let basePrice: number;
if (prevRule.items.length > 0) {
basePrice = Number(prevRule.items[0].special_price ?? prevRule.items[0].price_per_litre);
} else {
// Use default price
const settings = await pbList<any>("fuel_settings", { perPage: 1 }, token);
basePrice = Number(settings.items[0]?.default_price_per_litre || 0);
}
// Apply cents increase (not percentage — field is misnamed)
const newPrice = Number((basePrice + deltaCents / 100).toFixed(2));
if (existingRule.items.length > 0) {
// Update the rule (keep special_price if it existed)
const row = existingRule.items[0];
await pbUpdate("customer_pricing", row.id, { price_per_litre: newPrice }, token);
} else {
// Create new rule
await pbCreate("customer_pricing", {
id: pbId(),
customer: c.id,
year,
month,
price_per_litre: newPrice,
created_by: user?.id,
}, token);
}
}
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to apply increase" };
}
revalidatePath("/pricing");
return { ok: true };
}