Files
leocrm/.a0/briefings/T02_briefing.md
T
leocrm-bot dd16940bb2 T02: companies + contacts + import/export + N:M + soft-delete + GDPR + FTS
- Company CRUD with soft-delete, FTS search (tsvector + GIN), filter, pagination
- Contact CRUD with N:M company linking via company_contacts
- CSV/XLSX export, CSV import with dry-run preview
- GDPR hard-delete with deletion_log
- Audit log on all mutations
- 27 new tests (24 ACs), 56 total tests pass
- Migration 0002: contacts, company_contacts, FTS search_tsv
- Fixed T01 tests: POST→201, PATCH→PUT compatibility
2026-06-29 08:02:15 +02:00

5.8 KiB

T02: Company + Contact + Import/Export System

Context

  • Project: LeoCRM (greenfield rewrite, Option C)
  • Repo: /a0/usr/workdir/dev-projects/leocrm
  • T01 COMPLETE: auth, multi-tenant, RBAC, sessions, audit, notifications all working
  • T01 commit: 7a7daf8 (pushed to Forgejo)
  • Tech: FastAPI + SQLAlchemy 2.0 async + PostgreSQL + Redis + Pydantic v2

Existing T01 Code to Build On

  • app/models/company.py (40 lines) — Company model skeleton, needs Contact + CompanyContact models
  • app/routes/companies.py (210 lines) — Company CRUD skeleton, needs expansion + Contact routes
  • app/schemas/company.py (23 lines) — Company schema, needs Contact schemas
  • app/core/db/__init__.py — Engine, Session, Base, TenantMixin, set_tenant_context
  • app/deps.py — get_current_user, require_admin, get_tenant_id
  • app/core/audit.py — log_audit function
  • app/core/notifications.py — create_notification
  • tests/conftest.py — Test fixtures with TRUNCATE CASCADE (DO NOT modify truncate list without checking table names)

Requirements (25)

F-COMP-01..08, F-CONT-01..07, F-DATA-01..04, F-MIG-01, F-CORE-06, F-CORE-11, F-CORE-13, F-SEARCH-01, F-TEST-01

Acceptance Criteria (24)

  1. GET /api/v1/companies → 200 + paginated (total/page/page_size)
  2. GET /api/v1/companies?search=Tech → 200 + FTS results (tsvector)
  3. GET /api/v1/companies?industry=IT&sort_by=name&sort_order=asc → 200 + filtered+sorted
  4. POST /api/v1/companies valid → 201 + company object
  5. POST /api/v1/companies missing name → 422
  6. GET /api/v1/companies/{id} → 200 + detail inkl. contacts array
  7. PUT /api/v1/companies/{id} → 200 + updated
  8. DELETE /api/v1/companies/{id} → 204, deleted_at gesetzt (soft-delete)
  9. DELETE /api/v1/companies/{id}?cascade=true → 204, company + links geloescht
  10. POST /api/v1/companies/{id}/contacts/{cid} → 200, N:M link
  11. DELETE /api/v1/companies/{id}/contacts/{cid} → 204, N:M unlink
  12. GET /api/v1/companies/export?format=csv → 200 + text/csv
  13. GET /api/v1/companies/export?format=xlsx → 200 + openxmlformats
  14. GET /api/v1/contacts → 200 + paginated
  15. POST /api/v1/contacts mit company_ids array → 201 + N:M links
  16. GET /api/v1/contacts/{id} → 200 + detail inkl. companies array
  17. PUT /api/v1/contacts/{id} → 200
  18. DELETE /api/v1/contacts/{id} → 204, soft-delete
  19. DELETE /api/v1/contacts/{id}?gdpr=true → 204, hard-delete + deletion_log
  20. POST /api/v1/import CSV + entity_type=companies → 200 + result
  21. POST /api/v1/import/preview CSV → 200 + dry-run (no DB changes)
  22. GET /api/v1/companies/{id}/emails → 200 (empty array, mail plugin inactive)
  23. Audit log entry on every company/contact mutation
  24. Soft-deleted company not in GET list (deleted_at IS NULL filter)

Files to Create/Modify

New Files:

  • app/models/contact.py — Contact model + CompanyContact (N:M join table)
  • app/schemas/contact.py — Contact schemas (create/update/read/list)
  • app/services/company_service.py — Company CRUD + search + filter + pagination
  • app/services/contact_service.py — Contact CRUD + N:M linking
  • app/services/import_export_service.py — CSV import/export, XLSX export, dry-run preview
  • app/routes/contacts.py — Contact CRUD + N:M endpoints
  • app/routes/import_export.py — Import/export endpoints
  • tests/test_companies.py — Company CRUD + search + filter + export tests
  • tests/test_contacts.py — Contact CRUD + N:M + GDPR delete tests
  • tests/test_import_export.py — CSV import + preview + export tests

Modify:

  • app/models/company.py — Add soft-delete (deleted_at), FTS tsvector, ensure TenantMixin
  • app/routes/companies.py — Expand to full CRUD + search + filter + export + N:M endpoints
  • app/schemas/company.py — Add pagination, search, filter schemas
  • app/models/__init__.py — Register Contact, CompanyContact
  • app/routes/__init__.py — Register contacts + import_export routers
  • app/schemas/__init__.py — Register contact schemas
  • app/services/__init__.py — Register new services
  • tests/conftest.py — Add contacts, company_contacts to TRUNCATE list
  • alembic/versions/ — New migration for contacts + company_contacts + FTS indexes

Dependencies to Install

  • openpyxl>=3.1 — XLSX export (add to requirements.txt)

Forbidden Patterns

  • NO JWT tokens (session-based auth from T01)
  • NO SQLite (PostgreSQL only)
  • NO wildcard CORS
  • NO raw SQL without tenant context (use set_config or ORM filtering)
  • NO hardcoded secrets
  • NO legacy code reuse
  • NO .test TLD emails (Pydantic v2 rejects — use .com)
  • NO SET LOCAL with bound params (use SELECT set_config() instead)
  • NO raising HTTPException in middleware (return JSONResponse)
  • NO POST without status_code=201

Test Spec

  • Commands: cd /a0/usr/workdir/dev-projects/leocrm && source venv/bin/activate && python -m pytest tests/test_companies.py tests/test_contacts.py tests/test_import_export.py -v --tb=short
  • Coverage: python -m pytest tests/test_companies.py tests/test_contacts.py tests/test_import_export.py --cov=app/routes/companies --cov=app/routes/contacts --cov=app/services --cov-report=term-missing
  • Target: 85% for new modules
  • All 24 ACs must pass

Token Rule

  • Use text_editor:read for MODIFY, text_editor:write for NEW, code_execution_tool:terminal for test runs
  • Reference files by path, not inline
  • Multi-file output: separate files, not one big file

JSON Tool Examples

Use this format for all tool calls:

{"tool_name":"text_editor","tool_args":{"action":"write","path":"/a0/usr/workdir/dev-projects/leocrm/app/models/contact.py","content":"..."}}
{"tool_name":"code_execution_tool","tool_args":{"runtime":"terminal","session":0,"code":"cd /a0/usr/workdir/dev-projects/leocrm && source venv/bin/activate && python -m pytest tests/test_companies.py -v --tb=short"}}