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,