Files
boc/aamos-ledger/ui/js/views/reports.js
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

216 lines
8.7 KiB
JavaScript

// ═══════════════════════════════════════════════════════════════════════════
// LandveX Finance — Reports View
// ═══════════════════════════════════════════════════════════════════════════
export const Reports = {
async render(app) {
app.render(`
<div class="card">
<div class="card-header">
<h2 class="card-title">Rapporter</h2>
</div>
<div class="card-body">
<div style="display:grid;gap:12px">
<button class="btn btn-secondary" style="justify-content:flex-start" data-report="trial-balance">
<span style="font-size:1.2rem;margin-right:8px">◧</span>
<div style="text-align:left">
<div style="font-weight:600">Balansrapport</div>
<div style="font-size:.78rem;color:var(--text-2)">Sammanställning av alla konton</div>
</div>
</button>
<button class="btn btn-secondary" style="justify-content:flex-start" data-report="sie4">
<span style="font-size:1.2rem;margin-right:8px">↓</span>
<div style="text-align:left">
<div style="font-weight:600">SIE4-export</div>
<div style="font-size:.78rem;color:var(--text-2)">Exportera till SIE4-format</div>
</div>
</button>
<button class="btn btn-secondary" style="justify-content:flex-start" data-report="reconciliation">
<span style="font-size:1.2rem;margin-right:8px">✓</span>
<div style="text-align:left">
<div style="font-weight:600">Avstämning</div>
<div style="font-size:.78rem;color:var(--text-2)">Bankavstämning per period</div>
</div>
</button>
</div>
</div>
</div>
<div id="report-content"></div>
`);
document.querySelectorAll('[data-report]').forEach(btn => {
btn.addEventListener('click', () => {
const report = btn.dataset.report;
if (report === 'trial-balance') this.showTrialBalance(app);
else if (report === 'sie4') this.showSie4Export(app);
else if (report === 'reconciliation') this.showReconciliation(app);
});
});
},
async showTrialBalance(app) {
document.getElementById('report-content').innerHTML = `
<div class="card">
<div class="card-header">
<h2 class="card-title">Balansrapport 2026-06</h2>
</div>
<div class="card-body" id="tb-content">
<div class="skeleton" style="height:300px"></div>
</div>
</div>
`;
try {
const data = await app.api.get('/trial-balance?fiscal_year=2026&period=2026-06');
if (!data.ok) return;
document.getElementById('tb-content').innerHTML = `
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Konto</th>
<th class="num">Debet</th>
<th class="num">Kredit</th>
<th class="num">Saldo</th>
</tr>
</thead>
<tbody>
${data.accounts.map(a => `
<tr>
<td><strong>${a.account_number}</strong> ${a.account_name}</td>
<td class="num">${this.fmt(a.total_debit)}</td>
<td class="num">${this.fmt(a.total_credit)}</td>
<td class="num ${parseFloat(a.balance) >= 0 ? 'pos' : 'neg'}">${this.fmt(a.balance)}</td>
</tr>
`).join('')}
</tbody>
<tfoot style="border-top:2px solid var(--text);font-weight:700">
<tr>
<td>Totalt</td>
<td class="num">${this.fmt(data.totals.debit)}</td>
<td class="num">${this.fmt(data.totals.credit)}</td>
<td class="num ${data.totals.balanced ? 'pos' : 'neg'}">
${data.totals.balanced ? '✓ Balanserad' : '✗ Obalanserad'}
</td>
</tr>
</tfoot>
</table>
</div>
`;
} catch (e) {
document.getElementById('tb-content').innerHTML = `<p style="color:var(--red)">Fel: ${e.message}</p>`;
}
},
async showSie4Export(app) {
document.getElementById('report-content').innerHTML = `
<div class="card">
<div class="card-header">
<h2 class="card-title">SIE4-export</h2>
</div>
<div class="card-body">
<p style="margin-bottom:16px;font-size:.85rem;color:var(--text-2)">
Exportera bokföringsdata till SIE4-format för import i externa program.
</p>
<div class="form-group">
<label>Räkenskapsår</label>
<select id="sie4-year">
<option value="2026" selected>2026</option>
<option value="2025">2025</option>
</select>
</div>
<div style="display:flex;gap:8px">
<button class="btn btn-primary" id="btn-export">Exportera SIE4</button>
<button class="btn btn-secondary" id="btn-preview">Förhandsgranska</button>
</div>
<pre id="sie4-output" style="margin-top:16px;padding:12px;background:#f5f5f5;border-radius:8px;font-size:.75rem;overflow-x:auto;display:none"></pre>
</div>
</div>
`;
document.getElementById('btn-export').addEventListener('click', async () => {
const year = document.getElementById('sie4-year').value;
window.open(`/api/ledger/export/sie4?fiscal_year=${year}`, '_blank');
});
document.getElementById('btn-preview').addEventListener('click', async () => {
const year = document.getElementById('sie4-year').value;
try {
const data = await app.api.get(`/export/sie4/preview?fiscal_year=${year}`);
document.getElementById('sie4-output').style.display = 'block';
document.getElementById('sie4-output').textContent = data.content || 'Ingen data';
} catch (e) {
alert('Fel: ' + e.message);
}
});
},
async showReconciliation(app) {
document.getElementById('report-content').innerHTML = `
<div class="card">
<div class="card-header">
<h2 class="card-title">Bankavstämning</h2>
</div>
<div class="card-body">
<p style="font-size:.85rem;color:var(--text-2);margin-bottom:16px">
Välj konto och period för avstämning.
</p>
<div class="form-group">
<label>Bankkonto</label>
<select id="rec-account">
<option value="1930">1930 — Företagskonto / affärskonto</option>
<option value="1932">1932 — Revolut SEK Main</option>
<option value="1934">1934 — Revolut EUR Main</option>
<option value="1935">1935 — Revolut USD Main</option>
</select>
</div>
<div class="form-group">
<label>Period</label>
<select id="rec-period">
<option value="2026-06" selected>2026-06</option>
<option value="2026-05">2026-05</option>
<option value="2026-04">2026-04</option>
</select>
</div>
<button class="btn btn-primary" id="btn-rec">Starta avstämning</button>
<div id="rec-result" style="margin-top:16px"></div>
</div>
</div>
`;
document.getElementById('btn-rec').addEventListener('click', async () => {
const account = document.getElementById('rec-account').value;
const period = document.getElementById('rec-period').value;
try {
const data = await app.api.post('/reconciliation/sessions', {
account_number: account,
period,
fiscal_year: 2026,
bank_balance: 0
});
if (data.ok) {
document.getElementById('rec-result').innerHTML = `
<div style="padding:16px;background:rgba(22,163,74,.05);border-radius:8px">
<strong>Avstämning skapad!</strong><br>
Session: ${data.session.id}<br>
Bank: ${data.session.bank_balance}<br>
Ledger: ${data.session.ledger_balance}<br>
Diff: ${data.session.difference}
</div>
`;
}
} catch (e) {
document.getElementById('rec-result').innerHTML =
`<p style="color:var(--red)">Fel: ${e.data?.error || e.message}</p>`;
}
});
},
fmt(n) {
return new Intl.NumberFormat('sv-SE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(n);
}
};