Merge pull request #193 from Sea-Haven-Industries/fix/ab/sh-339-wizard-complete-image
Some checks failed
Deploy dev content / Deploy shoc-frontend-new-dev through Terraform (push) Waiting to run
Frontend checks / Build and test (push) Has been cancelled
Frontend checks / governance (push) Has been cancelled
Frontend checks / Visual regression (push) Has been cancelled

SH-339: Allow completing Reactive/Emergency WOs from the side panel after photo upload
This commit is contained in:
Alexandre Brandizzi 2026-09-17 16:14:07 -03:00 • committed by GitHub
commit d6510b262e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 1 deletions

View file

@ -269,6 +269,20 @@ export function boardMediaItemsToMediaFiles(items: WorkOrderBoardMediaItem[]): M
}));
}
/**
* `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, media: WorkOrderBoardMediaItem[]): number {
const record = asRecord(infoRaw);
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 {
const root = asRecord(raw);
const infoRaw = root.info ?? root.Info ?? root;
@ -283,8 +297,10 @@ export function mapWorkOrderBoardDetail(raw: unknown): WorkOrderBoardDetail {
: [];
const media = Array.isArray(mediaRaw) ? mediaRaw.map(mapBoardDetailMediaItem) : [];
const info = mapWorkOrderTableRow(infoRaw);
return {
info: mapWorkOrderTableRow(infoRaw),
info: { ...info, mediaCount: resolveDetailMediaCount(infoRaw, media) },
completion: mapCompletion(completionRaw ?? {}),
comments,
audit,

View file

@ -6,6 +6,10 @@ import {
mapBoardDetailMediaItem,
mapWorkOrderBoardDetail,
} from "@/domain/work-orders/mappers/work-order-board-detail-mapper";
import {
getClosabilityGaps,
tableRowToClosabilityInput,
} from "@/domain/work-orders/utils/wo-closability";
const SAMPLE_DETAIL = {
info: {
@ -279,3 +283,56 @@ describe("mapWorkOrderBoardDetail", () => {
expect(completionForm.get("file")).toBeInstanceOf(File);
});
});
describe("mapWorkOrderBoardDetail media count (SH-339)", () => {
const reactiveInfo = { ...SAMPLE_DETAIL.info, workOrderType: 6 };
it("derives mediaCount from stored media when detail info omits it", () => {
const detail = mapWorkOrderBoardDetail({ ...SAMPLE_DETAIL, info: reactiveInfo });
expect(detail.info.type).toBe("Reactive");
expect(detail.info.mediaCount).toBe(2);
expect(getClosabilityGaps(tableRowToClosabilityInput(detail.info))).not.toContain(
"At least 1 photo",
);
});
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,
info: reactiveInfo,
media: [
{
id: -1,
category: 1,
url: "https://cdn.example.com/legacy.jpg",
fileName: "legacy.jpg",
isLegacy: true,
},
],
});
expect(detail.info.mediaCount).toBe(0);
});
it("keeps an explicit info mediaCount", () => {
const detail = mapWorkOrderBoardDetail({
...SAMPLE_DETAIL,
info: { ...reactiveInfo, mediaCount: 5 },
});
expect(detail.info.mediaCount).toBe(5);
});
});