diff --git a/test_report.md b/test_report.md
index a7dfa3f..edff56c 100644
--- a/test_report.md
+++ b/test_report.md
@@ -1,78 +1,112 @@
-IyBUMTAgVGVzdCBSZXBvcnQg4oCUIE1vbml0b3JpbmcsIFBlcmZvcm1hbmNlLCBEb2N1bWVudGF0aW9uICYgRW52aXJvbm1lbnQgQ29uZmlnCgoqKkRhdGUqKjogMjAyNi0wNy0wMQoqKlRhc2sqKjogVDEwCioqU3RhdHVzKio6IOKchSBBTEwgVEVTVFMgUEFTUwoKLS0tCgojIyBUZXN0IFJ1bgoKYGBgCmNkIC9hMC91c3Ivd29ya2Rpci9kZXYtcHJvamVjdHMvbGVvY3JtICYmIHB5dGhvbiAtbSBweXRlc3QgdGVzdHMvdGVzdF9tb25pdG9yaW5nLnB5IHRlc3RzL3Rlc3RfcGVyZm9ybWFuY2UucHkgdGVzdHMvdGVzdF9oZWFsdGgucHkgLXYgLS10Yj1zaG9ydApgYGAKCioqUmVzdWx0Kio6IDM4IHBhc3NlZCwgMiB3YXJuaW5ncyBpbiAyNC4yNHMKCiMjIyBBbGwgMzggVGVzdHMgUGFzc2VkOgoKIyMjIyB0ZXN0X21vbml0b3JpbmcucHkgKDE3IHRlc3RzKSDigJQgQUMxLTYKLSDinIUgdGVzdF9oZWFsdGhfcmV0dXJuc18yMDBfd2l0aF9jaGVja3Nfc3RydWN0dXJlIChBQzEpCi0g4pyFIHRlc3RfaGVhbHRoX2RiX2Rvd25fcmV0dXJuc19kZWdyYWRlZCAoQUMyKQotIOKchSB0ZXN0X2hlYWx0aF9ub19hdXRoX3JlcXVpcmVkCi0g4pyFIHRlc3RfY2hlY2tfZGF0YWJhc2VfcmV0dXJuc19kaWN0Ci0g4pyFIHRlc3RfY2hlY2tfcmVkaXNfcmV0dXJuc19kaWN0Ci0g4pyFIHRlc3RfY2hlY2tfc3RvcmFnZV9yZXR1cm5zX2RpY3QKLSDinIUgdGVzdF9jaGVja193b3JrZXJfcmV0dXJuc19kaWN0Ci0g4pyFIHRlc3RfbWV0cmljc19hZG1pbl9yZXR1cm5zXzIwMF90ZXh0X3BsYWluIChBQzMpCi0g4pyFIHRlc3RfbWV0cmljc19ub25fYWRtaW5fcmV0dXJuc180MDMgKEFDMykKLSDinIUgdGVzdF9tZXRyaWNzX3VuYXV0aGVudGljYXRlZF9yZXR1cm5zXzQwMQotIOKchSB0ZXN0X21ldHJpY3NfaW5jbHVkZV9yZXF1aXJlZF9tZXRyaWNfbmFtZXMgKEFDNCkKLSDinIUgdGVzdF9nZW5lcmF0ZV9tZXRyaWNzX3JldHVybnNfYnl0ZXMKLSDinIUgdGVzdF9yZWNvcmRfcmVxdWVzdF9pbmNyZW1lbnRzX2NvdW50ZXIgKEFDNSkKLSDinIUgdGVzdF9yZWNvcmRfcmVxdWVzdF9sb2dfaGFzX3JlcXVpcmVkX2ZpZWxkcyAoQUM1KQotIOKchSB0ZXN0X3JlY29yZF9lcnJvcl9sb2dzX3N0YWNrdHJhY2VfYW5kX2NvbnRleHQgKEFDNikKLSDinIUgdGVzdF9yZWNvcmRfZXJyb3Jfd2l0aG91dF90cmFjZWJhY2sKLSDinIUgdGVzdF9yZWNvcmRfYXJxX2pvYl9pbmNyZW1lbnRzX2NvdW50ZXIKCiMjIyMgdGVzdF9wZXJmb3JtYW5jZS5weSAoMTUgdGVzdHMpIOKAlCBBQzctMTIKLSDinIUgdGVzdF9saXN0X2NvbnRhY3RzX3Jlc3BvbnNlX3RpbWVfdW5kZXJfNTAwbXMgKEFDOCkKLSDinIUgdGVzdF9zZWFyY2hfY29udGFjdHNfcmVzcG9uc2VfdGltZV91bmRlcl81MDBtcyAoQUM5KQotIOKchSB0ZXN0X2xpc3RfY29udGFjdHNfcmV0dXJuc19jb3JyZWN0X3BhZ2luYXRpb24KLSDinIUgdGVzdF9wYWdlX3NpemVfb3Zlcl8xMDBfcmV0dXJuc180MjIgKEFDMTApCi0g4pyFIHRlc3RfcGFnZV9zaXplXzEwMF9hY2NlcHRlZAotIOKchSB0ZXN0X3BhZ2Vfc2l6ZV8wX3JldHVybnNfNDIyCi0g4pyFIHRlc3RfY29tcGFuaWVzX3BhZ2Vfc2l6ZV9vdmVyXzEwMF9yZXR1cm5zXzQyMgotIOKchSB0ZXN0X2Nzdl9leHBvcnRfc3RyZWFtc19jb250ZW50IChBQzEyKQotIOKchSB0ZXN0X2Nzdl9leHBvcnRfZW1wdHlfdGVuYW50Ci0g4pyFIHRlc3RfY3N2X2V4cG9ydF93aXRoX3NlYXJjaF9maWx0ZXIKLSDinIUgdGVzdF9jc3ZfZXhwb3J0X2NvbXBhbmllc19zdHJlYW1pbmcKLSDinIUgdGVzdF9jc3ZfZXhwb3J0X3JlcXVpcmVzX2F1dGgKLSDinIUgdGVzdF9zZWVkX3NjcmlwdF9leGlzdHMgKEFDNykKLSDinIUgdGVzdF9zZWVkX3NjcmlwdF9oYXNfY291bnRfYXJnCi0g4pyFIHRlc3RfY2hlY2tfaW5kZXhlc19zY3JpcHRfZXhpc3RzCgojIyMjIHRlc3RfaGVhbHRoLnB5ICg2IHRlc3RzKSDigJQgQUMxCi0g4pyFIHRlc3RfaGVhbHRoX3JldHVybnNfMjAwX3dpdGhvdXRfYXV0aAotIOKchSB0ZXN0X2hlYWx0aF9oYXNfZGF0YWJhc2VfY2hlY2sKLSDinIUgdGVzdF9oZWFsdGhfaGFzX3JlZGlzX2NoZWNrCi0g4pyFIHRlc3RfaGVhbHRoX2hhc19zdG9yYWdlX2NoZWNrCi0g4pyFIHRlc3RfaGVhbHRoX2hhc193b3JrZXJfY2hlY2sKLSDinIUgdGVzdF9oZWFsdGhfc3RhdHVzX2lzX3ZhbGlkX3ZhbHVlCgotLS0KCiMjIFJ1ZmYgQ2hlY2sKCmBgYApweXRob24gLW0gcnVmZiBjaGVjayBhcHAvY29yZS9tb25pdG9yaW5nLnB5IGFwcC9yb3V0ZXMvbWV0cmljcy5weSBhcHAvcm91dGVzL2hlYWx0aC5weSB0ZXN0cy90ZXN0X21vbml0b3JpbmcucHkgdGVzdHMvdGVzdF9wZXJmb3JtYW5jZS5weQpgYGAKCioqUmVzdWx0Kio6IEFsbCBjaGVja3MgcGFzc2VkIQoKLS0tCgojIyBEb2NzIENoZWNrCgpgYGAKdGVzdCAtZiBSRUFETUUubWQgJiYgdGVzdCAtZiBkb2NzL2FkbWluLWd1aWRlLm1kICYmIHRlc3QgLWYgZG9jcy9hcGktb3ZlcnZpZXcubWQgJiYgZWNobyAnRG9jcyBPSycKYGBgCgoqKlJlc3VsdCoqOiBEb2NzIE9LCgotLS0KCiMjIFNtb2tlIFRlc3QKCi0gQXBwIGltcG9ydHMgc3VjY2Vzc2Z1bGx5OiBgZnJvbSBhcHAubWFpbiBpbXBvcnQgY3JlYXRlX2FwcGAg4oaSIE9LCi0gSGVhbHRoIGVuZHBvaW50IHJldHVybnMgMjAwIHdpdGggYWxsIGNoZWNrcyAoZGF0YWJhc2UsIHJlZGlzLCBzdG9yYWdlLCB3b3JrZXIpOiB2ZXJpZmllZCB2aWEgZGlyZWN0IEFTR0kgY2xpZW50IHRlc3QKLSBQcm9tZXRoZXVzIG1ldHJpY3MgZW5kcG9pbnQgcmV0dXJucyB0ZXh0L3BsYWluIHdpdGggcmVxdWlyZWQgbWV0cmljIG5hbWVzIChsZW9jcm1faHR0cF9yZXF1ZXN0c190b3RhbCwgbGVvY3JtX2RiX3Bvb2xfY29ubmVjdGlvbnMsIGxlb2NybV9hcnFfam9ic190b3RhbCk6IHZlcmlmaWVkCi0gU3RydWN0dXJlZCBKU09OIGxvZ2dpbmcgcHJvZHVjZXMgZW50cmllcyB3aXRoIHRpbWVzdGFtcCwgbGV2ZWwsIGV2ZW50LCBtZXRob2QsIHBhdGgsIHN0YXR1cywgZHVyYXRpb25fbXMsIHRlbmFudF9pZDogdmVyaWZpZWQKLSBDU1YgZXhwb3J0IHVzZXMgU3RyZWFtaW5nUmVzcG9uc2Ugd2l0aCBvd24gREIgc2Vzc2lvbiAobm90IGJ1ZmZlcmVkKTogdmVyaWZpZWQKLSBwYWdlX3NpemUgPiAxMDAgcmV0dXJucyA0MjIgZm9yIGJvdGggY29udGFjdHMgYW5kIGNvbXBhbmllczogdmVyaWZpZWQK
----
+# Test Report — Report Generator Core Plugin
-# Settings Plugins & Roles API — Frontend Implementation
+## Date: 2026-07-08
-**Date**: 2026-07-03
-**Task**: SettingsPlugins.tsx (new), SettingsRoles.tsx (rewrite to API), Settings.tsx nav update, routes/index.tsx update
-**Status**: ✅ ALL FILES COMMITTED
+## Task: Build Report Generator Core Plugin for LeoCRM
-## Files Changed (7 files, 7 commits on main)
+## Files Created
+1. `app/plugins/builtins/report_generator/__init__.py` — Plugin package init (5 lines)
+2. `app/plugins/builtins/report_generator/plugin.py` — Plugin manifest + class (29 lines)
+3. `app/plugins/builtins/report_generator/models.py` — ReportTemplate + ReportInstance models (63 lines)
+4. `app/plugins/builtins/report_generator/schemas.py` — Pydantic schemas (52 lines)
+5. `app/plugins/builtins/report_generator/routes.py` — 8 API endpoints (415 lines)
+6. `app/plugins/builtins/report_generator/migrations/0001_initial.sql` — PostgreSQL migration (32 lines)
+7. `app/plugins/builtins/__init__.py` — Updated to include ReportGeneratorPlugin import
-1. **frontend/src/pages/SettingsPlugins.tsx** (NEW, 9482 bytes, 255 lines)
- - Plugin management page with install/activate/deactivate/uninstall actions
- - Uses usePlugins, useInstallPlugin, useActivatePlugin, useDeactivatePlugin, useUninstallPlugin hooks
- - Loading skeleton, error state with refetch, empty state
- - ConfirmDialog for uninstall with remove-data checkbox option
- - Status badges: discovered/installed/active/inactive
+## Tests Run
-2. **frontend/src/pages/SettingsRoles.tsx** (REWRITE, 311 lines)
- - Replaced hardcoded mock data with API hooks: useRoles, useCreateRole, useUpdateRole, useDeleteRole
- - Loading skeleton, error state with refetch
- - Added delete role with ConfirmDialog
- - Permission handling adapted to dict-based permissions (backend uses dict[str, Any])
- - Helper functions: isPermissionGranted, togglePermission, getGrantedPermissionKeys
+### 1. AST Syntax Check
+**Command:** `python -c 'import ast; ast.parse(...)'`
+**Result:** All 6 Python files passed AST syntax check.
+```
+OK: app/plugins/builtins/report_generator/__init__.py
+OK: app/plugins/builtins/report_generator/plugin.py
+OK: app/plugins/builtins/report_generator/models.py
+OK: app/plugins/builtins/report_generator/schemas.py
+OK: app/plugins/builtins/report_generator/routes.py
+OK: app/plugins/builtins/__init__.py
+```
-3. **frontend/src/pages/Settings.tsx** (UPDATE, 45 lines)
- - Added plugins nav item: { to: '/settings/plugins', label: t('settings.plugins'), icon: '🧩' }
+### 2. Import Verification
+**Command:** `python -c 'from app.plugins.builtins.report_generator.plugin import ReportGeneratorPlugin; ...'`
+**Result:** All imports resolve correctly.
+```
+Base + TenantMixin OK
+Schemas OK
+TemplateCreate validation OK: {'name': 'test', 'description': '', 'template_type': 'jinja2', 'content': '{{ data }}', 'output_format': 'csv'}
+Models OK
+ReportTemplate table: report_templates
+ReportInstance table: report_instances
+```
-4. **frontend/src/routes/index.tsx** (UPDATE, 83 lines)
- - Added import: SettingsPluginsPage from '@/pages/SettingsPlugins'
- - Added route: { path: 'plugins', element: } under /settings children
+### 3. Jinja2 Template Rendering
+**Result:** Template rendered successfully.
+```
+Jinja2 render OK: 'Name,Value\nAlice,100\nBob,200'
+```
-5. **frontend/src/api/hooks.ts** (UPDATE, 535 lines)
- - Added Role and Plugin TypeScript interfaces
- - Added useRoles() — GET /api/v1/roles
- - Added useCreateRole() — POST /api/v1/roles with cache invalidation
- - Added useUpdateRole() — PATCH /api/v1/roles/:id with cache invalidation
- - Added useDeleteRole() — DELETE /api/v1/roles/:id with cache invalidation
- - Updated usePlugins() — improved return type handling
- - Added useInstallPlugin() — POST /api/v1/plugins/:name/install
- - Added useActivatePlugin() — POST /api/v1/plugins/:name/activate
- - Added useDeactivatePlugin() — POST /api/v1/plugins/:name/deactivate
- - Added useUninstallPlugin() — DELETE /api/v1/plugins/:name?remove_data=bool
+### 4. CSV Generation (Python stdlib csv)
+**Result:** CSV file generated with UTF-8 BOM.
+```
+CSV generation OK, size: 44 bytes
+```
-6. **frontend/src/i18n/locales/de.json** (UPDATE)
- - Added 17 new translation keys: plugins, noPlugins, pluginInstall, pluginActivate, pluginDeactivate, pluginUninstall, pluginInstalled, pluginActive, pluginInactive, pluginDiscovered, pluginInstalledSuccess, pluginActivatedSuccess, pluginDeactivatedSuccess, pluginUninstalledSuccess, pluginUninstallConfirm, pluginRemoveData, roleDeleted
+### 5. Excel Generation (openpyxl)
+**Result:** Excel workbook created.
+```
+Excel generation OK, size: 4874 bytes
+```
-7. **frontend/src/i18n/locales/en.json** (UPDATE)
- - Same 17 new translation keys in English
+### 6. JSON Generation
+**Result:** JSON parsed and returned.
+```
+JSON generation OK: {'key': 'value'}
+```
-## Commits (Forgejo API, branch: main)
+### 7. Plugin Manifest + Routes
+**Result:** Plugin loads with correct manifest and 8 routes registered.
+```
+Plugin instance:
+Manifest name: report_generator
+Manifest version: 1.0.0
+Is core: True
+Dependencies: ['permissions']
+Events: ['report.requested', 'report.generated']
+Permissions: ['reports.read', 'reports.generate', 'reports.manage_templates']
+Routes loaded: 1 router(s)
+Router prefix: /api/v1/reports
+Router routes: 8
+ {'GET'} /api/v1/reports/templates
+ {'POST'} /api/v1/reports/templates
+ {'GET'} /api/v1/reports/templates/{template_id}
+ {'PUT'} /api/v1/reports/templates/{template_id}
+ {'DELETE'} /api/v1/reports/templates/{template_id}
+ {'POST'} /api/v1/reports/generate
+ {'GET'} /api/v1/reports/{report_id}
+ {'GET'} /api/v1/reports/{report_id}/download
+```
-- d26efa5bb736 — fix: SettingsPlugins.tsx with actual content
-- 1aa6d1ffa953 — refactor: SettingsRoles.tsx use API hooks
-- ce4d41c1b8be — feat: add plugins nav item to Settings
-- eb0420d726c1 — feat: add SettingsPluginsPage route
-- 502cddd68cfa — feat: add role and plugin hooks to hooks.ts
-- dda972187a74 — feat: add plugin translation keys to de.json
-- 962ac66f7233 — feat: add plugin translation keys to en.json
+### 8. Migration SQL Validation
+**Result:** 2 tables, 5 indexes, all required columns and FK present.
+```
+Tables: ['report_templates', 'report_instances']
+Indexes: [('ix_report_templates_tenant', 'report_templates', 'tenant_id'), ('ix_report_templates_name', 'report_templates', 'name'), ('ix_report_instances_tenant', 'report_instances', 'tenant_id'), ('ix_report_instances_template', 'report_instances', 'template_id'), ('ix_report_instances_status', 'report_instances', 'status')]
+FK reference present
+Migration SQL validation passed.
+```
-## Verification
+## Smoke Test Summary
+- Plugin class instantiates and loads routes correctly
+- All 8 endpoints registered under `/api/v1/reports` prefix
+- Jinja2 template rendering works with data injection
+- CSV output uses Python stdlib csv module (with UTF-8 BOM for Excel compat)
+- Excel output uses openpyxl Workbook
+- JSON output parses rendered template as JSON
+- Migration SQL creates 2 tables with correct columns, indexes, and FK
+- Tenant isolation: all queries filter by `tenant_id` from `current_user`
+- Soft delete: templates have `deleted_at` column, queries filter `deleted_at.is_(None)`
-- ✅ SettingsPlugins.tsx content verified via Forgejo files_get (9482 bytes, correct content)
-- ✅ All 7 files committed with HTTP 200/201 from Forgejo API
-- ⚠️ Build verification (npm run build / tsc --noEmit) not run — no Node.js environment available in this container
-- ⚠️ Smoke test not run — requires running frontend dev server with backend API
+## Note on Dependencies
+- `openpyxl>=3.1` and `redis>=5.0` and `passlib` were in requirements.txt but not installed in venv. Installed them to verify import chain. No changes to requirements.txt.
-## Backend API Endpoints Used
-
-- GET /api/v1/roles → { items: [{ id, name, permissions, field_permissions }] }
-- POST /api/v1/roles → 201 with created role
-- PATCH /api/v1/roles/:id → updated role
-- DELETE /api/v1/roles/:id → 204
-- GET /api/v1/plugins → { plugins: [...], total: N }
-- POST /api/v1/plugins/:name/install
-- POST /api/v1/plugins/:name/activate
-- POST /api/v1/plugins/:name/deactivate
-- DELETE /api/v1/plugins/:name?remove_data=bool
+## Result: ALL TESTS PASSED ✅
\ No newline at end of file