Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
7.7 KiB
PRD: Mobile App — Documents, Products, and Avatars
Problem Statement
The Alga PSA mobile app is missing three capabilities that exist in the web app:
- Documents — No way to view, download, or attach files to tickets from mobile. Technicians in the field need to photograph issues and attach documentation.
- Products/Materials — No way to add inventory items (products) to tickets. Technicians need to record parts used during on-site work.
- 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
- In
TicketService.getById(), after fetching the ticket, call:getContactAvatarUrl(ticket.contact_name_id, context.tenant)if contact existsgetClientLogoUrl(ticket.client_id, context.tenant)if client exists
- Return both URLs in the response as
contact_avatar_urlandclient_logo_url
Mobile Changes
- In
TicketDetailScreen.tsx, render<Avatar>next to the contact name in the KeyValue component - Render
<Avatar>next to the client name - No type changes needed (
TicketDetailusesRecord<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
-
New endpoint:
POST /api/v1/tickets/{id}/documents- Accepts
multipart/form-datawith file - Uses
StorageService.uploadFile()to store - Creates document record + association with ticket
- Returns the created
IDocument - Follow pattern from
uploadDocument()server action
- Accepts
-
Existing endpoint:
GET /api/v1/tickets/{id}/documents- Already exists and works — returns
IDocument[]
- Already exists and works — returns
Mobile Changes
-
API layer (
src/api/documents.ts):getTicketDocuments(ticketId)— GET endpointuploadTicketDocument(ticketId, file)— POST with FormData
-
Documents section in ticket detail:
- New
DocumentsSectioncomponent below description - Shows document count badge
- Lists documents: name, type icon, size, upload date
- Tap document to download/open via
Linking.openURLorexpo-sharing - "Attach" button with options: Camera (expo-image-picker) or File (expo-document-picker)
- Upload progress indicator
- New
-
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
-
New endpoint:
GET /api/v1/tickets/{id}/materials- Returns
ITicketMaterial[]for the ticket - Joins
service_catalogforservice_nameandsku
- Returns
-
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
- Accepts:
-
Existing endpoint:
GET /api/v1/products- Already exists — used for product search/listing
Mobile Changes
-
API layer (
src/api/materials.ts):getTicketMaterials(ticketId)— GETaddTicketMaterial(ticketId, data)— POST- Product listing already available via
GET /api/v1/products
-
Materials section in ticket detail:
- New
MaterialsSectioncomponent - 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
- New
-
Product picker:
- Reuse
EntityPickerModalwith search - Show product name + SKU as subtitle
- After selection, show quantity/rate input modal
- Reuse
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
uploadDocumentpattern - 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 forgetTicketDocuments()anduploadTicketDocument()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 forgetTicketMaterials(),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
- Feature 15 (avatars) — smallest, server + mobile UI only, no new endpoints
- Feature 10 (documents) — medium, needs new upload endpoint
- Feature 11 (materials) — medium, needs two new endpoints + product picker