5defe42d679558a86f4beaa80848c8e95d8b7159
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).
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 trackingDistribution— cannabis distribution recordsMonthlyQuota— per-member monthly usage trackingBatch/Strain/StockMovement— inventory managementClub— association registrationUser— 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
@Filteractivated 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
Description
Languages
Java
61%
TypeScript
38.1%
JavaScript
0.4%
CSS
0.3%
Shell
0.2%