import { error, fail, redirect } from "@sveltejs/kit"; import type { Actions, PageServerLoad } from "./$types"; import type { StackqReceipt } from "$lib/types/receipts"; import type { ReceiptDataShape } from "$lib/types/receipts"; import { convertHeicFileToJpegIfNeeded } from "$lib/convert-heic-server"; import { parseNumWithDefault } from "$lib/parse-form"; import { validateImageUpload } from "$lib/validate-image-upload"; const COLLECTION = "Stackq_Receipts"; const STATUS_OPTIONS = ["New", "Data Entered", "Reviewed", "Consolidated"] as const; const TYPE_OPTIONS = ["Purchase", "Gas"] as const; const MAX_LINE_ITEMS = 500; export const load: PageServerLoad = async ({ params, locals }) => { const { id } = params; try { const record = await locals.pb .collection(COLLECTION) .getOne(id, { expand: "User" }); return { receipt: record }; } catch (err: unknown) { const is404 = err && typeof err === "object" && "status" in err && (err as { status: number }).status === 404; if (is404) throw error(404, "Receipt not found"); throw err; } }; export const actions: Actions = { update: async ({ request, locals, params }) => { const id = params.id; if (!id) return fail(400, { error: "Missing id", form: "update" }); const form = await request.formData(); const Status = (form.get("Status") as string)?.trim() || "New"; const Type = (form.get("Type") as string)?.trim() || "Purchase"; const dataJson = (form.get("dataJson") as string)?.trim() ?? "{}"; const imageFile = form.get("Image") as File | null; const hasImage = imageFile && imageFile.size > 0; if (hasImage) { const imgError = validateImageUpload(imageFile); if (imgError) return fail(400, { error: imgError, form: "update" }); } let imageToSave: File | null = hasImage ? imageFile : null; if (imageToSave) { try { imageToSave = await convertHeicFileToJpegIfNeeded(imageToSave); } catch { return fail(400, { error: "Could not convert HEIC image. Try saving as JPEG (iPhone: Settings → Camera → Formats) or use a different photo.", form: "update", }); } } let Data: ReceiptDataShape = {}; try { const parsed = JSON.parse(dataJson) as unknown; if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { const o = parsed as Record; if (Array.isArray(o.lineItems)) { const filtered = o.lineItems.filter( (row: unknown): row is { item: string; description: string; quantity: number; price: number } => row != null && typeof row === "object" && "item" in row && "quantity" in row && "price" in row ); Data.lineItems = filtered.slice(0, MAX_LINE_ITEMS).map((row) => ({ item: String(row.item ?? ""), description: String(row.description ?? ""), quantity: parseNumWithDefault(row.quantity, 0), price: parseNumWithDefault(row.price, 0), })); } if (typeof o.tax === "number" && Number.isFinite(o.tax)) Data.tax = o.tax; else if (typeof o.tax === "string") Data.tax = parseNumWithDefault(o.tax, 0); if (typeof o.receiptTotal === "number" && Number.isFinite(o.receiptTotal)) { Data.receiptTotal = o.receiptTotal; } else if (typeof o.receiptTotal === "string") { Data.receiptTotal = parseNumWithDefault(o.receiptTotal, 0); } } } catch { return fail(400, { error: "Invalid receipt data JSON", form: "update" }); } const body: Record = { Status: STATUS_OPTIONS.includes(Status as (typeof STATUS_OPTIONS)[number]) ? Status : "New", Type: TYPE_OPTIONS.includes(Type as (typeof TYPE_OPTIONS)[number]) ? Type : "Purchase", Data: Object.keys(Data).length ? Data : undefined, }; if (imageToSave) body.Image = imageToSave; try { await locals.pb.collection(COLLECTION).update(id, body); } catch (e: unknown) { const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Update failed"; return fail(500, { error: message, form: "update" }); } return { success: true }; }, delete: async ({ locals, params }) => { const id = params.id; if (!id) return fail(400, { error: "Missing id", form: "delete" }); try { await locals.pb.collection(COLLECTION).delete(id); } catch (e: unknown) { const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Delete failed"; return fail(500, { error: message, form: "delete" }); } throw redirect(303, "/receipts"); }, };