fix: crash bugs in TeamInvoices + faster load
Bug fixes: - TeamInvoices: add useAuth import/currentUser (invoice-create crash) - TeamInvoices: setChartYear -> setExportYear (year-dropdown crash) - TeamDashboard: drop redundant setState-in-effect (cascading renders) - Companies: delete dead _UnusedClientCompanies (illegal hook calls) - annotate intentional empty PDF-fallback catches Load speed: - preconnect/dns-prefetch to Supabase origin - lazy-load heic-to in Converters: page chunk 2737KB -> 9KB - split recharts into its own 'charts' vendor chunk Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,686 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Checked Sites Revision Audit</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f5f1e8;
|
||||
--panel: rgba(255,255,255,0.78);
|
||||
--panel-strong: rgba(255,255,255,0.92);
|
||||
--border: rgba(43,37,28,0.12);
|
||||
--text: #231d14;
|
||||
--muted: #6b6255;
|
||||
--accent: #b8831f;
|
||||
--accent-soft: rgba(184,131,31,0.18);
|
||||
--ok: #1f8a4c;
|
||||
--ok-soft: rgba(31,138,76,0.12);
|
||||
--warn: #b45309;
|
||||
--warn-soft: rgba(180,83,9,0.14);
|
||||
--review: #2563eb;
|
||||
--review-soft: rgba(37,99,235,0.13);
|
||||
--todo: #7c3aed;
|
||||
--todo-soft: rgba(124,58,237,0.12);
|
||||
--shadow: 0 18px 40px rgba(35,29,20,0.08);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(184,131,31,0.14), transparent 28%),
|
||||
radial-gradient(circle at top right, rgba(37,99,235,0.09), transparent 24%),
|
||||
linear-gradient(180deg, #fbf8f2 0%, var(--bg) 100%);
|
||||
}
|
||||
.wrap {
|
||||
width: min(1400px, calc(100vw - 40px));
|
||||
margin: 32px auto 48px;
|
||||
}
|
||||
.hero, .panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 18px;
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.hero {
|
||||
padding: 28px 30px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.eyebrow {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 34px;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 15px;
|
||||
max-width: 900px;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.card {
|
||||
padding: 20px 22px;
|
||||
}
|
||||
.label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.value {
|
||||
font-size: 34px;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
.sub {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.panel {
|
||||
padding: 20px 22px;
|
||||
}
|
||||
h2 {
|
||||
margin: 0 0 14px;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.bar-row {
|
||||
display: grid;
|
||||
grid-template-columns: 150px 1fr 36px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.bar-label, .bar-value {
|
||||
font-size: 13px;
|
||||
}
|
||||
.bar-track {
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(35,29,20,0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, var(--accent), #d7a84b);
|
||||
}
|
||||
.table-wrap {
|
||||
overflow: auto;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
th, td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(35,29,20,0.08);
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
}
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--panel-strong);
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
font-size: 11px;
|
||||
z-index: 1;
|
||||
}
|
||||
.mono {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 72px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ok { color: var(--ok); background: var(--ok-soft); border-color: rgba(31,138,76,0.2); }
|
||||
.warn { color: var(--warn); background: var(--warn-soft); border-color: rgba(180,83,9,0.2); }
|
||||
.review { color: var(--review); background: var(--review-soft); border-color: rgba(37,99,235,0.2); }
|
||||
.todo { color: var(--todo); background: var(--todo-soft); border-color: rgba(124,58,237,0.18); }
|
||||
.neutral { color: var(--muted); background: rgba(35,29,20,0.06); border-color: rgba(35,29,20,0.08); }
|
||||
.section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.small {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.two-col { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.wrap { width: min(100vw - 20px, 1400px); margin: 20px auto 32px; }
|
||||
.hero, .panel { padding: 18px; border-radius: 14px; }
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
h1 { font-size: 28px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<section class="hero">
|
||||
<div class="eyebrow">Fourge Portal Audit</div>
|
||||
<h1>Checked Sites Revision Audit</h1>
|
||||
<p class="subtitle">This report treats your checkmark as “R00 was completed,” then separates that from billing and from any newer active revisions. It is designed to make client-billing comparison easier at a glance.</p>
|
||||
</section>
|
||||
|
||||
<section class="grid">
|
||||
<div class="panel card">
|
||||
<div class="label">Checked Sites</div>
|
||||
<div class="value">26</div>
|
||||
<div class="sub">Sites found in the database from your checked list.</div>
|
||||
</div>
|
||||
<div class="panel card">
|
||||
<div class="label">Active Revisions</div>
|
||||
<div class="value">16</div>
|
||||
<div class="sub">Checked sites that now have an active R01+.</div>
|
||||
</div>
|
||||
<div class="panel card">
|
||||
<div class="label">R00 Billed</div>
|
||||
<div class="value">23</div>
|
||||
<div class="sub">Checked sites where the new-book unit is already on an invoice.</div>
|
||||
</div>
|
||||
<div class="panel card">
|
||||
<div class="label">R00 Unbilled</div>
|
||||
<div class="value">3</div>
|
||||
<div class="sub">Checked sites completed at R00 but not yet billed.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="two-col">
|
||||
<div class="panel">
|
||||
<h2>Active Revision Status</h2>
|
||||
|
||||
<div class="bar-row">
|
||||
<div class="bar-label">not_started</div>
|
||||
<div class="bar-track"><div class="bar-fill" style="width:100%"></div></div>
|
||||
<div class="bar-value">12</div>
|
||||
</div>
|
||||
|
||||
<div class="bar-row">
|
||||
<div class="bar-label">client_review</div>
|
||||
<div class="bar-track"><div class="bar-fill" style="width:25%"></div></div>
|
||||
<div class="bar-value">3</div>
|
||||
</div>
|
||||
|
||||
<div class="bar-row">
|
||||
<div class="bar-label">client_approved</div>
|
||||
<div class="bar-track"><div class="bar-fill" style="width:8%"></div></div>
|
||||
<div class="bar-value">1</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>Active Revision Depth</h2>
|
||||
|
||||
<div class="bar-row">
|
||||
<div class="bar-label">R01</div>
|
||||
<div class="bar-track"><div class="bar-fill" style="width:100%"></div></div>
|
||||
<div class="bar-value">10</div>
|
||||
</div>
|
||||
|
||||
<div class="bar-row">
|
||||
<div class="bar-label">R02</div>
|
||||
<div class="bar-track"><div class="bar-fill" style="width:60%"></div></div>
|
||||
<div class="bar-value">6</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel section">
|
||||
<h2>Sites With Active Revisions</h2>
|
||||
<p class="small">These are the ones where R00 may be done, but a newer version is still active or awaiting review/approval.</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:90px;">Site #</th>
|
||||
<th style="width:240px;">DB Title</th>
|
||||
<th style="width:80px;">Internal #</th>
|
||||
<th style="width:96px;">R00 Done</th>
|
||||
<th style="width:180px;">R00 Billed</th>
|
||||
<th style="width:90px;">Active R#</th>
|
||||
<th style="width:120px;">Active Status</th>
|
||||
<th style="width:170px;">Completed Revisions</th>
|
||||
<th style="width:220px;">Billed Versions</th>
|
||||
<th>Version Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="mono">00286</td>
|
||||
<td>00286 Riverside, CA</td>
|
||||
<td class="mono">31</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">30055</td>
|
||||
<td>30055 Moreno Valley, CA</td>
|
||||
<td class="mono">33</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill review">client_review</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=client_review</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">30096</td>
|
||||
<td>30096 Eerie, CO</td>
|
||||
<td class="mono">36</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R02</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>R01 (unbilled)</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=completed; R02=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68639</td>
|
||||
<td>68639 NSA Los Lunas NM</td>
|
||||
<td class="mono">40</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill review">client_review</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=client_review</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68663</td>
|
||||
<td>68663 NSA Oklahoma City OK</td>
|
||||
<td class="mono">41</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68664</td>
|
||||
<td>68664 NSA Oklahoma City OK</td>
|
||||
<td class="mono">42</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68666</td>
|
||||
<td>68666 NSA Oklahoma City, OK</td>
|
||||
<td class="mono">43</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68669</td>
|
||||
<td>68669 NSA Oklahoma City OK</td>
|
||||
<td class="mono">44</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68670</td>
|
||||
<td>68670 NSA Oklahoma City OK</td>
|
||||
<td class="mono">45</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R02</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>R01 (billed)</td>
|
||||
<td>R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=completed/billed; R02=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68671</td>
|
||||
<td>68671 NSA Oklahoma City OK</td>
|
||||
<td class="mono">46</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68673</td>
|
||||
<td>68673 NSA Oklahoma City OK</td>
|
||||
<td class="mono">47</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill review">client_review</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=client_review</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68721</td>
|
||||
<td>68721 NSA Mechanicsburg PA</td>
|
||||
<td class="mono">48</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R02</td>
|
||||
<td><span class="pill approved">client_approved</span></td>
|
||||
<td>R01 (billed), R02 (unbilled)</td>
|
||||
<td>R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=completed/billed; R02=completed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68808</td>
|
||||
<td>68808 Camas, WA</td>
|
||||
<td class="mono">49</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R01</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68809</td>
|
||||
<td>68809 Centralia, WA</td>
|
||||
<td class="mono">50</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R02</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>R01 (billed)</td>
|
||||
<td>R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=completed/billed; R02=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68810</td>
|
||||
<td>68810 Chehalis, WA</td>
|
||||
<td class="mono">51</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R02</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>R01 (billed)</td>
|
||||
<td>R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=completed/billed; R02=not_started</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68811</td>
|
||||
<td>68811 Kelso, WA</td>
|
||||
<td class="mono">52</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill review">Yes</span></td>
|
||||
<td class="mono">R02</td>
|
||||
<td><span class="pill todo">not_started</span></td>
|
||||
<td>R01 (unbilled)</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed; R01=completed; R02=not_started</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel section">
|
||||
<h2>Checked Sites With No Active Revision</h2>
|
||||
<p class="small">These are simpler to read: R00 completed, and no current revision chain is active right now.</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:90px;">Site #</th>
|
||||
<th style="width:240px;">DB Title</th>
|
||||
<th style="width:80px;">Internal #</th>
|
||||
<th style="width:96px;">R00 Done</th>
|
||||
<th style="width:180px;">R00 Billed</th>
|
||||
<th style="width:90px;">Active R#</th>
|
||||
<th style="width:120px;">Active Status</th>
|
||||
<th style="width:170px;">Completed Revisions</th>
|
||||
<th style="width:220px;">Billed Versions</th>
|
||||
<th>Version Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="mono">30062</td>
|
||||
<td>30062 Riverside, CA</td>
|
||||
<td class="mono">34</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">30094</td>
|
||||
<td>30094 Colorado Springs, CO</td>
|
||||
<td class="mono">35</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill warn">No</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>—</td>
|
||||
<td>R00=completed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">30258</td>
|
||||
<td>30258 Lebanon, NH</td>
|
||||
<td class="mono">38</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">30259</td>
|
||||
<td>30259 Hamburg, NJ</td>
|
||||
<td class="mono">39</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">30261</td>
|
||||
<td>30261 NSA Clovis NM</td>
|
||||
<td class="mono">70</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill warn">No</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>—</td>
|
||||
<td>R00=completed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68720</td>
|
||||
<td>68720 NSA Lancaster PA</td>
|
||||
<td class="mono">74</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill warn">No</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>—</td>
|
||||
<td>R00=completed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68724</td>
|
||||
<td>68724 NSA York PA</td>
|
||||
<td class="mono">75</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68752</td>
|
||||
<td>68752 NSA Brownsville TX</td>
|
||||
<td class="mono">76</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68753</td>
|
||||
<td>68753 NSA Brownsville TX</td>
|
||||
<td class="mono">77</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="mono">68758</td>
|
||||
<td>68758 NSA Brownsville TX</td>
|
||||
<td class="mono">81</td>
|
||||
<td><span class="pill ok">Yes</span></td>
|
||||
<td><span class="pill ok">INV-2026-006 (sent)</span></td>
|
||||
<td><span class="pill neutral">No</span></td>
|
||||
<td class="mono">—</td>
|
||||
<td><span class="pill neutral">—</span></td>
|
||||
<td>—</td>
|
||||
<td>R00: INV-2026-006 (sent)</td>
|
||||
<td>R00=completed/billed</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,340 @@
|
||||
[
|
||||
{
|
||||
"site": "00286",
|
||||
"dbTitle": "00286 Riverside, CA",
|
||||
"internalTaskNumber": 31,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=not_started"
|
||||
},
|
||||
{
|
||||
"site": "30055",
|
||||
"dbTitle": "30055 Moreno Valley, CA",
|
||||
"internalTaskNumber": 33,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "client_review",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=client_review"
|
||||
},
|
||||
{
|
||||
"site": "30062",
|
||||
"dbTitle": "30062 Riverside, CA",
|
||||
"internalTaskNumber": 34,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed"
|
||||
},
|
||||
{
|
||||
"site": "30094",
|
||||
"dbTitle": "30094 Colorado Springs, CO",
|
||||
"internalTaskNumber": 35,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "No",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "—",
|
||||
"versionSummary": "R00=completed"
|
||||
},
|
||||
{
|
||||
"site": "30096",
|
||||
"dbTitle": "30096 Eerie, CO",
|
||||
"internalTaskNumber": 36,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R02",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "R01 (unbilled)",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=completed; R02=not_started"
|
||||
},
|
||||
{
|
||||
"site": "30258",
|
||||
"dbTitle": "30258 Lebanon, NH",
|
||||
"internalTaskNumber": 38,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed"
|
||||
},
|
||||
{
|
||||
"site": "30259",
|
||||
"dbTitle": "30259 Hamburg, NJ",
|
||||
"internalTaskNumber": 39,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed"
|
||||
},
|
||||
{
|
||||
"site": "30261",
|
||||
"dbTitle": "30261 NSA Clovis NM",
|
||||
"internalTaskNumber": 70,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "No",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "—",
|
||||
"versionSummary": "R00=completed"
|
||||
},
|
||||
{
|
||||
"site": "68639",
|
||||
"dbTitle": "68639 NSA Los Lunas NM",
|
||||
"internalTaskNumber": 40,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "client_review",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=client_review"
|
||||
},
|
||||
{
|
||||
"site": "68663",
|
||||
"dbTitle": "68663 NSA Oklahoma City OK",
|
||||
"internalTaskNumber": 41,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68664",
|
||||
"dbTitle": "68664 NSA Oklahoma City OK",
|
||||
"internalTaskNumber": 42,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68666",
|
||||
"dbTitle": "68666 NSA Oklahoma City, OK",
|
||||
"internalTaskNumber": 43,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68669",
|
||||
"dbTitle": "68669 NSA Oklahoma City OK",
|
||||
"internalTaskNumber": 44,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68670",
|
||||
"dbTitle": "68670 NSA Oklahoma City OK",
|
||||
"internalTaskNumber": 45,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R02",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "R01 (billed)",
|
||||
"billedVersions": "R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=completed/billed; R02=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68671",
|
||||
"dbTitle": "68671 NSA Oklahoma City OK",
|
||||
"internalTaskNumber": 46,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68673",
|
||||
"dbTitle": "68673 NSA Oklahoma City OK",
|
||||
"internalTaskNumber": 47,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "client_review",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=client_review"
|
||||
},
|
||||
{
|
||||
"site": "68720",
|
||||
"dbTitle": "68720 NSA Lancaster PA",
|
||||
"internalTaskNumber": 74,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "No",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "—",
|
||||
"versionSummary": "R00=completed"
|
||||
},
|
||||
{
|
||||
"site": "68721",
|
||||
"dbTitle": "68721 NSA Mechanicsburg PA",
|
||||
"internalTaskNumber": 48,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R02",
|
||||
"activeStatus": "client_approved",
|
||||
"completedRevisions": "R01 (billed), R02 (unbilled)",
|
||||
"billedVersions": "R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=completed/billed; R02=completed"
|
||||
},
|
||||
{
|
||||
"site": "68724",
|
||||
"dbTitle": "68724 NSA York PA",
|
||||
"internalTaskNumber": 75,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed"
|
||||
},
|
||||
{
|
||||
"site": "68752",
|
||||
"dbTitle": "68752 NSA Brownsville TX",
|
||||
"internalTaskNumber": 76,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed"
|
||||
},
|
||||
{
|
||||
"site": "68753",
|
||||
"dbTitle": "68753 NSA Brownsville TX",
|
||||
"internalTaskNumber": 77,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed"
|
||||
},
|
||||
{
|
||||
"site": "68758",
|
||||
"dbTitle": "68758 NSA Brownsville TX",
|
||||
"internalTaskNumber": 81,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "No",
|
||||
"activeRevision": "—",
|
||||
"activeStatus": "—",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed"
|
||||
},
|
||||
{
|
||||
"site": "68808",
|
||||
"dbTitle": "68808 Camas, WA",
|
||||
"internalTaskNumber": 49,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R01",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "—",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68809",
|
||||
"dbTitle": "68809 Centralia, WA",
|
||||
"internalTaskNumber": 50,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R02",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "R01 (billed)",
|
||||
"billedVersions": "R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=completed/billed; R02=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68810",
|
||||
"dbTitle": "68810 Chehalis, WA",
|
||||
"internalTaskNumber": 51,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R02",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "R01 (billed)",
|
||||
"billedVersions": "R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=completed/billed; R02=not_started"
|
||||
},
|
||||
{
|
||||
"site": "68811",
|
||||
"dbTitle": "68811 Kelso, WA",
|
||||
"internalTaskNumber": 52,
|
||||
"r00Completed": "Yes",
|
||||
"r00Billed": "INV-2026-006 (sent)",
|
||||
"hasActiveRevision": "Yes",
|
||||
"activeRevision": "R02",
|
||||
"activeStatus": "not_started",
|
||||
"completedRevisions": "R01 (unbilled)",
|
||||
"billedVersions": "R00: INV-2026-006 (sent)",
|
||||
"versionSummary": "R00=completed/billed; R01=completed; R02=not_started"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
# Checked Sites Revision Audit
|
||||
|
||||
Definition used: your checkbox means `R00` was completed at some point, not that the task has no active revisions now.
|
||||
|
||||
| Site # | DB Title | Internal # | R00 Completed | R00 Billed | Active Revision? | Active R# | Active Status | Completed Revisions | Billed Versions | Version Summary |
|
||||
|---|---|---:|---|---|---|---|---|---|---|---|
|
||||
| 00286 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30055 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30062 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30094 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30096 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30258 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30259 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30261 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68639 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68663 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68664 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68666 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68669 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68670 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68671 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68673 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68720 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68721 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68724 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68752 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68753 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68758 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68808 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68809 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68810 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68811 | — | — | — | — | — | — | — | — | — | Missing from DB |
|
||||
@@ -0,0 +1,118 @@
|
||||
%PDF-1.4
|
||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 4 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 11 0 R /MediaBox [ 0 0 1008 612 ] /Parent 10 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 12 0 R /MediaBox [ 0 0 1008 612 ] /Parent 10 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 13 0 R /MediaBox [ 0 0 1008 612 ] /Parent 10 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 10 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Author (\(anonymous\)) /CreationDate (D:20260604232324-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260604232324-04'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 5 0 R 6 0 R 7 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3045
|
||||
>>
|
||||
stream
|
||||
Gb"/+D/\/u')q<+0u0K5Ru<Y^j)TE65PQfn?X::/'c.)l-to$RfLSt:U20P]hs;:rfqKnF)^CH.(Mg@hS=(DC4O<@l$jD6tWVJQE!:16Sh0TKn_;HP7&IB;E^sQQQIlgLW8YQ>UWJF-Gn.U9a!OJ8ddL%-cJ]''O3IH4\'+'Oti&n.FVg'4@^[uoO,W.VRUmqq]VTt]2n44fR0EQk5m/EI0j?UJ8_4NE+USn*G#5Pf]5qE\V?5pFQ.BOE0[+KTkRBqHZ$[1F"IH^]Q"M^Q$#C@i&pYkUdYb:QD;Qc9KmcC]0L%9o90gsMJ+O]-,oSG$6S&0ieKc+gq*fT[KVu.IYM^k>d5Q@dO(Xj#jDQet5V,8dL@['F4c29q-CDA_@5nCrSWkkX$j6ce-SQ/M2nuS:trUb7;6Z)39Rsfm)%[W.E4s=m#mGW0XH(7t3oipbt:lY#Yk5"1VQK_s\UTB'.ZfqUqMXe2)D]gR6D+@X`6^cin1WY;NnQ1`SXUF8'**7Z&>j(_<VF25#;j_*fEYsgp]W?EcPZF@"ME,u7cfTqI<'fYp[sKpVb$c?nntf0)M(cF6,r'gB3)a9K`TiEP(J3QYMXR_=S"bQc],Ku=%4c;eB1sDI-VcAL-%:l*;&[Cj#T0I=![_?m9FIa-9`r:JC`8J(M>dPo)]AE*e<?`#AhV1On["(dIV:JPa,-9oIhHP?&4Z0d)^opEbOLTT2Ak$Qe0hu]M3df'djG%uIYlb89Nu)%_ktSkNt-V&3e[+b_9mPorKJ3n2G-[:-o%RG`oIIAI)G&6Ytm0PRl(i3XT(8n7H`ue)<Gh.iEaOe\P8,3lS&@Z`Oo@iaT')kJ*o_Y#+e)U-r'1X77f$F6<F17*.kd3B:!:RaqBdEh8V0]7?hbM3$'Nf+GgpoiFdd]kbA`BQ)H*n_F=omQu:Vn)!+5OP;q>XYEdb4?3eFgVOWN)9^@p_njtFi:A<2=W+Ej54KF&r'L/s]U,+&iN,UinZUi8Hn:MCeTFCE0#uiS*$J/Ei`@oPKVo2s'eNA!#@am,u7dM)2HqB"E3uArE*'sVgiqWHHm.AJR%"/GDnKl2fW4BF.hSCU-[`qFj]7!$Gq$gS8/%CD&i>*Om\4Gkc9'?>d,gscL%g+G@XMH2;mdjna$jSDAoiM_h2[1)/Bdt_;(L8NZ+.o&C*NmP1)PhQ8iEug6B.[B!)s-9b?,9o/hn,=P5J_2>QjQ"ritgYN^+RNMjdoho1tX($!GiD=!,N;4ic+)`ZN1.A3#2tY2*RM"`30Pb9[;UPH2oi0I/l0`<rX2EWRKQMnJ?I.:YkUj#*V%C[TE="Q&Gp:&#Yt;';gWnLc0#a0c"Tt,4tuQ[*7Lo<`_-n=DWQcr7b!2eJ0mT4[%NXbE$g]6=2P:\W;)P@rg8Uh=!ciJj!1/qa7Gba%flUGZ`"7C8;;`ljcM\*:%ji]e/L=f&T>4]!6$W@?s1@%c7,?S)4NGB9:q\7AUOHg`bFpj"nuQ*(<c":O@fencCYR<.Z;,X3D^[8'$T%n.tm-gn-=!c8-p+-$3@UMNk@A$G9l0Y?2?-;mi;iMHRJn,T=Fca>@0S;9B=Oij?`BCd9dMOT7M=6baZ?=[YV+3o#KV;2s=q;'hqrPd]VXb%G9DL4?Sq6e"HSLJas*oaoTZ?!0dO.bcm5frC(Q'FQa`CoE@H5?'A9;A]B3G*hb6EB_`!*>"iE=`q=XKr#3Tn\@?j=TeV4>q-*-C[WQSjKmAt-*h6`r,Bg:r,sj;B(r>U9S"1t@?DEfSVV1rFjCQ.d7XaD.uqh)hOqjt+3h'H2cV5H)WO:3oJ)YN`RdYV#CE!Q'QhHeG!Sr17SlQaM(B5H?)^0/;FQ\:FeP73Tc,BWmjPRI/='gY^7ga^d!oL=RqOG-Iq(=CM0tK^^22C)Y:5T3D*6BdS>enpZNf$cqqo&HcJIPW)rQR5@.d0>5br9@M9[dME%1+)(hm,G`+$jd;N=O+HAjQ2,G^>i/5%F1VHiQ7o<Pt*IUU>ICKn']gn:q$-geb1^p^4.Q"5,$,/t6-;]QjW5X;N*-Zp$Z^(%=)D5eS19oH55_^:@P7;G-IY3q?rQ-H=`Y^-[5g/'p\%UA!k]&9)sGipL(?\qmf%pAa9YUmqg`)Rc'#I`d8q@W`h]>soETl`VN>]Q0sYlWuP5\MDgNk<pD227"jUChsCcLPu_(E'gCIP""p]Bo7_(<6]jmVQM<dd[L6m:1gBZ*OWZ1r84ds2C744Ka=3@0D2]e)5-"[[Ie3KWQ+M(tGANU\t8@+8Xb:^T3p90W0mDJtg(BS:FT7'lcQJCH=F`l$ZaP$<5EWXH7Vm&[]QNXH6%6b%HFb+.RJY6/^k5YLToZ<IA$(J0s9@YsI$pknDXa##4T'h>SNJ"sDd=jmHu"EGu'o>@*uEL(F-q-ng7^VMc8gkRW1U\E;@n$5G8c28H%o,U$.&!&5DNoR%0EVK4`-/[II-rmJn'6!#X/(K\OcCUldYf>\OkA#F\:n/utaDs%ZI%+a&k:p.MZL'VTiW1=$M_ifUVnQco'$=q%&=QKg`cu?kTVf=V`:,4dWWof4[B0UV26Vn#DY%J1Lgfqr47(kL-#T>d?n3\3YgUEM7=2$4IKS6&^ZU8,9JimgF/2/r;%VeO:=Cr,j@YUQ!([^AgQ5Gk=<aJ>V=%3Q`"/hV'lH%X>;Gc^k3?][I4IZrb)\[l:6lMF)\fNZs1b9lmVi:+g"%ij1IaRg&3i:Y($)6GhNNJt/TRkQd$$G<F*@7@'?^)0i!mn8&+g<[e."TOVPgK)"6&&s(oQd?)+]g>NpIG3KoX5B!a*TpQ`s4ojArVEf[B4h,NdO\f#`([jVLu+]4Gs["IF6NX]f'l6?0V&"cJAMX2K>M#a3Gf'k$mqb^;*P86M-aco`a!Nqp?F,5IQ!TI]^lhlZV*F`n11O?.8EAS[H$NgV,mHlY^P8?J2DZ2@JWZm_u'jNMU5F]cu3GgcuOLGt&=TAi%FdEA"Q!#9uk9&^tJa\Bo/SlOhlt0VVOBm"f@5,=3#%@U%)J/E;OH]61.lOTst?Li)r"qh?Qdr#aH1lVR~>endstream
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2064
|
||||
>>
|
||||
stream
|
||||
Gb!l"D/\/e&H;*)EPT;u7AKEZ</5p9dWOd-od)=R$j]\>WS4kgS^)CV%+pWunO97D=Jh'sl"8O\6LcHFn*]^/i[LZgn3cThZO+G+!$4>!5T'to!>U<=_+XQ;d/1.N</QoD5Z%hZ.ZM45-P8'0gmblH#go=7c<1UKme7&SnI?PZ[GVbfj7])_n!-jQ#:[%3J_eg/V\6WRAHe%.?ip9c_p:3EHSG3rKDT9E>?ju\(A6Z$9GdkJ\,#f!ENVS@Kk;=oAmrjbLh[-e9%g;ZnnYC#-:MK^XpAh2@>a$bAm2o`>/mQW6e5%kmY^bG(:,hPQ$h>#d>*QMbLML%cc^5ZK\2+!!)m>ObQ<NfO=B+H\T"[+#n?B7"pba31!gCfB$UCtBMgFVU5]P>'$[9:kogI)MfH3]W^S(PBmW$Le?eCY$pSk!A)+RXO1Jgfi9A4fgGi5rXND#Cj)P%&;]V435ZcAce^9ZYSt%B%=2D9CEOSSd,qtD@Z-m??PJqK\3UbHGPfflnk[O+*>VGB'E*Qb0k+diK[pE4cJn<Br@L;Mgfm92]Q:7tFLK=[-Q]oO1p/e^u@(Ig[YE<j!@hK,`)c.A0j7K4g_X#/<<_uRhh!.mCPpDK1(p_^nqC"-&33`cg(X+Co;'ES5Glk2L(_=qG-:9a2/V:h;f.U#..B`i,9I(lad7,"K;B][D6#!<k@/-tA.ldO=L[4ce/$uo@.Z$W8r9_L.E]Gg9";7Ua%C7&2\!jn*]\u]I49q>'1d@B5NL/d9L,3+-*&73orSQpkeUqF18,?^V!Stc&(:AHU9(XN1D,W>eBr&Y4Jk<p";kCaMGJFJ_#DB%q-H\'uEQDjD?;DB8pQeLU=I6uZ"6[YK0k#pg)o3c+@Mk,WYu4,-^ekD3mf>P97t?'l?1PGPf3m&^fnQ`Ik;Z1g+/]p5p[c@C[YXQuYV`(!`uA+&D-b8qd_[`4E8(>"1N/4d8"+'_DloJ3dnLn".`;LL_;BbNHkcG3KAej&4*VtLU*$!b"SpD)m`5)r5j2B#Hb:QhX2mc/Jn65IRN`\'[rkW,DWpKO-E@N-9$rVHU]!8E3';Wpp4dUniWm6N.EZ?*B_P%fW,.]>MM@p_->tY3'2=?OZEGXl+WI]iIS]O?"Ac9*@C]Nmhi9"g\j1?>e$B0hT&!ra,57-EZJoL!Q=pBA=22;sQs]t\,"iL>TLnq(EU-c%A$QQ;K@gseP@rnfHLh$9^)GPZJif==FR]LqGKLSEe'[Q.,5Z[aH];$f>/:nPPH7GTZIF>TWi`b0l^7fS>,M)&5!'i]7q_E(Qa>#S)/l+BQm/^AKMF3)eT?#'JsL6AQm+24dNu!-!4p8g[f]IC"MN(s_lmE[(K[`0`R:[p`lOYtQKqZaT]KepqrMq;8f*asQJTB+SL_;s\#sciB?/kOCkNZ]kGo:k]!JYS,H`d;9s$lE)GZ%Y#OkrdH*WkhWYG&D]sGAhWdSBJXOp,5:Ek%,Y(i3##AgV+TkqoHftY*Wah=,o'eDt%AgZ`eNh-O_cccM,*23CF_sqEM&K>7<=a'fWJS,(jZb);km2HpRL!Bm,&@/Vq2"IMpXkof<X_U>^Irga#c$/Xe?0*Y`Bq"mF/Oa^N$70Y5?CrIECp_=*C9?iO9Q[A1G.>%.pTM#j'g-2:Bq]2&La?ODfSW](s/oapH,Y!C-o?"uMPaaTjP?k5$8:]7r*lFL8krl-G=lOTQg?2mls7R[Do!q%RUfKf,jUfSEQQ5h5Q4<0X#hn;"fZj<Z(;b\_>djr-/gV($&(8;_hF@.2&YIh6saqZE8hD=RJ%Im_O85sQT'?2oq97rpq,n6lSH%d&gcKhdcr-e%iaD09!@+pPs+/>A\K27cr/UCBi`kk"=`@V/C:l$9T(<?m3(FU(<rYh*G&EDo1VpAg-I&"#r:BJ19%'p)"L_[G*".s(sL-M]B+3['XR=f]*^<?"uF?e<?lQt/K:+i$8T_u"P]WB`Sd,,dpKP].bcb!hMV;1`(5hgLntUoBgjPn>_GY,<Ku/2O;Or>ER>pa:/PJ16Xl:3`'oF!(q#H;i^.+-K#C*3`;"T!>Pj!.5En[d"o~>endstream
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2151
|
||||
>>
|
||||
stream
|
||||
Gb!lc>BA7Q'Z],&.F-/O'S[N3XP;%e""h[t"'oRrlnZIO851$o'@q0XiS3g`I#15Mf^9`H-FBV7-!C7hUnaP%fpDlZD]X@gY5jT;nG!k+/n"G\GT=[VHj,!0#QG5WVjXeE6nVNa=o=2m=UaQ6@$WagQj&I^V?qWr3/D%(B4*l:A'+7')8:Ls6Gs$:(>2"VZ[#u>Me*SA#D0e]$4bpO/eX6V@/(Uo%t3!;BM_hj(8/%DhAaS5&P>@-6NSHMZK-]fYFLllfuMDAE#"<;-6+"4-K?c$3LigLA-)^(Z:d0F_io'u\Ok^9\X=uqo@]!9hqJb/_N(bDYLueHKa;9!09\g+,OEG:14aFRe05`p?&gZW?`.TnbGO1THk*K.#+$PkE!3N.2G%>AgRAuP7U$=f_;KVl%O7!i?n,'b0AL3+_^m+OI*4?9`h=s(HDQL<[T5h_iS]TbE&]JTo3p^IW6We*H&`H'hUHKqq_lXNg)2D8Eaq#+DJtc?\D:2GIFHMCkI-k[FjC9RH>g&B`lf6Uh,B#5R,hfiBY\f7!9b?FU7>1@T@b<SrEO)r5V40T-h[*;L6HH-/B9=LdHS?3AP"p%:#WQN73VU0:^N^K##FEgAd=$"`,\A3kqkD='U4JFfV,CU+Q',bUUF,`3,S(cl!gG&Q)=fFca$Dk]kHQ)eqBL4o;f(D,X\l(.nd4S1-?1[b5%FA]-Q48Wg/G)AkO9PB@g3dU\%cs.L7QOgKs3V59X\ao-.LG=3.$b):P=65cE=:Y?JJR,RQ]Be:U\Y-hR-qGm)/`g42TXE86$i@:_<j>]?NZj\R\p7RMie2j4_Hru+90D?l<KO!CR0eIqoEotqD`)#GE:=&H`H*'F^69hhc&_"UnO-70($@Et@'d3aQ)$>U'riS],^`TQ2p;$6`LBar\B\_?UX\72[DKs!Md'kaVaRn+qZ8nVJ#]K6g$kX,c@dOQqiaHomj*%^U%m_aYlVk%Ml3Ti&4r-T'mZuEJq6%(gtg[F-uCDUpW@A0H-kLD+JX;*RRS9!2!mBdkj_XQZolR'],s)>>J&Qe/Q@8fRr`D?Zh.G[L(rYUt0g>e7K0,dYJG-lpHc-.[DO-dn?KQf-jc_pAVKHJ"T^8@+I`<&@*G:VlkrDmd.=)<p`?ZXLHAMKYDXRIs#8L8>/.c25W,`LWcaK,,sQ=c-SX57]2Z`A."dYq783ghXL>:F7f3bb++!ICk(G8TOsPIq:MJ!O?UCI5QH?>fp'G.Jh"[8=:@e>V#@Q/%73<.n>pa3e]eHNY_B>7"$iE3m"WTnV<,5'-J!?&"Q_:)@YHdBga7-CsU2W6P!`\d0[+^ZT/fGES?]Y(c:MOd>DFSr^$<?DeT1pQo`=-[*#QgRR]emHrF_[k2g8p5\RJZ."9lbNRVb]$nt!%Wpl7hBTV\]&/kBRaKPho/=0q7S@sMiT0>3*)G`VP6IJhDWNsZbD*Fp?&QDXHf`tqmB+aT8/I:f?+Iim-_8pr3<&LP$WN;sG26NadLpHiBkp*mZO0/p9BKg4$\WsdRZ0gq/4nepleP<>T2"(r:+kB%DM$K2gH/5q`^ptCf/du.;,'$d]7B`jaVC0iPj8`lXpD3J4>bg\<dI3G`@*,=9Y[3t?b-f;e7$gLRhEQJ[sVe3h4`ZXD%b:LksFbeH?P\/V";8-[FotS&r"p8T%bMkd)'(r4`"d8/_cNrCd1PT\$O&5G8H#6[NV6lCSP)>DrZ6L]_U*L2'`X$%X>0DB6WT83oN09h=UWpoL%;,l?lC7a,V#(Z3BX#,=];X"56>[2U0((UL!D<7=0F6D;/>b4)*Vp5^O5,]8Lj\$/YdNZKEH_58<L\cM,&`8W<AOk7Tf:F#lVV%VR6_g,@j6/bWcLp=[AkmE%cE\EU:HZhr&pP0We3AA:nN-'J,]&aV1@hrd:N=":Ks$`I]YV"fJF(bj-7&6otM\NN0G_HHCEMDCg+jKXtrAW?@@nDLbK&qdJVIT/4m^T=(X$V+3"/=.ZMY4DBdcDSj5rrRd-hl!OQn</@&,TKfq;CkHp?044MARi$ie[ioBA=4rZc2/\dBtr#?47cFbbIFg+!V1q5Z<f"+.loL_Xd%MK/.8G'R>b.12k$XFb,iQ*%qQna08h\<b48NF9]:5P_@*!9Qg$b&?^@UUAT,IF%/pL(+CR2~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 14
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000112 00000 n
|
||||
0000000219 00000 n
|
||||
0000000331 00000 n
|
||||
0000000436 00000 n
|
||||
0000000632 00000 n
|
||||
0000000828 00000 n
|
||||
0000001024 00000 n
|
||||
0000001093 00000 n
|
||||
0000001373 00000 n
|
||||
0000001445 00000 n
|
||||
0000004582 00000 n
|
||||
0000006738 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<dccedbca14e5931c52cd86c3e6456193><dccedbca14e5931c52cd86c3e6456193>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 9 0 R
|
||||
/Root 8 0 R
|
||||
/Size 14
|
||||
>>
|
||||
startxref
|
||||
8981
|
||||
%%EOF
|
||||
@@ -0,0 +1,339 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Fourge Portal Finance Summary</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--text: #161616;
|
||||
--muted: #5e5e5e;
|
||||
--accent: #f5a523;
|
||||
--border: #e5dccf;
|
||||
--surface: #faf7f2;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
line-height: 1.45;
|
||||
}
|
||||
.page {
|
||||
width: 100%;
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: 44px 40px 56px;
|
||||
}
|
||||
.hero {
|
||||
border: 1px solid var(--border);
|
||||
background: linear-gradient(135deg, #fffaf2 0%, #ffffff 55%, #fff5e2 100%);
|
||||
border-radius: 16px;
|
||||
padding: 28px 30px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 30px;
|
||||
line-height: 1.1;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.sub {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
h2 {
|
||||
margin: 28px 0 10px;
|
||||
font-size: 19px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
h3 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 15px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #7d6d58;
|
||||
}
|
||||
p { margin: 0 0 12px; }
|
||||
ul {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
li { margin: 0 0 6px; }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
background: var(--surface);
|
||||
padding: 16px 16px 14px;
|
||||
}
|
||||
.card p:last-child,
|
||||
.flow-box p:last-child { margin-bottom: 0; }
|
||||
.flow {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.flow-box {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
.flow-box + .flow-box::before {
|
||||
content: "↓";
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
}
|
||||
.pill {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
background: #fff4dd;
|
||||
border: 1px solid #f3d194;
|
||||
font-size: 12px;
|
||||
margin: 0 6px 6px 0;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
vertical-align: top;
|
||||
}
|
||||
th {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
}
|
||||
.code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 12px 14px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 28px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
@media print {
|
||||
.page { padding: 24px 24px 36px; }
|
||||
.hero, .card, .flow-box { break-inside: avoid; }
|
||||
h2, h3 { break-after: avoid; }
|
||||
table { break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<section class="hero">
|
||||
<h1>Fourge Portal Finance Summary</h1>
|
||||
<p class="sub">Role cheat sheet, finance lanes, and data flow for team, client, and subcontractor users.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>1. Executive Summary</h2>
|
||||
<p>The finance area is not one shared page for all roles. It is split into three lanes with different permissions and different page sets.</p>
|
||||
<div class="grid">
|
||||
<article class="card">
|
||||
<h3>Team</h3>
|
||||
<p>Full finance hub at <strong>/invoices</strong>.</p>
|
||||
<p>Handles client invoices, expenses, subcontractor invoices, and subcontractor purchase orders.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h3>Client</h3>
|
||||
<p>Invoice-only lane at <strong>/my-invoices</strong>.</p>
|
||||
<p>Clients can view invoices, filter by company, and download PDFs.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h3>Subcontractor</h3>
|
||||
<p>Two finance lanes: <strong>/my-invoices-sub</strong> and <strong>/my-purchase-orders</strong>.</p>
|
||||
<p>They can submit invoices to Fourge and approve POs sent by Fourge.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>2. Role-by-Role Cheat Sheet</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Role</th>
|
||||
<th>Main Routes</th>
|
||||
<th>Can Do</th>
|
||||
<th>Cannot Do</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Team</strong></td>
|
||||
<td>/invoices</td>
|
||||
<td>Create/send client invoices, track expenses, create POs, review subcontractor invoices, mark paid, export/send PDFs</td>
|
||||
<td>Not limited in finance UI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Client</strong></td>
|
||||
<td>/my-invoices</td>
|
||||
<td>View invoices, filter by company, download invoice PDFs</td>
|
||||
<td>No expenses, no invoice editing, no POs, no subcontractor finance tools</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Subcontractor / External</strong></td>
|
||||
<td>/my-invoices-sub<br>/my-purchase-orders</td>
|
||||
<td>Create/submit invoices to Fourge, view status, download receipts after payment, approve POs</td>
|
||||
<td>No team finance overview, no client invoice management, no internal expenses</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>3. Core Data Models</h2>
|
||||
<div class="card">
|
||||
<div class="pill">invoices</div>
|
||||
<div class="pill">invoice_items</div>
|
||||
<div class="pill">expenses</div>
|
||||
<div class="pill">subcontractor_invoices</div>
|
||||
<div class="pill">subcontractor_invoice_items</div>
|
||||
<div class="pill">subcontractor_payments</div>
|
||||
<div class="pill">subcontractor_po_items</div>
|
||||
<div class="pill">tasks</div>
|
||||
<div class="pill">submissions</div>
|
||||
<div class="pill">companies</div>
|
||||
<div class="pill">profiles</div>
|
||||
<p style="margin-top:10px;">Simple mental model: <strong>invoices</strong> bill clients, <strong>expenses</strong> track internal costs, <strong>subcontractor_invoices</strong> let subcontractors bill Fourge, and <strong>subcontractor_payments</strong> are POs Fourge sends to subcontractors.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>4. System Flow</h2>
|
||||
<div class="flow">
|
||||
<div class="flow-box">
|
||||
<h3>Client Billing Flow</h3>
|
||||
<p>Approved client work reaches <strong>tasks.status = client_approved</strong>.</p>
|
||||
<p>Team finance loads uninvoiced tasks and revisions, builds line items, saves an invoice, and emails the PDF to the client.</p>
|
||||
<p>Later, team marks the invoice paid.</p>
|
||||
</div>
|
||||
<div class="flow-box">
|
||||
<h3>Expense Flow</h3>
|
||||
<p>Team logs expenses, optionally with receipt files.</p>
|
||||
<p>Expenses feed overview charts, yearly summaries, and profit calculations.</p>
|
||||
</div>
|
||||
<div class="flow-box">
|
||||
<h3>Subcontractor Invoice Flow</h3>
|
||||
<p>Subcontractor creates an invoice from completed approved tasks assigned to them.</p>
|
||||
<p>Fourge reviews it, then marks it paid and sends a receipt PDF back.</p>
|
||||
</div>
|
||||
<div class="flow-box">
|
||||
<h3>Purchase Order Flow</h3>
|
||||
<p>Team creates a PO for a subcontractor, optionally tied to project tasks.</p>
|
||||
<p>Subcontractor reviews the PO and can approve it from their own portal view.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>5. Status Logic</h2>
|
||||
<div class="grid">
|
||||
<article class="card">
|
||||
<h3>Client Invoice</h3>
|
||||
<p><strong>draft</strong> → <strong>sent</strong> → <strong>paid</strong></p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h3>Subcontractor Invoice</h3>
|
||||
<p><strong>draft</strong> → <strong>submitted</strong> → <strong>paid</strong></p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h3>Purchase Order</h3>
|
||||
<p><strong>draft</strong> → <strong>sent</strong> → <strong>approved</strong> → <strong>ready_to_pay</strong> → <strong>paid</strong></p>
|
||||
<p><strong>cancelled</strong> is exit state.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>6. Key Business Rules</h2>
|
||||
<ul>
|
||||
<li>Client invoices only pull approved, uninvoiced work.</li>
|
||||
<li>Tasks and revisions are marked invoiced after invoice creation.</li>
|
||||
<li>Deleting a team invoice rolls linked invoice flags back.</li>
|
||||
<li>Team invoice status changes also push task status changes.</li>
|
||||
<li>Revision billing rule: <strong>R00 = new work</strong>, <strong>R01 = free</strong>, <strong>R02+ = billable increments</strong>.</li>
|
||||
<li>Subcontractor invoices are created by the external user and then reviewed/paid by team.</li>
|
||||
<li>POs are created by team and approved by the subcontractor.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>7. Database Relationship Chart</h2>
|
||||
<div class="code">COMPANIES
|
||||
└─ invoices
|
||||
└─ invoice_items
|
||||
├─ task_id → tasks
|
||||
└─ submission_id → submissions
|
||||
|
||||
PROJECTS
|
||||
└─ tasks
|
||||
└─ submissions
|
||||
|
||||
PROFILES (external)
|
||||
├─ subcontractor_invoices
|
||||
│ └─ subcontractor_invoice_items
|
||||
│ └─ task_id → tasks
|
||||
└─ subcontractor_payments (POs)
|
||||
└─ subcontractor_po_items
|
||||
└─ task_id → tasks
|
||||
|
||||
EXPENSES
|
||||
└─ standalone internal finance records</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>8. Quick Route Map</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Surface</th>
|
||||
<th>Route</th>
|
||||
<th>User</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Finance Hub</td><td>/invoices</td><td>Team</td></tr>
|
||||
<tr><td>Client Invoices</td><td>/my-invoices</td><td>Client</td></tr>
|
||||
<tr><td>Subcontractor Invoices</td><td>/my-invoices-sub</td><td>External</td></tr>
|
||||
<tr><td>Create Subcontractor Invoice</td><td>/my-invoices-sub/new</td><td>External</td></tr>
|
||||
<tr><td>Purchase Orders</td><td>/my-purchase-orders</td><td>External</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<p class="footer">Generated from current portal codebase summary on June 3, 2026.</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -0,0 +1,379 @@
|
||||
import fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const docsDir = new URL('.', import.meta.url);
|
||||
const inputPath = new URL('./checked-sites-revision-audit-2026-06-04.json', docsDir);
|
||||
const outputPath = new URL('./checked-sites-revision-audit-2026-06-04.html', docsDir);
|
||||
|
||||
const rows = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
||||
|
||||
const existingRows = rows.filter((row) => row.exists !== false);
|
||||
const activeRows = existingRows.filter((row) => row.hasActiveRevision === 'Yes');
|
||||
const noActiveRows = existingRows.filter((row) => row.hasActiveRevision !== 'Yes');
|
||||
const r00BilledRows = existingRows.filter((row) => row.r00Billed !== 'No');
|
||||
const r00UnbilledRows = existingRows.filter((row) => row.r00Billed === 'No');
|
||||
|
||||
const activeStatusCounts = activeRows.reduce((acc, row) => {
|
||||
const key = row.activeStatus || 'unknown';
|
||||
acc[key] = (acc[key] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const revisionDepthCounts = activeRows.reduce((acc, row) => {
|
||||
const key = row.activeRevision || '—';
|
||||
acc[key] = (acc[key] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const maxStatusCount = Math.max(1, ...Object.values(activeStatusCounts));
|
||||
const maxDepthCount = Math.max(1, ...Object.values(revisionDepthCounts));
|
||||
|
||||
const esc = (value) =>
|
||||
String(value ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
|
||||
function badgeClassForStatus(status) {
|
||||
if (status === 'client_review') return 'review';
|
||||
if (status === 'client_approved') return 'approved';
|
||||
if (status === 'not_started') return 'todo';
|
||||
if (status === 'in_progress') return 'progress';
|
||||
return 'neutral';
|
||||
}
|
||||
|
||||
function barChart(entries, maxValue) {
|
||||
return entries
|
||||
.map(([label, count]) => {
|
||||
const pct = Math.max(4, Math.round((count / maxValue) * 100));
|
||||
return `
|
||||
<div class="bar-row">
|
||||
<div class="bar-label">${esc(label)}</div>
|
||||
<div class="bar-track"><div class="bar-fill" style="width:${pct}%"></div></div>
|
||||
<div class="bar-value">${count}</div>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
function renderTableRows(items) {
|
||||
return items
|
||||
.map((row) => {
|
||||
const billedClass = row.r00Billed === 'No' ? 'warn' : 'ok';
|
||||
const activeClass = badgeClassForStatus(row.activeStatus);
|
||||
return `
|
||||
<tr>
|
||||
<td class="mono">${esc(row.site)}</td>
|
||||
<td>${esc(row.dbTitle)}</td>
|
||||
<td class="mono">${esc(row.internalTaskNumber)}</td>
|
||||
<td><span class="pill ${row.r00Completed === 'Yes' ? 'ok' : 'neutral'}">${esc(row.r00Completed)}</span></td>
|
||||
<td><span class="pill ${billedClass}">${esc(row.r00Billed)}</span></td>
|
||||
<td><span class="pill ${row.hasActiveRevision === 'Yes' ? 'review' : 'neutral'}">${esc(row.hasActiveRevision)}</span></td>
|
||||
<td class="mono">${esc(row.activeRevision)}</td>
|
||||
<td><span class="pill ${activeClass}">${esc(row.activeStatus)}</span></td>
|
||||
<td>${esc(row.completedRevisions)}</td>
|
||||
<td>${esc(row.billedVersions)}</td>
|
||||
<td>${esc(row.versionSummary)}</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
const html = `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Checked Sites Revision Audit</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f5f1e8;
|
||||
--panel: rgba(255,255,255,0.78);
|
||||
--panel-strong: rgba(255,255,255,0.92);
|
||||
--border: rgba(43,37,28,0.12);
|
||||
--text: #231d14;
|
||||
--muted: #6b6255;
|
||||
--accent: #b8831f;
|
||||
--accent-soft: rgba(184,131,31,0.18);
|
||||
--ok: #1f8a4c;
|
||||
--ok-soft: rgba(31,138,76,0.12);
|
||||
--warn: #b45309;
|
||||
--warn-soft: rgba(180,83,9,0.14);
|
||||
--review: #2563eb;
|
||||
--review-soft: rgba(37,99,235,0.13);
|
||||
--todo: #7c3aed;
|
||||
--todo-soft: rgba(124,58,237,0.12);
|
||||
--shadow: 0 18px 40px rgba(35,29,20,0.08);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(184,131,31,0.14), transparent 28%),
|
||||
radial-gradient(circle at top right, rgba(37,99,235,0.09), transparent 24%),
|
||||
linear-gradient(180deg, #fbf8f2 0%, var(--bg) 100%);
|
||||
}
|
||||
.wrap {
|
||||
width: min(1400px, calc(100vw - 40px));
|
||||
margin: 32px auto 48px;
|
||||
}
|
||||
.hero, .panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 18px;
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.hero {
|
||||
padding: 28px 30px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.eyebrow {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 34px;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 15px;
|
||||
max-width: 900px;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.card {
|
||||
padding: 20px 22px;
|
||||
}
|
||||
.label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.value {
|
||||
font-size: 34px;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
.sub {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.panel {
|
||||
padding: 20px 22px;
|
||||
}
|
||||
h2 {
|
||||
margin: 0 0 14px;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.bar-row {
|
||||
display: grid;
|
||||
grid-template-columns: 150px 1fr 36px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.bar-label, .bar-value {
|
||||
font-size: 13px;
|
||||
}
|
||||
.bar-track {
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(35,29,20,0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, var(--accent), #d7a84b);
|
||||
}
|
||||
.table-wrap {
|
||||
overflow: auto;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
th, td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(35,29,20,0.08);
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
}
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--panel-strong);
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
font-size: 11px;
|
||||
z-index: 1;
|
||||
}
|
||||
.mono {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 72px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ok { color: var(--ok); background: var(--ok-soft); border-color: rgba(31,138,76,0.2); }
|
||||
.warn { color: var(--warn); background: var(--warn-soft); border-color: rgba(180,83,9,0.2); }
|
||||
.review { color: var(--review); background: var(--review-soft); border-color: rgba(37,99,235,0.2); }
|
||||
.todo { color: var(--todo); background: var(--todo-soft); border-color: rgba(124,58,237,0.18); }
|
||||
.neutral { color: var(--muted); background: rgba(35,29,20,0.06); border-color: rgba(35,29,20,0.08); }
|
||||
.section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.small {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.two-col { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.wrap { width: min(100vw - 20px, 1400px); margin: 20px auto 32px; }
|
||||
.hero, .panel { padding: 18px; border-radius: 14px; }
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
h1 { font-size: 28px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<section class="hero">
|
||||
<div class="eyebrow">Fourge Portal Audit</div>
|
||||
<h1>Checked Sites Revision Audit</h1>
|
||||
<p class="subtitle">This report treats your checkmark as “R00 was completed,” then separates that from billing and from any newer active revisions. It is designed to make client-billing comparison easier at a glance.</p>
|
||||
</section>
|
||||
|
||||
<section class="grid">
|
||||
<div class="panel card">
|
||||
<div class="label">Checked Sites</div>
|
||||
<div class="value">${existingRows.length}</div>
|
||||
<div class="sub">Sites found in the database from your checked list.</div>
|
||||
</div>
|
||||
<div class="panel card">
|
||||
<div class="label">Active Revisions</div>
|
||||
<div class="value">${activeRows.length}</div>
|
||||
<div class="sub">Checked sites that now have an active R01+.</div>
|
||||
</div>
|
||||
<div class="panel card">
|
||||
<div class="label">R00 Billed</div>
|
||||
<div class="value">${r00BilledRows.length}</div>
|
||||
<div class="sub">Checked sites where the new-book unit is already on an invoice.</div>
|
||||
</div>
|
||||
<div class="panel card">
|
||||
<div class="label">R00 Unbilled</div>
|
||||
<div class="value">${r00UnbilledRows.length}</div>
|
||||
<div class="sub">Checked sites completed at R00 but not yet billed.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="two-col">
|
||||
<div class="panel">
|
||||
<h2>Active Revision Status</h2>
|
||||
${barChart(Object.entries(activeStatusCounts), maxStatusCount)}
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>Active Revision Depth</h2>
|
||||
${barChart(Object.entries(revisionDepthCounts), maxDepthCount)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel section">
|
||||
<h2>Sites With Active Revisions</h2>
|
||||
<p class="small">These are the ones where R00 may be done, but a newer version is still active or awaiting review/approval.</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:90px;">Site #</th>
|
||||
<th style="width:240px;">DB Title</th>
|
||||
<th style="width:80px;">Internal #</th>
|
||||
<th style="width:96px;">R00 Done</th>
|
||||
<th style="width:180px;">R00 Billed</th>
|
||||
<th style="width:90px;">Active R#</th>
|
||||
<th style="width:120px;">Active Status</th>
|
||||
<th style="width:170px;">Completed Revisions</th>
|
||||
<th style="width:220px;">Billed Versions</th>
|
||||
<th>Version Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${renderTableRows(activeRows)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel section">
|
||||
<h2>Checked Sites With No Active Revision</h2>
|
||||
<p class="small">These are simpler to read: R00 completed, and no current revision chain is active right now.</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:90px;">Site #</th>
|
||||
<th style="width:240px;">DB Title</th>
|
||||
<th style="width:80px;">Internal #</th>
|
||||
<th style="width:96px;">R00 Done</th>
|
||||
<th style="width:180px;">R00 Billed</th>
|
||||
<th style="width:90px;">Active R#</th>
|
||||
<th style="width:120px;">Active Status</th>
|
||||
<th style="width:170px;">Completed Revisions</th>
|
||||
<th style="width:220px;">Billed Versions</th>
|
||||
<th>Version Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${renderTableRows(noActiveRows)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
fs.writeFileSync(outputPath, html);
|
||||
console.log(fileURLToPath(outputPath));
|
||||
@@ -0,0 +1,362 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.enums import TA_LEFT
|
||||
from reportlab.lib.pagesizes import landscape, legal
|
||||
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.platypus import (
|
||||
BaseDocTemplate,
|
||||
Frame,
|
||||
LongTable,
|
||||
PageBreak,
|
||||
PageTemplate,
|
||||
Paragraph,
|
||||
Spacer,
|
||||
Table,
|
||||
TableStyle,
|
||||
)
|
||||
|
||||
|
||||
DOCS_DIR = Path(__file__).resolve().parent
|
||||
INPUT_PATH = DOCS_DIR / "checked-sites-revision-audit-2026-06-04.json"
|
||||
OUTPUT_PATH = DOCS_DIR / "checked-sites-revision-audit-2026-06-04.pdf"
|
||||
|
||||
BG = colors.HexColor("#F5F1E8")
|
||||
TEXT = colors.HexColor("#231D14")
|
||||
MUTED = colors.HexColor("#6B6255")
|
||||
ACCENT = colors.HexColor("#B8831F")
|
||||
ACCENT_SOFT = colors.HexColor("#F1E3C2")
|
||||
OK = colors.HexColor("#1F8A4C")
|
||||
OK_SOFT = colors.HexColor("#E5F4EB")
|
||||
WARN = colors.HexColor("#B45309")
|
||||
WARN_SOFT = colors.HexColor("#F7E7D7")
|
||||
REVIEW = colors.HexColor("#2563EB")
|
||||
REVIEW_SOFT = colors.HexColor("#E6EEFF")
|
||||
TODO = colors.HexColor("#7C3AED")
|
||||
TODO_SOFT = colors.HexColor("#F0E8FF")
|
||||
LINE = colors.HexColor("#D7CFC1")
|
||||
|
||||
|
||||
def page_template(canvas, doc):
|
||||
canvas.saveState()
|
||||
canvas.setFillColor(BG)
|
||||
canvas.rect(0, 0, doc.pagesize[0], doc.pagesize[1], fill=1, stroke=0)
|
||||
canvas.setFillColor(MUTED)
|
||||
canvas.setFont("Helvetica", 9)
|
||||
canvas.drawRightString(doc.pagesize[0] - 36, 20, f"Page {doc.page}")
|
||||
canvas.restoreState()
|
||||
|
||||
|
||||
def pill(text, fg, bg):
|
||||
return Paragraph(
|
||||
(
|
||||
f'<para alignment="center" backColor="{bg}" borderColor="{fg}" '
|
||||
f'borderWidth="0.7" borderPadding="4" textColor="{fg}"><b>{escape(text)}</b></para>'
|
||||
),
|
||||
styles["pill"],
|
||||
)
|
||||
|
||||
|
||||
def escape(value):
|
||||
return (
|
||||
str(value or "")
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
)
|
||||
|
||||
|
||||
def status_pill(value):
|
||||
value = value or "—"
|
||||
if value == "client_review":
|
||||
return pill(value, REVIEW, REVIEW_SOFT)
|
||||
if value == "client_approved":
|
||||
return pill(value, OK, OK_SOFT)
|
||||
if value == "not_started":
|
||||
return pill(value, TODO, TODO_SOFT)
|
||||
if value == "in_progress":
|
||||
return pill(value, ACCENT, ACCENT_SOFT)
|
||||
if value == "Yes":
|
||||
return pill(value, OK, OK_SOFT)
|
||||
if value == "No":
|
||||
return pill(value, WARN, WARN_SOFT)
|
||||
return pill(value, MUTED, colors.white)
|
||||
|
||||
|
||||
def text_para(value, style_name="cell"):
|
||||
return Paragraph(escape(value), styles[style_name])
|
||||
|
||||
|
||||
def build_summary_cards(rows):
|
||||
existing_rows = [row for row in rows if row.get("exists", True) is not False]
|
||||
active_rows = [row for row in existing_rows if row.get("hasActiveRevision") == "Yes"]
|
||||
billed_rows = [row for row in existing_rows if row.get("r00Billed") != "No"]
|
||||
unbilled_rows = [row for row in existing_rows if row.get("r00Billed") == "No"]
|
||||
|
||||
cards = [
|
||||
("Checked Sites", str(len(existing_rows)), "Sites found in the database from your checked list."),
|
||||
("Active Revisions", str(len(active_rows)), "Checked sites that now have an active R01+."),
|
||||
("R00 Billed", str(len(billed_rows)), "Checked sites where the new-book unit is already billed."),
|
||||
("R00 Unbilled", str(len(unbilled_rows)), "Checked sites where R00 is done but not billed yet."),
|
||||
]
|
||||
|
||||
card_rows = []
|
||||
for label, value, subtext in cards:
|
||||
card_rows.append(
|
||||
Table(
|
||||
[
|
||||
[Paragraph(label.upper(), styles["card_label"])],
|
||||
[Paragraph(value, styles["card_value"])],
|
||||
[Paragraph(subtext, styles["card_sub"])],
|
||||
],
|
||||
colWidths=[3.0 * inch],
|
||||
style=TableStyle(
|
||||
[
|
||||
("BACKGROUND", (0, 0), (-1, -1), colors.white),
|
||||
("BOX", (0, 0), (-1, -1), 0.8, LINE),
|
||||
("INNERPADDING", (0, 0), (-1, -1), 10),
|
||||
("ROUNDEDCORNERS", [10, 10, 10, 10]),
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
return Table([card_rows], colWidths=[3.1 * inch] * 4, hAlign="LEFT")
|
||||
|
||||
|
||||
def build_table(rows, title, intro):
|
||||
flow = [Paragraph(title, styles["section_title"]), Paragraph(intro, styles["small"]), Spacer(1, 10)]
|
||||
|
||||
header = [
|
||||
text_para("Site", "th"),
|
||||
text_para("DB Title", "th"),
|
||||
text_para("Internal #", "th"),
|
||||
text_para("R00 Done", "th"),
|
||||
text_para("R00 Billed", "th"),
|
||||
text_para("Active R#", "th"),
|
||||
text_para("Active Status", "th"),
|
||||
text_para("Completed Revisions", "th"),
|
||||
text_para("Billed Versions", "th"),
|
||||
text_para("Version Summary", "th"),
|
||||
]
|
||||
data = [header]
|
||||
|
||||
for row in rows:
|
||||
data.append(
|
||||
[
|
||||
text_para(row.get("site", ""), "cell_mono"),
|
||||
text_para(row.get("dbTitle", "")),
|
||||
text_para(row.get("internalTaskNumber", ""), "cell_mono"),
|
||||
status_pill(row.get("r00Completed", "No")),
|
||||
status_pill(row.get("r00Billed", "No")),
|
||||
text_para(row.get("activeRevision", "—"), "cell_mono"),
|
||||
status_pill(row.get("activeStatus", "—")),
|
||||
text_para(row.get("completedRevisions", "—")),
|
||||
text_para(row.get("billedVersions", "—")),
|
||||
text_para(row.get("versionSummary", "—")),
|
||||
]
|
||||
)
|
||||
|
||||
table = LongTable(
|
||||
data,
|
||||
colWidths=[
|
||||
0.78 * inch,
|
||||
1.7 * inch,
|
||||
0.72 * inch,
|
||||
0.75 * inch,
|
||||
0.85 * inch,
|
||||
0.6 * inch,
|
||||
1.0 * inch,
|
||||
1.45 * inch,
|
||||
1.2 * inch,
|
||||
2.15 * inch,
|
||||
],
|
||||
repeatRows=1,
|
||||
)
|
||||
table.setStyle(
|
||||
TableStyle(
|
||||
[
|
||||
("BACKGROUND", (0, 0), (-1, 0), colors.white),
|
||||
("BOX", (0, 0), (-1, -1), 0.8, LINE),
|
||||
("INNERGRID", (0, 0), (-1, -1), 0.4, LINE),
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("TOPPADDING", (0, 0), (-1, -1), 6),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
|
||||
("LEFTPADDING", (0, 0), (-1, -1), 6),
|
||||
("RIGHTPADDING", (0, 0), (-1, -1), 6),
|
||||
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#FBF8F2")]),
|
||||
]
|
||||
)
|
||||
)
|
||||
flow.append(table)
|
||||
return flow
|
||||
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="title_main",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=24,
|
||||
leading=28,
|
||||
textColor=TEXT,
|
||||
alignment=TA_LEFT,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="eyebrow",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=9,
|
||||
leading=11,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="subtitle",
|
||||
fontName="Helvetica",
|
||||
fontSize=11,
|
||||
leading=15,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="section_title",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=15,
|
||||
leading=18,
|
||||
textColor=TEXT,
|
||||
spaceAfter=2,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="small",
|
||||
fontName="Helvetica",
|
||||
fontSize=9,
|
||||
leading=12,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="card_label",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=8,
|
||||
leading=10,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="card_value",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=24,
|
||||
leading=26,
|
||||
textColor=TEXT,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="card_sub",
|
||||
fontName="Helvetica",
|
||||
fontSize=8.5,
|
||||
leading=11,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="th",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=8,
|
||||
leading=10,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="cell",
|
||||
fontName="Helvetica",
|
||||
fontSize=8.2,
|
||||
leading=10.5,
|
||||
textColor=TEXT,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="cell_mono",
|
||||
fontName="Courier",
|
||||
fontSize=8.1,
|
||||
leading=10.3,
|
||||
textColor=TEXT,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="pill",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=7.5,
|
||||
leading=9,
|
||||
alignment=1,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
rows = json.loads(INPUT_PATH.read_text())
|
||||
existing_rows = [row for row in rows if row.get("exists", True) is not False]
|
||||
active_rows = [row for row in existing_rows if row.get("hasActiveRevision") == "Yes"]
|
||||
no_active_rows = [row for row in existing_rows if row.get("hasActiveRevision") != "Yes"]
|
||||
|
||||
doc = BaseDocTemplate(
|
||||
str(OUTPUT_PATH),
|
||||
pagesize=landscape(legal),
|
||||
leftMargin=36,
|
||||
rightMargin=36,
|
||||
topMargin=28,
|
||||
bottomMargin=28,
|
||||
)
|
||||
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id="main")
|
||||
doc.addPageTemplates([PageTemplate(id="default", frames=[frame], onPage=page_template)])
|
||||
|
||||
story = [
|
||||
Paragraph("FOURGE PORTAL AUDIT", styles["eyebrow"]),
|
||||
Spacer(1, 4),
|
||||
Paragraph("Checked Sites Revision Audit", styles["title_main"]),
|
||||
Spacer(1, 6),
|
||||
Paragraph(
|
||||
"This report treats your checkmark as “R00 was completed,” then separates that from billing and from any newer active revisions. "
|
||||
"It is designed to make client-billing comparison easier at a glance.",
|
||||
styles["subtitle"],
|
||||
),
|
||||
Spacer(1, 18),
|
||||
build_summary_cards(rows),
|
||||
Spacer(1, 18),
|
||||
]
|
||||
|
||||
story.extend(
|
||||
build_table(
|
||||
active_rows,
|
||||
"Checked Sites With Active Revisions",
|
||||
"These are the sites where R00 is done, but there is still a newer revision active or awaiting billing.",
|
||||
)
|
||||
)
|
||||
story.append(PageBreak())
|
||||
story.extend(
|
||||
build_table(
|
||||
no_active_rows,
|
||||
"Checked Sites With No Active Revision",
|
||||
"These are the checked sites that currently do not have a newer active revision.",
|
||||
)
|
||||
)
|
||||
|
||||
doc.build(story)
|
||||
print(OUTPUT_PATH)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,301 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { createRequire } from 'node:module';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const { PDFDocument, StandardFonts, rgb } = require('pdf-lib');
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const outputPath = path.join(__dirname, 'finance-summary.pdf');
|
||||
|
||||
const pdf = await PDFDocument.create();
|
||||
const fontRegular = await pdf.embedFont(StandardFonts.Helvetica);
|
||||
const fontBold = await pdf.embedFont(StandardFonts.HelveticaBold);
|
||||
|
||||
const PAGE_W = 612;
|
||||
const PAGE_H = 792;
|
||||
const MARGIN = 42;
|
||||
const ACCENT = rgb(0.96, 0.65, 0.14);
|
||||
const TEXT = rgb(0.09, 0.09, 0.09);
|
||||
const MUTED = rgb(0.36, 0.36, 0.36);
|
||||
const BORDER = rgb(0.90, 0.86, 0.80);
|
||||
const SURFACE = rgb(0.985, 0.97, 0.94);
|
||||
const WHITE = rgb(1, 1, 1);
|
||||
|
||||
let page = pdf.addPage([PAGE_W, PAGE_H]);
|
||||
let y = PAGE_H - MARGIN;
|
||||
|
||||
function ensureSpace(heightNeeded = 40) {
|
||||
if (y - heightNeeded < MARGIN) {
|
||||
page = pdf.addPage([PAGE_W, PAGE_H]);
|
||||
y = PAGE_H - MARGIN;
|
||||
}
|
||||
}
|
||||
|
||||
function drawText(text, x, yy, size = 12, font = fontRegular, color = TEXT) {
|
||||
page.drawText(text, { x, y: yy, size, font, color });
|
||||
}
|
||||
|
||||
function wrapText(text, maxWidth, size = 12, font = fontRegular) {
|
||||
const words = String(text).split(/\s+/);
|
||||
const lines = [];
|
||||
let line = '';
|
||||
for (const word of words) {
|
||||
const test = line ? `${line} ${word}` : word;
|
||||
const width = font.widthOfTextAtSize(test, size);
|
||||
if (width <= maxWidth || !line) line = test;
|
||||
else {
|
||||
lines.push(line);
|
||||
line = word;
|
||||
}
|
||||
}
|
||||
if (line) lines.push(line);
|
||||
return lines;
|
||||
}
|
||||
|
||||
function drawParagraph(text, x, maxWidth, size = 12, color = TEXT, lineGap = 4, font = fontRegular) {
|
||||
const lines = wrapText(text, maxWidth, size, font);
|
||||
const lineHeight = size + lineGap;
|
||||
ensureSpace(lines.length * lineHeight + 4);
|
||||
for (const line of lines) {
|
||||
drawText(line, x, y, size, font, color);
|
||||
y -= lineHeight;
|
||||
}
|
||||
y -= 4;
|
||||
}
|
||||
|
||||
function drawSectionTitle(text) {
|
||||
ensureSpace(30);
|
||||
drawText(text, MARGIN, y, 18, fontBold, TEXT);
|
||||
y -= 26;
|
||||
}
|
||||
|
||||
function drawCard(x, yy, w, h, title, bodyLines) {
|
||||
page.drawRectangle({ x, y: yy - h, width: w, height: h, color: SURFACE, borderColor: BORDER, borderWidth: 1, borderRadius: 12 });
|
||||
drawText(title, x + 12, yy - 20, 11, fontBold, MUTED);
|
||||
let innerY = yy - 40;
|
||||
for (const line of bodyLines) {
|
||||
const wrapped = wrapText(line, w - 24, 11, fontRegular);
|
||||
for (const piece of wrapped) {
|
||||
drawText(piece, x + 12, innerY, 11, fontRegular, TEXT);
|
||||
innerY -= 15;
|
||||
}
|
||||
innerY -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
function drawFlowBox(title, lines) {
|
||||
const contentLines = lines.flatMap(line => wrapText(line, PAGE_W - MARGIN * 2 - 24, 11, fontRegular));
|
||||
const h = 40 + contentLines.length * 15 + 10;
|
||||
ensureSpace(h + 24);
|
||||
page.drawRectangle({ x: MARGIN, y: y - h, width: PAGE_W - MARGIN * 2, height: h, color: WHITE, borderColor: BORDER, borderWidth: 1, borderRadius: 12 });
|
||||
drawText(title, MARGIN + 14, y - 20, 11, fontBold, MUTED);
|
||||
let innerY = y - 40;
|
||||
for (const line of contentLines) {
|
||||
drawText(line, MARGIN + 14, innerY, 11, fontRegular, TEXT);
|
||||
innerY -= 15;
|
||||
}
|
||||
y -= h + 18;
|
||||
if (y > MARGIN + 40) {
|
||||
drawText("v", PAGE_W / 2 - 3, y + 3, 14, fontBold, ACCENT);
|
||||
}
|
||||
}
|
||||
|
||||
function drawBullet(text) {
|
||||
const bulletX = MARGIN + 4;
|
||||
const textX = MARGIN + 18;
|
||||
const maxWidth = PAGE_W - textX - MARGIN;
|
||||
const lines = wrapText(text, maxWidth, 11, fontRegular);
|
||||
const lineHeight = 15;
|
||||
ensureSpace(lines.length * lineHeight + 4);
|
||||
drawText("•", bulletX, y, 12, fontBold, TEXT);
|
||||
for (const line of lines) {
|
||||
drawText(line, textX, y, 11, fontRegular, TEXT);
|
||||
y -= lineHeight;
|
||||
}
|
||||
y -= 2;
|
||||
}
|
||||
|
||||
function drawTable(headers, rows, colWidths) {
|
||||
const tableW = colWidths.reduce((a, b) => a + b, 0);
|
||||
const startX = MARGIN;
|
||||
const rowPad = 8;
|
||||
const headerH = 24;
|
||||
ensureSpace(36);
|
||||
page.drawRectangle({ x: startX, y: y - headerH, width: tableW, height: headerH, color: SURFACE, borderColor: BORDER, borderWidth: 1 });
|
||||
let x = startX;
|
||||
headers.forEach((head, i) => {
|
||||
drawText(head, x + 6, y - 15, 10, fontBold, MUTED);
|
||||
x += colWidths[i];
|
||||
});
|
||||
y -= headerH;
|
||||
for (const row of rows) {
|
||||
const wrappedCells = row.map((cell, i) => wrapText(cell, colWidths[i] - 12, 10, fontRegular));
|
||||
const lines = Math.max(...wrappedCells.map(c => c.length));
|
||||
const rowH = Math.max(22, lines * 13 + rowPad);
|
||||
ensureSpace(rowH + 2);
|
||||
page.drawRectangle({ x: startX, y: y - rowH, width: tableW, height: rowH, borderColor: BORDER, borderWidth: 1 });
|
||||
let cellX = startX;
|
||||
wrappedCells.forEach((cellLines, i) => {
|
||||
let cellY = y - 14;
|
||||
for (const line of cellLines) {
|
||||
drawText(line, cellX + 6, cellY, 10, fontRegular, TEXT);
|
||||
cellY -= 12;
|
||||
}
|
||||
cellX += colWidths[i];
|
||||
});
|
||||
y -= rowH;
|
||||
}
|
||||
y -= 10;
|
||||
}
|
||||
|
||||
function newPage() {
|
||||
page = pdf.addPage([PAGE_W, PAGE_H]);
|
||||
y = PAGE_H - MARGIN;
|
||||
}
|
||||
|
||||
// Page 1
|
||||
page.drawRectangle({
|
||||
x: MARGIN,
|
||||
y: y - 88,
|
||||
width: PAGE_W - MARGIN * 2,
|
||||
height: 88,
|
||||
color: SURFACE,
|
||||
borderColor: BORDER,
|
||||
borderWidth: 1.2,
|
||||
borderRadius: 16,
|
||||
});
|
||||
drawText('Fourge Portal Finance Summary', MARGIN + 18, y - 30, 24, fontBold, TEXT);
|
||||
drawText('Role cheat sheet, finance lanes, and data flow for team, client, and subcontractor users.', MARGIN + 18, y - 54, 12, fontRegular, MUTED);
|
||||
y -= 112;
|
||||
|
||||
drawSectionTitle('1. Executive Summary');
|
||||
drawParagraph('The finance area is split into three separate role lanes. Team gets the full finance hub, clients get invoice viewing only, and subcontractors get invoice submission plus purchase-order review.', MARGIN, PAGE_W - MARGIN * 2, 12, TEXT);
|
||||
|
||||
ensureSpace(140);
|
||||
const gap = 12;
|
||||
const cardW = (PAGE_W - MARGIN * 2 - gap * 2) / 3;
|
||||
drawCard(MARGIN, y, cardW, 104, 'TEAM', [
|
||||
'Main route: /invoices',
|
||||
'Handles client invoices, expenses, subcontractor invoices, and POs.',
|
||||
]);
|
||||
drawCard(MARGIN + cardW + gap, y, cardW, 104, 'CLIENT', [
|
||||
'Main route: /my-invoices',
|
||||
'Can view invoices, filter by company, and download PDFs.',
|
||||
]);
|
||||
drawCard(MARGIN + (cardW + gap) * 2, y, cardW, 104, 'SUBCONTRACTOR', [
|
||||
'Main routes: /my-invoices-sub and /my-purchase-orders',
|
||||
'Can submit invoices to Fourge and approve POs.',
|
||||
]);
|
||||
y -= 126;
|
||||
|
||||
drawSectionTitle('2. Role-by-Role Cheat Sheet');
|
||||
drawTable(
|
||||
['ROLE', 'MAIN ROUTES', 'CAN DO', 'CANNOT DO'],
|
||||
[
|
||||
['Team', '/invoices', 'Client invoices, expenses, POs, subcontractor invoices, paid status, PDFs, email sends', 'No major finance restrictions in UI'],
|
||||
['Client', '/my-invoices', 'View invoices, filter by company, download invoice PDFs', 'No expenses, no edits, no POs, no subcontractor finance tools'],
|
||||
['Subcontractor', '/my-invoices-sub /my-purchase-orders', 'Submit invoices, view status, download receipt after paid, approve POs', 'No team finance overview, no client billing control'],
|
||||
],
|
||||
[72, 120, 205, 131],
|
||||
);
|
||||
|
||||
drawSectionTitle('3. Core Data Models');
|
||||
drawBullet('invoices + invoice_items: Fourge bills clients.');
|
||||
drawBullet('expenses: Fourge internal finance records.');
|
||||
drawBullet('subcontractor_invoices + subcontractor_invoice_items: subcontractors bill Fourge.');
|
||||
drawBullet('subcontractor_payments + subcontractor_po_items: Fourge sends purchase orders to subcontractors.');
|
||||
drawBullet('tasks and submissions connect work records to invoice line items.');
|
||||
|
||||
// Page 2
|
||||
newPage();
|
||||
drawSectionTitle('4. System Flow');
|
||||
drawFlowBox('CLIENT BILLING FLOW', [
|
||||
'Approved work reaches tasks.status = client_approved.',
|
||||
'Team finance loads uninvoiced tasks and revisions and builds invoice line items.',
|
||||
'Invoice is saved, PDF is generated, and email is sent to the client.',
|
||||
'Later team marks the invoice paid.',
|
||||
]);
|
||||
drawFlowBox('EXPENSE FLOW', [
|
||||
'Team logs internal expenses and can attach receipt files.',
|
||||
'Expenses feed overview charts, yearly finance summaries, and profit calculations.',
|
||||
]);
|
||||
drawFlowBox('SUBCONTRACTOR INVOICE FLOW', [
|
||||
'Subcontractor creates an invoice from completed approved tasks assigned to them.',
|
||||
'Fourge reviews the invoice, marks it paid, and can send a receipt PDF back.',
|
||||
]);
|
||||
drawFlowBox('PURCHASE ORDER FLOW', [
|
||||
'Team creates a PO for a subcontractor, optionally linked to project tasks.',
|
||||
'Subcontractor reviews and approves the PO from their own portal view.',
|
||||
]);
|
||||
|
||||
y -= 8;
|
||||
drawSectionTitle('5. Status Logic');
|
||||
drawBullet('Client invoice: draft -> sent -> paid');
|
||||
drawBullet('Subcontractor invoice: draft -> submitted -> paid');
|
||||
drawBullet('Purchase order: draft -> sent -> approved -> ready_to_pay -> paid');
|
||||
drawBullet('Cancelled is the exit state for a PO.');
|
||||
|
||||
drawSectionTitle('6. Key Business Rules');
|
||||
drawBullet('Client invoices only pull approved, uninvoiced work.');
|
||||
drawBullet('Tasks and revisions are marked invoiced after invoice creation.');
|
||||
drawBullet('Deleting a team invoice rolls linked invoice flags back.');
|
||||
drawBullet('Team invoice status changes also push task status changes.');
|
||||
drawBullet('Revision billing rule: R00 = new work, R01 = free, R02+ = billable increments.');
|
||||
drawBullet('Subcontractor invoices are created by the external user and then reviewed/paid by team.');
|
||||
drawBullet('POs are created by team and approved by the subcontractor.');
|
||||
|
||||
// Page 3
|
||||
newPage();
|
||||
drawSectionTitle('7. Database Relationship Chart');
|
||||
drawParagraph('Simple relationship map:', MARGIN, PAGE_W - MARGIN * 2, 12, TEXT, 4, fontBold);
|
||||
const diagram = [
|
||||
'COMPANIES',
|
||||
' -> invoices',
|
||||
' -> invoice_items',
|
||||
' -> task_id -> tasks',
|
||||
' -> submission_id -> submissions',
|
||||
'',
|
||||
'PROJECTS',
|
||||
' -> tasks',
|
||||
' -> submissions',
|
||||
'',
|
||||
'PROFILES (external)',
|
||||
' -> subcontractor_invoices',
|
||||
' -> subcontractor_invoice_items',
|
||||
' -> task_id -> tasks',
|
||||
' -> subcontractor_payments (POs)',
|
||||
' -> subcontractor_po_items',
|
||||
' -> task_id -> tasks',
|
||||
'',
|
||||
'EXPENSES',
|
||||
' -> standalone internal finance records',
|
||||
];
|
||||
page.drawRectangle({ x: MARGIN, y: y - 270, width: PAGE_W - MARGIN * 2, height: 270, color: SURFACE, borderColor: BORDER, borderWidth: 1, borderRadius: 12 });
|
||||
let codeY = y - 20;
|
||||
for (const line of diagram) {
|
||||
drawText(line, MARGIN + 14, codeY, 11, fontRegular, TEXT);
|
||||
codeY -= 14;
|
||||
}
|
||||
y -= 292;
|
||||
|
||||
drawSectionTitle('8. Quick Route Map');
|
||||
drawTable(
|
||||
['SURFACE', 'ROUTE', 'USER'],
|
||||
[
|
||||
['Finance Hub', '/invoices', 'Team'],
|
||||
['Client Invoices', '/my-invoices', 'Client'],
|
||||
['Subcontractor Invoices', '/my-invoices-sub', 'External'],
|
||||
['Create Subcontractor Invoice', '/my-invoices-sub/new', 'External'],
|
||||
['Purchase Orders', '/my-purchase-orders', 'External'],
|
||||
],
|
||||
[210, 230, 88],
|
||||
);
|
||||
|
||||
drawParagraph('Generated from the current portal codebase summary on June 3, 2026.', MARGIN, PAGE_W - MARGIN * 2, 10, MUTED);
|
||||
|
||||
const bytes = await pdf.save();
|
||||
await fs.writeFile(outputPath, bytes);
|
||||
console.log(outputPath);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
# Invoice Match Audit
|
||||
|
||||
Date: `2026-06-05`
|
||||
|
||||
Definition used: your checked sites mean `R00` was completed. This audit compares that list against live client invoice data.
|
||||
|
||||
- Checked sites: 26
|
||||
- Sites found in DB: 39
|
||||
- Checked sites with R00 complete but not billed: 3
|
||||
- Checked sites with completed unbilled versions: 6
|
||||
- Unchecked sites that are already billed: 0
|
||||
|
||||
## Main Finding
|
||||
|
||||
Client invoicing appears aligned with your checked list overall: there are **no unchecked sites currently billed** from this list.
|
||||
|
||||
## Checked But R00 Not Billed Yet
|
||||
|
||||
- `30094` 30094 Colorado Springs, CO -> R00, status `client_approved`
|
||||
- `30261` 30261 NSA Clovis NM -> R00, status `client_approved`
|
||||
- `68720` 68720 NSA Lancaster PA -> R00, status `client_approved`
|
||||
|
||||
## Completed Versions Still Unbilled
|
||||
|
||||
- `30094` 30094 Colorado Springs, CO -> unbilled: R00; billed: —; current R00 (client_approved)
|
||||
- `30096` 30096 Eerie, CO -> unbilled: R01; billed: R00: INV-2026-006 (sent); current R02 (not_started)
|
||||
- `30261` 30261 NSA Clovis NM -> unbilled: R00; billed: —; current R00 (client_approved)
|
||||
- `68720` 68720 NSA Lancaster PA -> unbilled: R00; billed: —; current R00 (client_approved)
|
||||
- `68721` 68721 NSA Mechanicsburg PA -> unbilled: R02; billed: R00: INV-2026-006 (sent) | R01: INV-2026-006 (sent); current R02 (client_approved)
|
||||
- `68811` 68811 Kelso, WA -> unbilled: R01; billed: R00: INV-2026-006 (sent); current R02 (not_started)
|
||||
|
||||
## Checked Sites With Active Revisions
|
||||
|
||||
| Site | Title | Current | Status | Billed Versions | Completed Unbilled |
|
||||
|---|---|---|---|---|---|
|
||||
| 00286 | 00286 Riverside, CA | R01 | not_started | R00: INV-2026-006 (sent) | — |
|
||||
| 30055 | 30055 Moreno Valley, CA | R01 | client_review | R00: INV-2026-006 (sent) | — |
|
||||
| 30096 | 30096 Eerie, CO | R02 | not_started | R00: INV-2026-006 (sent) | R01 |
|
||||
| 68639 | 68639 NSA Los Lunas NM | R01 | client_review | R00: INV-2026-006 (sent) | — |
|
||||
| 68663 | 68663 NSA Oklahoma City OK | R01 | not_started | R00: INV-2026-006 (sent) | — |
|
||||
| 68664 | 68664 NSA Oklahoma City OK | R01 | not_started | R00: INV-2026-006 (sent) | — |
|
||||
| 68666 | 68666 NSA Oklahoma City, OK | R01 | not_started | R00: INV-2026-006 (sent) | — |
|
||||
| 68669 | 68669 NSA Oklahoma City OK | R01 | not_started | R00: INV-2026-006 (sent) | — |
|
||||
| 68670 | 68670 NSA Oklahoma City OK | R02 | client_review | R00: INV-2026-006 (sent)<br>R01: INV-2026-006 (sent) | — |
|
||||
| 68671 | 68671 NSA Oklahoma City OK | R01 | client_review | R00: INV-2026-006 (sent) | — |
|
||||
| 68673 | 68673 NSA Oklahoma City OK | R01 | client_review | R00: INV-2026-006 (sent) | — |
|
||||
| 68721 | 68721 NSA Mechanicsburg PA | R02 | client_approved | R00: INV-2026-006 (sent)<br>R01: INV-2026-006 (sent) | R02 |
|
||||
| 68808 | 68808 Camas, WA | R01 | not_started | R00: INV-2026-006 (sent) | — |
|
||||
| 68809 | 68809 Centralia, WA | R02 | not_started | R00: INV-2026-006 (sent)<br>R01: INV-2026-006 (sent) | — |
|
||||
| 68810 | 68810 Chehalis, WA | R02 | not_started | R00: INV-2026-006 (sent)<br>R01: INV-2026-006 (sent) | — |
|
||||
| 68811 | 68811 Kelso, WA | R02 | not_started | R00: INV-2026-006 (sent) | R01 |
|
||||
|
||||
## Unchecked But Already Billed
|
||||
|
||||
- None
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,185 @@
|
||||
# Site Completion and Billing Audit
|
||||
|
||||
Generated: 2026-06-04T13:58:27.846Z
|
||||
|
||||
- Listed sites: 95
|
||||
- Found in database: 39
|
||||
- Missing from database: 56
|
||||
- Checked vs DB mismatches: 15
|
||||
|
||||
## Missing From Database
|
||||
|
||||
- 00278
|
||||
- 00279
|
||||
- 00281
|
||||
- 00282
|
||||
- 00284
|
||||
- 00290
|
||||
- 00291
|
||||
- 30051
|
||||
- 30054
|
||||
- 30056
|
||||
- 30058
|
||||
- 30061
|
||||
- 30063
|
||||
- 30068
|
||||
- 30095
|
||||
- 30098
|
||||
- 30210
|
||||
- 30262
|
||||
- 30319
|
||||
- 30342
|
||||
- 00288
|
||||
- 30344
|
||||
- 30345
|
||||
- 30348
|
||||
- 30349
|
||||
- 30350
|
||||
- 30355
|
||||
- 30357
|
||||
- 30407
|
||||
- 68175
|
||||
- 30412
|
||||
- 30413
|
||||
- 68215
|
||||
- 68216
|
||||
- 30437
|
||||
- 68220
|
||||
- 68432
|
||||
- 68516
|
||||
- 68617
|
||||
- 68620
|
||||
- 68631
|
||||
- 68636
|
||||
- 68637
|
||||
- 68638
|
||||
- 68715
|
||||
- 68723
|
||||
- 68751
|
||||
- 68776
|
||||
- 68777
|
||||
- 68778
|
||||
- 68779
|
||||
- 68780
|
||||
- 68781
|
||||
- 68782
|
||||
- 68784
|
||||
- 68785
|
||||
|
||||
## Checked vs Database Mismatches
|
||||
|
||||
- 00286 00286 Riverside, CA: you marked completed, DB status is not_started
|
||||
- 30055 30055 Moreno Valley, CA: you marked completed, DB status is client_review
|
||||
- 30096 30096 Eerie, CO: you marked completed, DB status is not_started
|
||||
- 68639 68639 NSA Los Lunas NM: you marked completed, DB status is client_review
|
||||
- 68663 68663 NSA Oklahoma City OK: you marked completed, DB status is not_started
|
||||
- 68664 68664 NSA Oklahoma City OK: you marked completed, DB status is not_started
|
||||
- 68666 68666 NSA Oklahoma City, OK: you marked completed, DB status is not_started
|
||||
- 68669 68669 NSA Oklahoma City OK: you marked completed, DB status is not_started
|
||||
- 68670 68670 NSA Oklahoma City OK: you marked completed, DB status is not_started
|
||||
- 68671 68671 NSA Oklahoma City OK: you marked completed, DB status is not_started
|
||||
- 68673 68673 NSA Oklahoma City OK: you marked completed, DB status is client_review
|
||||
- 68808 68808 Camas, WA: you marked completed, DB status is not_started
|
||||
- 68809 68809 Centralia, WA: you marked completed, DB status is not_started
|
||||
- 68810 68810 Chehalis, WA: you marked completed, DB status is not_started
|
||||
- 68811 68811 Kelso, WA: you marked completed, DB status is not_started
|
||||
|
||||
## Audit Table
|
||||
|
||||
| Site # | DB Title | Internal # | In DB | Your Check | DB Completed | Match | Current R# | Active Status | R00 Billed | Billed Revisions | Completed Unbilled Revisions | Version Status Summary |
|
||||
|---|---|---:|---:|---:|---:|---:|---|---|---|---|---|---|
|
||||
| 00278 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 00279 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 00281 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 00282 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 00284 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 00285 | 00285 NSA Moreno Valley CA | 65 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 00286 | 00286 Riverside, CA | 31 | Yes | Yes | No | No | R01 | not_started | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: not_started |
|
||||
| 00290 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 00291 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30049 | 30049 Elk Grove, CA | 32 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 30051 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30054 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30055 | 30055 Moreno Valley, CA | 33 | Yes | Yes | No | No | R01 | client_review | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: client_review |
|
||||
| 30056 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30058 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30060 | 30060 NSA Palmdale CA | 66 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 30061 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30062 | 30062 Riverside, CA | 34 | Yes | Yes | Yes | Yes | R00 | invoiced | Yes (INV-2026-006 (sent)) | — | — | R00: invoiced / billed |
|
||||
| 30063 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30068 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30091 | 30091 NSA Colorado Springs CO | 67 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 30094 | 30094 Colorado Springs, CO | 35 | Yes | Yes | Yes | Yes | R00 | client_approved | No | — | — | R00: client_approved |
|
||||
| 30095 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30096 | 30096 Eerie, CO | 36 | Yes | Yes | No | No | R02 | not_started | Yes (INV-2026-006 (sent)) | — | R01 | R00: completed / billed; R01: completed; R02: not_started |
|
||||
| 30098 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30101 | 30101 Monument, CO | 37 | Yes | No | No | Yes | R00 | on_hold | No | — | — | R00: on_hold |
|
||||
| 30210 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30211 | 30211 NSA Post Falls ID | 68 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 30212 | 30212 NSA Sandpoint ID | 69 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 30258 | 30258 Lebanon, NH | 38 | Yes | Yes | Yes | Yes | R00 | invoiced | Yes (INV-2026-006 (sent)) | — | — | R00: invoiced / billed |
|
||||
| 30259 | 30259 Hamburg, NJ | 39 | Yes | Yes | Yes | Yes | R00 | invoiced | Yes (INV-2026-006 (sent)) | — | — | R00: invoiced / billed |
|
||||
| 30261 | 30261 NSA Clovis NM | 70 | Yes | Yes | Yes | Yes | R00 | client_approved | No | — | — | R00: client_approved |
|
||||
| 30262 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30319 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30342 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 00288 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30344 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30345 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30348 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30349 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30350 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30355 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30357 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30407 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68175 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30412 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30413 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68215 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68216 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 30437 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68220 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68432 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68452 | 68452 NSA Houston TX | 71 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 68453 | 68453 NSA Katy TX | 72 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 68454 | 68454 NSA Katy TX | 73 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 68516 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68617 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68620 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68631 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68636 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68637 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68638 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68639 | 68639 NSA Los Lunas NM | 40 | Yes | Yes | No | No | R01 | client_review | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: client_review |
|
||||
| 68663 | 68663 NSA Oklahoma City OK | 41 | Yes | Yes | No | No | R01 | not_started | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: not_started |
|
||||
| 68664 | 68664 NSA Oklahoma City OK | 42 | Yes | Yes | No | No | R01 | not_started | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: not_started |
|
||||
| 68666 | 68666 NSA Oklahoma City, OK | 43 | Yes | Yes | No | No | R01 | not_started | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: not_started |
|
||||
| 68669 | 68669 NSA Oklahoma City OK | 44 | Yes | Yes | No | No | R01 | not_started | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: not_started |
|
||||
| 68670 | 68670 NSA Oklahoma City OK | 45 | Yes | Yes | No | No | R02 | not_started | Yes (INV-2026-006 (sent)) | R01 [INV-2026-006 (sent)] | — | R00: completed / billed; R01: completed / billed; R02: not_started |
|
||||
| 68671 | 68671 NSA Oklahoma City OK | 46 | Yes | Yes | No | No | R01 | not_started | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: not_started |
|
||||
| 68673 | 68673 NSA Oklahoma City OK | 47 | Yes | Yes | No | No | R01 | client_review | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: client_review |
|
||||
| 68715 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68720 | 68720 NSA Lancaster PA | 74 | Yes | Yes | Yes | Yes | R00 | client_approved | No | — | — | R00: client_approved |
|
||||
| 68721 | 68721 NSA Mechanicsburg PA | 48 | Yes | Yes | Yes | Yes | R02 | client_approved | Yes (INV-2026-006 (sent)) | R01 [INV-2026-006 (sent)] | R02 | R00: completed / billed; R01: completed / billed; R02: client_approved |
|
||||
| 68723 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68724 | 68724 NSA York PA | 75 | Yes | Yes | Yes | Yes | R00 | invoiced | Yes (INV-2026-006 (sent)) | — | — | R00: invoiced / billed |
|
||||
| 68751 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68752 | 68752 NSA Brownsville TX | 76 | Yes | Yes | Yes | Yes | R00 | invoiced | Yes (INV-2026-006 (sent)) | — | — | R00: invoiced / billed |
|
||||
| 68753 | 68753 NSA Brownsville TX | 77 | Yes | Yes | Yes | Yes | R00 | invoiced | Yes (INV-2026-006 (sent)) | — | — | R00: invoiced / billed |
|
||||
| 68754 | 68754 NSA Brownsville TX | 78 | Yes | No | No | Yes | R00 | in_progress | No | — | — | R00: in_progress |
|
||||
| 68755 | 68755 NSA Brownsville TX | 79 | Yes | No | No | Yes | R00 | not_started | No | — | — | R00: not_started |
|
||||
| 68756 | 68756 NSA Brownsville TX | 80 | Yes | No | No | Yes | R00 | in_progress | No | — | — | R00: in_progress |
|
||||
| 68758 | 68758 NSA Brownsville TX | 81 | Yes | Yes | Yes | Yes | R00 | invoiced | Yes (INV-2026-006 (sent)) | — | — | R00: invoiced / billed |
|
||||
| 68776 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68777 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68778 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68779 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68780 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68781 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68782 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68784 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68785 | — | — | No | No | — | — | — | — | — | — | — | Missing from DB |
|
||||
| 68808 | 68808 Camas, WA | 49 | Yes | Yes | No | No | R01 | not_started | Yes (INV-2026-006 (sent)) | — | — | R00: completed / billed; R01: not_started |
|
||||
| 68809 | 68809 Centralia, WA | 50 | Yes | Yes | No | No | R02 | not_started | Yes (INV-2026-006 (sent)) | R01 [INV-2026-006 (sent)] | — | R00: completed / billed; R01: completed / billed; R02: not_started |
|
||||
| 68810 | 68810 Chehalis, WA | 51 | Yes | Yes | No | No | R02 | not_started | Yes (INV-2026-006 (sent)) | R01 [INV-2026-006 (sent)] | — | R00: completed / billed; R01: completed / billed; R02: not_started |
|
||||
| 68811 | 68811 Kelso, WA | 52 | Yes | Yes | No | No | R02 | not_started | Yes (INV-2026-006 (sent)) | — | R01 | R00: completed / billed; R01: completed; R02: not_started |
|
||||
Reference in New Issue
Block a user