From a9d8416f0f3b805c2154b5806888c1b0f8bccc73 Mon Sep 17 00:00:00 2001 From: Codex Review Integration Date: Wed, 16 Sep 2026 18:05:50 -0300 Subject: [PATCH] fix(work-orders): prefer stored media over a zero detail media count The detail endpoint's info inherits MediaCount from the board row DTO but the service never sets it, so it serializes as 0. The previous guard trusted any numeric value and returned that 0, leaving the slide-over completion gate reporting "At least 1 photo" even after an upload. Take the larger of the reported count and the stored (non-legacy) attachments so a positive API count still wins while a spurious or omitted 0 no longer masks present media. Dropping the unused mapped-row argument also removes a flat-payload path that would have counted legacy items. --- .../mappers/work-order-board-detail-mapper.ts | 29 +++++++------------ .../work-order-board-detail-mapper.test.ts | 12 ++++++++ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/domain/work-orders/mappers/work-order-board-detail-mapper.ts b/src/domain/work-orders/mappers/work-order-board-detail-mapper.ts index 144f5e3f..54b333ed 100644 --- a/src/domain/work-orders/mappers/work-order-board-detail-mapper.ts +++ b/src/domain/work-orders/mappers/work-order-board-detail-mapper.ts @@ -6,10 +6,7 @@ import type { WorkOrderBoardMediaItem, } from "@/domain/work-orders/types/work-order-board-detail"; import { BOARD_MEDIA_CATEGORY_API } from "@/domain/work-orders/types/work-order-board-detail"; -import type { - CompletionDocStatus, - WorkOrderTableRow, -} from "@/domain/work-orders/types/work-order-table-row"; +import type { CompletionDocStatus } from "@/domain/work-orders/types/work-order-table-row"; import type { WorkOrderAuditEntry, WorkOrderComment, @@ -273,21 +270,17 @@ export function boardMediaItemsToMediaFiles(items: WorkOrderBoardMediaItem[]): M } /** - * `GET workorders/{id}/detail` info omits the board row's `MediaCount`, so the table-row mapper - * defaults it to 0 and the slide-over closability gate would report "At least 1 photo" even - * after an upload. Keep an explicit count when present; otherwise count the same - * set the board counts — stored (non-legacy) attachments. + * `GET workorders/{id}/detail` info serializes the board row's `MediaCount` as 0 — the detail + * DTO inherits the field but the service never sets it — so trusting it verbatim would leave the + * slide-over closability gate reporting "At least 1 photo" even after an upload. Take the larger + * of the reported count and the stored (non-legacy) attachments: a positive API count still wins, + * but a spurious (or omitted) 0 can no longer mask media that are actually present. */ -function resolveDetailMediaCount( - infoRaw: unknown, - info: WorkOrderTableRow, - media: WorkOrderBoardMediaItem[], -): number { +function resolveDetailMediaCount(infoRaw: unknown, media: WorkOrderBoardMediaItem[]): number { const record = asRecord(infoRaw); - if (typeof (record.mediaCount ?? record.MediaCount) === "number") { - return info.mediaCount; - } - return media.filter((item) => !item.isLegacy).length; + const explicit = record.mediaCount ?? record.MediaCount; + const nonLegacyCount = media.filter((item) => !item.isLegacy).length; + return Math.max(typeof explicit === "number" ? explicit : 0, nonLegacyCount); } export function mapWorkOrderBoardDetail(raw: unknown): WorkOrderBoardDetail { @@ -307,7 +300,7 @@ export function mapWorkOrderBoardDetail(raw: unknown): WorkOrderBoardDetail { const info = mapWorkOrderTableRow(infoRaw); return { - info: { ...info, mediaCount: resolveDetailMediaCount(infoRaw, info, media) }, + info: { ...info, mediaCount: resolveDetailMediaCount(infoRaw, media) }, completion: mapCompletion(completionRaw ?? {}), comments, audit, diff --git a/src/test/domain/work-orders/mappers/work-order-board-detail-mapper.test.ts b/src/test/domain/work-orders/mappers/work-order-board-detail-mapper.test.ts index b393f716..fb24f6b5 100644 --- a/src/test/domain/work-orders/mappers/work-order-board-detail-mapper.test.ts +++ b/src/test/domain/work-orders/mappers/work-order-board-detail-mapper.test.ts @@ -297,6 +297,18 @@ describe("mapWorkOrderBoardDetail media count (SH-339)", () => { ); }); + it("prefers stored media when detail info reports an explicit zero", () => { + const detail = mapWorkOrderBoardDetail({ + ...SAMPLE_DETAIL, + info: { ...reactiveInfo, mediaCount: 0 }, + }); + + expect(detail.info.mediaCount).toBe(2); + expect(getClosabilityGaps(tableRowToClosabilityInput(detail.info))).not.toContain( + "At least 1 photo", + ); + }); + it("does not count legacy URL-column media, matching the board row count", () => { const detail = mapWorkOrderBoardDetail({ ...SAMPLE_DETAIL,