mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-05 02:32:08 +00:00
The completion-doc upload endpoint sets DocStatus=Yes and bumps the work-order row version in one transaction. Both signed-PDF upload paths then re-ran a "docStatus=Yes" work-order patch using the pre-upload row version (the comp-doc dialog re-ran the whole Generate flow; the slide-over posted docStatus directly). That patch always 409'd — and in the dialog it also demanded a Date Work Completed — so a successful upload surfaced an error to the user. Both paths now stop after the upload settles: the mutation's onSuccess refreshes the board and the UI reflects the generated state locally, with no second docStatus patch.
183 lines
5.9 KiB
TypeScript
183 lines
5.9 KiB
TypeScript
import type { QueryClient } from "@tanstack/react-query";
|
|
import { toast } from "react-toastify";
|
|
import { showCompDocDraftSavedToast } from "@/app/(protected)/workorders/_components/comp-doc/comp-doc-draft-toast";
|
|
import { locationsApi } from "@/domain/locations/api/locations-api";
|
|
import type { Location } from "@/domain/locations/types/location";
|
|
import type {
|
|
CompDocData,
|
|
CompDocSaveStatus,
|
|
DocFields,
|
|
DocState,
|
|
} from "@/domain/work-orders/types/completion-doc";
|
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
|
import { buildDocHtml, printDoc } from "@/domain/work-orders/utils/completion-doc-html";
|
|
import { fmtDocNow } from "@/domain/work-orders/utils/completion-doc-state";
|
|
import { queryKeys } from "@/infra/query-key/query-key";
|
|
|
|
type CompDocSite = { street: string; fullAddress: string };
|
|
|
|
export function buildCompDocAddressLine(site: CompDocSite): string {
|
|
if (site.street.trim()) return site.street.trim();
|
|
return site.fullAddress !== "—" ? site.fullAddress : "";
|
|
}
|
|
|
|
function locationUpdatePayload(location: Location, address: string): Location {
|
|
return {
|
|
...location,
|
|
address,
|
|
};
|
|
}
|
|
|
|
type CreateCompDocDialogActionsParams = {
|
|
row: WorkOrderTableRow;
|
|
notes: string;
|
|
generatedAt: string;
|
|
editDateCompleted: string;
|
|
editAddress: string;
|
|
docState: DocState;
|
|
docFields: DocFields;
|
|
site: CompDocSite;
|
|
locId: string;
|
|
isMockLocation: boolean;
|
|
isSiteFrozen: boolean;
|
|
locationDetail: Location | undefined;
|
|
queryClient: QueryClient;
|
|
onSave: (data: CompDocData, status: CompDocSaveStatus) => void | Promise<void>;
|
|
onDateCompletedChange?: (date: string) => void | Promise<void>;
|
|
setDocState: (state: DocState) => void;
|
|
setGeneratedAt: (value: string) => void;
|
|
};
|
|
|
|
export function createCompDocDialogActions({
|
|
row,
|
|
notes,
|
|
generatedAt,
|
|
editDateCompleted,
|
|
editAddress,
|
|
docState,
|
|
docFields,
|
|
site,
|
|
locId,
|
|
isMockLocation,
|
|
isSiteFrozen,
|
|
locationDetail,
|
|
queryClient,
|
|
onSave,
|
|
onDateCompletedChange,
|
|
setDocState,
|
|
setGeneratedAt,
|
|
}: CreateCompDocDialogActionsParams) {
|
|
async function persist(extra: Partial<CompDocData>, status: CompDocSaveStatus): Promise<void> {
|
|
await onSave({ notes, generatedAt, ...extra }, status);
|
|
}
|
|
|
|
async function syncCompletedDateIfChanged(): Promise<void> {
|
|
if (editDateCompleted !== (row.completedDate || "")) {
|
|
await onDateCompletedChange?.(editDateCompleted);
|
|
}
|
|
}
|
|
|
|
async function syncAddressIfChanged(): Promise<void> {
|
|
const next = editAddress.trim();
|
|
const current = buildCompDocAddressLine(site);
|
|
if (next === current) return;
|
|
if (isSiteFrozen || isMockLocation || !locId) return;
|
|
if (!locationDetail) {
|
|
throw new Error("Location details are still loading. Try saving again.");
|
|
}
|
|
|
|
await locationsApi.update(locId, locationUpdatePayload(locationDetail, next));
|
|
void queryClient.invalidateQueries({ queryKey: queryKeys.locations.detail(locId) });
|
|
void queryClient.invalidateQueries({ queryKey: queryKeys.locations.all });
|
|
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all });
|
|
}
|
|
|
|
function refreshAuthoritativeState() {
|
|
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all });
|
|
if (locId && !isMockLocation) {
|
|
void queryClient.invalidateQueries({ queryKey: queryKeys.locations.detail(locId) });
|
|
void queryClient.invalidateQueries({ queryKey: queryKeys.locations.all });
|
|
}
|
|
}
|
|
|
|
async function handleMarkNotRequired() {
|
|
const previous = docState;
|
|
setDocState("not-required");
|
|
try {
|
|
await persist({}, "NN");
|
|
} catch (error) {
|
|
setDocState(previous);
|
|
refreshAuthoritativeState();
|
|
toast.error(
|
|
error instanceof Error ? error.message : "Failed to mark document as not required",
|
|
);
|
|
}
|
|
}
|
|
|
|
async function handleUndo() {
|
|
const previous = docState;
|
|
setDocState("draft");
|
|
try {
|
|
await persist({}, "No");
|
|
} catch (error) {
|
|
setDocState(previous);
|
|
refreshAuthoritativeState();
|
|
toast.error(error instanceof Error ? error.message : "Failed to undo not-required");
|
|
}
|
|
}
|
|
|
|
async function handleSaveDraft() {
|
|
try {
|
|
await syncCompletedDateIfChanged();
|
|
await syncAddressIfChanged();
|
|
await persist({}, "No");
|
|
showCompDocDraftSavedToast();
|
|
} catch (error) {
|
|
refreshAuthoritativeState();
|
|
toast.error(error instanceof Error ? error.message : "Failed to save draft");
|
|
}
|
|
}
|
|
|
|
async function handleGenerate() {
|
|
if (!editDateCompleted.trim()) {
|
|
toast.error("Date Work Completed is required to generate the document.");
|
|
return;
|
|
}
|
|
const ts = fmtDocNow();
|
|
try {
|
|
// Prerequisites first; only mark Generated after completedDate/address persist.
|
|
await syncCompletedDateIfChanged();
|
|
await syncAddressIfChanged();
|
|
await persist({ generatedAt: ts }, "Yes");
|
|
setGeneratedAt(ts);
|
|
setDocState("generated");
|
|
} catch (error) {
|
|
// Keep Draft UI; refresh so any partially persisted date/address is authoritative.
|
|
refreshAuthoritativeState();
|
|
toast.error(error instanceof Error ? error.message : "Failed to generate document");
|
|
}
|
|
}
|
|
|
|
function handleDownloadPdf() {
|
|
printDoc(buildDocHtml(row, { notes, generatedAt }, docFields));
|
|
}
|
|
|
|
// Uploading a signed PDF finalizes the document server-side: the endpoint sets DocStatus=Yes
|
|
// and bumps the work-order row version in one transaction. Reflect that in the UI and refresh
|
|
// authoritative state, but do not re-run the Generate persist — that patch would carry the
|
|
// pre-upload row version and 409, surfacing an error right after a successful upload.
|
|
function markSignedPdfUploaded() {
|
|
setGeneratedAt(fmtDocNow());
|
|
setDocState("generated");
|
|
refreshAuthoritativeState();
|
|
}
|
|
|
|
return {
|
|
handleMarkNotRequired,
|
|
handleUndo,
|
|
handleSaveDraft,
|
|
handleGenerate,
|
|
handleDownloadPdf,
|
|
markSignedPdfUploaded,
|
|
};
|
|
}
|