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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
14 KiB
14 KiB
Scratchpad — Mobile Remaining Features (10, 11, 15)
Key Discoveries
Feature 10: Documents
GET /api/v1/tickets/{id}/documentsexists, returnsIDocument[]viacreateSuccessResponse- No upload endpoint in API v1 — uploads use server action
uploadDocument()via FormData - Need to create:
POST /api/v1/tickets/{id}/documentsfor mobile upload - Document download:
GET /api/documents/download/{fileId}(different base path, not v1) - Documents have:
document_name,type_name,type_icon,file_size,mime_type,created_by_full_name,updated_at getTicketDocuments()already returns enriched data with joinsStorageService.uploadFile()handles actual storage (Local or S3 provider)- Auto-files into
/Tickets/Attachmentsfolder - Document types resolved from MIME via
getDocumentTypeId()
Feature 11: Inventory/Products (Materials)
- Products live in
service_catalogtable withitem_kind = 'product' - Product-ticket link via
ticket_materialstable (quantity, rate, currency, description) - No API v1 endpoint for materials — web app uses server actions
- Need to create:
GET /api/v1/tickets/{id}/materialsandPOST /api/v1/tickets/{id}/materials - Web component:
TicketMaterialsCard— searchable product picker with multi-currency prices - Key actions:
searchServiceCatalogForPicker(),getServicePrices(),listTicketMaterials(),addTicketMaterial() - Material fields:
service_id,quantity,rate,currency_code,description,is_billed - Products have SKU, vendor, manufacturer fields
GET /api/v1/productsexists for listing/searching products
Feature 15: Contact & Client Avatars
getContactAvatarUrl(contactId, tenant)in@alga-psa/formatting/avatarUtilsgetClientLogoUrl(clientId, tenant)in same file- Both use
getEntityImageUrl()which queriesdocument_associations+documents+external_files - Returns URL like
/api/documents/view/{fileId}?t={timestamp}or null - Comments already use this pattern: batch-fetch user avatar URLs, map into response
TicketService.getById()hascontact_name_idandclient_idavailable from the ticket- Simple addition: call both functions after ticket query, add to response
- Mobile
TicketDetailtype uses& Record<string, unknown>so new fields work without type changes
Decisions
- Feature 15 is the quickest win (server change only + minor mobile UI)
- Feature 10 needs a new upload endpoint — can follow the pattern from
ApiAssetController - Feature 11 needs two new endpoints (list + create materials)
- For mobile, simplified product picker without multi-currency (use
default_rate)
Key File Paths
server/src/lib/api/services/TicketService.ts— main ticket serviceserver/src/lib/api/controllers/ApiTicketController.ts— ticket API controllerpackages/formatting/src/avatarUtils.ts— avatar/logo URL helperspackages/documents/src/actions/documentActions.ts— document upload actionpackages/tickets/src/actions/materialCatalogActions.ts— material actionsee/mobile/src/screens/TicketDetailScreen.tsx— mobile ticket detailee/mobile/src/api/tickets.ts— mobile ticket API
Progress Log
- F001 complete:
TicketService.getById()now resolvescontact_avatar_urlviagetContactAvatarUrl()whencontact_name_idis present; implemented together with the rest of Feature 15 to avoid partial dead code. - F002 complete: the same
Promise.allenrichment inTicketService.getById()now resolvesclient_logo_urlviagetClientLogoUrl()whenclient_idis present, matching the PRD’s server contract. - F003 complete: ticket detail responses now include both
contact_avatar_urlandclient_logo_urlalongside the existing enriched ticket payload and documents list. - F004 complete:
TicketDetailScreennow renders anAvatarplus name row for the contact field insideKeyValue, with the API key passed through for protected image fetches. - F005 complete: the client
KeyValuefield now uses the same avatar row pattern so logos render beside the client name instead of plain text only. - F006 complete: null
contact_avatar_url/client_logo_urlnow fall back cleanly because the screen always renders the sharedAvatarcomponent andKeyValueaccepts rich value content instead of string-only text. - F010 complete:
POST /api/v1/tickets/{id}/documentsnow exists via the ticket documents route plusApiTicketController.uploadDocument(), using API-key auth and ticket update permission checks before delegating to the service upload path. - F011 complete: the upload controller reads
multipart/form-datawithreq.formData(), extracts thefilefield, andTicketService.uploadTicketDocument()persists the payload throughStorageService.validateFileUpload()andStorageService.uploadFile(). - F012 complete:
TicketService.uploadTicketDocument()now inserts adocumentsrow and a matchingdocument_associationsrow withentity_type: 'ticket'inside one transaction, after resolving the folder path and document type. - F013 complete: after upload, the service reloads the enriched
IDocumentviagetDocumentById()and the controller returns that object in the API success payload with HTTP 201. - F014 complete:
ee/mobile/src/api/documents.tsnow exposesgetTicketDocuments()as the mobile wrapper aroundGET /api/v1/tickets/{ticketId}/documents, including the requiredx-api-keyheader and typedTicketDocument[]response. - F015 complete:
uploadTicketDocument()now postsFormDatato the same ticket documents endpoint, andApiClient.request()was updated so multipart bodies bypass JSON encoding and omit the JSON content type header. - F016 complete:
DocumentsSectionis now mounted inTicketDetailScreenbelow the description card and loads ticket documents into a dedicated card section on first render. - F017 complete: each document row now renders the document name plus a secondary metadata line composed from the resolved type label, formatted file size, and formatted
updated_attimestamp. - F018 complete: the documents card header now includes a neutral
Badgeshowing the current document count next to the attach action, satisfying the count badge requirement. - F019 complete: tapping a document row now downloads the file to Expo FileSystem with the API key header and immediately hands the cached URI off to
Linking.openURL()for the platform handler. - F020 complete: the attach UI now exposes a Camera option backed by
expo-image-picker, including explicit camera permission checks and upload of the captured asset through the document API wrapper. - F021 complete: the same attach affordance now exposes a File option backed by
expo-document-picker, allowing arbitrary picked files to be posted as ticket documents. - F022 complete: while an upload request is in flight,
DocumentsSectionswitches on anActivityIndicatorplus localized uploading text so technicians get explicit progress feedback instead of a dead UI. - F023 complete: successful uploads now call
loadDocuments()before clearing the upload state, so the section refreshes immediately and the new attachment appears without reopening the screen. - F024 complete: when the ticket has no attachments, the documents card now renders a dedicated localized empty-state message instead of an empty container.
- F025 complete: document loading/open/upload failures now surface localized errors in the section, with a specific camera-permission denial path and fallback server/network error messaging for rejected uploads.
- F030 complete:
GET /api/v1/tickets/{id}/materialsnow exists through the new ticket materials route plusApiTicketController.getMaterials(), and the service exposesgetTicketMaterials()for the shared list path. - F031 complete:
getTicketMaterials()andgetTicketMaterialById()both joinservice_catalogso material payloads includeservice_nameandskudirectly from the product catalog. - F032 complete:
POST /api/v1/tickets/{id}/materialsnow exists viaApiTicketController.addMaterial()and the dedicated ticket materials route, returning a created material payload with HTTP 201. - F033 complete: create requests now pass through
createTicketMaterialSchemafor body validation and are revalidated in the service for positive quantity, non-negative rate, and product-backedservice_idenforcement before insert. - F034 complete:
addTicketMaterial()now looks up the ticket first and copiesclient_idfrom the ticket row intoticket_materials, so mobile callers do not send client context explicitly. - F035 complete:
ee/mobile/src/api/materials.tsnow exposesgetTicketMaterials()as the typed wrapper aroundGET /api/v1/tickets/{ticketId}/materials. - F036 complete: the same mobile API module now exposes
addTicketMaterial()for posting{ service_id, quantity, rate, currency_code, description? }to the ticket materials endpoint. - F037 complete:
listProducts()now wraps the existingGET /api/v1/productsendpoint with mobile-friendly search and limit parameters, reusing the catalog’sdefault_rateandskufields for picker display and defaults. - F038 complete:
MaterialsSectionis now mounted inTicketDetailScreenas a dedicated ticket detail card that loads and manages material data independently of comments/documents. - F039 complete: each material row now renders the product name, SKU, localized quantity/rate line, and a billed or unbilled badge directly in the ticket detail UI.
- F040 complete: the materials card now exposes an
Add Productaction that opensEntityPickerModal, loads product results from/api/v1/products, and supports search-driven filtering. - F041 complete: selecting a product now closes the picker and opens a dedicated modal with quantity and rate inputs so the technician can confirm billable details before creation.
- F042 complete: the rate input is seeded from the selected product’s
default_rate, converted from minor units into a currency input string so the default price is editable rather than blank. - F043 complete: saving from the material modal now posts the selected product, quantity, and rate, then closes the modal and reloads the materials list so the new row is visible immediately.
- F044 complete: when a ticket has no materials, the section now renders a dedicated localized empty-state message instead of an empty list shell.
- F045 complete: materials loading, product search, input validation, and add failures now surface localized or server-provided errors in the section/modal instead of failing silently.
- T010/T011 complete: added
server/src/test/unit/api/ticketDocuments.service.test.tsto exerciseTicketService.getTicketDocuments()directly for both populated and empty ticket attachment lists, closing the remaining gap in document list coverage. - T001-T005 complete:
server/src/test/unit/api/ticketService.avatarUrls.test.tscovers populated contact/client image URLs, null helper fallbacks, and the no-contact/no-client branch so the ticket detail API enrichment is exercised end to end at the service layer. - F050/T006-T008 complete:
ee/mobile/src/screens/TicketDetailScreen.avatars.test.tsverifies both avatar image rendering paths and the initials fallback path for contact/client rows in the mobile detail screen. - T012-T016 complete:
ticketDocumentUpload.service.test.tsandticketDocumentsUpload.contract.test.tscover document creation, ticket association creation, response reloading, missing-file rejection, and the shared authenticated controller flow for upload requests. - F052/F053/T017-T026/T051-T052 complete:
ee/mobile/src/api/documents.test.tsandee/mobile/src/features/ticketDetail/components/DocumentsSection.test.tscover the document API wrappers, list metadata, count badge, download/open action, camera/file uploads, upload progress, refresh-after-upload, empty state, upload failure messaging, and camera-permission denial behavior. - T030-T035 complete:
server/src/test/unit/api/ticketMaterials.service.test.tsandticketMaterials.contract.test.tsexercise list-with-join behavior, empty lists, successful material creation, ticket-derivedclient_id, validation failures, invalidservice_id, and the controller’s authenticated validation path. - F055/F056/T036-T043/T053-T054 complete:
ee/mobile/src/api/materials.test.tsandee/mobile/src/features/ticketDetail/components/MaterialsSection.test.tscover material API wrappers, rendered product metadata and billed badges, picker search, SKU display, quantity/rate modal defaults, create-and-refresh flow, empty state, and add-failure messaging. - F051/F054/F057/T050 complete:
cd ee/mobile && npx vitest runnow passes with the new avatar/document/material suites included. The only follow-up needed was mockingDocumentsSectionandMaterialsSectioninside the rich-text screen tests so those suites stay isolated from Expo native file-system bindings. - F058/T055 complete: the final full mobile suite run now passes at
45test files /169tests, confirming the pre-existing suite still passes alongside the new avatar, document, and material coverage.
Commands / Runbooks
- Server targeted test:
cd server && npx vitest run src/test/unit/api/ticketService.avatarUrls.test.ts - Mobile targeted test:
cd ee/mobile && npx vitest run src/screens/TicketDetailScreen.avatars.test.ts - Server targeted tests:
cd server && npx vitest run src/test/unit/api/ticketMaterials.service.test.ts src/test/unit/api/ticketMaterials.contract.test.ts src/test/unit/api/ticketDocumentsUpload.contract.test.ts src/test/unit/api/ticketDocumentUpload.service.test.ts src/test/unit/api/ticketService.avatarUrls.test.ts - Mobile targeted tests:
cd ee/mobile && npx vitest run src/api/materials.test.ts src/features/ticketDetail/components/MaterialsSection.test.ts src/api/documents.test.ts src/features/ticketDetail/components/DocumentsSection.test.ts src/screens/TicketDetailScreen.avatars.test.ts