RJBULK-APP/actions/delivery-notes.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

147 lines
5.2 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { pbCreate, pbDelete, pbUpdate } from "@/lib/pocketbase";
import { getAuth } from "@/lib/auth";
import { pbId } from "@/lib/pb";
import { DELIVERY_STATUSES } from "@/lib/types";
import {
getCustomerPriceForDate,
cleanEmpty,
} from "@/lib/delivery-notes";
const noteSchema = z.object({
number: z.string().min(1, "Note # is required"),
date: z.string().min(1, "Date is required"),
customer: z.string().min(1, "Customer is required"),
vehicle_registration: z.string().optional().default(""),
qty_litres_supplied: z.coerce.number().positive("Qty must be > 0"),
plant_dispensing: z.string().optional().default(""),
status: z.string().optional().default("Pending"),
price_per_litre: z.coerce.number().optional(),
total_value: z.coerce.number().optional(),
pricing_source: z.string().optional().default(""),
});
export type ActionResult = { ok: true } | { ok: false; error: string };
export async function createDeliveryNote(_: unknown, formData: FormData): Promise<ActionResult> {
const { token } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
const raw = {
number: formData.get("number"),
date: formData.get("date"),
customer: formData.get("customer"),
vehicle_registration: formData.get("vehicle_registration") || "",
qty_litres_supplied: formData.get("qty_litres_supplied"),
plant_dispensing: formData.get("plant_dispensing") || "",
status: formData.get("status") || "Pending",
price_per_litre: formData.get("price_per_litre") || undefined,
total_value: formData.get("total_value") || undefined,
pricing_source: formData.get("pricing_source") || "",
};
const parsed = noteSchema.safeParse(raw);
if (!parsed.success) {
return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
}
// Auto-fill price/total if not provided
let price = parsed.data.price_per_litre;
let total = parsed.data.total_value;
let source = parsed.data.pricing_source;
if (!price) {
const p = await getCustomerPriceForDate(parsed.data.customer, parsed.data.date, token);
price = p.price;
source = p.source;
}
if (price && !total) {
total = Number((price * parsed.data.qty_litres_supplied).toFixed(2));
}
try {
await pbCreate(
"delivery_notes",
{
id: pbId(),
...cleanEmpty({ ...parsed.data, price_per_litre: price, total_value: total, pricing_source: source }),
},
token,
);
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to create delivery note" };
}
revalidatePath("/delivery-notes");
revalidatePath("/dashboard");
return { ok: true };
}
export async function updateDeliveryNote(id: string, _: unknown, formData: FormData): Promise<ActionResult> {
const { token } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
const raw = {
number: formData.get("number"),
date: formData.get("date"),
customer: formData.get("customer"),
vehicle_registration: formData.get("vehicle_registration") || "",
qty_litres_supplied: formData.get("qty_litres_supplied"),
plant_dispensing: formData.get("plant_dispensing") || "",
status: formData.get("status") || "Pending",
price_per_litre: formData.get("price_per_litre") || undefined,
total_value: formData.get("total_value") || undefined,
pricing_source: formData.get("pricing_source") || "",
};
const parsed = noteSchema.safeParse(raw);
if (!parsed.success) {
return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
}
try {
await pbUpdate("delivery_notes", id, cleanEmpty(parsed.data), token);
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to update delivery note" };
}
revalidatePath("/delivery-notes");
revalidatePath(`/delivery-notes/${id}`);
revalidatePath("/dashboard");
return { ok: true };
}
export async function deleteDeliveryNote(id: string): Promise<ActionResult> {
const { token } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
try {
await pbDelete("delivery_notes", id, token);
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to delete" };
}
revalidatePath("/delivery-notes");
revalidatePath("/dashboard");
return { ok: true };
}
export async function updateStatus(id: string, status: string): Promise<ActionResult> {
const { token } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
try {
await pbUpdate("delivery_notes", id, { status }, token);
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to update status" };
}
revalidatePath("/delivery-notes");
return { ok: true };
}
export async function bulkUpdateStatus(ids: string[], status: string): Promise<ActionResult> {
const { token } = await getAuth();
if (!token) return { ok: false, error: "Not signed in" };
try {
await Promise.all(ids.map((id) => pbUpdate("delivery_notes", id, { status }, token)));
} catch (err: any) {
return { ok: false, error: err?.message || "Failed to bulk update" };
}
revalidatePath("/delivery-notes");
return { ok: true };
}