# Customer Mobile API Guide

API for the **End User (customer) app**. The technician / staff app has its own guide: [STAFF_MOBILE_API.md](STAFF_MOBILE_API.md).

## Base URL and authentication

| Environment | Base URL |
|---|---|
| **Live** | **`https://odyssey-crm.dextragroups.com`** |
| Local testing | `http://127.0.0.1:8000` (same computer) or the computer's LAN IP, e.g. `http://192.168.1.10:8000` |

All endpoints are under `/api/client`, e.g. `https://odyssey-crm.dextragroups.com/api/client/login`.

The customer gets their login (phone / email + password) by email/SMS when the order is confirmed. Log in once, store the `token` securely, and send it with every protected request:

```http
Authorization: Bearer <token>
Accept: application/json
```

A customer token works only on `/api/client/*` (a staff token is rejected with `403`, and vice versa). Dates are ISO-8601 in UTC (`…Z`) unless shown otherwise — convert to local time for display. File links (photos, drawings) are full URLs; PDF downloads need the bearer token.

### Common responses

- `401` — missing / invalid / revoked token: log in again.
- `403` — the token is not a customer token.
- `404` — the thing asked for doesn't exist yet (e.g. no site visit scheduled).
- `422` — invalid fields, or the action isn't allowed right now (e.g. step not open yet). Body: `{ "message": "...", "errors": { "field": ["..."] } }`.

## Endpoint overview

| # | API | Method | Path |
|---|---|---|---|
| **Account** ||||
| 1 | Login with phone | `POST` | `/api/client/login` |
| 2 | Login with email | `POST` | `/api/client/login-email` |
| 3 | Login with phone or email | `POST` | `/api/client/login-flexible` |
| 4 | My profile | `GET` | `/api/client/me` |
| 5 | Logout | `POST` | `/api/client/logout` |
| **Order tracking (journey)** ||||
| 6 | Journey — all steps | `GET` | `/api/client/journey` |
| 7 | Journey — progress summary | `GET` | `/api/client/journey/status` |
| 8 | Acknowledge a completed step | `POST` | `/api/client/journey/{stepKey}/confirm` |
| **Site details & site visit** ||||
| 9 | Site details — get | `GET` | `/api/client/journey/site-details` |
| 10 | Site details — submit / update | `POST` | `/api/client/journey/site-details` |
| 11 | Site visit appointment | `GET` | `/api/client/journey/site-visit/status` |
| 12 | Accept site visit | `POST` | `/api/client/journey/site-visit/accept` |
| 13 | Decline site visit | `POST` | `/api/client/journey/site-visit/reject` |
| 14 | Ask to reschedule site visit | `POST` | `/api/client/journey/site-visit/request-reschedule` |
| 14a | Site Visit Report — preview | `GET` | `/api/client/journey/site-visit/report` |
| 14b | Site Visit Report — PDF | `GET` | `/api/client/journey/site-visit/report.pdf` |
| **Drawing** ||||
| 15 | Drawing — get | `GET` | `/api/client/journey/drawing` |
| 16 | Approve drawing | `POST` | `/api/client/journey/drawing/approve` |
| 17 | Reject drawing | `POST` | `/api/client/journey/drawing/reject` |
| **Site readiness** ||||
| 18 | Site readiness — get | `GET` | `/api/client/journey/site-readiness` |
| 19 | Submit site readiness (photos) | `POST` | `/api/client/journey/site-readiness` |
| **Pre-Installation Visit (PIV)** ||||
| 20 | PIV status | `GET` | `/api/client/journey/piv` |
| 21 | PIV report PDF | `GET` | `/api/client/journey/piv/report.pdf` |
| **Lift delivery** ||||
| 22 | Delivery status | `GET` | `/api/client/journey/delivery` |
| 23 | Confirm delivery | `POST` | `/api/client/journey/delivery` |
| **Feedback** ||||
| 24 | Feedback — get | `GET` | `/api/client/journey/feedback` |
| 25 | Give feedback (rating 1–5) | `POST` | `/api/client/journey/feedback` |
| **Quotations & projects** ||||
| 26 | Quotations | `GET` | `/api/client/quotations` · `/api/client/quotations/{id}` |
| 27 | Projects | `GET` | `/api/client/projects` · `/api/client/projects/{project}` |
| 28 | Project handover | `GET` | `/api/client/projects/{project}/handover` |

## The journey (order tracking)

The customer's order moves through these steps (the admin can rename / reorder them; always use `key`):

| # | `key` | Title | Done by | Customer app action |
|---|---|---|---|---|
| 1 | `welcome_call` | Welcome Call | CRM executive | — |
| 2 | `site_details` | Site Details | **Customer** | #9–#10 |
| 3 | `site_visit_appointment` | Site Visit Appointment | Technical team | #11–#14 accept / decline / reschedule |
| 4 | `technical_assign` | Technical Person Assigned | Technical head | — |
| 5 | `site_visit_confirmation` | Site Visit Confirmation | Technician (report) | — |
| 6 | `drawing_upload` | Drawing Upload | CRM team | #15 view the drawing |
| 7 | `drawing_confirmation` | Drawing Confirmation | **Customer** | #16–#17 approve / reject |
| 8 | `payment` | Payment | CRM executive | — |
| 9 | `moved_to_production` | Moved to Production | CRM head | — |
| 10 | `bom` | BOM | Technical head | — |
| 11 | `inventory` | Inventory | Technical head | — |
| 12 | `site_readiness` | Site Readiness | **Customer** | #18–#19 upload site photos (opens after BOM + Inventory) |
| 13 | `piv` | Pre-Installation Visit (PIV) | Technician (report, customer signs on site) | #20–#21 status and signed report |
| 14 | `lift_dispatch` | Lift Dispatch | CRM head | — |
| 15 | `delivery_confirmation` | Lift Delivery Confirmation | **Customer** | #22–#23 confirm the lift arrived |
| 16 | `technical_tasks` | Technical Tasks (installation) | Technical team | — |
| 17 | `site_handover` | Site Handover | CRM / technical team | #28 (project handover) |
| 18 | `client_feedback` | Client Feedback | **Customer** | #24–#25 rating 1–5 |
| 19 | `project_closed` | Project Closed | CRM head | — |

### 1–3. Login

`POST /api/client/login` — `{ "phone": "9876543210", "password": "...", "device_name": "android" }`

```json
{
  "status": "success",
  "token": "12|plain-text-token",
  "customer": {
    "id": 10, "customer_code": "CUST-2026-00010", "name": "Customer Name", "phone": "9876543210",
    "email": "customer@example.com", "company_name": "…", "address": "…", "city": "Chennai",
    "state": "Tamil Nadu", "pincode": "600042", "country": "India", "status": "quotation"
  },
  "quotations": [
    { "id": 3, "source": "proposal", "quotation_number": "PROP-HP-2026-0001", "lift_type": "ORBIT STD 1100",
      "no_of_stops": 3, "amount": 2030475, "currency": "INR", "status": "approved", "project": null }
  ],
  "projects": [
    { "id": 9, "project_code": "PRJ-2026-00009", "status": "confirmed", "lift_type": "ORBIT STD 1100",
      "no_of_stops": 3, "site_address": "…", "total_amount": "2030475.00", "currency": "INR",
      "advance_amount": "250000.00", "advance_paid_at": "2026-10-26" }
  ],
  "summary": { "total_quotations": 1, "total_projects": 1 }
}
```

`login-email` takes `{ "email", "password", "device_name?" }`; `login-flexible` takes `phone` **or** `email` plus `password`. Both return `{ "token", "customer": { id, customer_code, name, phone, email, status } }`. Wrong credentials → `422` (`errors.phone` / `errors.email` / `errors.credentials`).

### 4–5. Profile, logout

`GET /api/client/me` → `{ "status": "success", "customer": { …customer fields… } }` · `POST /api/client/logout` → `{ "message": "Logged out." }`.

### 6. Journey — all steps

`GET /api/client/journey`

```json
{
  "customer_id": 10,
  "customer_code": "CUST-2026-00010",
  "customer_name": "Customer Name",
  "customer_email": "customer@example.com",
  "customer_phone": "9876543210",
  "customer_status": "quotation",
  "current_step": "BOM",
  "steps": [
    {
      "key": "site_visit_appointment",
      "label": "Site Visit Appointment",
      "completed": true,
      "completed_at": "2026-10-26T05:17:00.000000Z",
      "remarks": "Site visit assigned to technician",
      "client_approved": null,
      "client_confirmed": true,
      "client_confirmed_at": "2026-10-26T05:20:00.000000Z",
      "client_confirmed_remarks": null,
      "scheduled_date_time": "2026-10-26T06:15:00.000000Z",
      "rescheduled_date_time": null,
      "appointment_notes": null,
      "appointment_status": "confirmed",
      "image_urls": []
    }
  ],
  "site_details": { "submitted": true, "site_address": "…", "map_url": "…" },
  "site_visit": { "id": 6, "status": "completed", "scheduled_at": "…", "technician": { "name": "Technician", "phone": "…" }, "completed_at": "…" }
}
```

**Stages (show these as the tracker).** The client's journey has 11 stages: Quote → Order Confirmation → Welcome Call → Site Survey → Drawing Upload & Confirmation → Moved to Production → PIV → Dispatch & Delivery → Installation → Handover → S&M. `stages` (with `current_stage`, its label) lists them in order: `{ "no": 4, "key": "site_survey", "label": "Site Survey", "state": "current", "completed_at": null, "done": 1, "total": 4, "steps": [{ "key": "site_details", "label": "Site Details", "state": "completed", "completed_at": "…" }, …] }`. `state` is `completed`, `current` or `pending`; `done`/`total` count the working steps inside the stage (show "1/4" on the current stage, and its `steps` as sub-steps). Quote and Order Confirmation are always `completed`. Stage keys: `quote`, `order_confirmation`, `welcome_call`, `site_survey`, `drawing`, `production` (payment, production, BOM, inventory), `piv` (site readiness, PIV), `dispatch_delivery`, `installation`, `handover` (handover report, feedback), `service` (project closed → service & maintenance).

Show a step as **done** when `completed` is `true`; the first step with `completed: false` is the current one (`current_step`). `image_urls[].type` is `image` or `video`. Use `steps` for step details and actions (site details, drawing, site readiness, delivery, feedback).

### 7. Progress summary

`GET /api/client/journey/status` → `{ "customer_code", "current_step", "progress_percent": 42.11, "stats": { total_steps, completed_steps, confirmed_steps, pending_steps, approval_pending }, "status_breakdown": { pending, in_progress, completed } }`.

### 8. Acknowledge a completed step

`POST /api/client/journey/{stepKey}/confirm` — `{ "remarks": "optional" }`. Marks that the customer has seen a completed step (it does **not** complete it). `422` if the step isn't done yet. → `{ "message": "Welcome Call confirmed.", "step_key", "client_confirmed_at" }`.

## Site details & site visit

### 9–10. Site details (after the welcome call)

`GET /api/client/journey/site-details` → `{ "step_key": "site_details", "can_edit": true, "site_details": {...} }`

`POST /api/client/journey/site-details` (JSON):

| Field | Rule |
|---|---|
| `latitude`, `longitude` | Required — pin from the phone's map |
| `site_address` | Required |
| `contact_person_name`, `contact_person_phone` | Required (phone 7–20 characters) |
| `landmark`, `city`, `state`, `pincode`, `alternate_phone`, `notes` | Optional |
| `preferred_visit_date` | Optional, `YYYY-MM-DD`, today or later |

→ `{ "message": "Site details received. …", "site_details": {...} }`. `422` once the site visit is complete (`can_edit: false`).

### 11. Site visit appointment — preview

`GET /api/client/journey/site-visit/status` (`404` until a visit is scheduled)

The customer app should show this as a **preview card** when the site visit appointment is scheduled. It tells the customer when the technician is coming, who is coming, whether they have accepted/declined, and the current survey status.

```json
{
  "step_key": "site_visit_appointment",
  "scheduled_date_time": "2026-10-27T05:00:00.000000Z",
  "rescheduled_date_time": null,
  "appointment_status": "scheduled",
  "appointment_notes": null,
  "client_confirmed_at": null,
  "client_confirmed_remarks": null,
  "technician": { "name": "Technician Name", "phone": "9000000001" },
  "technicians": [{ "name": "Technician Name", "phone": "9000000001" }, { "name": "Second Technician", "phone": "9000000002" }],
  "visit_status": "assigned",
  "survey_completed_at": null,
  "can_accept": true,
  "can_reject": true
}
```

**Preview card fields:**

| Field | What to show |
|---|---|
| `scheduled_date_time` | Appointment date and time (convert UTC → local) |
| `rescheduled_date_time` | If set, show "Rescheduled to …" instead |
| `appointment_status` | Appointment status badge (see table below) |
| `technicians` | List of technicians coming; show name + phone for easy calling |
| `technician` | Main/lead technician (shown first in `technicians`) |
| `visit_status` | Survey progress badge (see table below) |
| `survey_completed_at` | If set, show "Survey completed on …" |
| `can_accept` / `can_reject` | Show Accept / Decline buttons only when both are `true` |

**`appointment_status` values:**

| Value | Meaning | Badge / message |
|---|---|---|
| `scheduled` | Assigned; customer has not responded yet | "Appointment scheduled" |
| `confirmed` | Customer accepted | "Confirmed by you" |
| `rejected` | Customer declined | "Declined — team will reschedule" |
| `reschedule_requested` | Customer asked for another date | "Reschedule requested" |
| `rescheduled` | Date changed by the technical team | "Rescheduled" |

**`visit_status` values (the survey progress):**

| Value | Meaning | Badge |
|---|---|---|
| `assigned` | Scheduled, technician not yet at site | "Upcoming" |
| `in_progress` | Technician is currently at the site | "In progress" |
| `completed` | Site survey done | "Survey completed" |
| `cancelled` | Appointment cancelled | "Cancelled" |

Show the Accept / Decline buttons only when `can_accept` and `can_reject` are `true` (i.e. appointment is open and the customer hasn't responded yet). Once the customer accepts/declines, both flags become `false`.

### 12–14. Respond to the site visit

| API | Body |
|---|---|
| `POST /api/client/journey/site-visit/accept` | `{ "remarks": "optional" }` |
| `POST /api/client/journey/site-visit/reject` | `{ "rejection_reason": "optional" }` |
| `POST /api/client/journey/site-visit/request-reschedule` | `{ "preferred_date_time": "2026-10-28 11:00:00", "reason": "optional" }` (`Y-m-d H:i:s`, future, optional) |

Each returns `{ "message": "..." }`; the technical team is notified on decline / reschedule. `404` when there is no upcoming visit.

### 14a. Site Visit Report — preview

`GET /api/client/journey/site-visit/report` — the report the technician submitted after the site visit, to show in the app. `report` is `null` (with a `message`) until it is submitted.

```json
{
  "report": {
    "id": 6,
    "completed_at": "2026-10-26T06:20:00.000000Z",
    "technicians": [{ "name": "Technician Name", "phone": "9000000001" }],
    "sections": [
      { "section": "Site Details", "items": [
        { "question": "Type of building", "type": "select", "value": "Villa" },
        { "question": "Site condition", "type": "select", "value": "Under construction" }
      ] },
      { "section": "Lift Model", "items": [ … ] },
      { "section": "Checklist", "items": [ { "question": "Checklist (tick what you have checked)", "type": "multi_select", "value": ["Pit area / floor level area"] } ] },
      { "section": "Floor Measurements", "items": [ { "question": "Ground to first floor (mm)", "type": "number", "value": "3100" } ] },
      { "section": "Customer Acknowledgement", "items": [ … ] }
    ],
    "photo_urls": ["https://odyssey-crm.dextragroups.com/storage/…/site-1.jpg"],
    "technician_signature_url": "https://odyssey-crm.dextragroups.com/storage/…/technician-sign.png",
    "customer_signature_url": "https://odyssey-crm.dextragroups.com/storage/…/customer-sign.png",
    "remarks": "Shaft area clear.",
    "report_pdf_url": "https://odyssey-crm.dextragroups.com/api/client/journey/site-visit/report.pdf"
  }
}
```

Show each section with its items; `value` is a list for `multi_select` and a list of image URLs for `photo` questions. Signatures may be `null`.

### 14b. Site Visit Report — PDF

`GET /api/client/journey/site-visit/report.pdf` (with the bearer token) → the report in the Odyssey Site Visit Report layout, with both signatures. `404` until it is submitted.

## Drawing

### 15. Drawing — get

`GET /api/client/journey/drawing`

```json
{
  "drawing": {
    "uploaded": true,
    "file_url": "https://odyssey-crm.dextragroups.com/storage/customer-drawings/10/abc.pdf",
    "uploaded_at": "2026-10-26T05:25:00.000000Z",
    "remarks": "First layout",
    "status": "pending_approval",
    "customer_remarks": null,
    "decided_at": null,
    "can_respond": true
  }
}
```

`status`: `not_uploaded`, `pending_approval`, `approved`, `rejected`. The file may be PDF, JPG, PNG, DWG or DXF.

### 16–17. Approve / reject

- `POST /api/client/journey/drawing/approve` — `{ "remarks": "optional" }` → completes **Drawing Confirmation**.
- `POST /api/client/journey/drawing/reject` — `{ "remarks": "Move the door to the left" }` (**required**) → the CRM team uploads a revised drawing, which comes back as `pending_approval`.

Both return `{ "message", "drawing": {...} }`. `422` when there is nothing to respond to (`can_respond: false`).

## Site readiness

Opens after **BOM and Inventory** are completed. The customer prepares the site (power, pit, scaffolding, core cutting as per the Customer Scope) and uploads photos.

### 18. Site readiness — get

`GET /api/client/journey/site-readiness`

```json
{
  "site_readiness": {
    "available": true,
    "status": "pending",
    "locked_reason": null,
    "submitted_at": null,
    "remarks": null,
    "photos": []
  }
}
```

`status`: `locked` (show `locked_reason` — "Opens after BOM and Inventory are completed."), `pending` (show the upload form), `submitted` (show `photos` and `remarks`).

### 19. Submit site readiness

`POST /api/client/journey/site-readiness` — **multipart/form-data**:

| Field | Rule |
|---|---|
| `photos[]` | **Required**, 1–10 images (jpg / png / webp), max 10 MB each |
| `remarks` | Optional, max 1000 characters |

`201` → `{ "message": "Site readiness submitted.", "site_readiness": { "status": "submitted", "photos": ["https://…/storage/site-readiness/customer-10/x.jpg"], … } }`. Submitted once only; `422` if locked or already submitted.

## Pre-Installation Visit (PIV)

The technical team visits to re-check the site before dispatch; the customer signs on the technician's phone. The customer app shows a **read-only preview** — the customer cannot accept/decline a PIV, only the site survey appointment.

### 20. PIV status — preview

`GET /api/client/journey/piv`

Shows the current PIV status. The customer sees this as a preview card at the PIV journey step. Returns `{ "piv": { "status": "not_requested" } }` until a PIV is created.

**Responses by status:**

**Before PIV is requested:**
```json
{
  "piv": { "status": "not_requested" }
}
```

**PIV requested or assigned (coming soon):**
```json
{
  "piv": {
    "status": "assigned",
    "scheduled_at": "2026-10-28T05:00:00.000000Z",
    "technician": { "name": "Technician Name", "phone": "9000000001" },
    "completed_at": null,
    "site_ready": null,
    "issues": [],
    "remarks": null,
    "report_pdf_url": null
  }
}
```

**PIV completed:**
```json
{
  "piv": {
    "status": "completed",
    "scheduled_at": "2026-10-28T05:00:00.000000Z",
    "technician": { "name": "Technician Name", "phone": "9000000001" },
    "completed_at": "2026-10-28T06:10:00.000000Z",
    "site_ready": false,
    "issues": ["Earthing (Copper-2Nos): Not Available"],
    "remarks": "Earthing pending from customer side",
    "report_pdf_url": "https://odyssey-crm.dextragroups.com/api/client/journey/piv/report.pdf"
  }
}
```

**Preview card fields:**

| Field | What to show |
|---|---|
| `status` | Status badge (see table below) |
| `scheduled_at` | If set, "Visit scheduled for …" (convert UTC → local) |
| `technician` | Technician name and phone (call button) |
| `completed_at` | If set, "Completed on …" |
| `site_ready` | `null` = visit not done; `true` = site is ready; `false` = issues found |
| `issues` | If `site_ready` is `false`, list each issue — the customer must fix these before the lift is dispatched |
| `remarks` | Technician's note from the visit |
| `report_pdf_url` | If set, show "Download signed report" button (requires bearer token) |

**`status` values:**

| Value | Meaning | Badge |
|---|---|---|
| `not_requested` | PIV not yet created | Hidden / not shown |
| `requested` | CRM team created the PIV; technician being assigned | "Being scheduled" |
| `assigned` | Technician and date set | "Scheduled" |
| `in_progress` | Technician is currently at the site | "In progress" |
| `completed` | Report submitted and signed | `site_ready: true` → "Site ready" · `site_ready: false` → "Action required" |
| `cancelled` | Cancelled | "Cancelled" |

### 21. PIV report PDF

`GET /api/client/journey/piv/report.pdf` (with the bearer token) → the signed Pre-Installation Survey as a PDF. `404` until the PIV is submitted.

Use the `report_pdf_url` from the PIV status response directly — it already contains the full URL. Always send the bearer token when fetching it.

## Lift delivery

### 22. Delivery status

`GET /api/client/journey/delivery` → `{ "delivery": { "status": "dispatched", "dispatched_at": "…", "dispatch_remarks": "Truck TN01", "delivered_at": null, "can_confirm": true } }`

`status`: `not_dispatched`, `dispatched`, `delivered`.

### 23. Confirm delivery

`POST /api/client/journey/delivery` — `{ "remarks": "optional" }` → completes **Lift Delivery Confirmation**; installation can start. `422` if not dispatched yet or already confirmed.

## Feedback

Opens after the **Site Handover**.

### 24. Feedback — get

`GET /api/client/journey/feedback` → `{ "feedback": { "status": "pending", "can_submit": true, "rating": null, "comments": null, "submitted_at": null } }` (`status`: `locked`, `pending`, `submitted`).

### 25. Give feedback

`POST /api/client/journey/feedback` — `{ "rating": 5, "comments": "optional" }` (`rating` required, 1–5) → `201` `{ "message": "Thank you for your feedback.", "feedback": {...} }`. Once only.

## Quotations, projects, handover (26–28)

- `GET /api/client/quotations`, `GET /api/client/quotations/{id}` — the customer's quotations.
- `GET /api/client/projects`, `GET /api/client/projects/{project}` — the customer's projects (a project is created when the order moves to production).
- `GET /api/client/projects/{project}/handover` — handover details: walkthrough / training dates, feedback, and links to the warranty, manual, signature and completion certificate (`404` until the handover is recorded).

### Older project-based endpoints

These were built for an earlier version of the flow and work only once a project exists. The journey endpoints above replace them; use them only if a screen specifically needs project data:

`projects/{project}/appointments…`, `projects/{project}/drawings…`, `projects/{project}/site-visit…`, `projects/{project}/site-readiness/submit` and `/status` (same as #18–#19), `projects/{project}/installation`, `projects/{project}/installations`, `projects/{project}/testing`, `site-visits`, `journey/{stepKey}/appointment-status`, `journey/{stepKey}/appointment-confirm`, `journey/{stepKey}/appointment-cancel`, `journey/{stepKey}/confirm-appointment`, `quotations/{id}/update-status`, `quotations/{quotation}/accept`, `POST quotations`.

## Do not use from the app

These exist on the server but are **not** for the customer app (testing / internal, and some expose other customers' data — to be secured or removed before go-live):

| Path | Why |
|---|---|
| `POST /api/client/debug-login` | Debug login |
| `GET /api/debug/customers` | Lists customers — debug only |
| `POST /api/client/send-credentials` | Resets a customer's password by ID — internal |
| `POST /api/client/register` | Customers are created by the CRM, not self-registered |
| `GET /api/client/journey-status/{customerId}`, `GET /api/client/interview-status/{id}` | Public, by ID |
| `GET /api/client/journey/customer/{id}`, `GET /api/client/journey/all-customers` | Show other customers' journeys |
| `POST /api/client/journey/site-visit/reschedule` | Staff token only |
