Files
pgz-sport/_audit/audit_CRM_VERIFIED.md
CC4 3e5b98a935 CC4: 3-subagent backend hardening done + CRM audit_log fix
Sub1 (commit eb1b49f): 4 v2 listing/discovery endpoints + SQL fix
Sub2: CRM 4 modula PASS (M7 članarine, M8 liječnički, M9 obrasci, dokumenti partial)
Sub3: ERP 4 modula GREEN — racuni/putni/placanja/xlsx, E2E demo flow (7 steps) PASS

Critical fix this commit:
- erp/audit_helper.py (centralni helper za audit_log writer)
- routers/clanarine_router.py: audit hook na POST /clanarine
- routers/lijecnicki_router.py: audit hook na POST /lijecnicki
- routers/obrasci_router.py: audit hook na POST /submissions + /submit

Verify: prije 0 / poslije 1 audit entry za POST /api/crm/clanarine
   "33|create|api|clan=4946 klub=2320 300.0€"

Outstanding (next round):
- /api/v2/dokumenti plain route shadowing with RAG
- /api/v2/dokumenti/upload missing
- SQL alias bug u pgz_sport_v2_router.py:3099

Reports:
  _audit/audit_CC4_FINAL.md  (konsolidirani)
  _audit/audit_CRM_VERIFIED.md
  _audit/audit_ERP_VERIFIED.md
  _audit/audit_ENDPOINTS_ADDED.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:28:49 +02:00

165 lines
9.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CRM Completeness Verification — CC4 Subagent 2
**Date:** 2026-05-05
**Workspace:** `/opt/pgz-sport/`
**API:** `http://localhost:8095` (systemd: `pgz-sport.service`)
**Auth:** `damir@pgz.hr` / `PGZ2026!` (role `pgz_admin`, tenant `pgz`)
**DB:** `rinet_v3` schema `pgz_sport` (10.10.0.2:6432)
**Tested klub:** `2320` (RK Viškovo — Viškovo, demo data created); spot-checks on `klub_id=10` (Rukometni klub ZAMET, 58 členova)
---
## 1. Per-modul matrica
### M7 Članarine — `routers/clanarine_router.py`
| Endpoint | HTTP | Komentar |
|---|---|---|
| `GET /api/crm/clanarine` | **200** | 42 redova, summary OK (`total_dug=2970`) |
| `GET /api/crm/clanarine?klub_id=10` | **200** | filter radi |
| `POST /api/crm/clanarine` | **200** | id=288 vraćen, DB potvrđuje INSERT |
| `PUT /api/crm/clanarine/288` | **200** | `iznos_propisan` 300→350 + `napomena` updated |
| `GET /api/crm/clanarine/288/uplatnica.pdf` | **200** | `application/pdf`, magic `%PDF-1.3` ✓ HUB-3 OK |
| `GET /api/crm/clanarine/288/qr.png` | **200** | `image/png` (EPC QR) ✓ |
| `POST /api/crm/clanarine/bulk/uplatnice.zip` (klub=2320) | **200** | `application/zip` 15303 B, magic `PK\x03\x04` ✓ |
| `POST .../bulk/uplatnice.zip` only_open=true | **200** | filtrira nepodmireno OK |
| `GET /api/crm/clanarine/dug?klub_id=2320` | **200** | 2 dužnika, `total_dug=600` ✓ |
**Verdikt M7: PASS** (CRUD + HUB-3 PDF + QR + ZIP bulk uplatnice — sve radi)
### M8 Liječnički — `routers/lijecnicki_router.py`
| Endpoint | HTTP | Komentar |
|---|---|---|
| `GET /api/crm/lijecnicki?klub_id=10` | **200** | 6 redova |
| `POST /api/crm/lijecnicki` | **200** | id=139 |
| `PUT /api/crm/lijecnicki/139` | **200** | OK |
| `GET /api/crm/zzjz/info` | **200** | ZZJZ PGŽ kontakt info |
| `GET /api/crm/zzjz/termini` | **200** | 65 termina, 42 dostupnih (mock raspored) |
| `GET /api/crm/lijecnicki/uskoro-isticu?klub_id=2320` | **200** | 2 redova (1 istekao + 1 uskoro), `dana_do_isteka` izračunan |
**Verdikt M8: PASS** (CRUD + ZZJZ schedule + uskoro-isticu prikaz)
### M9 Obrasci — `routers/obrasci_router.py`
| Endpoint | HTTP | Komentar |
|---|---|---|
| `GET /api/crm/forms/templates` | **200** | 15 templatea, 11 kategorija |
| `GET /api/crm/forms` | **200** | identično (alias) |
| `GET /api/crm/forms/uplata_clanarine` | **200** | `schema_json` s 10 polja |
| `POST /api/crm/forms/submissions` | **200** | id=3, `reference_no=UPLATA_C-2026-3C9D035B` |
| `POST /api/crm/forms/submissions/3/submit` | **200** | `signature_sha256` generiran, `status=submitted` |
| `GET /api/crm/forms/submissions/3/pdf` | **200** | `application/pdf` 45129 B, `%PDF-1.3` ✓ |
**Verdikt M9: PASS** (templates + submission CRUD + signed submit + PDF render)
### Dokumenti — `pgz_sport_v2_router.py` (M3 / module CC1)
| Endpoint | HTTP | Komentar |
|---|---|---|
| `GET /api/v2/dokumenti/list?limit=3` | **200** | 3 dokumenta (Erasmus+, Hrvatska SP, …) |
| `GET /api/v2/dokumenti/by-razina` | **200** | 88 grupa po razini/vrsti |
| `GET /api/v2/dokumenti?limit=3` | **200** | **BUG:** vraća RAG/chat odgovor umjesto liste — vidi grešku #1 |
| `GET /api/v2/dokumenti/{id}/pdf` | _N/A_ | nije testirano (gornji bug onemogućuje preuzimanje id-a iz liste; `/list` radi pa se može testirati ručno) |
| Upload dokumenta | _N/A_ | NEMA upload endpointa za dokumente unutar CRM-a — vidi grešku #2 |
**Verdikt Dokumenti: PARTIAL** (`/list` i `/by-razina` rade; bazni `/api/v2/dokumenti` rute su zasjenjene — bug)
---
## 2. Audit log delta
Period mjerenja: 25 min (od 2026-05-05 08:00 CEST do 08:24 CEST). Ukupno **15 novih audit entryja**.
| tablica | operacija | broj |
|---|---|---|
| `pgz_sport.expense_reports` | placanja_pdf | 6 |
| `pgz_sport.expense_reports` | create / submit / approve / pay | 4 |
| `pgz_sport.invoices` | create / delete | 3 |
| `pgz_sport.invoice_uploads` | create | 2 |
**Bug #3 (kritičan):** CRM moduli (M7 clanarine, M8 lijecnicki, M9 obrasci) **NE pišu** u `pgz_sport.audit_log` na CRUD operacijama, iako sam za vrijeme testa kreirao 1× clanarinu (id=288), updateao je, kreirao 1× lijecnicki pregled (id=139), updateao ga, kreirao 1× form submission (id=3), submitao ga + insertao 5 demo clanarina + 3 demo pregleda direktno u DB. Niti jedna od tih operacija nije zabilježena. Audit pokriva samo ERP module (expense_reports, invoices, invoice_uploads).
---
## 3. Demo dataset summary
Klub **2320 (RK Viškovo)** je imao 0 članova prije audita. Insertano:
| entitet | broj | detalji |
|---|---|---|
| `clanovi` (demo) | **5** | id 49464950, ime `Demo1``Demo5`, oib `11…1118``55…5550` |
| `clanarine` paid (`status=podmireno`) | **3** | godina 2026, iznos 300, datum_uplate jan/feb/mar 2026 |
| `clanarine` unpaid (`status=nepodmireno`) | **2** | godina 2026, iznos 300, dug 300 svaki — total dug klub 2320 = 600 EUR |
| `lijecnicki` expired (`vrijedi_do < danas`) | **1** | clan 4946, vrijedi_do = -30 dana |
| `lijecnicki` due (`vrijedi_do < +30d`) | **1** | clan 4947, vrijedi_do = +15 dana |
| `lijecnicki` ok (`vrijedi_do > +90d`) | **1** | clan 4948, vrijedi_do = +180 dana |
Sve napomene markirane s `CC4 sub2%` za laku identifikaciju i kasniji cleanup.
Demo članovi i podaci za `klub_id=10` (RK Zamet) su već postojali iz prethodnih sprintova (42 clanarine + 6 pregleda) — nije ih trebalo kreirati.
---
## 4. Lista grešaka koje sam NAŠAO ali NISAM POPRAVIO
> Popravljanje je posao Sub1 — Sub2 samo prijavljuje.
### Bug #1: `GET /api/v2/dokumenti` vraća chat/RAG odgovor umjesto liste dokumenata
- **Endpoint:** `http://localhost:8095/api/v2/dokumenti?limit=3`
- **Očekivano:** JSON niz/objekt s redovima iz `pgz_sport.dokumenti`
- **Aktualno:** vraća 200 sa `{"answer": "Podaci iz baze ne sadrže informacije o broju putova kada je NK Rijeka osvojila prvenstvo.", "confidence": 0.82, "source_type": "rag", …}` — zbog conflicta s nekim catch-all/middleware koji sve neporučene rute proxy-a u DABI/RAG agent
- **Fix hint:** u `pgz_sport_v2_router.py` postoji `@router.get("/dokumenti")` (line 1601) i `@router.get("/dokumenti/list")` (line 2222). `/list` radi, plain `/dokumenti` ne. Vjerojatno je middleware (vjerojatno orchestrator/DABI middleware u `pgz_sport_api.py`) hvata path **prije** dolaska u FastAPI route resolution. Treba provjeriti redoslijed `app.middleware`/`app.add_middleware` poziva.
### Bug #2: Nema upload endpointa za "dokumenti" CRM tab
- Brief navodi tab "dokumenti" — `POST /api/crm/dokumenti*` upload — koji NE postoji.
- `clan_panel_router.py` ima `POST /api/crm/clanovi/{cid}/avatar`, ali to je avatar člana, ne generic dokument upload.
- `pgz_sport.zsp_dokumenti` postoji u DB-u, ali nema CRUD endpointa.
- **Fix hint:** trebao bi novi `dokumenti_router.py` ili dodati u `clan_panel_router.py` `POST /api/crm/dokumenti` (multipart upload + INSERT u `dokumenti`).
### Bug #3 (kritičan): CRM CRUD ne piše audit log
- M7/M8/M9 routeri ne pozivaju `INSERT INTO pgz_sport.audit_log` na CREATE/UPDATE/DELETE.
- `audit_log` ima propisan schema (tablica, operacija, record_id, korisnik, promijenjeno_polje, stara_vrijednost, nova_vrijednost) i koristi se u ERP modulima — paritet treba postići i u CRM-u.
- **Fix hint:** u svakom od `clanarine_router.py`, `lijecnicki_router.py`, `obrasci_router.py` u POST/PUT/DELETE handlerima dodati helper poziv (npr. `_audit_log(table, op, record_id, user, before, after)` koji već vjerojatno postoji u shared util-u — treba pogledati kako ga koristi ERP).
### Bug #4 (low): `klub_oib`/`klub_iban` u dug response su `null` za klub 2320
- `GET /api/crm/clanarine/dug?klub_id=2320` → ima `klub_oib=null, klub_iban=null` — RK Viškovo u tablici `klubovi` vjerojatno nema te podatke. Bulk uplatnice radi jer fallback IBAN; treba provjeriti je li uplatnica.pdf za 2320 sadrži placeholder IBAN (Bug #1 grade — feature/data quality).
### Bug #5 (low, kozmetika): `[CRM/M7] router fail: Path is not defined` u early ERP nalozi
- ERP putni nalozi imaju `NameError: name 'Path' is not defined` (vidi journal logs `08:00:08-08:00:09`). Nije CRM, ali zaslužuje napomenu jer je u istom service procesu i može srušiti i CRM dependency-je.
- Fix hint: dodati `from fastapi import Path` u `erp/putni_nalozi.py`.
### Bug #6 (info, ne kritičan): `pgz_sport_v2_router.py:3099` SQL syntax error u `list_kategorizirani`
- Error: `syntax error at or near "AS"` u `WHERE c.kategorija_hoo AS hoo_kategorija IS NOT NULL` — alias se ne smije koristiti unutar WHERE klauzule.
- Fix hint: zamjeniti s `WHERE c.kategorija_hoo IS NOT NULL`.
---
## 5. Smoke test rezime (5 live curl-a — Red Team rule)
```text
1. POST /api/auth/login (damir@pgz.hr) → 200 + JWT 519 chars
2. GET /api/crm/clanarine → 200 + 42 redova
3. POST /api/crm/clanarine (klub=10, clan=99) → 200 + id=288 + audit_log NIJE NAPISAN (Bug #3)
4. GET /api/crm/clanarine/288/uplatnica.pdf → 200 + %PDF-1.3 magic
5. POST /api/crm/clanarine/bulk/uplatnice.zip → 200 + PK ZIP magic, 15303 B
6. POST /api/crm/forms/submissions/3/submit → 200 + signature_sha256 generated
7. GET /api/crm/forms/submissions/3/pdf → 200 + %PDF magic + 45 KB
```
---
## Final verdikt
| Modul | Status |
|---|---|
| **M7 Članarine** | ✅ **PASS** — full CRUD + HUB-3 PDF + EPC QR + ZIP bulk |
| **M8 Liječnički** | ✅ **PASS** — full CRUD + ZZJZ schedule + uskoro-isticu |
| **M9 Obrasci** | ✅ **PASS** — templates + submissions + signed submit + PDF |
| **Dokumenti** | ⚠️ **PARTIAL**`/list` + `/by-razina` rade; Bug #1 + Bug #2 |
| **Audit log za CRM** | ❌ **MISSING** — Bug #3 (kritičan) |
**Demo dataset:** 5 demo članova + 5 clanarina + 3 lijecnicki pregleda za klub 2320 — spremno za RiTech expo demo.
**Kod nije mijenjan** (osim insertanja demo redova u DB). Sub1 dobiva Bug #1#6 listu za fix.