/** * AAMOS SIE4 Import Service * Port: 3251 */ import express from 'express'; import { randomUUID } from 'crypto'; import { parseSIE4 } from './parser.mjs'; const PORT = parseInt(process.env.PORT || '3251', 10); const LEDGER_BASE = process.env.LEDGER_URL || 'http://127.0.0.1:3250'; const app = express(); app.use(express.json({ limit: '10mb' })); // ── Health ─────────────────────────────────────────────────────────────────── app.get('/health', (_req, res) => { res.json({ ok: true, service: 'aamos-sie-import', port: PORT }); }); // ── SIE4 Import ────────────────────────────────────────────────────────────── app.post('/api/sie/import', async (req, res) => { const { sie_content, tenant_id, dry_run = false } = req.body ?? {}; if (!sie_content || typeof sie_content !== 'string') { return res.status(400).json({ ok: false, error: 'sie_content (string) is required' }); } const trace_id = randomUUID(); const correlation_id = randomUUID(); // ── Parse ──────────────────────────────────────────────────────────────── let parsed; try { parsed = parseSIE4(sie_content); } catch (err) { return res.status(400).json({ ok: false, error: `SIE4 parse error: ${err.message}`, trace_id, }); } const { journal_entries } = parsed; // ── Dry run ─────────────────────────────────────────────────────────────── if (dry_run) { return res.json({ ok: true, dry_run: true, imported_count: 0, skipped_count: journal_entries.length, parsed_entries: journal_entries.length, company: parsed.company, org_nr: parsed.org_nr, fiscal_year: parsed.fiscal_year, accounts_count: parsed.accounts.length, errors: [], trace_id, correlation_id, }); } // ── Live import ─────────────────────────────────────────────────────────── const errors = []; let imported_count = 0; let skipped_count = 0; const baseHeaders = { 'Content-Type': 'application/json', 'X-Tenant-Id': tenant_id || '', 'X-User-Id': 'sie-import', 'X-Trace-Id': trace_id, 'X-Correlation-Id': correlation_id, }; for (const entry of journal_entries) { const entryRef = `${entry.series}${entry.number}`; const journalBody = { date: entry.date, description: entry.description, source_type: 'import_sie', series: entry.series, series_number: entry.number, correlation_id, lines: entry.transactions.map(t => ({ account: t.account, amount: t.amount, date: t.date, description: t.description, })), }; try { // 1. Create journal entry const createResp = await fetch(`${LEDGER_BASE}/api/ledger/journal`, { method: 'POST', headers: baseHeaders, body: JSON.stringify(journalBody), }); if (!createResp.ok) { const text = await createResp.text().catch(() => createResp.status.toString()); errors.push({ entry: entryRef, step: 'create', error: text }); skipped_count++; continue; } const created = await createResp.json().catch(() => null); const journalId = created?.id ?? created?.journal_id ?? created?.data?.id ?? null; if (!journalId) { errors.push({ entry: entryRef, step: 'create', error: 'No journal ID in response' }); skipped_count++; continue; } // 2. Post / commit the journal entry const postResp = await fetch(`${LEDGER_BASE}/api/ledger/journal/${journalId}/post`, { method: 'POST', headers: baseHeaders, body: JSON.stringify({}), }); if (!postResp.ok) { const text = await postResp.text().catch(() => postResp.status.toString()); errors.push({ entry: entryRef, step: 'post', error: text }); skipped_count++; continue; } imported_count++; } catch (err) { errors.push({ entry: entryRef, step: 'network', error: err.message }); skipped_count++; } } res.json({ ok: errors.length === 0, imported_count, skipped_count, errors, trace_id, correlation_id, }); }); // ── Start ───────────────────────────────────────────────────────────────────── app.listen(PORT, '127.0.0.1', () => { console.log(`[aamos-sie-import] Listening on 127.0.0.1:${PORT}`); });