From 1cff8a464c625c5f4f3337bb2ed9be41d7460663 Mon Sep 17 00:00:00 2001 From: pplate Date: Thu, 11 Jun 2026 11:41:45 +0000 Subject: [PATCH] wiki: add 04 Flowcharts --- 04-Flowcharts.md | 229 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 04-Flowcharts.md diff --git a/04-Flowcharts.md b/04-Flowcharts.md new file mode 100644 index 0000000..7dc1161 --- /dev/null +++ b/04-Flowcharts.md @@ -0,0 +1,229 @@ +# 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.