Initial import: 10 Radix skills across 3 categories
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.
This commit is contained in:
+167
@@ -0,0 +1,167 @@
|
||||
# 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
|
||||
|
||||
1. **Store original_data as JSON** — Always preserve raw Excel row data for traceability.
|
||||
2. **Separate `amount` and `abs_amount`** — `amount` keeps original sign for net calculations; `abs_amount` for matching.
|
||||
3. **MatchResult captures both sides** — Primary match (transaction_a → transaction_b) + optional additional_b_ids for many-to-one.
|
||||
4. **InvoiceStatus is computed, not stored per-transaction** — Recalculated after each matching run.
|
||||
5. **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:
|
||||
|
||||
```python
|
||||
# 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
|
||||
|
||||
```css
|
||||
: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
|
||||
|
||||
```yaml
|
||||
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 migrate` runs on container startup via entrypoint.sh
|
||||
- `python manage.py createsuperuser` for first admin login
|
||||
- Uploads stored in MEDIA_ROOT (mounted volume in production)
|
||||
- Static files collected via `collectstatic` for production (nginx)
|
||||
|
||||
## Next Steps for a New Reconciliation Project
|
||||
|
||||
1. Copy project structure from template
|
||||
2. Adapt parsers for the specific Excel formats
|
||||
3. Run analysis script to determine match strategy
|
||||
4. Configure AMT_TOL and DATE_TOL based on data quality
|
||||
5. Build import UI with column mapping fallback
|
||||
6. Implement match engine phases iteratively
|
||||
7. Add manual match/approve/reject with audit log
|
||||
8. Add reports and bonus calculation framework
|
||||
Reference in New Issue
Block a user