mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
ARC imports: record Buildium decision votes + richer decision notes; PDF spacing
- Buildium API exposes no ARC comment threads or member votes (verified live); surface the final decision instead: vote row dated to the actual decision, decision_notes 'Approved by X on date (via Buildium)' - record_buildium_arc_vote RPC bypasses the finalized-ARC write lock that was silently swallowing import votes; 69 existing imports backfilled - Application Record PDF: paragraph break between comments and decision notes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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) {
|
if (application.decision_notes) {
|
||||||
|
y += 14;
|
||||||
if (y > pageHeight - 100) { doc.addPage(); y = 40; }
|
if (y > pageHeight - 100) { doc.addPage(); y = 40; }
|
||||||
doc.setFont("helvetica", "bold");
|
doc.setFont("helvetica", "bold");
|
||||||
doc.setFontSize(11);
|
doc.setFontSize(11);
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ Deno.serve(async (req) => {
|
|||||||
const buildiumAssocId: string | null = p._arc_buildium_association_id || null;
|
const buildiumAssocId: string | null = p._arc_buildium_association_id || null;
|
||||||
const buildiumArcId: string | null = p.buildium_arc_request_id || null;
|
const buildiumArcId: string | null = p.buildium_arc_request_id || null;
|
||||||
const deciderName: string | null = p._arc_decider_name || null;
|
const deciderName: string | null = p._arc_decider_name || null;
|
||||||
|
const deciderDate: string | null = p._arc_decider_date || null;
|
||||||
|
|
||||||
const clean = stripPrivate(p);
|
const clean = stripPrivate(p);
|
||||||
|
|
||||||
@@ -288,21 +289,16 @@ Deno.serve(async (req) => {
|
|||||||
? "approve"
|
? "approve"
|
||||||
: (decisionRaw.includes("den") || decisionRaw.includes("reject") ? "deny" : null);
|
: (decisionRaw.includes("den") || decisionRaw.includes("reject") ? "deny" : null);
|
||||||
if (appId && voteDir) {
|
if (appId && voteDir) {
|
||||||
const voterName = `${deciderName || "Buildium"} (Buildium)`;
|
// Finalized ARC apps are write-locked (prevent_writes_on_locked_arc),
|
||||||
await supabase
|
// and Buildium imports arrive already finalized — so the vote goes
|
||||||
.from("entity_votes")
|
// through the import RPC, which bypasses the lock for this one write.
|
||||||
.delete()
|
const { error: voteErr } = await supabase.rpc("record_buildium_arc_vote", {
|
||||||
.eq("entity_type", "arc_application")
|
p_application_id: appId,
|
||||||
.eq("entity_id", appId)
|
p_vote: voteDir,
|
||||||
.is("user_id", null)
|
p_voter_name: `${deciderName || "Buildium"} (Buildium)`,
|
||||||
.ilike("voter_name", "% (Buildium)");
|
// Date the vote when the decision was actually made in Buildium,
|
||||||
const { error: voteErr } = await supabase.from("entity_votes").insert({
|
// not when the import ran.
|
||||||
entity_type: "arc_application",
|
p_vote_date: deciderDate,
|
||||||
entity_id: appId,
|
|
||||||
vote: voteDir,
|
|
||||||
user_id: null,
|
|
||||||
voter_name: voterName,
|
|
||||||
recorded_by: null,
|
|
||||||
});
|
});
|
||||||
if (voteErr) console.warn(`ARC vote record failed for ${appId}: ${voteErr.message}`);
|
if (voteErr) console.warn(`ARC vote record failed for ${appId}: ${voteErr.message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -519,9 +519,6 @@ Deno.serve(async (req) => {
|
|||||||
const decisionDate = isFinalDecision
|
const decisionDate = isFinalDecision
|
||||||
? (String(r.DecisionDateTime || r.LastUpdatedDateTime || "").split("T")[0] || null)
|
? (String(r.DecisionDateTime || r.LastUpdatedDateTime || "").split("T")[0] || null)
|
||||||
: 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);
|
const status = mapArcStatus(r.Status, r.Decision);
|
||||||
|
|
||||||
// Buildium exposes the user who last touched the request — use as best-available "decider"
|
// Buildium exposes the user who last touched the request — use as best-available "decider"
|
||||||
@@ -533,6 +530,15 @@ Deno.serve(async (req) => {
|
|||||||
?.toString()
|
?.toString()
|
||||||
.split("T")[0] || null;
|
.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
|
// Optional file pull
|
||||||
let files: Array<{ id: string; name: string; download_url: string | null }> = [];
|
let files: Array<{ id: string; name: string; download_url: string | null }> = [];
|
||||||
if (includeArcFiles) {
|
if (includeArcFiles) {
|
||||||
|
|||||||
Reference in New Issue
Block a user