# Update — 28 Jul 2026

Backend changes for this round. Copy `app/`, `config/`, `database/`, `resources/`
and `routes/` over the existing project, then:

```bash
php artisan migrate
php artisan db:seed --class=RolePermissionSeeder
php artisan optimize:clear
```

The seeder is safe to re-run: it only adds the new permissions and re-grants
existing ones.

All code and UI text is English.

---

## 1. Office time 09:00 and H:M:S lateness

- **New** `config/attendance.php` — `office_start` is `09:00:00` for every
  employee, field force and office staff alike. `late_grace_minutes` defaults
  to `0`, so 09:00:01 already counts as late. Both are overridable through
  `ATTENDANCE_OFFICE_START` / `ATTENDANCE_LATE_GRACE_MINUTES` in `.env`.
- **Migration** `add_seconds_to_attendances_table` — adds `late_seconds` and
  `worked_seconds` and backfills them from the existing minute columns. The
  minute columns stay for anything already reading them.
- Lateness is measured from 09:00, not from the end of the grace window: the
  grace only decides *whether* the day is flagged late.
- `Attendance::hms()` plus `late_hms` / `worked_hms` accessors render
  `HH:MM:SS` (hours are not wrapped at 24, so a long shift reads `26:10:00`).
- Shown as H:M:S in the daily attendance screen, the attendance report, the
  PDF/Excel export and every attendance API response.

## 2. D.I.C — depot scoped

- `HasSubordinates` gained depot scoping. A Depot in Charge sits outside the
  MPO → AM → RSM line, so `descendants()` returned nothing for them and every
  screen came up empty. They are now scoped to the depot they are posted to:
  its employees, orders, invoices, attendance and reports, and nothing else.
- New permissions for the role: `order.approve` (invoice approval — an order
  is approved here and the invoice is generated from it on delivery),
  `order.deliver`, `payment.collect`, `customer.view`, `doctor.view`,
  `attendance.view-all`, `report.view`, `report.export`.
- Depot filtering is applied on top of, not instead of, the employee filter,
  because an order raised by head office carries no `employee_id` and would
  otherwise slip past.
- Acting on another depot's order or invoice returns 403 — route model
  binding resolves any id on its own, so the check is explicit.
- The invoice list gained a **Collect** button and modal for anyone with
  `payment.collect`, so a D.I.C can take money straight from the list.

## 3. Sales targets

- **Assign by depot.** The form now asks for a depot first and lists only that
  depot's active employees. Changing the depot clears a selection that no
  longer belongs to it.
- **Edit and delete.** New `targets.edit` / `targets.update` routes, with Edit
  and Delete buttons on the target list and an Edit button on the achievement
  page. Deleting a target removes its product breakdown with it.
- **Auto-calculation.** Each product row carries a unit price (pre-filled from
  the product's trade price) and fills Target Value from qty × price as you
  type. The figure stays editable for a negotiated number, and typing in it
  stops the auto-fill for that row. A live footer totals quantity and value,
  and *Use product total* copies the total into the main sales target.
  The price is stored (`sales_target_products.unit_price`) so reopening a
  target shows the same numbers.
- **Roll-up to the top.** `rolledUpTarget()` now always sums the employee's own
  target plus every subordinate's, so a target set on an M.P.O is visible all
  the way up. Call and collection targets and the product breakdown roll up
  the same way.
  - Previously a manager's own figure short-circuited the roll-up, which hid
    the team's targets and paired a non-rolled-up target with a rolled-up
    achievement.
  - Consequence: a manager's own figure is a personal top-up *on top of* the
    team, not a restatement of the team total.
  - The list shows **Own Target** and **Team Target (rolled up)** side by side;
    the page totals use the own figures so nothing is counted twice.

## 4. Customers / chemists / doctors added from the app

- **Migration** `add_approval_to_parties_tables` — adds `photo`,
  `approval_status`, `approved_by`, `approved_at` and `rejection_reason` to
  `customers`, `chemists` and `doctors`. The column defaults to `approved`, so
  every existing row and anything created from the admin panel stays live; the
  API sets `pending` explicitly.
- API `POST /customers`, `/chemists`, `/doctors` accept a `photo` upload and
  `latitude` / `longitude`, and save the record as **pending**.
- The creator's direct supervisor is notified; the creator is notified of the
  decision, with the reason on a rejection.
- **Upline review**
  - App: `GET /approvals/pending`, `GET /approvals/pending-count`,
    `POST /approvals/{type}/{id}/approve`, `POST /approvals/{type}/{id}/reject`.
  - Web: new **Approvals** page with photo, GPS link, submitter, and
    approve/reject with a reason. Sidebar link carries a pending badge.
  - Both are scoped to the reviewer's own team, and nobody can approve a
    record they created themselves.
- Pending parties are excluded from order creation at every level — the
  customer dropdown, the ownership check and `OrderService` itself.
- The three party lists gained an Approval column, a photo link and an
  approval-status filter.

## 5. Profile self-service (API)

- `GET /profile`, `POST /profile` (name / phone), `POST /profile/photo`,
  `POST /profile/password`.
- Changing the password revokes every *other* device's token and leaves the
  current one signed in.
- Replacing the profile picture deletes the previous file.
- `photo_url` is returned by login, `/me` and the profile endpoints.

## 6. One route moved

`GET /file/{path}` moved **outside** the auth group. It serves the `public`
disk — the same files the `public/storage` symlink would expose — and the
Android app loads profile pictures and shop photos from it with a plain image
request that carries no session cookie and no bearer token. Behind auth those
images would have redirected to the login page.
