# 04 — Business Logic Flow Charts

**Project:** CannaManage — B2B SaaS for German Cannabis Social Clubs (Anbauvereinigungen)  
**Phase:** 2 of 5 — Architecture & Data Model  
**Last updated:** 2026-04-06

All flows are implemented in the Spring Boot service layer. Mermaid `flowchart TD` syntax.

---

## Flow 1: Distribution Recording

Records a cannabis distribution to a member. This is the most compliance-critical path in the system. Every step that can fail returns a user-facing error with actionable detail (remaining quota, batch status, etc.).

```mermaid
flowchart TD
    START([🟢 Admin clicks\n'Record Distribution']) --> SEL_MEMBER[Select member from list]
    SEL_MEMBER --> LOAD_MEMBER[Load member profile\nfrom MemberRepository]
    LOAD_MEMBER --> CHECK_ACTIVE{Member status\n= ACTIVE?}

    CHECK_ACTIVE -->|No — SUSPENDED\nor EXPELLED| ERR_MEMBER[❌ Error: Member not eligible\nShow status reason]
    CHECK_ACTIVE -->|Yes| CHECK_AGE{is_under_21\n= true?}

    CHECK_AGE -->|Under 21| MAX_MONTHLY_30[Monthly limit = 30g]
    CHECK_AGE -->|Adult ≥ 21| MAX_MONTHLY_50[Monthly limit = 50g]

    MAX_MONTHLY_30 --> ENTER_QTY[Admin enters quantity\nin grams]
    MAX_MONTHLY_50 --> ENTER_QTY

    ENTER_QTY --> VALIDATE_QTY{quantity > 0\nand ≤ 25g?}
    VALIDATE_QTY -->|No| ERR_QTY[❌ Error: Invalid quantity\nDaily max is 25g per visit]
    VALIDATE_QTY -->|Yes| CHECK_DAILY[ComplianceService:\nSum distributions today\nfor this member]

    CHECK_DAILY --> DAILY_OK{today_total +\nquantity ≤ 25g?}
    DAILY_OK -->|No| ERR_DAILY[❌ Error: Daily limit exceeded\nShow remaining today]
    DAILY_OK -->|Yes| CHECK_MONTHLY[ComplianceService:\nLoad MonthlyQuota\ncurrent month]

    CHECK_MONTHLY --> MONTHLY_OK{monthly_total +\nquantity ≤ max_allowed?}
    MONTHLY_OK -->|No| ERR_MONTHLY[❌ Error: Monthly quota exceeded\nShow remaining this month\nand reset date]
    MONTHLY_OK -->|Yes| SEL_BATCH[Admin selects batch]

    SEL_BATCH --> LOAD_BATCH[Load batch from\nBatchRepository]
    LOAD_BATCH --> CHECK_BATCH{Batch status\n= AVAILABLE?}
    CHECK_BATCH -->|RECALLED| ERR_RECALLED[❌ Error: Batch recalled\nSelect a different batch]
    CHECK_BATCH -->|EXHAUSTED| ERR_EXHAUSTED[❌ Error: Batch exhausted\nNo stock remaining]
    CHECK_BATCH -->|AVAILABLE| CHECK_STOCK{batch.quantity_grams\n≥ requested quantity?}

    CHECK_STOCK -->|No| ERR_STOCK[❌ Error: Insufficient stock\nShow available quantity]
    CHECK_STOCK -->|Yes| CONFIRM[Admin reviews and confirms\ndistribution details]

    CONFIRM --> SAVE_DIST["💾 Save Distribution record\n(immutable = true,\nrecorded_by = currentUser)"]
    SAVE_DIST --> UPD_QUOTA["💾 UPDATE MonthlyQuota\ntotal_distributed += quantity\n(@Version optimistic lock)"]
    UPD_QUOTA --> UPD_STOCK["💾 INSERT StockMovement\n(type = OUT, batch_id, qty)"]
    UPD_STOCK --> UPD_BATCH["💾 UPDATE Batch\nquantity_grams -= quantity\n(if = 0 → status = EXHAUSTED)"]
    UPD_BATCH --> SUCCESS([✅ Success\nShow confirmation\nwith updated quota display])
```

---

## Flow 2: Member Registration

Registers a new member in the club. Includes DSGVO consent, age validation, under-21 flag assignment, and automatic portal account creation.

```mermaid
flowchart TD
    START([🟢 Admin opens\n'Add Member' form]) --> ENTER_DATA[Admin enters member data:\nfirst/last name, email,\ndate of birth, address]

    ENTER_DATA --> VALIDATE_EMAIL{Email unique\nin this club?}
    VALIDATE_EMAIL -->|Already exists| ERR_EMAIL[❌ Error: Email already\nregistered in this club]
    VALIDATE_EMAIL -->|Unique| VALIDATE_AGE{Age ≥ 18?}

    VALIDATE_AGE -->|Under 18| ERR_AGE[❌ Error: Member must be\nat least 18 years old\n§ 10 KCanG]
    VALIDATE_AGE -->|18 or older| CHECK_UNDER21{18 ≤ age < 21?}

    CHECK_UNDER21 -->|Yes| SET_FLAG_TRUE["Set is_under_21 = true\nMonthly limit will be 30g"]
    CHECK_UNDER21 -->|No, ≥ 21| SET_FLAG_FALSE["Set is_under_21 = false\nMonthly limit will be 50g"]

    SET_FLAG_TRUE --> CHECK_CAPACITY[Check Club.max_members\nvs current member count]
    SET_FLAG_FALSE --> CHECK_CAPACITY

    CHECK_CAPACITY --> CAPACITY_OK{Club has\nfree capacity?}
    CAPACITY_OK -->|No| ERR_CAPACITY[❌ Error: Club at max capacity\nCannot register more members]
    CAPACITY_OK -->|Yes| GEN_NUMBER["Generate membership_number\n(club prefix + sequential ID)"]

    GEN_NUMBER --> DSGVO[Show DSGVO consent dialog:\n• Data usage explanation\n• Right to erasure\n• Admin must confirm consent obtained]
    DSGVO --> DSGVO_OK{Admin confirms\nconsent obtained?}
    DSGVO_OK -->|No| ABORT([🔴 Abort — member\ncannot be registered\nwithout DSGVO consent])
    DSGVO_OK -->|Yes| SAVE_MEMBER["💾 Save Member\n(status = ACTIVE,\nmembership_date = today)"]

    SAVE_MEMBER --> CREATE_USER["💾 Create User account\n(role = ROLE_MEMBER,\ngenerate temp password)"]
    CREATE_USER --> SEND_EMAIL["📧 Send welcome email:\n• Membership number\n• Temp login credentials\n• Portal URL\n• DSGVO information sheet PDF"]
    SEND_EMAIL --> SUCCESS([✅ Member registered\nShow member profile\nwith membership number])
```

---

## Flow 3: Contamination Batch Recall

Handles the recall of a contaminated batch. This flow is time-critical — speed of notification is essential for member safety. All affected distributions are identified and the prevention officer is notified.

```mermaid
flowchart TD
    START([🟢 Admin selects batch\nand clicks 'Flag Recall']) --> CONFIRM_RECALL{Confirm recall\nof batch?\nThis cannot be undone.}

    CONFIRM_RECALL -->|Cancel| CANCEL([🔴 Cancelled — batch\nstatus unchanged])
    CONFIRM_RECALL -->|Confirm| QUERY_DIST["🔍 Query all Distributions\nWHERE batch_id = :batchId\n(across all members)"]

    QUERY_DIST --> HAS_DIST{Any distributions\nfound?}

    HAS_DIST -->|No distributions| NO_DIST["⚠️ Batch was never distributed\n(still flag as RECALLED\nfor inventory integrity)"]
    HAS_DIST -->|Yes| BUILD_LIST["Build affected member list:\n• member name\n• distribution date\n• quantity received\n• contact email"]

    NO_DIST --> FLAG_BATCH
    BUILD_LIST --> SHOW_LIST[Show affected member list\nto admin for review]

    SHOW_LIST --> ADMIN_REVIEW{Admin reviews\nand confirms recall?}
    ADMIN_REVIEW -->|Cancel| CANCEL
    ADMIN_REVIEW -->|Proceed| FLAG_BATCH["💾 UPDATE Batch\nstatus = RECALLED\ncontamination_flag = true"]

    FLAG_BATCH --> LOG_MOVEMENT["💾 INSERT StockMovement\n(type = RECALL,\nbatch_id, reason)"]
    LOG_MOVEMENT --> EXPORT_LIST["📄 Generate export:\n• CSV: affected_members_recall_{batchCode}.csv\n• PDF: recall_report_{batchCode}.pdf\n(via iText 7)"]

    EXPORT_LIST --> NOTIFY_OFFICER["📧 Email Prevention Officer:\n• Batch code and details\n• Affected member count\n• Attached CSV/PDF"]
    NOTIFY_OFFICER --> AUDIT_LOG["💾 INSERT AuditLog\n(action = BATCH_RECALL,\nperformedBy, timestamp)"]
    AUDIT_LOG --> SUCCESS([✅ Recall complete\nOffer download of\nexport files])
```

---

## Flow 4: Compliance Report Generation

Generates the monthly compliance report required by § 22 KCanG. Covers all distributions within a calendar month, with per-member quota analysis and club metadata for regulatory submission.

```mermaid
flowchart TD
    START([🟢 Admin opens\nReports section]) --> SELECT_PERIOD[Admin selects\nmonth and year]

    SELECT_PERIOD --> VALIDATE_PERIOD{Period in the\npast or current\nmonth?}
    VALIDATE_PERIOD -->|Future month| ERR_FUTURE[❌ Error: Cannot generate\nreport for future periods]
    VALIDATE_PERIOD -->|Valid| LOAD_CLUB[Load Club metadata:\nlicense number,\nprevention officer name]

    LOAD_CLUB --> QUERY_DIST["🔍 ReportService:\nSELECT * FROM distributions\nWHERE month = :month\nAND year = :year\nAND tenant_id = :tenantId"]

    QUERY_DIST --> HAS_DATA{Any distributions\nin this period?}

    HAS_DATA -->|No data| EMPTY_REPORT[Generate empty report\nwith zero totals\n(still valid compliance submission)]
    HAS_DATA -->|Yes| AGG_MEMBER["Aggregate by member:\n• total_distributed_grams\n• number_of_visits\n• quota_usage_percent\n• is_under_21 flag"]

    EMPTY_REPORT --> AGG_STRAIN
    AGG_MEMBER --> AGG_STRAIN["Aggregate by strain/batch:\n• strain name, THC%, CBD%\n• quantity distributed\n• batch codes used"]

    AGG_STRAIN --> ADD_METADATA["Add club metadata:\n• Club name + license number\n• Prevention officer name\n• Report generation timestamp\n• Total members active in period"]

    ADD_METADATA --> RENDER_PDF["📄 iText 7:\nRender PDF report\n• Cover page with club details\n• Summary table\n• Per-member breakdown\n• Strain/batch appendix"]

    RENDER_PDF --> RENDER_CSV["📊 Generate CSV:\n• One row per distribution\n• member_id, name, date,\n  quantity, strain, batch_code"]

    RENDER_CSV --> STORE_FILES["💾 Store generated files\ntemporarily in server /tmp\n(TTL: 1 hour)"]

    STORE_FILES --> SUCCESS([✅ Report ready\nOffer download:\n📄 PDF  📊 CSV])
```

---

## Flow 5: Member Login & Quota Display

The member portal entry flow. Members log in to view their current monthly quota, remaining allowance, and recent distribution history. This is a read-only portal — members cannot modify any data.

```mermaid
flowchart TD
    START([🟢 Member navigates\nto member portal URL]) --> SHOW_LOGIN[Show login form:\nemail + password]

    SHOW_LOGIN --> SUBMIT[Member submits credentials]
    SUBMIT --> FIND_USER["🔍 Spring Security:\nSELECT FROM users\nWHERE email = ?\nAND active = true"]

    FIND_USER --> USER_FOUND{User found?}
    USER_FOUND -->|No| ERR_NOTFOUND[❌ Invalid credentials\n(generic — do not reveal\nwhether email exists)]
    USER_FOUND -->|Yes| VERIFY_PW{BCrypt.verify\n(password, hash)\nmatches?}

    VERIFY_PW -->|No| ERR_PW[❌ Invalid credentials]
    VERIFY_PW -->|Yes| CHECK_MEMBER{User has\nmember_id set?}

    CHECK_MEMBER -->|No — admin account| ERR_NOTMEMBER[❌ Error: Use admin portal\nfor admin accounts]
    CHECK_MEMBER -->|Yes| ISSUE_JWT["🔑 Issue JWT:\n• role = ROLE_MEMBER\n• tenantId = user.tenantId\n• memberId = user.memberId\n• expiry = 8h"]

    ISSUE_JWT --> UPDATE_LOGIN["💾 UPDATE users\nlast_login = NOW()"]
    UPDATE_LOGIN --> LOAD_PORTAL["Load member portal\n(JSF view or SPA)"]

    LOAD_PORTAL --> CALL_QUOTA["📡 GET /api/v1/members/me/quota\n(JWT in Authorization header)"]
    CALL_QUOTA --> FETCH_QUOTA["🔍 QuotaController:\nLoad MonthlyQuota\nfor current month\n(create if not exists)"]

    FETCH_QUOTA --> CALC_REMAINING{Quota record\nexists?}
    CALC_REMAINING -->|No — new month| CREATE_QUOTA["Create MonthlyQuota row:\ntotal_distributed = 0\nmax_allowed = 30g or 50g"]
    CALC_REMAINING -->|Yes| RETURN_QUOTA["Return QuotaStatus:\n• totalAllowed\n• totalUsed\n• remaining\n• percentUsed"]

    CREATE_QUOTA --> RETURN_QUOTA

    RETURN_QUOTA --> DISPLAY_PROGRESS["Display quota progress bar:\n🟩🟩🟩⬜⬜  e.g. 15g of 50g used\nColor: green < 60% / yellow < 85% / red ≥ 85%"]

    DISPLAY_PROGRESS --> CALL_HISTORY["📡 GET /api/v1/distributions\n?memberId=me&limit=10\n&sort=distributed_at,desc"]
    CALL_HISTORY --> DISPLAY_HISTORY["Display last 10 distributions:\n• Date, quantity, strain name\n• Batch code\n• Recorded by (staff name)"]

    DISPLAY_HISTORY --> SUCCESS([✅ Member portal loaded\nQuota + history visible])
```

---

## Flow Summary

| Flow | Trigger | Key Service | Critical Constraint |
|---|---|---|---|
| Distribution Recording | Admin records handout | `ComplianceService` | Daily 25g + monthly 30g/50g limits |
| Member Registration | Admin adds new member | `MemberService` | Age ≥ 18, DSGVO consent mandatory |
| Batch Recall | Admin flags contamination | `ComplianceService.recallBatch()` | Immediate prevention officer notification |
| Report Generation | Admin requests monthly report | `ReportService` | iText 7 PDF + CSV for regulatory filing |
| Member Login | Member accesses portal | `AuthService` + `QuotaController` | JWT stateless, read-only member view |

### Error Handling Conventions

All flows follow these conventions for user-facing error messages:

- **Compliance errors** (`422 Unprocessable Entity`): Always include remaining quota/allowance so the admin knows what quantity would be valid
- **Validation errors** (`400 Bad Request`): Include the specific `field` and a human-readable `message` in German (UI locale)
- **Permission errors** (`403 Forbidden`): Generic message — do not reveal tenant or role details
- **System errors** (`500 Internal Server Error`): Log full stack trace; show generic user message; alert via email to club admin

### Transaction Boundaries

The Distribution Recording flow (Flow 1) executes steps `SAVE_DIST → UPD_QUOTA → UPD_STOCK → UPD_BATCH` in a **single `@Transactional` block**. If any step fails (e.g., optimistic lock collision on `MonthlyQuota`), the entire transaction rolls back and no partial state is persisted.
