# Planif_livr (PHP)

Port of the Lightelec "Planif_livr" delivery-planning app onto the mandated
stack: HTML5/CSS3, PHP 8.2+, MariaDB via PDO.

**Foundation** (done): project skeleton, database schema, authentication, the
DEV/ADMIN/USER role hierarchy, mandatory TOTP 2FA for ADMIN/DEV credential
changes, and the PWA shell.

**Business modules** — all 5 "core" modules are done; accessible to every
logged-in role (USER/ADMIN/DEV) unless noted, fully collaborative data (no
ownership restriction), matching the original app:

- **Planning** (`planning.php`) — month-view delivery calendar, pending
  sidebar, native HTML5 drag-and-drop to schedule/unschedule, partial-delivery
  splitting, >4-pallet capacity warnings, holidays. `src/Models/DeliveryRepository.php`
  / `HolidayRepository.php`, `public/api/deliveries-move.php`.
- **Emplacements Commandes** (`orders.php`) — two-column kanban (Réception/DG),
  drag-and-drop, full or partial transfer into a pending Planning delivery.
  `src/Models/OrderLocationRepository.php`, `public/api/order-locations-move.php`.
- **Retours** (`returns.php`) — fixed 7-status lifecycle, mandatory refusal
  reason, JSON history log, 15-day auto-archive. `src/Models/ReturnRepository.php`.
- **Mvt. Stock** (`stock.php`) — stock discrepancy table with computed delta,
  archive-and-clear into a JSON snapshot. `src/Models/StockMovementRepository.php`
  / `StockArchiveRepository.php`.
- **Matériel** (`materials.php`) — 4 supplier columns, item CRUD. **Only module
  with a role split**: recurring items and column rename require ADMIN/DEV,
  enforced server-side (not just hidden in the UI). `src/Models/MaterialRepository.php`.

Every module except Planning/Orders is pure PRG form POSTs (no JS/fetch at
all); Planning and Orders each have exactly one small JSON endpoint for their
drag-and-drop gesture. **Deferred to a later polish pass, not silently
dropped**: PDF export (Retours/Stock/Matériel all had a PDF button in the
original), configurable return-status colors, and week/day calendar views for
Planning (month-view only so far).

## Requirements

- PHP >= 8.2 with the `pdo_mysql`, `sodium`, `gd` (QR code rendering), `zip`
  (Composer package extraction), `mbstring`, `openssl`, and `curl` extensions
  enabled
- MariaDB 10.6+ (or MySQL 8+)
- Composer
- HTTPS in production (secure session cookies require it — see below)

## Setup

1. **Install dependencies**

   ```
   composer install
   ```

2. **Create the database and apply migrations**, in order:

   ```
   mysql -u root -p -e "CREATE DATABASE planif_livr CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
   mysql -u root -p planif_livr < database/migrations/001_create_users.sql
   mysql -u root -p planif_livr < database/migrations/002_create_totp_recovery_codes.sql
   mysql -u root -p planif_livr < database/migrations/003_create_pending_credential_changes.sql
   mysql -u root -p planif_livr < database/migrations/004_create_credential_audit_log.sql
   mysql -u root -p planif_livr < database/migrations/005_create_login_throttle.sql
   ```

3. **Configure secrets**: `config/secrets.php` already exists with a locally-generated
   TOTP encryption key and placeholder DB credentials — edit the `db` section
   to match your MariaDB user/password. For a fresh clone, copy the template
   instead:

   ```
   cp config/secrets.example.php config/secrets.php
   php -r "echo base64_encode(sodium_crypto_secretbox_keygen());"   # paste into totp_encryption_key_base64
   ```

   **Never commit `config/secrets.php`** — it's gitignored.

4. **Seed the root DEV account** (exactly once — this account can never be
   demoted or deleted through the app, only by direct database access):

   ```
   php database/seed/seed_root_dev.php --email=you@example.com --name="Your Name"
   ```

5. **Point your web server's document root at `public/`.** Everything
   outside `public/` (`src/`, `config/`, `templates/`, `database/`) must stay
   web-inaccessible. `.htaccess` files are included in `config/`, `src/`,
   `database/`, and `templates/` as defense-in-depth for Apache (in case a
   host ever points the vhost at the project root instead of `public/`) —
   Nginx doesn't read `.htaccess`, so on Nginx make sure your server block's
   `root` directive points at `public/` directly. For local dev with PHP's
   built-in server:

   ```
   php -S localhost:8000 -t public
   ```

## First login

1. Go to `/login.php`, sign in with the root DEV's email + password.
2. You'll be forced to set a new password immediately (no 2FA prompt yet —
   there's no secret to protect on a brand-new account).
3. You'll then be forced into TOTP enrollment: scan the QR code with an
   authenticator app (Google Authenticator, Microsoft Authenticator, Authy…),
   confirm with a 6-digit code, and save the recovery codes shown — they're
   displayed exactly once.
4. From then on, every ADMIN/DEV login requires that 2FA code, and any
   change to another ADMIN/DEV's email, password, or role must be confirmed
   by the target account's own 2FA on `/admin/pending-approvals.php` before
   it takes effect.

## Roles at a glance

| Role | Login | Notes |
|---|---|---|
| DEV | email + password | Full rights; only DEV can create/change ADMIN accounts or promote to DEV; the seed root DEV can never be demoted/deleted in-app. |
| ADMIN | email + password | Manages USER and other ADMIN accounts (email/password), but cannot touch DEV accounts or promote/demote to/from ADMIN/DEV. |
| USER | 3-letter code + password | Basic application access only. |

## Current dev environment

- **Database**: MariaDB 11.4 on `192.168.0.22:3306`, database `planif_ard`
  (reachable over VPN). A dedicated `planif_livr_app` MariaDB user was
  created with least-privilege DML-only grants (`SELECT/INSERT/UPDATE/DELETE`
  — no DDL) and is configured in `config/secrets.php`. All 5 migrations have
  been applied there.
- **PHP/Composer**: PHP 8.3.33 (thread-safe, x64) and Composer 2.10.2 were
  installed locally at `C:\php83` (not previously present on this machine)
  and added to this Windows user's `PATH`. `composer install` has been run;
  `vendor/` is populated.
- **Root DEV account**: seeded (id=1, `valentin.mathelin@outlook.be`). Its
  password was set at seed time; **TOTP is deliberately left un-enrolled** —
  the full login → forced-onboarding → enrollment → 2FA-gated-login →
  admin-console flow was verified end-to-end against this real database
  during setup (using a programmatically-derived code, not a phone), then
  the TOTP secret/recovery codes were reset so the very first real
  enrollment (QR scan with an actual authenticator app) happens for you, not
  for this session.
- **Not yet done**: the app has only been run via PHP's built-in dev server
  (`php -S`) for verification, not deployed behind a real Apache/Nginx vhost.
  Follow step 5 above (or your own hosting setup) to actually serve it.

## Security checklist verified during this build

Prepared statements everywhere, `htmlspecialchars()` on all dynamic output,
CSRF tokens, secure session flags, TOTP secrets encrypted at rest with
libsodium (confirmed via a real encrypt → store → fetch → decrypt → verify
round trip against the live database), role checks re-validated server-side
on every credential-changing action. All 54 PHP files pass `php -l`.
