Add 4 AI bookkeeping features
Feature 7: Bank Rec Auto-Match — AiSuggestMatches endpoint scores uncleared transactions vs statement ending balance; AI Auto-Match panel in Reconcile.cshtml with confidence highlights and Apply All button. Feature 8: Late Payment Prediction — PredictLatePayments endpoint scores open AR customers by risk (high/medium/low) using historical avg-days-to-pay + late rate; rendered as badge table in AR Aging view via ar-aging-ai.js. Feature 9: Natural Language Financial Queries — FinancialQuery GET page + RunFinancialQuery POST; 12-month context snapshot pre-loaded; answers grounded in real data with supporting facts, follow-up suggestions, session history, and example chips. Feature 10: Recurring Bill Detection — RunRecurringDetection scans 12 months of bills for vendor payment patterns (monthly/quarterly/annual); card grid view in Bills/RecurringDetection.cshtml with confidence badges, next-expected-date, and suggested actions. Supporting: 4 new DTO groups in AccountingAiDtos.cs, 4 method signatures in IAccountingAiService.cs, 4 implementations in AccountingAiService.cs, 4 new AiFeatures constants, 2 new Landing page AI report cards. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -115,6 +115,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Auto-Match panel -->
|
||||
<div class="card shadow-sm mb-3 border-0 bg-light">
|
||||
<div class="card-body d-flex align-items-center gap-3 flex-wrap">
|
||||
<div>
|
||||
<span class="fw-semibold"><i class="bi bi-robot text-primary me-1"></i>AI Auto-Match</span>
|
||||
<span class="text-muted small ms-2">Let Claude suggest which transactions to clear based on amounts and dates.</span>
|
||||
</div>
|
||||
<button id="aiMatchBtn" class="btn btn-outline-primary btn-sm ms-auto" type="button">
|
||||
<i class="bi bi-magic me-1"></i>Suggest Matches
|
||||
</button>
|
||||
</div>
|
||||
<div id="aiMatchResult" class="d-none px-3 pb-3">
|
||||
<div id="aiMatchInsights" class="mb-2 text-muted small"></div>
|
||||
<div id="aiMatchActions" class="d-flex gap-2 flex-wrap">
|
||||
<button id="aiMatchAccept" class="btn btn-sm btn-success d-none">
|
||||
<i class="bi bi-check-all me-1"></i>Apply All Suggestions
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Complete" method="post" id="completeForm">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@recon?.Id" />
|
||||
@@ -184,6 +205,88 @@
|
||||
});
|
||||
|
||||
recalculate();
|
||||
|
||||
// ── AI Auto-Match ──────────────────────────────────────────────────────────
|
||||
let aiSuggestions = [];
|
||||
|
||||
document.getElementById('aiMatchBtn')?.addEventListener('click', async function() {
|
||||
const btn = this;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Analyzing…';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/BankReconciliations/AiSuggestMatches', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'RequestVerificationToken': token
|
||||
},
|
||||
body: new URLSearchParams({ reconId })
|
||||
});
|
||||
const data = await resp.json();
|
||||
|
||||
const resultEl = document.getElementById('aiMatchResult');
|
||||
const insightsEl = document.getElementById('aiMatchInsights');
|
||||
resultEl.classList.remove('d-none');
|
||||
|
||||
if (!data.success) {
|
||||
insightsEl.innerHTML = `<span class="text-danger"><i class="bi bi-exclamation-triangle me-1"></i>${data.errorMessage || 'AI unavailable.'}</span>`;
|
||||
return;
|
||||
}
|
||||
|
||||
aiSuggestions = data.suggestedCleared || [];
|
||||
|
||||
// Highlight suggested rows
|
||||
aiSuggestions.forEach(s => {
|
||||
const row = document.querySelector(`.recon-row[data-type="${s.entityType}"][data-id="${s.entityId}"]`);
|
||||
if (row) {
|
||||
row.classList.add('table-info');
|
||||
const td = row.querySelector('td:last-child');
|
||||
if (td) {
|
||||
const pct = Math.round(s.confidence * 100);
|
||||
td.insertAdjacentHTML('afterend', `<td class="small text-info" style="white-space:nowrap">${pct}% — ${s.reason}</td>`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Insights
|
||||
const insights = data.insights || [];
|
||||
insightsEl.innerHTML = insights.map(i => `<i class="bi bi-lightbulb me-1 text-warning"></i>${i}`).join('<br>');
|
||||
|
||||
if (aiSuggestions.length > 0) {
|
||||
document.getElementById('aiMatchAccept').classList.remove('d-none');
|
||||
} else {
|
||||
insightsEl.innerHTML += '<br><span class="text-muted">No high-confidence suggestions found — review items manually.</span>';
|
||||
}
|
||||
} catch (err) {
|
||||
document.getElementById('aiMatchInsights').innerHTML = '<span class="text-danger">Error contacting AI service.</span>';
|
||||
document.getElementById('aiMatchResult').classList.remove('d-none');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="bi bi-magic me-1"></i>Suggest Matches';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('aiMatchAccept')?.addEventListener('click', async function() {
|
||||
for (const s of aiSuggestions) {
|
||||
const row = document.querySelector(`.recon-row[data-type="${s.entityType}"][data-id="${s.entityId}"]`);
|
||||
if (!row) continue;
|
||||
const cb = row.querySelector('.cleared-checkbox');
|
||||
if (!cb || cb.checked) continue;
|
||||
cb.checked = true;
|
||||
// Persist via the existing toggle endpoint
|
||||
try {
|
||||
await fetch('/BankReconciliations/ToggleCleared', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'RequestVerificationToken': token },
|
||||
body: new URLSearchParams({ reconId, entityType: s.entityType, entityId: s.entityId, isCleared: true })
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
recalculate();
|
||||
this.textContent = 'Applied';
|
||||
this.disabled = true;
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user