Quote close out sometimes shows "Charge" services

Objective

  • Quote worklists (e.g. Manager: Quote Close Out and other custom quote lists) must show the same charge / service names and amounts staff see on Open Quotes (/work/quotes), so managers are not misled by generic "Charge" or Charge N rows at $0.00 when triaging aging quotes.
  • Remove apparent intermittency: the same quote can look correct in one place and wrong in another at the same time (FHI, RIA, and other brands).

Background

  • Product feedback: Pending quotes / Open Quotes show correct services (e.g. "Home/Condo Inspection" at $425.91), while some custom quote worklists show "Charge" or "Charge 1" at $0.00 in the expanded row—and sometimes after opening the quote from that worklist. Reports mention quotes that sat pending for a few days; behavior has also been described as "fixing itself," which likely reflects different list entry points or worklist configs, not data self-healing.

  • Open Quotes loads via GET /quote with addQuotePopulationStages() in attik-backend/src/util/functions/quote/addQuotePopulationStages.ts, which fully populates charges (including nested services, bundles, discount codes).

  • Custom quote worklists load via GET /worklist/:id/data in attik-backend/src/routes/worklist.ts. Conditions are split in separateConditions() / requiresPopulation(): only attributes like service-ids, required-info-*, and event-inspection fields are treated as post-population. Typical close-out filters (status, age, address, created-by, etc.) stay pre-population only.

  • For quote worklists without post-population conditions, buildWorklistPopulationStages() uses getInspectionPopulationPipeline({ includeCharges: false }) (~lines 1016–1027), so charges are returned as unpopulated refs (ObjectIds), not full charge documents.

  • Worklists with post-population but without service-ids may populate charges via getInspectionPopulationPipeline({ includeCharges: true }), which is still a different shape than addQuotePopulationStages() used by Open Quotes.

  • Worklists with service-ids use a lightweight charge lookup for filtering, then a follow-up lookup—a third path.

  • Two custom quote worklists can disagree at the same time for the same quote: one whose conditions trigger post-pop (e.g. includes service-ids or required-info-*) may show real service names; one with only pre-pop filters (common for close-out lists) will not.

  • On the frontend, SingleQuoteDropdown uses fallbacks when charge.name / charge.amount are missing (Charge N labels and $0.00), so missing population surfaces as generic labels and zero dollars—not as an empty state.

  • SE region is attempting to isolate quotes that are abandoned status by using the following configuration. The only worklists that have this issue are both using this same group of conditions

Repro / examples (screenshots)

Custom worklist showing incorrect charge data:

Open Quotes list showing correct service and amount:

FHI Example 1

FHI example 2

RIA

Scope

Backend

  • GET /worklist/:id/dataattik-backend/src/routes/worklist.ts: requiresPopulation(), buildWorklistFilterStages(), buildWorklistPopulationStages(), and the aggregate pipeline that applies population after sort/limit.
  • Quote population today: default quote branch sets includeCharges: false when hasPostPopConditions is false; alternate branches when post-pop or service-ids are present use getInspectionPopulationPipeline() from attik-backend/src/util/functions/aggregation/inspectionPopulation.ts instead of addQuotePopulationStages().
  • Reference implementation for correct shape: attik-backend/src/util/functions/quote/addQuotePopulationStages.ts (used by attik-backend/src/routes/quote.ts for list/detail).
  • Requirement: All quote worklist responses should expose populated charges with name and amount (and nested service data if the UI depends on it), regardless of which condition branch was used for filtering.
  • Decision needed: Whether lightweight charge lookups used only for service-ids post-pop $match should remain separate from the final response shape returned to the client.
  • Decision needed: Whether to add regression coverage on the worklist quote aggregate (e.g. pre-pop-only worklist returns charges[0].name).

Frontend

  • Worklist quote UI: attik-frontend/src/components/task-check/WorklistResolver.tsxSingleQuoteBar.tsxSingleQuoteDropdown.tsx (expanded charges section with fallbacks).
  • Open Quotes comparison path: attik-frontend/src/components/task-check/QuoteList.tsx → same SingleQuoteBar / SingleQuoteDropdown, fed by GET quote with full population.
  • Segment filtering on Open Quotes uses primaryCharge?._serviceId?._segmentId in QuoteList.tsx; worklists with unpopulated charges may also affect segment-related display where that pattern is reused.
  • Decision needed: Whether to keep UI fallbacks (Charge N, $0.00) when data is missing, or surface an explicit empty/error state after backend is fixed.

Out of scope (unless product asks)

  • Changing quote business logic, pricing, or charge creation.
  • Inspection or event worklist population (only quote worklists are in scope for this bug).

References

  • Slack report (FHI / Quote Close Out)
  • attik-backend/src/routes/worklist.ts
  • attik-backend/src/util/functions/quote/addQuotePopulationStages.ts
  • attik-backend/src/util/functions/aggregation/inspectionPopulation.ts
  • attik-backend/src/routes/quote.ts
  • attik-frontend/src/components/task-check/SingleQuoteDropdown.tsx
  • attik-frontend/src/components/task-check/WorklistResolver.tsx

Please authenticate to join the conversation.

Upvoters
Status

Next Up

Board
🏠

Main App

Date

23 days ago

Author

Linear

Subscribe to post

Get notified by email when there are changes.