Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

202 lines
7.7 KiB
Markdown

# PRD: Mobile App — Documents, Products, and Avatars
## Problem Statement
The Alga PSA mobile app is missing three capabilities that exist in the web app:
1. **Documents** — No way to view, download, or attach files to tickets from mobile. Technicians in the field need to photograph issues and attach documentation.
2. **Products/Materials** — No way to add inventory items (products) to tickets. Technicians need to record parts used during on-site work.
3. **Contact & Client Avatars** — Ticket detail shows contact and client names as plain text without avatars/logos, making it harder to quickly identify entities.
## Goals
- Allow mobile users to view attached documents and upload new files (photos + files) to tickets
- Allow mobile users to add products from the catalog to tickets as materials
- Show contact avatar and client logo in the ticket detail view
## Non-Goals
- Document editing/renaming from mobile
- Document preview/inline viewing (just list + download + open)
- Multi-currency product pricing (use default rate)
- Product creation from mobile (only pick existing products)
- Deleting materials from mobile (can be added later)
## Target Users
Field technicians and support staff who use the mobile app to manage tickets on the go.
---
## Feature 15: Contact & Client Avatars in Ticket Detail
### Overview
Add `contact_avatar_url` and `client_logo_url` to the ticket detail API response, then display them in the mobile ticket detail UI next to the contact and client names.
### Server Changes
1. In `TicketService.getById()`, after fetching the ticket, call:
- `getContactAvatarUrl(ticket.contact_name_id, context.tenant)` if contact exists
- `getClientLogoUrl(ticket.client_id, context.tenant)` if client exists
2. Return both URLs in the response as `contact_avatar_url` and `client_logo_url`
### Mobile Changes
1. In `TicketDetailScreen.tsx`, render `<Avatar>` next to the contact name in the KeyValue component
2. Render `<Avatar>` next to the client name
3. No type changes needed (`TicketDetail` uses `Record<string, unknown>`)
### Acceptance Criteria
- Contact avatar shows next to contact name (or initials fallback)
- Client logo shows next to client name (or initials fallback)
- No avatar/logo fields = graceful fallback (no crash, just initials)
---
## Feature 10: Documents on Tickets
### Overview
Show document list in ticket detail with count, allow downloading, and allow uploading new files (camera photos and file picker).
### Server Changes
1. **New endpoint: `POST /api/v1/tickets/{id}/documents`**
- Accepts `multipart/form-data` with file
- Uses `StorageService.uploadFile()` to store
- Creates document record + association with ticket
- Returns the created `IDocument`
- Follow pattern from `uploadDocument()` server action
2. **Existing endpoint: `GET /api/v1/tickets/{id}/documents`**
- Already exists and works — returns `IDocument[]`
### Mobile Changes
1. **API layer** (`src/api/documents.ts`):
- `getTicketDocuments(ticketId)` — GET endpoint
- `uploadTicketDocument(ticketId, file)` — POST with FormData
2. **Documents section in ticket detail**:
- New `DocumentsSection` component below description
- Shows document count badge
- Lists documents: name, type icon, size, upload date
- Tap document to download/open via `Linking.openURL` or `expo-sharing`
- "Attach" button with options: Camera (expo-image-picker) or File (expo-document-picker)
- Upload progress indicator
3. **Dependencies**: `expo-image-picker`, `expo-document-picker`, `expo-file-system`
### Data Model (from server)
```
IDocument {
document_id, document_name, type_name, type_icon,
mime_type, file_size, created_by_full_name, updated_at,
file_id (for download URL)
}
```
### Acceptance Criteria
- Document list visible in ticket detail with count
- Tap to download/open a document
- Upload photo from camera
- Upload file from file picker
- Upload shows progress, refreshes list on completion
- Empty state when no documents
---
## Feature 11: Products/Materials on Tickets
### Overview
Allow mobile users to add products (inventory items) to tickets as materials, recording quantity and rate.
### Server Changes
1. **New endpoint: `GET /api/v1/tickets/{id}/materials`**
- Returns `ITicketMaterial[]` for the ticket
- Joins `service_catalog` for `service_name` and `sku`
2. **New endpoint: `POST /api/v1/tickets/{id}/materials`**
- Accepts: `{ service_id, quantity, rate, currency_code, description? }`
- Requires ticket's `client_id` (fetched internally)
- Returns created `ITicketMaterial`
3. **Existing endpoint: `GET /api/v1/products`**
- Already exists — used for product search/listing
### Mobile Changes
1. **API layer** (`src/api/materials.ts`):
- `getTicketMaterials(ticketId)` — GET
- `addTicketMaterial(ticketId, data)` — POST
- Product listing already available via `GET /api/v1/products`
2. **Materials section in ticket detail**:
- New `MaterialsSection` component
- Lists existing materials: product name, SKU, quantity, rate, billed status
- "Add Product" button opens product picker (EntityPickerModal with search)
- After selecting product, prompt for quantity (default 1) and rate (pre-filled from product default_rate)
- Submit creates material
3. **Product picker**:
- Reuse `EntityPickerModal` with search
- Show product name + SKU as subtitle
- After selection, show quantity/rate input modal
### Data Model
```
ITicketMaterial {
ticket_material_id, service_id, service_name, sku,
quantity, rate (cents), currency_code, description,
is_billed, created_at
}
```
### Acceptance Criteria
- Materials list visible in ticket detail
- Add product via searchable picker
- Set quantity and rate before adding
- Billed/unbilled status badge shown
- Empty state when no materials
---
## Risks
- **Document upload endpoint** doesn't exist yet — needs server-side implementation following the existing `uploadDocument` pattern
- **Materials endpoints** don't exist yet — need two new API routes
- **expo-image-picker** and **expo-document-picker** may need Expo plugin configuration in `app.json`
- Large file uploads on mobile networks may timeout — consider reasonable file size limits
## Testing Requirements
Each feature must include actual test files that are written and verified to pass. The mobile app uses **Vitest** with React Test Renderer. Run tests with `npx vitest run` from `ee/mobile/`.
### Feature 15 Tests
- `ee/mobile/src/screens/TicketDetailScreen.avatars.test.ts` — Verify Avatar components render with contact/client URLs from ticket data, and fallback to initials when URLs are null
### Feature 10 Tests
- `ee/mobile/src/api/documents.test.ts` — Unit tests for `getTicketDocuments()` and `uploadTicketDocument()` API wrappers (mock API client)
- `ee/mobile/src/features/ticketDetail/components/DocumentsSection.test.ts` — Verify document list renders items, empty state, upload trigger
### Feature 11 Tests
- `ee/mobile/src/api/materials.test.ts` — Unit tests for `getTicketMaterials()`, `addTicketMaterial()`, `listProducts()` API wrappers (mock API client)
- `ee/mobile/src/features/ticketDetail/components/MaterialsSection.test.ts` — Verify materials list renders items with product name/SKU/quantity/rate, empty state, add product flow
### Test Execution
After implementing each feature, run `npx vitest run` from `ee/mobile/` and verify all tests pass (both new and existing). Fix any failures before moving to the next feature. The existing test suite has 126 tests that must continue to pass.
## Implementation Order
1. **Feature 15** (avatars) — smallest, server + mobile UI only, no new endpoints
2. **Feature 10** (documents) — medium, needs new upload endpoint
3. **Feature 11** (materials) — medium, needs two new endpoints + product picker