bae705aa97
- 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
216 lines
8.7 KiB
JavaScript
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);
|
|
}
|
|
};
|