Fibretrace Monet docs/Backend integration audit/About the audit

Dashboard API Integration Audit

Generated: Thu Jun 4 13:55:40 +07 2026 Source: audit.json (machine-readable, same directory)


Summary

Status Count Meaning
EXISTS 5 Wired or already implemented — can integrate now
PARTIAL 1 Partial implementation; read side works, gaps documented
GAP 23 BE endpoint does not exist — hand to backend team
misWired 1 Real path bug — wrong URL in the RTK slice

EXISTS units: scanners, audits, companies-public, invoices, notification-prefs PARTIAL units: collections-orders misWired units: userApi.ts:97 (upload-avatar)


1. EXISTS — Integrate Now

scanners

Wire MAPPERS["scanners"] in apiAdapter.ts to scannersApi.endpoints.getScanners.initiate. The RTK slice at apps/dashboard/src/features/scanners/services/scannersApi.ts already has all 5 endpoints.

Method Path RTK Hook Canvas Source
GET company/scanners useGetScannersQuery scannersApi.ts:44-53
POST company/scanner useCreateScannerMutation scannersApi.ts:55-62
POST post-auditor-update-scanner useUpdateScannerMutation scannersApi.ts:95-102
PUT company/scanner/transfer/:id useTransferScannerMutation scannersApi.ts:103-110
GET get-list-scanner-status-condition useGetScannerStatusConditionQuery scannersApi.ts:63-94

Adapter mapper (add to apps/dashboard/src/lib/apiAdapter.ts):

// Add imports:
// import scannersApi from "@/features/scanners/services/scannersApi";
// import type { BeScanner } from "@/features/scanners/services/scannersApi";

function mapBeScannerToAdapterRow(s: BeScanner): Record<string, unknown> {
  return {
    id: String(s.id),
    serial_number: s.code,
    model: s.type ?? "",
    name: s.name,
    status: s.status ?? "active",
    status_id: s.status_id,
    condition: s.condition ?? "",
    condition_id: s.condition_id,
    type: s.type ?? "",
    type_id: s.type_id,
    auditor: s.auditor ?? null,
    assigned_date: s.assigned_date ?? null,
    created_at: s.assigned_date ?? "",
  };
}

MAPPERS["scanners"] = {
  fetch: async (filters) => {
    try {
      const result = await store
        .dispatch(
          scannersApi.endpoints.getScanners.initiate({
            page: 1,
            number_per_page: 100,
            sort: "desc",
            sort_by: "assigned_date",
          }),
        )
        .unwrap();
      const rows = (result.data ?? []).map(mapBeScannerToAdapterRow);
      return { data: applyFilters(rows, filters) as unknown[], error: null };
    } catch (error) {
      console.error("MAPPERS[scanners] fetch failed:", error);
      return { data: null, error: error as Error };
    }
  },
};

// scanner_facility_assignments: GAP — noop stub stays until BE ships endpoint.

Caveats:

  1. useScanners.ts already bypasses the adapter (calls useGetScannersQuery directly). The mapper covers only residual supabase.from("scanners") call sites (e.g. confidence.ts indirect paths).
  2. scanner_facility_assignments is a confirmed GAP — see BE spec below. Scanners page degrades gracefully: facility column shows “Pending assignment”. Confidence score underweights scanner count (always 0) until BE ships.
  3. “last used by” column uses a mock hash function — no BE audit-log source. Intentional per inline comment.
  4. No DELETE /company/scanner endpoint exists. Delete button already disabled in AdminScanners.tsx.
  5. transferScanner takes user_id (numeric), NOT facility_id. The “Assign to Facility” dialog is intentionally disabled (BE-blocked).

audits

MAPPERS["audits"] is already wired at apiAdapter.ts:226-254. The existing mapper is correct. One potential fix noted in caveats.

Method Path RTK Hook Canvas Source
GET sat-audits useGetSatAuditsQuery auditApi.ts:17 (SAT), auditsApi.ts:70 (Dashboard)
POST sat-audit useSubmitSatAuditMutation auditApi.ts:33 (SAT)
GET user/audits/:uid/attribute-template useGetOrderProcessAuditTemplateQuery canvas auditApi.ts:301

Adapter mapper (already live — reproduced for reference):

function mapSatAuditToRow(a: SatAuditItem): Record<string, unknown> {
  const poNumber = (a.reference as any)?.purchase_order
    ?? (a.reference as any)?.po_number
    ?? null;
  return {
    id: String(a.id),
    session_id: a.id,
    ft_id: a.fibretrace_id ?? null,
    blockchain_guid: null,
    result: a.status ?? null,
    notes: poNumber ? `PO: ${poNumber}` : (a.reference as any)?.notes ?? null,
    created_at: a.created_at,
    deleted_at: null,
  };
}

MAPPERS["audits"] = {
  fetch: async (filters) => {
    try {
      const result = await store
        .dispatch(
          auditsApi.endpoints.getSatAudits.initiate({
            page: 1,
            number_per_page: 100,
            sort: "desc",
            sort_by: "created_at",
          }),
        )
        .unwrap();
      const rows = (result.data ?? []).map(mapSatAuditToRow);
      return { data: applyFilters(rows, filters) as unknown[], error: null };
    } catch (error) {
      console.error("MAPPERS[audits] fetch error:", error);
      return { data: null, error: error as Error };
    }
  },
};

Caveats:

  1. Reference field name discrepancy: dashboard’s SatAuditListReference uses po_number but canonical SAT uses purchase_order. The fixed mapper above reads both with a fallback. Verify actual BE response field before deploying.
  2. session_id uses audit’s own id as stand-in (sat-audits has no real session join). Sessions column shows “—” — correct behavior.
  3. blockchain_guid is always null. AuditsTable does not render this column.
  4. result maps BE “PROCESSED”/“PENDING” to Lovable’s “pass”/“fail”/“warning” column. Badge color will fall through to “warning” style — semantic mismatch until BE exposes pass/fail/warning.
  5. All AdminVerifications mutations already stub-blocked with toast.info(). No call-site RTK migration needed.

companies-public

MAPPERS["companies"] and companiesPublicApi.ts are already live. No action needed.

Method Path RTK Hook Canvas Source
GET public-companies useGetPublicCompaniesQuery companyApi.ts:210-217

Adapter mapper (already live — DO NOT re-apply):

function mapPublicCompanyToRow(c: PublicCompanyBeRow): Record<string, unknown> {
  return {
    id: String(c.uid ?? c.id ?? ""),
    name: c.name ?? "",
    confidence_score: null,       // no BE source; Lovable-only field
    confidence_rating: null,      // no BE source; Lovable-only field
    confidence_summary: null,     // no BE source; Lovable-only field
  };
}

MAPPERS["companies"] = {
  fetch: async (filters) => {
    try {
      const result = await store
        .dispatch(companiesPublicApi.endpoints.getPublicCompanies.initiate({}))
        .unwrap();
      const rows = (result.data ?? []).map(mapPublicCompanyToRow);
      return { data: applyFilters(rows, filters) as unknown[], error: null };
    } catch (error) {
      return { data: null, error: error as Error };
    }
  },
};

Caveats:

  1. confidence_score / confidence_rating hard-coded null — BE returns only uid + name. Confidence UI remains empty until BE adds those fields.
  2. No mutations target companies. usePartners.ts mutations target company_partners — separate scope.
  3. Task is DONE — no new files or edits required.

invoices

MAPPERS["invoices"] and billingApi.ts are already wired. No action needed for fetch path.

Method Path RTK Hook Canvas Source
GET company/invoices useGetCompanyInvoicesQuery billingApi.ts:15-24
GET company/invoices/:code useGetInvoiceByCodeQuery billingApi.ts:26-31

Adapter mapper (already live — reproduced for reference):

function mapBeInvoiceToRow(inv: BeInvoice): Record<string, unknown> {
  return {
    id: String(inv.id),
    invoice_number: inv.code,          // BE: code -> Lovable: invoice_number
    purchase_order: inv.po_number ?? "",
    amount: Number(inv.total ?? 0),    // BE: total (string) -> Lovable: amount (number)
    status: inv.status ?? "",
    issued_at: inv.created_at ?? "",   // BE: created_at -> Lovable: issued_at
    pdf_url: "",                       // no BE source; Lovable-only field
    notes: "",                         // no BE source; Lovable-only field
    companies: null,                   // list endpoint does not join company name
  };
}

MAPPERS["invoices"] = {
  fetch: async (filters) => {
    try {
      const result = await store
        .dispatch(
          billingApi.endpoints.getCompanyInvoices.initiate({
            page: 1,
            number_per_page: 100,
            sort: "desc",
            sort_by: "created_at",
            search: "",
          }),
        )
        .unwrap();
      const rows = (result.data ?? []).map(mapBeInvoiceToRow);
      return { data: applyFilters(rows, filters) as unknown[], error: null };
    } catch (error) {
      return { data: null, error: error as Error };
    }
  },
};

Caveats:

  1. Already implemented — no new files or edits needed for the fetch path.
  2. companies(name) join: list endpoint does not join company info. Column always shows “—” for single-company users.
  3. pdf_url and notes are Lovable-only fields — mapped to empty strings.
  4. Admin invoice CRUD mutations already throw not_implemented. No canvas equivalent.
  5. Verify BE returns paginated shape { data: Invoice[]; meta: { total: number } } — if flat Invoice[], change mapper to read result as Invoice[].

notification-prefs

SettingsNotifications.tsx is fully wired via useGetUserQuery + useUpdateNotificationEmailsMutation. No action needed.

Method Path RTK Hook Canvas Source
GET user useGetUserQuery userApi.ts:55-60
PUT v2/user useUpdateNotificationEmailsMutation userApi.ts:70-74

Caveats:

  1. notification_preferences / SettingsNotifications.tsx is FULLY WIRED. The apiAdapter no-op stub is a crash-guard only.
  2. notification_templates is a genuine GAP (see Section 3 below).
  3. PUT /api/v2/user: VITE_API_URL must be the bare API origin without /api/v1 suffix. Verify .env.
  4. is_company_admin gating: canvas gates 3 admin-only OPT_IN keys. Dashboard currently shows all 10 unconditionally. Wire user.is_company_admin if product requires gating.

2. PARTIAL — Integrate Read Part, Flag the Rest

collections-orders

Collections list is already wired (MAPPERS["collections"] at apiAdapter.ts:388-408). Sessions and full verifications are GAPs requiring BE extension.

What works:

Method Path RTK Hook Canvas Source Status
GET orders useGetOrdersQuery orderApi.ts:32 WIRED (MAPPERS[“collections”])
GET orders/:orderCode useGetOrderByCodeQuery orderApi.ts:278 EXISTS
GET orders/:orderCode/processes useGetOrderProcessesQuery processesApi.ts:24 EXISTS (basis for verifications mapper)
GET orders/:orderCode/processes/:processId useGetOneProcessQuery processesApi.ts:29 EXISTS (needs slice extension)

What is missing / field-set gaps:

  • MAPPERS["verifications"]: can partially wire to GET /orders/:code/processes. Filtered calls (.eq("collection_id", orderCode)) will work; unfiltered calls return []. name and status fields are absent from ProcessProcesList — stub with safe defaults until BE extends the response.
  • MAPPERS["sessions"]: GAP. ProcessProcesList is missing session_code, conducted_at, fibre_detected, pigment_id, facility_id, auditor_name. Stub returns [].
  • MAPPERS["session_shares"]: GAP. Returns [].

Adapter changes (apply these):

// MAPPERS["collections"] is ALREADY wired — DO NOT replace.

// MAPPERS["verifications"] — wire to GET /orders/:code/processes.
MAPPERS["verifications"] = {
  fetch: async (filters) => {
    try {
      const orderCode = filters.eq?.["collection_id"] as string | undefined;
      if (!orderCode) {
        return { data: [], error: null };
      }
      const result = await store
        .dispatch(
          collectionsApi.endpoints.getOrderProcesses.initiate(orderCode),
        )
        .unwrap();
      const rows = (result.data ?? []).map((p: OrderProcess) =>
        mapProcessToVerification(p, orderCode),
      );
      return { data: applyFilters(rows, filters) as unknown[], error: null };
    } catch (error) {
      console.error("MAPPERS[verifications] fetch error:", error);
      return { data: null, error: error as Error };
    }
  },
};

function mapProcessToVerification(
  p: OrderProcess,
  orderCode: string,
): Record<string, unknown> {
  return {
    id: String(p.id),
    collection_id: orderCode,
    name: (p as any).name ?? `Process ${p.id}`,
    status: (p as any).status != null ? String((p as any).status) : "active",
  };
}

// MAPPERS["sessions"] — GAP stub.
MAPPERS["sessions"] = {
  fetch: async (_filters) => ({ data: [], error: null }),
};

// MAPPERS["session_shares"] — GAP stub.
MAPPERS["session_shares"] = {
  fetch: async () => ({ data: [], error: null }),
};

Slice extension (add getOneProcess to collectionsApi.ts):

getOneProcess: builder.query<{ data: OrderProcess }, { orderCode: string; processId: string | number }>({
  query: ({ orderCode, processId }) => ({
    url: `orders/${orderCode}/processes/${processId}`,
    method: 'GET',
  }),
  providesTags: (_result, _error, { orderCode, processId }) => [
    { type: 'Verifications', id: `${orderCode}-process-${processId}` },
  ],
}),

Call-site mutations that need BE-pending toasts:

  • CreateCollectionFlow.tsx:151-160supabase.from('collections').insert(...) — adapter throws. Replace with BE-pending toast until BE confirms POST /orders field mapping.
  • CreateCollectionFlow.tsx:174-177supabase.from('verifications').update({ collection_id }) — GAP mutation. BE-pending toast.
  • AddVerificationsModal.tsx:137-140 — same GAP mutation. BE-pending toast.

BE spec for gaps:

TABLE: sessions
Required columns (from SessionDetail.tsx, CollectionDetail.tsx, useVerifications.ts):
  id                   uuid  PK
  session_code         text
  conducted_at         timestamptz
  fibre_detected       text
  scan_count           int
  facility_id          uuid  FK -> facilities.id
  verification_id      uuid  FK -> verifications.id
  production_record_id uuid  nullable FK
  auditor_name         text
  pigment_id           uuid  nullable FK -> pigment_ids.id
  deleted_at           timestamptz nullable

Nearest BE candidate: GET /orders/:code/processes/:id (OrderProcessDetailData)
Field-set mismatch: BE has process_scanned_date, process_scanned_by, process_name, etc.
  UI needs: session_code, conducted_at, fibre_detected, facility_id, verification_id, pigment_id, auditor_name

BE spec option A: extend GET /orders/:code/processes/:id response to include a `session` sub-object.
BE spec option B: new endpoint GET /orders/:code/sessions returning the above shape per-row.

TABLE: verifications
Required columns: id, collection_id, name, status
Nearest BE candidate: GET /orders/:code/processes (ProcessProcesList)
Field-set mismatch: BE has id, due_date, scan_count, auditor, manufacturer_process; missing name, status.
BE spec: extend GET /orders/:code/processes items to include { name, status, collection_id (= order_code) }.

3. GAP — Hand to Backend Team

These 23 tables have no confirmed Fibretrace REST endpoint. The adapter stubs return [] to prevent crashes.

Table Affected Consumers Notes
sessions CollectionDetail.tsx, SessionDetail.tsx, DirectorySearchModal.tsx, AddVerificationsModal.tsx, LinkVerificationModal.tsx, useVerifications.ts, useGuideSignals.ts, ProducerDashboard.tsx Nearest BE: /orders/:code/processes/:id but field-set mismatch. See PARTIAL section for spec.
session_shares useVerifications.ts, AdminVerifications.tsx Lovable-only many-to-many share join. Canvas POST /user/audits/:uid/send has different semantics.
verifications CollectionDetail.tsx, VerificationPreview.tsx, CreateCollectionFlow.tsx, AddVerificationsModal.tsx, useVerifications.ts Lovable join between collections and sessions. Partial wire possible via processes — see PARTIAL section.
fibre_programmes useProgrammes.ts, useEvidenceRequests.ts, useProgrammeHeatmap.ts, FibreCreation.tsx, AdminProgrammes.tsx Entirely new BE domain. MAPPERS[“fibre_programmes”] has TODO: ‘wire to /programs endpoint once BE confirms’. Zero /programs or /fibre-programmes routes found across all 6 canvas inventory files.
programme_participants useProgrammes.ts, AdminProgrammes.tsx Part of Programmes domain. No canvas or legacy endpoint.
programme_producers useProgrammes.ts, AdminProgrammes.tsx Part of Programmes domain. No canvas or legacy endpoint.
programme_producer_facilities useProgrammes.ts, AdminProgrammes.tsx Part of Programmes domain. No canvas or legacy endpoint.
programme_reservations useProgrammes.ts Part of Programmes domain. No canvas or legacy endpoint.
pigment_ids useProgrammes.ts, useProgrammeHeatmap.ts, AdminPigmentIds.tsx, SessionDetail.tsx Lovable-native fibre pigment tracking. No canvas or legacy /pigment-ids found.
pigment_orders useProgrammes.ts, useProgrammeHeatmap.ts Lovable-native pigment ordering. No canvas or legacy endpoint.
pigment_company_assignments useProgrammes.ts Lovable-native pigment-to-company join. No canvas or legacy endpoint.
production_records FibreCreation.tsx, LogProductionModal.tsx, useGuideSignals.ts, AdminFibreProduction.tsx No /production-records endpoint. Note: sduApi.ts correctly wires GET /sdu/devices (Nathan spec 2026-05-29) — separate from this table.
production_claims FibreCreation.tsx, usePersona.ts, AdminFibreProduction.tsx No /production-claims endpoint.
sliver_delivery_units SDU.tsx, AdminSDU.tsx, useGuideSignals.ts SDU CRUD only. READ side is already wired via sduApi.ts -> GET /sdu/devices (called directly via hooks, not through this adapter). Noop stub remains for Lovable code paths.
evidence_requests useEvidenceRequests.ts, EvidencePack.tsx Lovable-native evidence pack workflow. No /evidence-requests endpoint.
evidence_request_shares useEvidenceRequests.ts Lovable-native evidence share join. No endpoint.
facility_licenses AdminFacilities.tsx, AdminLicensing.tsx No /facility-licenses endpoint.
onboarding_tasks AdminOnboarding.tsx No /onboarding-tasks endpoint.
company_details AdminCompanies.tsx, EvidencePack.tsx Canvas closest: POST /company-meta but semantics differ (company_code field + direct CRUD). Multiple candidates, no clean 1:1.
company_users AdminUsers.tsx, AdminCompanies.tsx Canvas GET /users exists (teamApi.ts) but admin insert/update/delete semantics differ from useSetUserStatusMutation/useDeleteUserMutation/useRestoreUserMutation.
notification_templates_mutations AdminNotifications.tsx Admin CRUD on notification templates. No canvas write endpoint. Read path (GET /service-tab) separately flagged as fixable. Supabase shape: { id, created_at, description, is_default_enabled, label }. BE spec: GET/POST /notification-templates, PUT/DELETE /notification-templates/:id.
scanner_facility_assignments ProducerDashboard.tsx, useScanners.ts Lovable-only scanner-facility join. Canvas GET /company/scanners returns count_assigned_scanner in meta but no flat join table endpoint. BE spec: GET /company/scanner-facility-assignments { data: Array<{ id, scanner_id, facility_id, created_at }> }; POST body { scanner_id, facility_id }; DELETE /:id.
companies_confidence_score useConfidence.ts, useVerifications.ts, useProgrammeHeatmap.ts, AdminCronJobs.tsx PARTIAL: MAPPERS[“companies”] is wired to GET /public-companies (returns uid + name). confidence_score and confidence_rating are Lovable-only and resolve to null — no Fibretrace endpoint returns these fields.

4. misWired — Real Path Bugs

Slice File Bad Path Evidence Suggested Fix
apps/dashboard/src/services/userApi.ts:97 POST upload-avatar Not present in any of the 6 canvas inventory files (01-06-*.md), not in legacy storefront/b2c/fim API files, flagged in gap-scan-2026-05-31/canvas-missed.json as ‘new endpoint not in scanned 24’. Canvas avatar/logo uploads go through POST /media (commonApi.ts:60). Consumed by SettingsAccount.tsx:126. POST /media (commonApi.ts useUploadToS3Mutation) — verify with BE whether /upload-avatar is a real separate endpoint or should route to /media

5. Recommended Order

Work in this sequence to maximize unblocked wiring and surface BE gaps early:

  1. Fix misWireduserApi.ts:97 upload-avatar path. 1 file, low risk. Verify with BE first.
  2. Wire scanners adapter mapperapiAdapter.ts: add MAPPERS["scanners"] block + imports. Slice already complete. Unblocks confidence.ts fallback paths.
  3. Wire verifications adapter mapperapiAdapter.ts: add MAPPERS["verifications"] + mapProcessToVerification + session/session_shares stubs. Extend collectionsApi.ts with getOneProcess. Unblocks CollectionDetail.tsx filtered views.
  4. Fix mutation crash sitesCreateCollectionFlow.tsx and AddVerificationsModal.tsx: replace supabase mutation calls with BE-pending toasts.
  5. notification_templates read (worklist.fixable) — new RTK endpoint for GET /service-tab -> wire MAPPERS[“notification_templates”] read. Admin mutations remain BE-blocked.
  6. Hand BE spec to backend — sessions, verifications field extension, scanner_facility_assignments, notification_templates CRUD, fibre_programmes (see Section 3 above for full specs).
  7. After BE ships each endpoint: wire the corresponding adapter mapper + RTK slice + remove noop stub.