148 lines
5.2 KiB
TypeScript
148 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 };
|
||
|
|
}
|