236053cd7f
Kategoriserer danske regnskabs-/ERP-skills, ERP-data-afstemnings-
skill og to devops-workflows (gitea-issue-agent, kanban-workflows)
som Radix-medlemmers AI-agenter kan dele.
Struktur:
skills/<kategori>/<skill-navn>/SKILL.md
skills/<kategori>/<skill-navn>/{references,templates,scripts}/
Indekseret af index.json (genereret af scripts/rebuild_index.py).
Kør `python3 scripts/rebuild_index.py --check` i CI for at fange
synkroniseringsfejl.
5.8 KiB
5.8 KiB
Reconciliation Webapp Architecture (Django)
Reference architecture for building a webapp on top of a reconciliation engine. Based on Coop-Uniconta project (Radix), May 2026.
Stack
- Backend: Django 4.2 + Django REST Framework
- Database: MariaDB 11.4
- Cache/Queue: Redis + Celery (for background matching)
- Frontend: Django Templates + vanilla JS (no React needed for internal tools)
- Deployment: Docker Compose (web, db, redis)
- Auth: Django built-in + admin, later MS Entra/OIDC
Project Structure
backend/
config/
settings/base.py # Shared config
settings/local.py # Dev overrides (DEBUG=True, admin enabled)
urls.py # URL routing
wsgi.py / asgi.py # Entry points
celery.py # Celery app
apps/
core/ # Models: Transaction, MatchResult, InvoiceStatus, AuditLog, BonusRule
models.py
admin.py
matching.py # 5-phase match engine
import_/ # Excel parsers
parsers.py # Coop + Uniconta import with preview
reconciliation/ # Views: dashboard, import, matching, reports
views.py
templates/
templates/
base.html # Dark-themed layout (sidebar nav + main content)
static/
css/app.css # Dark theme: --bg-primary: #0f172a, --accent: #3b82f6
manage.py
entrypoint.sh # wait-for-db + migrate + runserver
Dockerfile # python:3.12-slim
requirements.txt
Data Model
AccountingYear (year, start_date, end_date)
SourceFile (source, file_name, file_path, row_count, parsed_count, error_count, accounting_year)
Transaction (source_file, source, year, month, date, amount, abs_amount,
doc_type, reference, faktura, text, text_normalized,
konteringstype, bilag, doc_number,
match_status, match_type, match_score, match_group,
original_data JSON)
MatchResult (group_id, match_type, score, status,
transaction_a, transaction_b, additional_b_ids JSON,
amount_diff, date_diff_days, explanation, comment,
approved_by, approved_at)
InvoiceStatus (faktura_number, accounting_year, invoice_total, paid_total, remaining,
status, invoice_date, first_invoice_text,
coop_payment_count, uniconta_payment_count, invoice_count)
AuditLog (action, user, match_result, details JSON)
BonusRule (name, year, period_start, period_end, conditions JSON, percentage, min_amount)
BonusCalculation (rule, transaction, calculated_amount, status)
Key Design Decisions
- Store original_data as JSON — Always preserve raw Excel row data for traceability.
- Separate
amountandabs_amount—amountkeeps original sign for net calculations;abs_amountfor matching. - MatchResult captures both sides — Primary match (transaction_a → transaction_b) + optional additional_b_ids for many-to-one.
- InvoiceStatus is computed, not stored per-transaction — Recalculated after each matching run.
- AuditLog for every manual action — Match approval, rejection, bonus adjustment.
Match Engine Integration
The match engine runs as a Celery task triggered from the web UI:
# apps/core/matching.py
AMT_TOL = Decimal("5.00")
DATE_TOL_REF = 21
DATE_TOL_GEN = 21
def run_matching(accounting_year=None):
# Phase 1: Reference match (RE↔Faktura, RG↔Kreditnota)
# Phase 2: ZV↔Faktura
# Phase 3: RE↔Betaling without reference
# Phase 4: General abs(amount)+date
# Phase 5: Many-to-one (sum of 2-3 Uni = 1 Coop)
# Bulk create MatchResult, bulk update Transaction statuses
UI Pages
| Page | Purpose |
|---|---|
| Dashboard | Year selector, stat cards, invoice status summary, monthly overview |
| Import | Upload Excel, show preview, map columns if auto-detection fails |
| Reconciliation List | Filterable table of all transactions, paginated (50/page) |
| Transaction Detail | Raw data, normalized data, potential matches, match history |
| Faktura Status | All invoices with BETALT/DELVIST/UBETALT, filterable |
| Reports | By type, by month, match summary, export to Excel |
| Run Match | Trigger background matching for selected year |
Dark Theme CSS Variables
:root {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--accent: #3b82f6;
--success: #22c55e;
--warning: #f59e0b;
--danger: #ef4444;
--border: #334155;
--radius: 8px;
}
Inspired by Radix-ERP visual style: sidebar navigation, card-based stats, data tables with badges, filter bars above tables.
Docker Compose
services:
web:
build: ./backend
command: ["web-dev"]
ports: ["8000:8000"]
depends_on:
db: {condition: service_healthy}
env_file: [.env]
db:
image: mariadb:11.4
environment:
MARIADB_DATABASE: ${DB_NAME}
MARIADB_USER: ${DB_USER}
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
redis:
image: redis:7-alpine
Deployment Notes
python manage.py migrateruns on container startup via entrypoint.shpython manage.py createsuperuserfor first admin login- Uploads stored in MEDIA_ROOT (mounted volume in production)
- Static files collected via
collectstaticfor production (nginx)
Next Steps for a New Reconciliation Project
- Copy project structure from template
- Adapt parsers for the specific Excel formats
- Run analysis script to determine match strategy
- Configure AMT_TOL and DATE_TOL based on data quality
- Build import UI with column mapping fallback
- Implement match engine phases iteratively
- Add manual match/approve/reject with audit log
- Add reports and bonus calculation framework