# Rental System

Produkcyjny szkielet systemu do zarządzania wynajmem sprzętu IT dla firmy rentalowej:

- `apps/api`: Express + TypeScript + PostgreSQL + JWT + Swagger
- `apps/web`: Next.js + React + TypeScript + Tailwind
- `packages/shared`: wspólne typy, enumy i helpery dat

System został zaprojektowany pod codzienną pracę operacyjną:

- timeline po pojedynczych egzemplarzach sprzętu
- zaznaczanie zakresu komórek jak w Excelu
- rezerwacje z blokadą konfliktów na poziomie bazy danych
- wspólne projekty dla wielu urządzeń z automatycznym `projectNumber`
- rozszerzanie istniejącego tasku na kolejne urządzenia w pionie na timeline
- panel administracyjny sprzętu i użytkowników
- per-user zapis kolejności typów sprzętu i poszczególnych urządzeń
- pełne REST API do integracji z ERP/CRM
- czytelny widok mobilny z trybem zaznaczania na telefonie

## Funkcje

- timeline z grupowaniem po typach sprzętu
- sticky header dat i sticky pierwsza kolumna
- zaznaczanie wielu dni dla jednego lub wielu urządzeń
- tworzenie rezerwacji po zaznaczeniu
- edycja i usuwanie rezerwacji z poziomu UI
- rozszerzanie jednej rezerwacji na kilka laptopów lub tabletów przez pionowe rozciąganie paska
- automatyczne generowanie `orderNumber` i `projectNumber` z możliwością ręcznej zmiany
- filtrowanie po typie sprzętu, statusie, kliencie i wyszukiwaniu po nazwie/SN
- zarządzanie kategoriami i egzemplarzami sprzętu
- szybkie przełączanie statusu sprzętu i inline edycja `S/N` / `TAG`
- ustawianie kolejności kategorii i urządzeń z zapisem per użytkownik
- role: `admin`, `operator`, `viewer`
- JWT auth i RBAC
- Swagger / OpenAPI pod `/docs`

## Struktura projektu

```text
RENTAL_SYSTEM/
├── apps/
│   ├── api/
│   │   ├── src/
│   │   │   ├── config/
│   │   │   ├── db/
│   │   │   ├── middleware/
│   │   │   ├── modules/
│   │   │   ├── routes/
│   │   │   └── docs/
│   │   └── tests/
│   └── web/
│       ├── app/
│       ├── components/
│       └── lib/
├── packages/
│   └── shared/
├── deploy/systemd/
├── scripts/
├── docker-compose.yml
└── .env.example
```

## Wymagania

- Node.js 20+
- npm 10+
- PostgreSQL 16+

Opcjonalnie:

- Docker + Docker Compose do lokalnego Postgresa

## Start lokalny

### 1. Zmienne środowiskowe

Plik `.env` jest potrzebny do uruchamiania backendu z hosta.

```bash
cp .env.example .env
```

Domyślnie `.env.example` jest przygotowany pod lokalne uruchamianie usług z hosta:

- API zakłada bazę na `localhost:5432`
- frontend korzysta z same-origin `/api`, a Next proxy przekazuje ruch do `INTERNAL_API_BASE_URL`

### 2. Uruchom PostgreSQL

Najprościej:

```bash
docker compose up -d postgres
```

### 3. Migracje i dane demo

```bash
npm install
npm run db:migrate
npm run db:seed
```

### 4. Start aplikacji

W osobnych terminalach:

```bash
npm run dev:api
npm run dev:web
```

Adresy:

- frontend: `http://localhost:3000`
- API health: `http://localhost:4000/health`
- Swagger UI: `http://localhost:4000/docs`
- OpenAPI JSON: `http://localhost:4000/docs/openapi.json`

## Szybki deploy

Repo ma rozdzielone tryby wdrożenia, żeby nie budować całego monorepo przy każdej małej zmianie.

Domyślny tryb:

```bash
./scripts/deploy-remote.sh
```

To jest tryb `smart`. Skrypt porównuje lokalny stan z tym, co jest już wdrożone na serwerze, a potem uruchamia tylko potrzebne kroki:

- zmiana tylko w `apps/web` -> sync + build web + restart `rental-web.service`
- zmiana tylko w `apps/api` -> sync + build api + restart `rental-api.service`
- zmiana w `packages/shared` -> build `shared` + `api` + `web`
- zmiana tylko w migracjach -> sync + migracje + restart `rental-api.service`
- brak zmian runtime -> sam sync bez builda i restartów

Tryby ręczne:

```bash
./scripts/deploy-remote.sh web
./scripts/deploy-remote.sh api
./scripts/deploy-remote.sh migrate
./scripts/deploy-remote.sh full
./scripts/deploy-remote.sh sync
```

Możesz też uruchomić sam podgląd planu bez wdrożenia:

```bash
PLAN_ONLY=1 ./scripts/deploy-remote.sh
```

Dostępne są też skróty w `npm`:

```bash
npm run deploy:smart
npm run deploy:web
npm run deploy:api
npm run deploy:migrate
npm run deploy:full
```

## Konto demo

Po seedzie dostępne są konta:

- `admin@rental.local` / `Admin12345!`
- `operator@rental.local` / `Operator123!`
- `viewer@rental.local` / `Viewer123!`

## Testy i walidacja

Lint:

```bash
npm run lint
```

Build:

```bash
npm run build
```

Testy backendu:

```bash
npm run test
```

Testy obejmują:

- logowanie
- pobranie timeline
- blokadę konfliktu rezerwacji
- zapis preferencji kolejności widoku per użytkownik
- rozszerzanie zakresu jednego projektu na kilka urządzeń

## Architektura

### Backend

Backend jest modułowy:

- `auth`: logowanie, JWT, aktualny użytkownik
- `auth`: logowanie, JWT, aktualny użytkownik, preferencje widoku użytkownika
- `users`: CRUD użytkowników i kontrola ostatniego aktywnego admina
- `categories`: CRUD typów sprzętu
- `items`: CRUD pojedynczych egzemplarzy sprzętu
- `bookings`: tworzenie i edycja rezerwacji, scope projektu na wiele urządzeń
- `timeline`: agregacja widoku osi czasu
- `statuses`: słownik statusów urządzeń

Najważniejsza reguła biznesowa jest egzekwowana także w bazie:

- brak podwójnej rezerwacji tego samego urządzenia

Realizacja:

- PostgreSQL `EXCLUDE USING GIST`
- `daterange(start_date, end_date, '[]') && ...`

To oznacza, że konflikt jest blokowany nawet wtedy, gdyby dwa procesy próbowały zapisać nakładające się terminy równolegle.

### Frontend

Frontend używa App Routera Next.js i jest zbudowany jako aplikacja operacyjna:

- widok timeline jest zoptymalizowany pod szybkie filtrowanie i nawigację
- na desktopie działa zaznaczanie pointerem jak w siatce
- na telefonie dostępny jest jawny `Tryb zaznaczania`, żeby nie mieszać scrolla z wyborem zakresu
- administracja sprzętem i użytkownikami działa w tych samych komponentach, bez przeładowywania strony

## API

Główne endpointy:

- `POST /api/auth/login`
- `GET /api/auth/me`
- `GET /api/auth/preferences`
- `PATCH /api/auth/preferences`
- `GET /api/timeline`
- `GET /api/equipment-categories`
- `POST /api/equipment-categories`
- `GET /api/equipment-items`
- `POST /api/equipment-items`
- `GET /api/bookings`
- `POST /api/bookings`
- `PATCH /api/bookings/:id/scope`
- `PATCH /api/bookings/:id`
- `DELETE /api/bookings/:id`
- `GET /api/users`
- `POST /api/users`
- `PATCH /api/users/:id`
- `DELETE /api/users/:id`
- `GET /api/device-statuses`

Swagger:

- [http://localhost:4000/docs](http://localhost:4000/docs)

OpenAPI JSON:

- [http://localhost:4000/docs/openapi.json](http://localhost:4000/docs/openapi.json)

### Przykładowe requesty

Logowanie:

```bash
curl -X POST http://localhost:4000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@rental.local","password":"Admin12345!"}'
```

Pobranie timeline:

```bash
curl "http://localhost:4000/api/timeline?from=2026-04-15&to=2026-05-26" \
  -H "Authorization: Bearer <JWT_TOKEN>"
```

Tworzenie rezerwacji dla dwóch urządzeń:

```bash
curl -X POST http://localhost:4000/api/bookings \
  -H "Authorization: Bearer <JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "equipmentItemIds": ["UUID_1", "UUID_2"],
    "customerName": "Acme Events",
    "projectName": "Roadshow Q3",
    "notes": "Dostawa dzień wcześniej",
    "startDate": "2026-05-02",
    "endDate": "2026-05-07",
    "dayRate": 150
  }'
```

Jeśli `orderNumber` zostanie pominięty albo przesłany jako pusty string, system wygeneruje go automatycznie w formacie `ZAM-YYYYMMDD-0001`. Własny numer można nadal wpisać ręcznie.
Jeśli `projectNumber` zostanie pominięty albo przesłany jako pusty string, system wygeneruje go automatycznie w formacie `PRJ-YYYYMMDD-0001`. Ten numer jest współdzielony przez wszystkie urządzenia należące do jednego tasku.
Jeśli podasz `dayRate`, a pominiesz `totalPrice`, system automatycznie policzy cenę całkowitą jako `liczba dni × stawka dzienna`. `totalPrice` można też wpisać ręcznie.

## Dane demo

Seed generuje między innymi:

- laptopy 14", 15", Lenovo Legion, MacBook Pro
- tablety Samsung, iPad, iPad Pro
- monitory 21,5" i 24"
- Logitech Spotlight
- przykładowe urządzenia w statusach `active`, `service`, `retired`
- przykładowe rezerwacje w przeszłości i przyszłości

## Produkcyjny model wdrożenia

### Wariant rekomendowany dla tej firmy

Najpraktyczniejszy model dla codziennej pracy operacyjnej:

1. PostgreSQL jako osobna usługa na serwerze lub osobny kontener.
2. `api` i `web` uruchamiane przez `systemd`.
3. Reverse proxy na hostcie:
   - `/` -> frontend
   - `/api` -> backend
4. HTTPS na poziomie reverse proxy.
5. Regularny backup PostgreSQL.

### Pliki deploymentowe w repo

- `deploy/systemd/rental-api.service`
- `deploy/systemd/rental-web.service`
- `scripts/start-api.sh`
- `scripts/start-web.sh`
- `scripts/deploy-api.sh`
- `scripts/deploy-web.sh`
- `scripts/run-migrations.sh`
- `scripts/deploy-remote.sh`

### Docelowa ścieżka na serwerze

Repo jest przygotowane pod ścieżkę:

```text
/opt/rental-system
```

To celowo odpowiada modelowi użytemu w projekcie `TRACKER`.

### Wdrożenie na host `192.168.2.19`

Przewidziany użytkownik systemowy:

```text
pr
```

Minimalna sekwencja po skopiowaniu projektu:

```bash
cd /opt/rental-system
npm install
npm run db:migrate
npm run build
sudo systemctl restart rental-api
sudo systemctl restart rental-web
```

Jeżeli frontend ma działać pod jedną domeną i jednym portem, rekomendowany układ jest taki:

- `WEB_PORT=8103`
- `API_PORT=4100`
- `APP_BASE_URL=http://ntbk.visau.duckdns.org:8103`
- `API_BASE_URL=http://ntbk.visau.duckdns.org:8103`
- `INTERNAL_API_BASE_URL=http://127.0.0.1:4100`
- `NEXT_PUBLIC_API_BASE_URL=/api`

Wtedy:

- użytkownik otwiera tylko `http://ntbk.visau.duckdns.org:8103`
- frontend woła `/api/...`
- Next.js proxy przekazuje `/api` do lokalnego backendu na `4100`

Jeżeli na hostcie ma być pełna automatyzacja z lokalnej maszyny, można użyć:

```bash
./scripts/deploy-remote.sh
```

Wymagane jest działające logowanie SSH do `pr@192.168.2.19`.

## Rozwój w przyszłości

Architektura jest przygotowana pod:

- eksport CSV
- eksport PDF
- batch operations dla wielu rezerwacji
- integrację z ERP / CRM
- dodatkowe statusy logistyczne
- historię zmian i audit log
- webhooki / synchronizację z systemami zewnętrznymi
