diff --git a/src/lib/arcApplicationRecordPdf.ts b/src/lib/arcApplicationRecordPdf.ts index a146810..641d48e 100644 --- a/src/lib/arcApplicationRecordPdf.ts +++ b/src/lib/arcApplicationRecordPdf.ts @@ -231,8 +231,9 @@ export async function generateArcApplicationRecord(application: any) { } } - // Decision notes + // Decision notes — full paragraph break after the comments thread if (application.decision_notes) { + y += 14; if (y > pageHeight - 100) { doc.addPage(); y = 40; } doc.setFont("helvetica", "bold"); doc.setFontSize(11); diff --git a/supabase/functions/buildium-import-apply/index.ts b/supabase/functions/buildium-import-apply/index.ts index abebc32..6eb5ea0 100644 --- a/supabase/functions/buildium-import-apply/index.ts +++ b/supabase/functions/buildium-import-apply/index.ts @@ -261,6 +261,7 @@ Deno.serve(async (req) => { const buildiumAssocId: string | null = p._arc_buildium_association_id || null; const buildiumArcId: string | null = p.buildium_arc_request_id || null; const deciderName: string | null = p._arc_decider_name || null; + const deciderDate: string | null = p._arc_decider_date || null; const clean = stripPrivate(p); @@ -288,21 +289,16 @@ Deno.serve(async (req) => { ? "approve" : (decisionRaw.includes("den") || decisionRaw.includes("reject") ? "deny" : null); if (appId && voteDir) { - const voterName = `${deciderName || "Buildium"} (Buildium)`; - await supabase - .from("entity_votes") - .delete() - .eq("entity_type", "arc_application") - .eq("entity_id", appId) - .is("user_id", null) - .ilike("voter_name", "% (Buildium)"); - const { error: voteErr } = await supabase.from("entity_votes").insert({ - entity_type: "arc_application", - entity_id: appId, - vote: voteDir, - user_id: null, - voter_name: voterName, - recorded_by: null, + // Finalized ARC apps are write-locked (prevent_writes_on_locked_arc), + // and Buildium imports arrive already finalized — so the vote goes + // through the import RPC, which bypasses the lock for this one write. + const { error: voteErr } = await supabase.rpc("record_buildium_arc_vote", { + p_application_id: appId, + p_vote: voteDir, + p_voter_name: `${deciderName || "Buildium"} (Buildium)`, + // Date the vote when the decision was actually made in Buildium, + // not when the import ran. + p_vote_date: deciderDate, }); if (voteErr) console.warn(`ARC vote record failed for ${appId}: ${voteErr.message}`); } diff --git a/supabase/functions/buildium-import-stage/index.ts b/supabase/functions/buildium-import-stage/index.ts index 759cf51..2c31eb7 100644 --- a/supabase/functions/buildium-import-stage/index.ts +++ b/supabase/functions/buildium-import-stage/index.ts @@ -519,9 +519,6 @@ Deno.serve(async (req) => { const decisionDate = isFinalDecision ? (String(r.DecisionDateTime || r.LastUpdatedDateTime || "").split("T")[0] || null) : null; - const decisionNotes = r.DecisionDescription - ? String(r.DecisionDescription) - : (r.Decision && !/pending/i.test(String(r.Decision)) ? `Decision: ${r.Decision}` : null); const status = mapArcStatus(r.Status, r.Decision); // Buildium exposes the user who last touched the request — use as best-available "decider" @@ -533,6 +530,15 @@ Deno.serve(async (req) => { ?.toString() .split("T")[0] || null; + // Decision notes: Buildium's API has no comment thread, so record the + // richest decision summary it does expose (who + when). Keep the format + // in sync with the SQL backfill so re-staging doesn't flap diffs. + const decisionNotes = r.DecisionDescription + ? String(r.DecisionDescription) + : (r.Decision && !/pending/i.test(String(r.Decision)) + ? `${r.Decision} by ${deciderName || "Buildium"}${deciderDate ? ` on ${deciderDate}` : ""} (via Buildium)` + : null); + // Optional file pull let files: Array<{ id: string; name: string; download_url: string | null }> = []; if (includeArcFiles) {