PHASE 8: CRM, Support & Accounting
Trigger: "Execute Phase 8 as per masterplan."
Goal: CRM with lead pipeline and deal tracking, support ticketing with SLA and knowledge base, and accounting with SKR03 chart of accounts, DATEV export, and period-close workflow. All three modules fully functional with admin UI.
Prerequisite: Phase 7 is completed.
Step 8.0 — Technical Conception
Create doc/developer/conception/phase-8-conception.md.
(reference: Chapter 05 — Feature Catalog (Packages 10, 11, 6.2), Chapter 11 — Data Model, Chapter 12 — Integration (DATEV))
Content: - Scope: membership-crm, membership-support, membership-accounting - CRM Design: Lead lifecycle, configurable pipeline stages, activity tracking, conversion analytics - Support Design: Ticket lifecycle (SLA-aware), auto-routing, knowledge base, CSAT measurement - Accounting Design: Double-entry bookkeeping, SKR03 mapping, DATEV CSV/XML format, period-close - Integration Points: CRM lead -> member conversion, billing event -> accounting entry, support ticket -> CRM activity - Design Chapter References: Ch05 (Packages 6.2, 10, 11), Ch11, Ch12 (DATEV section)
Result: Technical conception for Phase 8.
Step 8.1 — CRM Entities and Service
Create the membership-crm module.
(reference: Chapter 05 — Package 10, Chapter 11 — Data Model, Chapter 10 — API Design)
Approach:
- Create JPA entities:
Lead fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | Primary key |
| entityId | Long | FK NOT NULL | Organization |
| firstName | String(100) | NOT NULL | Contact first name |
| lastName | String(100) | NOT NULL | Contact last name |
| String(200) | NOT NULL | Contact email | |
| phone | String(50) | Phone (E.164) | |
| organizationName | String(200) | Company/club name | |
| source | String(30) | NOT NULL | WEBSITE / REFERRAL / WALK_IN / CAMPAIGN / IMPORT / PARTNER / COLD_OUTREACH |
| status | String(30) | default 'NEW' | NEW / CONTACTED / QUALIFIED / UNQUALIFIED / CONVERTED / LOST |
| assignedTo | Long | FK nullable | Assigned sales user |
| expectedValue | BigDecimal(19,4) | nullable | Expected monthly revenue |
| expectedTier | String(20) | nullable | STARTER / TEAM / PROFESSIONAL / ENTERPRISE |
| lostReason | String(500) | nullable | Why lead was lost |
| notes | String(4000) | Free-text notes | |
| customFields | JSONB | default '{}' | Extensible fields |
| lastContactedAt | Instant | nullable | Last outreach timestamp |
| nextFollowUpAt | Instant | nullable | Scheduled follow-up |
| createdAt | Instant | auto | |
| updatedAt | Instant | auto | |
| version | Long | @Version |
Deal fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| entityId | Long | FK NOT NULL | Organization |
| leadId | Long | FK NOT NULL | Source lead |
| title | String(200) | NOT NULL | Deal title (e.g., "Fitnessstudio XY — Team") |
| pipelineStage | String(50) | NOT NULL | Current pipeline stage (from PipelineConfig) |
| expectedValue | BigDecimal(19,4) | Expected monthly revenue | |
| probability | Integer | default 50 | Close probability 0-100% |
| expectedCloseDate | LocalDate | Expected close date | |
| actualCloseDate | LocalDate | nullable | Actual close date |
| wonLostReason | String(500) | nullable | Why deal was won/lost |
| contractId | Long | FK nullable | Created contract (after conversion) |
| createdAt | Instant | auto | |
| updatedAt | Instant | auto | |
| version | Long | @Version |
Activity fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| entityId | Long | FK NOT NULL | Organization |
| leadId | Long | FK nullable | Related lead |
| dealId | Long | FK nullable | Related deal |
| type | String(20) | NOT NULL | CALL / EMAIL / MEETING / NOTE / TASK / DEMO |
| description | String(4000) | NOT NULL | Activity content/notes |
| scheduledAt | Instant | nullable | Planned time |
| completedAt | Instant | nullable | Completion time |
| durationMinutes | Integer | nullable | Duration of call/meeting |
| createdBy | Long | FK NOT NULL | User who created |
| createdAt | Instant | auto |
PipelineConfig: id, entityId, stages (JSONB array of {name, order, color, autoActions})
2. Create CrmService:
- Lead management: CRUD, search, assign, bulk operations
- Lead source tracking with attribution
- Configurable pipeline: stages defined per entity via PipelineConfig:
- Default pipeline stages:
| Order | Stage | Color | Auto-Actions |
|:-----:|-------|-------|-------------|
| 1 | NEW | `#90CAF9` (blue) | Auto-assign to sales manager, send welcome email |
| 2 | CONTACTED | `#CE93D8` (purple) | — |
| 3 | DEMO_SCHEDULED | `#FFB74D` (orange) | Send calendar invite |
| 4 | DEMO_COMPLETED | `#FFF176` (yellow) | Create follow-up task (48h) |
| 5 | TRIAL | `#80CBC4` (teal) | Provision trial tenant, start drip sequence |
| 6 | PROPOSAL_SENT | `#EF9A9A` (red) | Create follow-up task (7d) |
| 7 | NEGOTIATION | `#A5D6A7` (green) | — |
| 8 | WON | `#4CAF50` (dark green) | Convert to member, create contract |
| 9 | LOST | `#9E9E9E` (grey) | Record reason, schedule win-back (90d) |
- Admin can add/remove/reorder stages and customize auto-actions per stage
- Deal tracking: expected value, close date, probability (default 10% at NEW → 90% at PROPOSAL_SENT)
- Activity log: calls, emails, meetings, notes (linked to lead or deal)
- Convert lead to member:
convertToMember(Long leadId)creates Member + User from Lead data, copies name/email/phone, marks lead as CONVERTED - Trial-to-member conversion tracking: funnel metrics per stage
- Automated follow-up reminders (RabbitMQ: scheduled messages based on
nextFollowUpAt) 3. Create tests:CrmServiceTest
Result: CRM entities and business logic.
Step 8.2 — CRM Controller and Admin UI
Approach:
- Create controller:
-
POST /api/crm/leads— Create lead -GET /api/crm/leads— Search leads (paginated, filterable) -GET /api/crm/leads/{id}— Get lead details -PUT /api/crm/leads/{id}— Update lead -POST /api/crm/leads/{id}/convert— Convert to member -POST /api/crm/deals— Create deal -GET /api/crm/deals— Search deals -PUT /api/crm/deals/{id}— Update deal (move pipeline stage) -POST /api/crm/activities— Create activity -GET /api/crm/activities?leadId={id}— Activity timeline -GET /api/crm/pipeline/config— Get pipeline configuration -PUT /api/crm/pipeline/config— Update pipeline configuration -GET /api/crm/reports/conversion— Conversion rates per stage (funnel: % drop-off at each stage) -GET /api/crm/reports/pipeline— Pipeline analytics:- Total pipeline value (sum of deal expectedValue * probability)
- Average deal velocity (days from NEW to WON)
- Win rate (WON / (WON + LOST) per period)
- Lead source effectiveness (leads, conversions, revenue per source)
GET /api/crm/reports/forecast?months={n}— Revenue forecast based on pipeline probabilityGET /api/crm/reports/activity?userId={id}&from={}&to={}— Activity report per sales user
- Create Flyway:
V800__create_crm.sql - Create admin UI:
- Lead list: DataTable with columns: name, email, source (icon), status (chip), assigned to, expected value, next follow-up, created date. Filters: status, source, assigned user, date range. Bulk actions: assign, change status, export CSV.
- Pipeline board: Kanban-style drag-and-drop (stages as columns, deals as cards). Card shows: title, expected value, probability %, expected close date, days in stage. Drag deal to move stage (auto-updates via API). Color-coded by deal value tier.
- Lead/deal detail page:
- Header: name, status badge, assigned user avatar, source icon
- Left panel: contact info, organization name, expected value, tier
- Center panel: Activity timeline (reverse chronological) with type icons (phone, email, meeting, note)
- Right panel: Quick actions — add note, schedule call, send email, convert to member
- Bottom: linked deals (for lead), contract info (after conversion)
- Reports page (4 charts):
- Funnel chart: leads per stage, conversion % between stages
- Pipeline value chart: total value per stage (bar chart)
- Source distribution: pie chart of leads by source
- Velocity chart: average days in each stage (bar chart)
- Tests: controller tests, pipeline stage transition tests
Result: CRM with pipeline board, lead management, and analytics.
Step 8.3 — Support Entities and Service
Create the membership-support module.
(reference: Chapter 05 — Package 11, Chapter 11 — Data Model)
Approach:
- Create JPA entities:
Ticket fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| entityId | Long | FK NOT NULL | Organization |
| ticketNumber | String(20) | UNIQUE per entity | Auto-generated: T-{5-digit-seq} |
| memberId | Long | FK nullable | Reporting member (null if internal) |
| subject | String(300) | NOT NULL | Ticket title |
| description | String(10000) | NOT NULL | Detailed description |
| category | String(30) | NOT NULL | BUG_REPORT / FEATURE_REQUEST / BILLING_QUESTION / ONBOARDING_HELP / GENERAL / ACCESS_ISSUE |
| priority | String(20) | default 'P3_MEDIUM' | P1_CRITICAL / P2_HIGH / P3_MEDIUM / P4_LOW |
| status | String(30) | default 'OPEN' | OPEN / IN_PROGRESS / WAITING_CUSTOMER / RESOLVED / CLOSED |
| assignedTo | Long | FK nullable | Assigned support agent |
| slaDeadline | Instant | nullable | Calculated from priority + tier SLA config |
| slaBreach | boolean | default false | True if slaDeadline passed while not resolved |
| resolvedAt | Instant | nullable | Resolution timestamp |
| closedAt | Instant | nullable | Close timestamp |
| csatScore | Integer | nullable | Customer satisfaction 1-5 (after resolution) |
| csatComment | String(2000) | nullable | CSAT free-text feedback |
| tags | JSONB | default '[]' | Searchable tags |
| createdAt | Instant | auto | |
| updatedAt | Instant | auto | |
| version | Long | @Version |
TicketComment fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| ticketId | Long | FK NOT NULL | Parent ticket |
| authorId | Long | FK NOT NULL | Comment author |
| content | String(10000) | NOT NULL | Comment text (Markdown) |
| isInternal | boolean | default false | True = visible to agents only, hidden from member |
| createdAt | Instant | auto |
KnowledgeBaseArticle fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| entityId | Long | FK nullable | Null = global (Membership One), set = per-entity |
| title | String(300) | NOT NULL | Article title |
| slug | String(200) | UNIQUE | URL-safe identifier |
| content | String | NOT NULL | Full article (Markdown) |
| category | String(50) | NOT NULL | GETTING_STARTED / BILLING / MEMBERS / CHECK_IN / COURSES / INTEGRATIONS / TROUBLESHOOTING |
| tags | JSONB | default '[]' | Searchable tags |
| status | String(20) | default 'DRAFT' | DRAFT / PUBLISHED / ARCHIVED |
| publishedAt | Instant | nullable | Publication timestamp |
| viewCount | Long | default 0 | View counter (incremented on read) |
| helpfulCount | Long | default 0 | "Was this helpful?" yes counter |
| createdAt | Instant | auto | |
| updatedAt | Instant | auto | |
| version | Long | @Version | |
2. Create SupportService: |
|||
| - Ticket lifecycle: create, assign, comment, resolve, close, reopen | |||
| - SLA configuration per pricing tier: |
| Tier | P1 (Critical) | P2 (High) | P3 (Medium) | P4 (Low) | Business Hours |
|------|:-------------:|:---------:|:-----------:|:--------:|:--------------|
| Starter | — | — | — | — | Community only (KB self-service) |
| Team | 24h | 48h | 72h | 5 days | Mon-Fri 09:00-17:00 CET |
| Professional | 4h | 24h | 48h | 72h | Mon-Fri 08:00-20:00 CET |
| Enterprise | 1h | 4h | 24h | 48h | Mon-Sun 07:00-22:00 CET |
- SLA deadline calculation (business hours, timezone-aware, excludes German public holidays)
- SLA breach detection: scheduled job every 15 min checks tickets approaching deadline, sets
slaBreach = trueand sends alert to assigned agent + team lead - Auto-routing: assign by category and entity location
- Canned responses / macros (stored as
CommunicationTemplatewith type SUPPORT_MACRO) - Internal notes (visible to agents only)
- CSAT survey: auto-send on resolution, 1-5 scale
3. Create
KnowledgeBaseService: - Article CRUD, Markdown rendering, full-text search
- Categories and tags, view count tracking
- Public access (no auth required for published articles)
4. Create tests:
SupportServiceTest,KnowledgeBaseServiceTest
Result: Support ticketing with SLA and knowledge base.
Step 8.4 — Support Controller and Admin UI
Approach:
- Create controller:
-
POST /api/support/tickets— Create ticket -GET /api/support/tickets— Search tickets (paginated) -GET /api/support/tickets/{id}— Get ticket with comments -PUT /api/support/tickets/{id}— Update ticket (status, assignment, priority) -POST /api/support/tickets/{id}/comments— Add comment -POST /api/support/tickets/{id}/resolve— Resolve ticket -POST /api/support/tickets/{id}/csat— Submit CSAT score -GET /api/support/kb/articles— Search knowledge base -POST /api/support/kb/articles— Create article -PUT /api/support/kb/articles/{id}— Update article -GET /api/support/sla/config— Get SLA configuration -PUT /api/support/sla/config— Update SLA configuration -GET /api/support/reports/sla— SLA compliance report -GET /api/support/reports/csat— CSAT report - Create Flyway:
V801__create_support.sql - Create admin UI:
- Ticket queue: DataTable with columns: ticket#, subject, priority (color-coded: P1=red, P2=orange, P3=yellow, P4=grey), status, member name, assigned to, SLA countdown timer (green→yellow→red), created date. Filters: status, priority, assigned to, category. Sort by: priority (default), SLA deadline, created date. Real-time update via polling (30s).
- Ticket detail page:
- Header: ticket#, subject, priority badge, status badge, SLA countdown
- Left panel: member info (name, email, tier, last login), ticket metadata (category, created, assigned)
- Center panel: conversation thread (alternating left/right bubbles for member/agent). Internal notes: toggle "Show internal" → yellow background notes visible to agents only
- Right panel: Quick actions — assign to me, change priority, add canned response (dropdown), link to KB article, resolve, close
- Bottom: related tickets (by same member), similar KB articles (keyword match)
- KB editor: Markdown editor with live preview (side-by-side), category picker, tag input, SEO slug auto-generation, publish/draft toggle. Article list with view count, helpful count, last updated.
- Reports (4 charts):
- SLA compliance: % of tickets resolved within SLA, per priority, per month (target: >95%)
- CSAT trend: average score per month (line chart), score distribution (1-5 histogram)
- Ticket volume: by category (pie), by month (bar), by day of week (heatmap)
- Resolution time: average time to resolve per priority, per agent
- Member app: create ticket (from profile → "Help" button), view own tickets with status, read knowledge base (searchable, categorized)
Result: Support system with ticketing, SLA, knowledge base, and CSAT.
Step 8.5 — Accounting Entities and Service
Create the membership-accounting module.
(reference: Chapter 05 — Package 6.2, Chapter 11 — Data Model, Chapter 12 — DATEV)
Approach:
- Create JPA entities:
AccountingEntry fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| entityId | Long | FK NOT NULL | Organization |
| entryNumber | String(20) | UNIQUE per entity | Sequential: B-{YYYY}-{5-digit-seq} |
| entryDate | LocalDate | NOT NULL | Booking date |
| accountCode | String(10) | FK NOT NULL | Debit account (from ChartOfAccount) |
| counterAccountCode | String(10) | FK NOT NULL | Credit account |
| amount | BigDecimal(19,4) | NOT NULL | Posting amount (always positive) |
| currency | String(3) | default 'EUR' | ISO 4217 |
| taxRate | BigDecimal(5,2) | nullable | VAT rate if applicable |
| taxAmount | BigDecimal(19,4) | nullable | VAT amount |
| description | String(500) | NOT NULL | Booking text (Buchungstext) |
| reference | String(100) | nullable | Source reference (e.g., transaction ID, invoice number) |
| referenceType | String(30) | nullable | TRANSACTION / INVOICE / MANUAL / STORNO |
| costCenterId | Long | FK nullable | Cost center assignment |
| periodYear | Integer | NOT NULL | Booking year (for period close) |
| periodMonth | Integer | NOT NULL | Booking month (1-12) |
| periodClosed | boolean | default false | True if period is closed (immutable) |
| createdBy | Long | FK NOT NULL | User who created entry |
| createdAt | Instant | auto | |
| version | Long | @Version |
CostCenter fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| entityId | Long | FK NOT NULL | Organization |
| code | String(20) | UNIQUE per entity | Cost center code (e.g., "KST-100") |
| name | String(200) | NOT NULL | Display name (e.g., "Fitnessstudio Hauptgebäude") |
| description | String(500) | Purpose description | |
| active | boolean | default true | Active flag |
| createdAt | Instant | auto | |
| version | Long | @Version |
(ChartOfAccount already in seed data from Phase 0)
2. Create AccountingService:
- General ledger: double-entry bookkeeping (every entry creates debit + credit pair)
- Automated posting from billing events (via RabbitMQ listener on billing.* events):
| Business Event | Debit Account | Credit Account | Description |
|----------------|:------------:|:--------------:|-------------|
| Invoice created (membership) | 1410 Forderungen Mitgliedsbeiträge | 4400 Erlöse Mitgliedsbeiträge | "Mitgliedsbeitrag {month} {memberName}" |
| Invoice created (course) | 1450 Forderungen Kurse | 4401 Erlöse Kurse/Events | "Kurs: {courseName}" |
| Invoice created (shop) | 1460 Forderungen Shop | 4402 Erlöse Shop | "Shop-Bestellung {orderNumber}" |
| SEPA payment received | 1200 Bank | 1410/1450/1460 Forderungen | "SEPA-Einzug {transactionRef}" |
| Storno (full) | 4410 Erlösschmälerungen | 1410 Forderungen | "Storno: {invoiceNumber}" |
| Storno (partial) | 4410 Erlösschmälerungen | 1410 Forderungen | "Teilstorno: {invoiceNumber} ({amount})" |
| SEPA rejection (Rücklastschrift) | 1510 Forderungen Rücklastschriften | 1200 Bank | "Rücklastschrift: {memberName}" |
| Rücklastschrift fee | 6820 Rücklastschrift-Kosten | 1200 Bank | "Gebühr Rücklastschrift" |
| Payment provider fee | 6815 Provider-Gebühren | 1200 Bank | "Transaktionsgebühr {provider}" |
| Fremdbeträge pass-through | 1710 Verbindlichkeiten Fremdbeträge | 4440 Durchlaufposten | "Fremdbeträge {entityName}" |
Each posting creates exactly 2 `AccountingEntry` rows (debit + credit, same amount) — enforced by `AccountingService.createDoubleEntry()`.
- Cost center management: CRUD, assign to entries, default cost center per organization
- Period close: lock all entries for a month (
periodClosed = true), prevent further modifications, require all entries posted - Financial reports:
- P&L (GuV): Revenue accounts (4xxx) minus expense accounts (6xxx/7xxx), grouped by account
- Balance by account: Opening + movements + closing per account
- Cash flow: Bank account movements, grouped by week/month
- Receivables aging: Unpaid invoices grouped by age (0-30d, 30-60d, 60-90d, >90d)
3. Create tests:
AccountingServiceTest
Result: Accounting with double-entry bookkeeping and SKR03.
Step 8.6 — DATEV Export and Reconciliation
(reference: Chapter 12 — Integration (DATEV section))
Approach:
-
Create
DatevExportService: - DATEV CSV format (Buchungsstapel, per DATEV specification):- Header record (row 1):
"EXTF";700;21;"Buchungsstapel";13;... - Format version: 700
- Data category: 21 (Buchungsstapel)
- Format name: "Buchungsstapel"
- Berater-Nr (tax consultant number, configurable)
- Mandanten-Nr (client number = entity ID)
- WJ-Beginn (fiscal year start, default 01/01)
- Sachkontenlange (4 for SKR03)
- Datum-von / Datum-bis (export period)
- Data rows (row 2+):
Column DATEV Field Source Example 1 Umsatz (S/H) amount29.00 2 S/H-Kennzeichen derived S(Soll/debit) orH(Haben/credit)3 WKZ Umsatz currencyEUR 7 Konto accountCode1400 8 Gegenkonto counterAccountCode4400 9 BU-Schlüssel derived from taxRate 3 (19% USt) or 2 (7% USt) 10 Belegdatum entryDate2202 (DDMM) 11 Belegfeld 1 entryNumberB-2026-00042 13 Buchungstext description"Mitgliedsbeitrag Jan 2026" 14 Kostenstelle 1 costCenter.codeKST-100 - Header record (row 1):
- DATEV XML format (optional, for Unternehmen Online): implement only if customer demand exists
- Export filter: date range, cost center, account range
- Validation: all entries in period must be posted, no gaps in numbering
- File naming:
EXTF_Buchungsstapel_{entityId}_{YYYY-MM}.csv2. CreateReconciliationService: - Compare accounting entries with Cash360 transaction data
- Identify discrepancies: missing entries, amount mismatches
-
Reconciliation algorithm (per month):
- Fetch all Cash360 transactions for entity + period from Cash360 API
- Fetch all AccountingEntry records for entity + period from local DB
- Match by: transactionId (reference field), amount, date (±1 day tolerance)
-
Categorize results:
Category Description Action MATCHED Transaction + entry exist, amounts equal No action AMOUNT_MISMATCH Both exist, amounts differ Flag for manual review MISSING_ENTRY Transaction exists, no accounting entry Auto-create suggested entry ORPHAN_ENTRY Accounting entry exists, no transaction Flag for investigation TIMING_DIFFERENCE Transaction in period N, entry in period N+1 Informational warning -
Generate reconciliation report with summary totals per category - Reconciliation report: matched count, unmatched count, suggested corrections, total variance
- Create controller:
-
GET /api/accounting/entries— Search accounting entries -POST /api/accounting/entries— Create manual entry -GET /api/accounting/cost-centers— List cost centers -POST /api/accounting/cost-centers— Create cost center -GET /api/accounting/chart-of-accounts— List accounts -POST /api/accounting/datev/export?from={date}&to={date}— Generate DATEV export -GET /api/accounting/datev/export/{id}/download— Download DATEV file -POST /api/accounting/period/close?month={YYYY-MM}— Close accounting period -GET /api/accounting/reconciliation?month={YYYY-MM}— Run reconciliation -GET /api/accounting/reports/pnl?from={date}&to={date}— P&L report (GuV) -GET /api/accounting/reports/balance?date={date}— Balance by account - Create Flyway:
V802__create_accounting.sql - Create admin UI: - Ledger view: DataTable with account, date, amount, description, cost center - DATEV export: Date range picker, download button, export history - Period close: Month selector, close button, status indicator - Reports: P&L chart, balance table, reconciliation differences
Result: Accounting with DATEV export, reconciliation, and period-close.
Step 8.7 — Phase 8 Documentation
- Update
doc/enduser/admin-manual.md(CRM, support, accounting sections) - Update
doc/enduser/consumer-manual.md(support tickets, knowledge base) - Update
doc/developer/backend-guide.md(CRM, support, accounting architecture) - Update
doc/developer/api-reference.md(new endpoints)
Step 8.8 — Update Intranet
Run python doc/intranet/build.py.
Phase 8 — Quality Gate
| # | Check | Target |
|---|---|---|
| 1 | Conception | exists |
| 2 | Compilation + tests | 0 errors, 0 failures |
| 3 | Coverage | >= 70% |
| 4 | CRM | create lead -> advance pipeline -> convert to member |
| 5 | Pipeline board | Kanban drag-and-drop works |
| 6 | Support ticket | create -> assign -> comment -> resolve -> CSAT survey |
| 7 | SLA | deadline calculated correctly per tier |
| 8 | Knowledge base | create article -> search -> view count |
| 9 | Accounting | billing event creates ledger entries (double-entry) |
| 10 | DATEV export | produces valid CSV with correct header |
| 11 | Period close | locks entries, prevents modification |
| 12 | Reconciliation | identifies discrepancies |
| 13 | Documentation + intranet | updated |
| 14 | CLAUDE.md | updated |
Report: "Phase 8 completed."