Add Phase 20 stored bank reuse and richer reconciliation
This commit is contained in:
@@ -65,7 +65,7 @@
|
||||
<input type="checkbox" name="confirm_same_client" value="1" class="mt-1 rounded border-slate-300">
|
||||
<span>I confirm that all statements uploaded in this analysis belong to the selected client or one of its ERP-recorded legal/trade/business names.</span>
|
||||
</label>
|
||||
<p class="mt-2 text-xs text-indigo-700">When a client is selected, the analyzer validates every statement owner against the client name, trade name, business-unit names, registration legal/trade names and branch names. On successful analysis, the source PDFs and generated workbook are archived using the existing Engagement Documents storage pipeline.</p>
|
||||
<p class="mt-2 text-xs text-indigo-700">When a client is selected, the analyzer validates every statement owner against the client name, trade name, business-unit names, registration legal/trade names and branch names. On successful analysis, newly uploaded source PDFs and the generated workbook are archived using the existing Engagement Documents storage pipeline. Previously stored PDFs selected for reuse remain linked to their existing document/version and are not duplicated.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div><label class="mb-1 block text-sm font-semibold text-slate-700">Bank</label><select name="bank_selection" class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">{% for value, label in bank_options %}<option value="{{ value }}" {% if selected_bank == value %}selected{% endif %}>{{ label }}</option>{% endfor %}</select><p class="mt-1 text-xs text-slate-500">Keep Auto Detect or select a bank for direct parser validation.</p></div>
|
||||
@@ -74,7 +74,36 @@
|
||||
<div><label class="mb-1 block text-sm font-semibold text-slate-700">Account number override <span class="font-normal text-slate-400">(optional)</span></label><input name="account_number" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Use only when statement extraction needs correction"></div>
|
||||
</div>
|
||||
<div class="mt-5 rounded-xl border border-slate-200 bg-slate-50 p-4"><label class="flex items-start gap-3"><input type="checkbox" name="enable_classification" value="1" {% if classification_enabled %}checked{% endif %} class="mt-1 h-4 w-4 rounded border-slate-300"><span><span class="block text-sm font-semibold text-slate-800">Enable narration-based transaction classification</span><span class="mt-1 block text-xs text-slate-500">Adds category, party, review and draft financial helper sheets. Final classification must be verified with books and supporting records.</span></span></label></div>
|
||||
<div class="mt-5 rounded-xl border border-brand-200 bg-brand-50 p-4 text-sm text-slate-700"><strong>Multi-bank contra detection:</strong> when the client has more than one bank account, upload all relevant statements in the same analysis job. There is no fixed statement-count limit unless your administrator configures one. The analyzer pairs only conservative equal-and-opposite transfers across different account numbers and keeps every pair reviewable.</div><div class="mt-5"><label class="mb-1 block text-sm font-semibold text-slate-700">PDF bank statements — select all accounts/statements for this client</label><input type="file" name="statements" accept="application/pdf,.pdf" multiple required class="block w-full rounded-xl border border-slate-300 bg-white px-3 py-3 text-sm"></div>
|
||||
<div class="mt-5 rounded-xl border border-brand-200 bg-brand-50 p-4 text-sm text-slate-700"><strong>Multi-bank contra detection:</strong> when the client has more than one bank account, upload all relevant statements in the same analysis job. There is no fixed statement-count limit unless your administrator configures one. The analyzer pairs only conservative equal-and-opposite transfers across different account numbers and keeps every pair reviewable.</div><div class="mt-5 grid gap-4 lg:grid-cols-2">
|
||||
<div class="rounded-xl border border-slate-200 bg-white p-4">
|
||||
<label class="mb-1 block text-sm font-semibold text-slate-700">Upload new PDF bank statements</label>
|
||||
<input type="file" name="statements" accept="application/pdf,.pdf" multiple class="block w-full rounded-xl border border-slate-300 bg-white px-3 py-3 text-sm">
|
||||
<p class="mt-2 text-xs text-slate-500">Select any number of new statements. They can be combined with previously stored statements below.</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-indigo-200 bg-indigo-50/40 p-4">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-slate-800">Reuse bank statements already stored for this client</div>
|
||||
<p class="mt-1 text-xs text-slate-500">Existing BANK_STATEMENT documents are reused from Engagement Documents/local storage and are not archived again as duplicates.</p>
|
||||
</div>
|
||||
<button type="button" id="refresh-stored-bank-statements" class="rounded-lg border border-indigo-300 bg-white px-3 py-2 text-xs font-semibold text-indigo-800">Refresh</button>
|
||||
</div>
|
||||
<div id="stored-bank-statements" class="mt-3 max-h-64 space-y-2 overflow-y-auto">
|
||||
{% for row in stored_bank_statements or [] %}
|
||||
<label class="flex items-start gap-2 rounded-lg border border-indigo-100 bg-white p-3 text-xs">
|
||||
<input type="checkbox" name="stored_version_ids" value="{{ row.version_id }}" class="mt-1">
|
||||
<span>
|
||||
<span class="block font-semibold text-slate-800">{{ row.title }}</span>
|
||||
<span class="block text-slate-500">{{ row.filename }} · v{{ row.version_id }} · {{ row.storage_status|replace('_',' ')|title }}</span>
|
||||
</span>
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="text-xs text-slate-500">Select an ERP client to load previously stored bank statements.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 rounded-xl bg-slate-50 p-4 text-sm text-slate-600"><div class="font-semibold text-slate-800">Queue limits</div><div class="mt-1">Maximum three processing jobs across all users. Each user may have up to three queued or processing jobs. Completed workbooks and original uploaded statements remain available for 24 hours. Failed-job statements are also retained for 24 hours for debugging.</div></div>
|
||||
<div class="mt-6 flex flex-wrap gap-3"><button class="rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-brand-700">Submit Analysis</button></div>
|
||||
</form>
|
||||
@@ -85,7 +114,7 @@
|
||||
<div class="mt-5 grid gap-4 sm:grid-cols-2 lg:grid-cols-4"><div class="rounded-xl bg-slate-50 p-4"><div class="text-xs text-slate-500">Files</div><div class="mt-1 text-lg font-bold">{{ active_job.file_count }}</div></div><div class="rounded-xl bg-slate-50 p-4"><div class="text-xs text-slate-500">Queue position</div><div id="queue-position" class="mt-1 text-lg font-bold">{{ active_job.queue_position or '—' }}</div></div><div class="rounded-xl bg-slate-50 p-4"><div class="text-xs text-slate-500">Estimated wait</div><div id="estimated-wait" class="mt-1 text-sm font-bold">{{ active_job.estimated_wait or '—' }}</div></div><div class="rounded-xl bg-slate-50 p-4"><div class="text-xs text-slate-500">Progress</div><div id="progress-text" class="mt-1 text-lg font-bold">{{ active_job.progress_percent }}%</div></div></div>
|
||||
<div class="mt-4 h-2 overflow-hidden rounded-full bg-slate-200"><div id="progress-bar" class="h-full bg-brand-600 transition-all" style="width: {{ active_job.progress_percent }}%"></div></div>
|
||||
{% if active_job.status == 'completed' %}
|
||||
<div class="mt-6 rounded-xl border border-emerald-200 bg-emerald-50 p-5"><h3 class="font-bold text-emerald-900">Analysis completed</h3><div class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4"><div><div class="text-xs text-emerald-700">Statements</div><div class="font-bold">{{ active_job.summary.statement_count or 0 }}</div></div><div><div class="text-xs text-emerald-700">Transactions extracted</div><div class="font-bold">{{ active_job.summary.rows_extracted or 0 }}</div></div><div><div class="text-xs text-emerald-700">Exact duplicates</div><div class="font-bold">{{ active_job.summary.exact_duplicate_rows or 0 }}</div></div><div><div class="text-xs text-emerald-700">Review items</div><div class="font-bold">{{ active_job.summary.review_items or 0 }}</div></div><div><div class="text-xs text-emerald-700">Inter-bank contra pairs</div><div class="font-bold">{{ active_job.summary.contra_pairs or 0 }}</div></div></div><div class="mt-5 flex flex-wrap gap-3"><a href="/tools/bank-statement-analyzer/jobs/{{ active_job.id }}/download" class="rounded-xl bg-emerald-700 px-5 py-2.5 text-sm font-semibold text-white">Download Excel</a>{% for file in active_job.original_files %}<a href="/tools/bank-statement-analyzer/jobs/{{ active_job.id }}/statements/{{ file.index }}/download" class="rounded-xl border border-emerald-300 bg-white px-5 py-2.5 text-sm font-semibold text-emerald-800">Download Statement {{ file.index }}</a>{% endfor %}<a href="/tools/bank-statement-analyzer" class="rounded-xl border border-emerald-300 bg-white px-5 py-2.5 text-sm font-semibold text-emerald-800">Analyze Another Bank</a></div><p class="mt-3 text-xs text-emerald-700">The workbook and original statement{{ 's' if active_job.file_count != 1 else '' }} remain available until {{ active_job.expires_at or '24 hours after completion' }}.</p></div>
|
||||
<div class="mt-6 rounded-xl border border-emerald-200 bg-emerald-50 p-5"><h3 class="font-bold text-emerald-900">Analysis completed</h3><div class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4"><div><div class="text-xs text-emerald-700">Statements</div><div class="font-bold">{{ active_job.summary.statement_count or 0 }}</div></div><div><div class="text-xs text-emerald-700">Transactions extracted</div><div class="font-bold">{{ active_job.summary.rows_extracted or 0 }}</div></div><div><div class="text-xs text-emerald-700">Exact duplicates</div><div class="font-bold">{{ active_job.summary.exact_duplicate_rows or 0 }}</div></div><div><div class="text-xs text-emerald-700">Review items</div><div class="font-bold">{{ active_job.summary.review_items or 0 }}</div></div><div><div class="text-xs text-emerald-700">Inter-bank contra pairs</div><div class="font-bold">{{ active_job.summary.contra_pairs or 0 }}</div></div></div>{% if active_job.summary.stored_statement_reuse_count %}<div class="mt-3 rounded-lg border border-indigo-200 bg-indigo-50 p-3 text-xs text-indigo-800">Reused {{ active_job.summary.stored_statement_reuse_count }} statement(s) from existing Engagement Documents without creating duplicate source documents.</div>{% endif %}{% if active_job.summary.statement_overlap_warnings %}<div class="mt-3 rounded-lg border border-amber-200 bg-amber-50 p-3 text-xs text-amber-900"><div class="font-semibold">Overlapping statement periods detected</div>{% for warning in active_job.summary.statement_overlap_warnings %}<div class="mt-1">{{ warning.message }}</div>{% endfor %}<div class="mt-1 text-amber-700">Transactions are still deduplicated by the existing analyzer rules; verify overlapping-period coverage before reconciliation.</div></div>{% endif %}<div class="mt-5 flex flex-wrap gap-3"><a href="/tools/bank-statement-analyzer/jobs/{{ active_job.id }}/download" class="rounded-xl bg-emerald-700 px-5 py-2.5 text-sm font-semibold text-white">Download Excel</a>{% for file in active_job.original_files %}<a href="/tools/bank-statement-analyzer/jobs/{{ active_job.id }}/statements/{{ file.index }}/download" class="rounded-xl border border-emerald-300 bg-white px-5 py-2.5 text-sm font-semibold text-emerald-800">Download Statement {{ file.index }}</a>{% endfor %}<a href="/tools/bank-statement-analyzer" class="rounded-xl border border-emerald-300 bg-white px-5 py-2.5 text-sm font-semibold text-emerald-800">Analyze Another Bank</a></div><p class="mt-3 text-xs text-emerald-700">The workbook and original statement{{ 's' if active_job.file_count != 1 else '' }} remain available until {{ active_job.expires_at or '24 hours after completion' }}.</p></div>
|
||||
{% elif active_job.status == 'failed' %}<div class="mt-5 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-800"><strong>Analysis failed.</strong><div class="mt-1">{{ active_job.error_message }}</div>{% if active_job.original_files %}<div class="mt-4 flex flex-wrap gap-3">{% for file in active_job.original_files %}<a href="/tools/bank-statement-analyzer/jobs/{{ active_job.id }}/statements/{{ file.index }}/download" class="rounded-xl border border-red-300 bg-white px-4 py-2 text-sm font-semibold text-red-800">Download Statement {{ file.index }}</a>{% endfor %}</div><p class="mt-3 text-xs text-red-700">Original statement{{ 's are' if active_job.file_count != 1 else ' is' }} retained until {{ active_job.expires_at or '24 hours after failure' }} for debugging.</p>{% endif %}<a href="/tools/bank-statement-analyzer" class="mt-3 inline-block font-semibold underline">Analyze another statement</a></div>
|
||||
{% else %}<div id="live-message" class="mt-5 rounded-xl border border-blue-200 bg-blue-50 p-4 text-sm text-blue-800">{% if active_job.status == 'queued' %}Your job is queued. You may safely leave this page and return through My Analysis Jobs.{% else %}Your statements are being processed.{% endif %}</div>{% endif %}
|
||||
{% if active_job.status == 'completed' and active_job.purpose in ['accounting_entries','bank_reconciliation'] %}<div class="mt-4 rounded-xl border border-blue-200 bg-blue-50 p-4 text-sm text-blue-900">Accounting import: {{ active_job.accounting_import_status|replace('_',' ')|title }}{% if active_job.summary.client_id %} · <a class="font-semibold underline" href="/tools/accounting/bank-posting?client_id={{ active_job.summary.client_id }}">Open accounting queue</a>{% endif %}</div>{% endif %}</section>
|
||||
@@ -114,8 +143,46 @@
|
||||
if (!selectedStillVisible) engagement.value = "";
|
||||
engagement.required = !!selectedClient;
|
||||
}
|
||||
client.addEventListener("change", filterEngagements);
|
||||
async function loadStoredStatements() {
|
||||
const box = document.getElementById("stored-bank-statements");
|
||||
if (!box) return;
|
||||
const selectedClient = client.value;
|
||||
if (!selectedClient) {
|
||||
box.innerHTML = '<div class="text-xs text-slate-500">Select an ERP client to load previously stored bank statements.</div>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = '<div class="text-xs text-slate-500">Loading stored statements…</div>';
|
||||
try {
|
||||
const response = await fetch(`/tools/bank-statement-analyzer/stored-statements?client_id=${encodeURIComponent(selectedClient)}`, {headers: {"Accept":"application/json"}});
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw new Error(data.detail || "Unable to load stored statements.");
|
||||
const items = data.items || [];
|
||||
if (!items.length) {
|
||||
box.innerHTML = '<div class="text-xs text-slate-500">No stored BANK_STATEMENT documents are available for this client.</div>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = "";
|
||||
items.forEach((row) => {
|
||||
const label = document.createElement("label");
|
||||
label.className = "flex items-start gap-2 rounded-lg border border-indigo-100 bg-white p-3 text-xs";
|
||||
label.innerHTML = `<input type="checkbox" name="stored_version_ids" value="${row.version_id}" class="mt-1"><span><span class="block font-semibold text-slate-800"></span><span class="block text-slate-500"></span></span>`;
|
||||
label.querySelector(".font-semibold").textContent = row.title || row.filename;
|
||||
label.querySelector(".text-slate-500").textContent = `${row.filename} · version ${row.version_id} · ${(row.storage_status || "").replaceAll("_"," ")}`;
|
||||
box.appendChild(label);
|
||||
});
|
||||
} catch (error) {
|
||||
box.innerHTML = `<div class="text-xs text-red-700">${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
client.addEventListener("change", () => {
|
||||
filterEngagements();
|
||||
loadStoredStatements();
|
||||
});
|
||||
const refreshButton = document.getElementById("refresh-stored-bank-statements");
|
||||
if (refreshButton) refreshButton.addEventListener("click", loadStoredStatements);
|
||||
filterEngagements();
|
||||
if (client.value) loadStoredStatements();
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user