Patrick Plate 5defe42d67 feat(sprint10): Phase 3 — BankImportService + REST API
Implements the orchestrator and REST endpoints for the bank statement
import wizard (Sprint 10 Phase 3).

Service layer (cannamanage-service):
- BankImportService: upload → SHA-256 dedup → parse → match → persist
  in two transactional steps (file I/O outside @Transactional, persist
  in @Transactional helper). Methods: uploadAndParse, confirmMatch,
  confirmAllMatched (≥90% confidence), manualAssign, skipTransaction,
  completeSession, query helpers.
- GoBD §147 AO immutability guard: assertSessionMutable() rejects any
  mutation on COMPLETED/FAILED sessions with German error messages.
- Hard 5MB upload cap enforced before parsing.
- Audit events: BANK_IMPORT_STARTED / BANK_PAYMENT_CONFIRMED /
  BANK_IMPORT_COMPLETED. Uploader notified via NotificationService.

REST layer (cannamanage-api):
- BankImportController under /api/v1/finance/import/*:
  POST sessions (multipart), GET sessions/single/transactions(?status=),
  POST {id}/transactions/{txnId}/confirm|assign|skip,
  POST {id}/confirm-all, POST {id}/complete,
  GET/POST/DELETE csv-mappings.
- Permission: FINANCE_IMPORT with MANAGE_FINANCES fallback.
- Defence-in-depth tenant check on every path-parameter ID.

DTOs (cannamanage-api/dto/bankimport):
- ImportSessionResponse, TransactionResponse, ConfirmRequest,
  AssignRequest, SkipRequest, BulkConfirmResponse, CreateMappingRequest.

Persistence:
- V33__bank_import_file_hash.sql: adds file_hash VARCHAR(64) + unique
  partial index (club_id, file_hash) for duplicate-upload detection.
- BankImportSession.fileHash field, repository.existsByClubIdAndFileHash.

Configuration:
- application.properties: multipart enabled, max-file-size=5MB,
  max-request-size=6MB.

Build: mvn package -DskipTests  (cannamanage-api fat JAR 92MB).
2026-06-15 17:47:27 +02:00

CannaManage

Multi-tenant cannabis club management platform for German Anbauvereinigungen (cultivation associations) under CanG §19.

Overview

CannaManage handles member management, distribution tracking, and legal compliance for cannabis cultivation clubs in Germany. It enforces the strict quotas mandated by the Cannabis Act (CanG) — including monthly limits (50g adult / 30g under-21), daily limits (25g), and THC restrictions for minors.

Tech Stack

Component Technology
Runtime Java 21 (Temurin)
Framework Spring Boot 4.0.6
Security Spring Security 7.0 + JWT (JJWT 0.12.6)
ORM Hibernate 7 / JPA
Database PostgreSQL (prod), H2 (test)
Migrations Flyway 10
API Docs SpringDoc OpenAPI 2.8.6
Build Maven (multi-module)
Container Docker Compose (Postgres + app)

Project Structure

cannamanage/
├── cannamanage-domain/      # JPA entities, enums, TenantContext
├── cannamanage-service/     # Business logic, repositories, ComplianceService
├── cannamanage-api/         # Spring Boot app, controllers, security, DTOs
├── docs/
│   └── sprint-2/           # Sprint planning docs
└── docker-compose.yml       # Local dev environment

Modules

cannamanage-domain

JPA entities with multi-tenant isolation via @Filter("tenantFilter"):

  • Member — club members with age tracking
  • Distribution — cannabis distribution records
  • MonthlyQuota — per-member monthly usage tracking
  • Batch / Strain / StockMovement — inventory management
  • Club — association registration
  • User — authentication accounts

cannamanage-service

  • ComplianceService — CanG §19 quota enforcement (25 unit tests)
  • Repositories for all entities

cannamanage-api

  • Auth — JWT login + refresh token rotation (SHA-256 hashed)
  • Members — CRUD for association members
  • Distributions — compliance-gated distribution recording
  • Stock — batch and inventory management
  • Compliance — quota status API
  • Multi-tenant isolation via TenantFilterAspect (Hibernate @Filter activation)

API Endpoints

Method Path Auth Description
POST /api/v1/auth/login Public Login with email + password
POST /api/v1/auth/refresh Public Refresh token rotation
GET /api/v1/compliance/quota/{memberId} ADMIN, MEMBER Monthly quota status
GET/POST/PUT /api/v1/members/** ADMIN, MEMBER Member CRUD
POST /api/v1/distributions/** ADMIN, MEMBER Record distributions
GET/POST /api/v1/stock/** ADMIN Stock management

Swagger UI: http://localhost:8080/swagger-ui.html

Running Locally

# Start PostgreSQL
docker compose up -d

# Run the app
JAVA_HOME=/path/to/jdk-21 ./mvnw spring-boot:run -pl cannamanage-api

# Run all tests (H2 in-memory)
JAVA_HOME=/path/to/jdk-21 ./mvnw clean verify

Testing

  • 37 tests total — all green
  • 25 unit tests (ComplianceServiceTest) — quota enforcement logic
  • 7 integration tests (AuthControllerIntegrationTest) — full HTTP auth flow
  • 5 integration tests (ComplianceControllerIntegrationTest) — quota API with JWT

Integration tests use @SpringBootTest(webEnvironment = RANDOM_PORT) with H2 and Spring's RestClient.

Security Model

  • Stateless JWT — no session, no UserDetailsService
  • Roles: ADMIN (full access), MEMBER (self-service), STAFF (Sprint 3)
  • Multi-tenancy: Hibernate @Filter activated per-request via AOP aspect
  • Refresh tokens: SHA-256 hashed (Spring Security 7 enforces BCrypt 72-byte limit)
  • Token rotation on refresh — old tokens invalidated

Sprint History

Sprint Focus Status
1 Domain entities, ComplianceService, 25 tests Done
2 REST API, Spring Security, JWT, OpenAPI, integration tests Done
3 Member portal, STAFF role, real-time notifications 📋 Planned

License

Private — Patrick Plate

S
Description
CannaManage — B2B SaaS for German Cannabis Social Clubs (Anbauvereinigungen)
Readme 7.8 MiB
Languages
Java 61%
TypeScript 38.1%
JavaScript 0.4%
CSS 0.3%
Shell 0.2%