# Nadogradnja: magacioneri i pravno lice opreme

## Šta je promenjeno

Postojeći modul opreme je zadržan, bez redizajna i bez promene toka tiketa. Uvedene su dve namenske poslovne role, eksplicitno polje `equipment.organization_unit`, serversko ograničavanje svih upita i operacija, provera pravnog lica pri dodeli, jedinstven inventarski broj unutar pravnog lica, prikaz/filter i kolona u Excel izvozu.

Centralne provere su `can_manage_equipment()`, `require_equipment_manager()`, `equipment_access_scope_sql()` i `can_manage_equipment_item()`. Magacionerske role su izuzete iz skupa uloga koje obrađuju tikete i ne daju pristup administraciji korisnika ili podešavanjima zahteva.

## Nove role

| Kod | Naziv | Scope |
|---|---|---|
| `WHF` | Magacioner — Fondacija | Samo `F` oprema |
| `WHDS` | Magacioner — Dečije selo | Samo `DS` oprema |

Administracija korisnika na backendu odbija `USERF + WHDS`, `USERDS + WHF`, obe magacionerske role istovremeno i magacionersku rolu bez odgovarajuće osnovne organizacione uloge.

## Matrica dozvola

| Operacija | SYSTEM_ADMIN | WHF | WHDS | Običan zaposleni |
|---|---:|---:|---:|---:|
| Pregled opreme | F + DS + stavke za proveru | samo F | samo DS | ne |
| Kreiranje/izmena/brisanje | F + DS | samo F | samo DS | ne |
| Dodela/razduženje | F + DS, samo kompatibilnom korisniku | samo F korisniku F | samo DS korisniku DS | ne |
| Promena pravnog lica | da, tek posle razduženja | ne | ne | ne |
| Excel izvoz | sve ili izabrano pravno lice | samo F | samo DS | ne |
| Korisnici/podešavanja/sistemska administracija | da | ne | ne | ne |

## DB migracija

Primeni `20260823_magacioner_organization_unit.sql` nakon rezervne kopije baze. Migracija:

1. dodaje `WHF` i `WHDS`;
2. dodaje nullable `equipment.organization_unit`;
3. migrira samo opremu dodeljenu korisniku sa tačno jednom ulogom `USERF` ili `USERDS`;
4. ostavlja nejasne/nedodeljene stavke kao `NULL` i vraća `REQUIRES_MANUAL_REVIEW` izveštaj;
5. dodaje audit tabelu i ciljane indekse;
6. dodaje `UNIQUE (organization_unit, inventory_no)` samo ako nema duplikata.

`organization_unit` je u prvoj fazi nullable isključivo zbog bezbedne migracije postojećih podataka. Aplikacija ne dozvoljava kreiranje nove stavke bez `F` ili `DS`; magacioneri ne vide `NULL` stavke. Kada se ručna obrada završi, izvršiti:

```sql
ALTER TABLE equipment MODIFY organization_unit varchar(8) NOT NULL;
```

## Pravilo inventarskog broja

Inventarski broj nije globalno jedinstven. Jedinstven je par:

```text
organization_unit + inventory_no
```

Prazni brojevi se čuvaju kao `NULL`, pa je dozvoljeno više stavki bez inventarskog broja. Pre dodavanja indeksa migracija proverava duplikate i, ako ih ima, ispisuje ih i preskače constraint umesto da prekine ostatak migracije.

## Migracija i ručna provera

Automatsko mapiranje se radi samo preko trenutno dodeljenog korisnika i njegove osnovne organizacione uloge. Ne koriste se inventarski broj, serijski broj, lokacija ili tip opreme. Ručno pregledati:

```sql
SELECT id, asset_type, asset_name, inventory_no, user_id
FROM equipment
WHERE organization_unit IS NULL;
```

Za svaku potvrđenu stavku postaviti `F` ili `DS`, zatim ponoviti proveru duplikata iz migracije i dodati/finalizovati unique/not-null constraint. U dostavljenom dump-u stavka opreme ID `7` je nedodeljena i zato je namerno označena za ručnu proveru.

## Autorizacija i IDOR zaštita

Scope se dodaje u SQL (`e.organization_unit = ?`) pre čitanja podataka. Isti scope se koristi za listu, statistiku, pretragu, update, delete i izvoz. Ručna promena `equipment_id` na zapis drugog pravnog lica vraća `404`. Za create se pravno lice WHF/WHDS korisnika uzima iz sesije i ignoriše manipulisana POST vrednost. Kod update-a magacioner zadržava pravno lice učitano iz baze.

Dodela ponovo učitava korisnika i njegove aktivne role iz baze. Operacija se odbija ako `equipment.organization_unit` nije isto što i pravno lice korisnika.

## Audit

`equipment_audit_events` beleži `equipment_id`, `actor_user_id`, akciju, staru/novu vrednost i vreme za kreiranje, izmenu, brisanje, dodelu, razduženje, promenu pravnog lica i inventarskog broja. Postojeći `equipment_assignment_events` ostaje netaknut. Audit ne briše `equipment_id` nakon fizičkog brisanja opreme.

## Security testovi

Automatska statička/regresiona provera je u `qa_magacioner_organization_unit.js`. Obuhvata:

- odvojeno pravo u odnosu na `SYSTEM_ADMIN`;
- WHF/WHDS SQL scope;
- ignorisanje manipulisane organizacije pri create/update;
- scoped update/delete/export;
- proveru pravnog lica dodeljenog korisnika;
- validaciju kombinacije korisničkih rola;
- composite unique pravilo i bezbedan duplicate pre-check;
- kolonu pravnog lica u UI-u i Excel-u;
- audit događaje i migracioni izveštaj.

Za produkcioni smoke test napraviti test naloge WHF i WHDS i pokušati GET/POST/DELETE sa ID-em druge organizacije; očekivan rezultat je `404`. Pokušaj `F equipment -> DS user` i obrnuto mora vratiti validacionu grešku bez promene baze. SYSTEM_ADMIN mora moći da filtrira i izvozi F, DS ili sve.

## Brisanje i poznati rizici

Zadržano je postojeće fizičko brisanje kako se poslovna logika ne bi širila van zadatka. Preporuka je buduća migracija na soft-delete (`is_deleted`, `deleted_at`, `deleted_by`) ili status `Rashodovano`. Pre deploymenta obavezno obraditi `REQUIRES_MANUAL_REVIEW` i eventualne duplikate. Ako unique indeks bude preskočen, aplikaciona provera ostaje aktivna, ali DB constraint treba dodati odmah nakon ručne korekcije.

## Rollback

1. onemogućiti pristup aplikaciji i vratiti rezervnu kopiju baze (preporučeno);
2. vratiti izmenjene aplikacione fajlove iz prethodnog release-a;
3. ako se radi ručni DB rollback: ukloniti dodele rola WHF/WHDS iz `user_roles`, zatim role; ukloniti indekse `uq_equipment_org_inventory`, `idx_equipment_organization_unit`, `idx_equipment_org_status`; ukloniti `equipment_audit_events`; na kraju ukloniti kolonu `equipment.organization_unit`;
4. ne uklanjati kolonu dok se ne potvrdi da prethodna verzija više ne izvršava nove upite.

