diff --git a/apps/as/src/entities/elapr/api/elapr-branch-service.ts b/apps/as/src/entities/elapr/api/elapr-branch-service.ts new file mode 100644 index 000000000..3ee120a9e --- /dev/null +++ b/apps/as/src/entities/elapr/api/elapr-branch-service.ts @@ -0,0 +1,15 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { ElaprBranchDto } from "../model/elapr-forkjoin-types"; + +const BASE = ENDPOINT.AS_ELAPR_SERVICE.ELAPR_BRANCH; + +async function getBranchesByDocNo(elaprDocNo: string): Promise { + const response = await client.get(`${BASE}/doc/${elaprDocNo}`); + return response.data; +} + +export const elaprBranchService = { + getBranchesByDocNo, +}; diff --git a/apps/as/src/entities/elapr/api/queries.ts b/apps/as/src/entities/elapr/api/queries.ts index 9908ef44f..1a3a2494e 100644 --- a/apps/as/src/entities/elapr/api/queries.ts +++ b/apps/as/src/entities/elapr/api/queries.ts @@ -23,6 +23,7 @@ import type { } from "../model/elapr-tmplt-interface"; import { elaprAplnService } from "./elapr-apln-service"; +import { elaprBranchService } from "./elapr-branch-service"; import { elaprDmndService } from "./elapr-dmnd-service"; import { elaprPrgrsSttsService } from "./elapr-prgrs-stts-service"; import { elaprTmpltService } from "./elapr-tmplt-service"; @@ -31,6 +32,7 @@ const elaprTmpltBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_TMPLT] as const; const elaprAplnBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_APLN] as const; const elaprDmndBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND] as const; const elaprPrgrsSttsBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_PRGRS_STTS] as const; +const elaprBranchBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_BRANCH] as const; function invalidateElaprPrgrsSttsQueries(queryClient: ReturnType) { queryClient.invalidateQueries({ queryKey: elaprPrgrsSttsBaseKey }); @@ -360,3 +362,16 @@ export function useInsertElaprPrgrsSttsApprover() { }, }); } + +export function useElaprBranchesByDocNo( + elaprDocNo: string | null | undefined, + options?: { enabled?: boolean; structuralSharing?: boolean }, +) { + return useQuery({ + queryKey: [...elaprBranchBaseKey, "getBranchesByDocNo", elaprDocNo], + queryFn: () => elaprBranchService.getBranchesByDocNo(elaprDocNo!), + enabled: Boolean(elaprDocNo) && (options?.enabled ?? true), + structuralSharing: options?.structuralSharing ?? true, + }); +} + diff --git a/apps/as/src/entities/elapr/index.ts b/apps/as/src/entities/elapr/index.ts index 59cc4e50c..4e58d71ae 100644 --- a/apps/as/src/entities/elapr/index.ts +++ b/apps/as/src/entities/elapr/index.ts @@ -1,4 +1,5 @@ export { elaprAplnService } from "./api/elapr-apln-service"; +export { elaprBranchService } from "./api/elapr-branch-service"; export { elaprDmndService } from "./api/elapr-dmnd-service"; export { elaprPrgrsSttsService } from "./api/elapr-prgrs-stts-service"; export { elaprTmpltService } from "./api/elapr-tmplt-service"; @@ -9,6 +10,7 @@ export { useDeleteElaprTmplt, useElaprApln, useElaprAplnPage, + useElaprBranchesByDocNo, useElaprDmnd, useElaprDmndPage, useElaprDocPrgrsByDocNo, diff --git a/apps/as/src/entities/elapr/model/elapr-forkjoin-types.ts b/apps/as/src/entities/elapr/model/elapr-forkjoin-types.ts index 64aaaa61c..2294c3041 100644 --- a/apps/as/src/entities/elapr/model/elapr-forkjoin-types.ts +++ b/apps/as/src/entities/elapr/model/elapr-forkjoin-types.ts @@ -27,6 +27,7 @@ export type BranchSttsCd = | "WAITING" | "IN_PROGRESS" | "COMPLETED" + | "SUPPLEMENT" | "REJECTED" | "CANCELLED"; diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts b/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts index 3dbfa15b0..052004649 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts @@ -329,6 +329,8 @@ export interface GetButtonConfigsOptions { canRetryDraftGen?: boolean; canRetryCostVrfc?: boolean; isForkJoin?: boolean; + branchSeCd?: "SP" | "SF" | "TOTAL_AGG" | "MAIN" | string; + branchSttsCd?: "WAITING" | "IN_PROGRESS" | "COMPLETED" | "SUPPLEMENT" | "REJECTED" | string; } const PENDING_APPROVAL_ACTIONS = new Set([ @@ -345,9 +347,30 @@ function resolveRoleUser01AtrzButtonActions( atrzSeComCdList: string[], options?: GetButtonConfigsOptions, ): FnrsCnsltnViewButtonAction[] { + let effectiveStatus = prgrsSttsComCd; + + if (options?.isForkJoin && options?.branchSttsCd) { + switch (options.branchSttsCd) { + case "SUPPLEMENT": + // 본인 브랜치가 보완요청 중이면 'SL' 맵핑 적용 (검토/승인 버튼 숨김, 실무협의서관리만 노출) + effectiveStatus = "SL"; + break; + case "IN_PROGRESS": + // 본인 브랜치가 정상 진행 중이면 원천 상태('TE' 또는 'RK') 정상 적용! + effectiveStatus = prgrsSttsComCd === "RK" ? "RK" : "TE"; + break; + case "COMPLETED": + // 본인 브랜치 승인 완료 후 타 브랜치 대기 중이면 조회 전용 맵 적용 + effectiveStatus = prgrsSttsComCd === "RK" ? "RR" : "TP"; + break; + case "WAITING": + return [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng]; + } + } + const atrzStatusMap = options?.isForkJoin - ? ROLE_USER01_FORK_JOIN_ATRZ_STATUS_ACTIONS[prgrsSttsComCd] - : ROLE_USER01_ATRZ_STATUS_ACTIONS[prgrsSttsComCd]; + ? ROLE_USER01_FORK_JOIN_ATRZ_STATUS_ACTIONS[effectiveStatus] + : ROLE_USER01_ATRZ_STATUS_ACTIONS[effectiveStatus]; if (!atrzStatusMap || atrzSeComCdList.length === 0) return []; const seen = new Set(); @@ -382,12 +405,12 @@ function resolveRoleUser01AtrzButtonActions( } if (options?.hasCompletedDraft != null) { - if (prgrsSttsComCd === "SL" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "RK") { + if (effectiveStatus === "SL" || effectiveStatus === "TE" || effectiveStatus === "RK") { if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng) { return options.hasCompletedDraft; } } - if (prgrsSttsComCd === "TE") { + if (effectiveStatus === "TE") { if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) { return options.hasCompletedDraft; } diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx index 7e54665fa..7d33cb820 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx @@ -27,6 +27,7 @@ import { type ElaprTmpltPickerConfirmPayload, ElaprTmpltPickerDialog, hasCurrentUserPendingApprovalRequest, + useElaprBranchesByDocNo, useElaprDocPrgrsByDocNo, } from "@/entities/elapr"; import { @@ -139,8 +140,13 @@ function FloatingActionButtons({ const docNo = data.detDto?.fnrsCnsltnDocNo ?? fnrsCnsltnDocNo; const isRegister = data.detDto?.rgtr === userInfo?.userUnqId; + const isForkJoin = isForkJoinTask(data.detDto?.taskSeComCd); + const normalizedTaskSeComCd = normalizeTaskSeComCdForElapr(data.detDto?.taskSeComCd); + const needsAtrzResolve = needsRoleUser01AtrzResolve(dtlInstCd, prgrsSttsComCd); - const needsElaprPrgrsCheck = needsAtrzResolve && ["TE", "RK"].includes(prgrsSttsComCd); + const needsElaprPrgrsCheck = + needsAtrzResolve && + (["TE", "RK"].includes(prgrsSttsComCd) || (isForkJoin && prgrsSttsComCd === "SL")); const needsPwrkDraftCheck = dtlInstCd === "ROLE_USER01" && ["SL", "TE", "RK"].includes(prgrsSttsComCd); const needsPwrkForRb = dtlInstCd === "ROLE_USER05" && prgrsSttsComCd === "RB"; @@ -151,6 +157,10 @@ function FloatingActionButtons({ const { data: elaprDocPrgrsList = [], isLoading: isElaprDocPrgrsLoading } = useElaprDocPrgrsByDocNo(needsElaprPrgrsCheck ? docNo : null); + const { data: elaprBranchList = [], isLoading: isElaprBranchLoading } = useElaprBranchesByDocNo( + isForkJoin && (needsElaprPrgrsCheck || prgrsSttsComCd === "SL") ? docNo : null, + ); + const { data: pwrkCnsltnData, isLoading: isPwrkCnsltnLoading } = usePwrkCnsltnMngByUpDocNo( docNo, needsPwrkLookup, @@ -190,32 +200,101 @@ function FloatingActionButtons({ elaprPrgrsForPending, userInfo?.userUnqId, ); + const currentUserPrgrsRow = elaprPrgrsForPending.find( + (row) => row.aprvrUuid === userInfo?.userUnqId, + ); const userPendingPrgrs = elaprPrgrsForPending.find( (row) => row.aprvrUuid === userInfo?.userUnqId && row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_002", ); - const userPendingBranchSeCd = (userPendingPrgrs as { branchSeCd?: string } | undefined)?.branchSeCd; + const userBranchSeCd = (userPendingPrgrs?.branchSeCd ?? currentUserPrgrsRow?.branchSeCd) as + | "SP" + | "SF" + | "TOTAL_AGG" + | "MAIN" + | undefined; + const userPendingBranchSeCd = userPendingPrgrs?.branchSeCd ?? userBranchSeCd; const userPendingAtrzSeComCd = userPendingPrgrs?.atrzSeComCd; - const normalizedTaskSeComCd = normalizeTaskSeComCdForElapr(data.detDto?.taskSeComCd); - const isForkJoin = isForkJoinTask(data.detDto?.taskSeComCd); - const effectiveAtrzSeComCdList = useMemo(() => { if (isForkJoin && userPendingAtrzSeComCd) { return [userPendingAtrzSeComCd]; } + if (isForkJoin && currentUserPrgrsRow?.atrzSeComCd) { + return [currentUserPrgrsRow.atrzSeComCd]; + } return atrzSeComCdList; - }, [atrzSeComCdList, isForkJoin, userPendingAtrzSeComCd]); + }, [atrzSeComCdList, currentUserPrgrsRow?.atrzSeComCd, isForkJoin, userPendingAtrzSeComCd]); + + const supplementDmndBranch = useMemo(() => { + if (!isForkJoin) return undefined; + + // 1순위: 전자결재 브랜치(tb_as_elapr_branch) 테이블에서 상태가 SUPPLEMENT인 부서 직접 확인 + const splmntBranch = elaprBranchList.find((b) => b.branchSttsCd === "SUPPLEMENT"); + if (splmntBranch?.branchSeCd && ["SP", "SF"].includes(splmntBranch.branchSeCd)) { + return splmntBranch.branchSeCd as "SP" | "SF"; + } + + // 2순위: 미완료 보완요청 항목의 등록자 UUID 매칭 + const uncompletedItems = data?.splmntDmndList?.filter((item) => item.cmptnYn !== "Y") ?? []; + if (uncompletedItems.length > 0) { + for (const splmnt of uncompletedItems) { + const matchedPrgrs = elaprDocPrgrsList.find( + (p) => p.aprvrUuid?.toLowerCase() === splmnt.rgtr?.toLowerCase(), + ); + if (matchedPrgrs?.branchSeCd && ["SP", "SF"].includes(matchedPrgrs.branchSeCd)) { + return matchedPrgrs.branchSeCd as "SP" | "SF"; + } + } + } + return undefined; + }, [data?.splmntDmndList, elaprBranchList, elaprDocPrgrsList, isForkJoin]); + + const userBranchSttsCd = useMemo(() => { + if (!isForkJoin || !userBranchSeCd) return undefined; + + // 1. elaprBranchList API 응답에서 사용자 브랜치 상태 확인 + const matchedBranch = elaprBranchList.find((b) => b.branchSeCd === userBranchSeCd); + if (matchedBranch?.branchSttsCd) { + return matchedBranch.branchSttsCd; + } + + // 2. Fallback: 문서 상태가 SL일 때 보완요청 발생 부서(supplementDmndBranch)와 비교 + if (prgrsSttsComCd === "SL") { + if (supplementDmndBranch && userBranchSeCd === supplementDmndBranch) { + return "SUPPLEMENT"; + } + const branchRows = elaprPrgrsForPending.filter((row) => row.branchSeCd === userBranchSeCd); + const hasUnfinished = branchRows.some( + (row) => + row.atrzPrgrsSttsComCd !== "ATRZ_PRGRS_003" && row.atrzPrgrsSttsComCd !== "ATRZ_PRGRS_999", + ); + return hasUnfinished ? "IN_PROGRESS" : "COMPLETED"; + } + + if (["TE", "RK"].includes(prgrsSttsComCd)) { + return "IN_PROGRESS"; + } + if (["TP", "RR"].includes(prgrsSttsComCd)) { + return "COMPLETED"; + } + + return undefined; + }, [elaprBranchList, elaprPrgrsForPending, isForkJoin, prgrsSttsComCd, supplementDmndBranch, userBranchSeCd]); + + const isBranchInProgress = + isForkJoin && userBranchSttsCd === "IN_PROGRESS" && dtlInstCd === "ROLE_USER01"; const viewOnlyPwrkCnsltnDoc = - prgrsSttsComCd === "SL" || + (prgrsSttsComCd === "SL" && dtlInstCd !== "ROLE_USER05" && !isBranchInProgress) || (prgrsSttsComCd === "RB" && dtlInstCd !== "ROLE_USER05") || (prgrsSttsComCd === "RK" && dtlInstCd === "ROLE_USER03"); const actionButtons = useMemo(() => { if (needsAtrzResolve && isRvwLoginUserLoading) return []; if (needsElaprPrgrsCheck && isElaprDocPrgrsLoading) return []; + if (isForkJoin && isElaprBranchLoading) return []; if (needsPwrkDraftCheck && isPwrkCnsltnLoading) return []; if (needsAiAutoLatest && isAiAutoLatestLoading) return []; return getButtonConfigs(dtlInstCd, prgrsSttsComCd, effectiveAtrzSeComCdList, { @@ -225,6 +304,8 @@ function FloatingActionButtons({ canRetryDraftGen: needsAiAutoLatest ? canRetryDraftGen : undefined, canRetryCostVrfc: needsAiAutoLatest ? canRetryCostVrfc : undefined, isForkJoin, + branchSeCd: userBranchSeCd, + branchSttsCd: userBranchSttsCd, }); }, [ canRetryCostVrfc, @@ -235,6 +316,7 @@ function FloatingActionButtons({ hasCompletedDraft, hasPendingApprovalRequest, isAiAutoLatestLoading, + isElaprBranchLoading, isElaprDocPrgrsLoading, isForkJoin, isPwrkCnsltnLoading, @@ -244,6 +326,8 @@ function FloatingActionButtons({ needsElaprPrgrsCheck, needsPwrkDraftCheck, prgrsSttsComCd, + userBranchSeCd, + userBranchSttsCd, ]); const canEdit = @@ -287,6 +371,9 @@ function FloatingActionButtons({ queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.detail(fnrsCnsltnDocNo), }); + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_BRANCH], + }); }, onError: (error) => { let errorMessage = ""; @@ -526,7 +613,7 @@ function FloatingActionButtons({ data, checklistFormState, "KECO", - "TE", + prgrsSttsComCd === "SL" ? "SL" : prgrsSttsComCd === "RK" ? "RK" : "TE", "SV", getReviewOpinionFormData(), opnrNm, @@ -541,8 +628,9 @@ function FloatingActionButtons({ } function handleApprovalOpen() { - if (prgrsSttsComCd === "TE" && userPendingBranchSeCd) { - const branchCd = (userPendingBranchSeCd as BranchSeCd) || (isForkJoin ? "SP" : "MAIN"); + const isTeStep = prgrsSttsComCd === "TE" || (isForkJoin && prgrsSttsComCd === "SL"); + if (isTeStep && (userBranchSeCd || userPendingBranchSeCd)) { + const branchCd = (userBranchSeCd as BranchSeCd) || (userPendingBranchSeCd as BranchSeCd) || (isForkJoin ? "SP" : "MAIN"); const validation = validateChecklistFormStateByBranch({ rows: data.cklstResultList ?? [], formState: checklistFormState, @@ -560,8 +648,9 @@ function FloatingActionButtons({ } async function handleRvwCmptnClick() { - if (prgrsSttsComCd === "TE") { - const branchCd = (userPendingBranchSeCd as BranchSeCd) || (isForkJoin ? "SP" : "MAIN"); + const isTeStep = prgrsSttsComCd === "TE" || (isForkJoin && prgrsSttsComCd === "SL"); + if (isTeStep) { + const branchCd = (userBranchSeCd as BranchSeCd) || (userPendingBranchSeCd as BranchSeCd) || (isForkJoin ? "SP" : "MAIN"); const validation = validateChecklistFormStateByBranch({ rows: data.cklstResultList ?? [], formState: checklistFormState, @@ -924,6 +1013,9 @@ function FloatingActionButtons({ queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.detail(fnrsCnsltnDocNo), }); + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_BRANCH], + }); }} /> @@ -932,11 +1024,13 @@ function FloatingActionButtons({ fnrsCnsltnDocNo={docNo} atrzSeComCdList={effectiveAtrzSeComCdList} prgrsSttsComCd={prgrsSttsComCd} - branchSeCd={userPendingBranchSeCd} + branchSeCd={userBranchSeCd ?? userPendingBranchSeCd} isForkJoin={isForkJoin} userRole={dtlInstCd} + supplementDmndBranch={supplementDmndBranch} canSave={ - (needsElaprPrgrsCheck && !hasPendingApprovalRequest) || viewOnlyPwrkCnsltnDoc + (needsElaprPrgrsCheck && !hasPendingApprovalRequest && !isBranchInProgress) || + viewOnlyPwrkCnsltnDoc ? false : null } diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx index a509820f2..341b6ea3b 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx @@ -18,6 +18,7 @@ import { pwrkCnsltnDocCrtRsltService, } from "@/entities/pwrk-cnsltn-doc-crt-rslt"; import { usePwrkCnsltnDocPjtcoAjmtMngPage } from "@/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng"; +import { useElaprBranchesByDocNo } from "@/entities/elapr"; import { findCompletedPwrkDraft, pwrkCnsltnMngKeys, @@ -83,22 +84,38 @@ function resolveDefaultBranchSeCd({ userRole, prgrsSttsComCd, supplementDmndBranch, + supplementBranches, }: { branchSeCd?: string; isForkJoin?: boolean; userRole?: string; prgrsSttsComCd?: string; supplementDmndBranch?: string; + supplementBranches?: Set; }): string { - if (userRole === "ROLE_USER05" && prgrsSttsComCd === "SL") { - return supplementDmndBranch || branchSeCd || "SP"; - } + // 1. 공단 사용자 등 본인 부서(branchSeCd)가 있는 경우 본인 부서 탭을 1순위로 지정 if (branchSeCd === "TOTAL_AGG") { return "MAIN"; } if (branchSeCd && ["SP", "SF", "MAIN"].includes(branchSeCd)) { return branchSeCd; } + + // 2. 지자체 사용자(ROLE_USER05) 또는 본인 부서가 없는 경우: 보완요청 부서 우선 선택 + if (isForkJoin && supplementBranches && supplementBranches.size > 0) { + if (supplementBranches.size > 1) { + return "SP"; + } + if (supplementBranches.has("SP")) return "SP"; + if (supplementBranches.has("SF")) return "SF"; + } + if (isForkJoin && supplementDmndBranch && ["SP", "SF"].includes(supplementDmndBranch)) { + return supplementDmndBranch; + } + if (userRole === "ROLE_USER05" && (prgrsSttsComCd === "SL" || prgrsSttsComCd === "RB")) { + return supplementDmndBranch || "SP"; + } + return isForkJoin ? "SP" : "MAIN"; } @@ -121,6 +138,33 @@ function PwrkCnsltnDocMngDialog({ const [uploadedFiles, setUploadedFiles] = useState([]); const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false); const [isEvlDialogOpen, setIsEvlDialogOpen] = useState(false); + + // 전자결재 브랜치 목록 직접 조회 (Single Source of Truth) + const { data: elaprBranchList = [] } = useElaprBranchesByDocNo( + fnrsCnsltnDocNo, + open && Boolean(isForkJoin), + ); + + // 전자결재 브랜치 테이블(tb_as_elapr_branch)에서 branchSttsCd === 'SUPPLEMENT'인 부서들을 모두 수집 (SP, SF) + const supplementBranches = useMemo>(() => { + const branches = new Set(); + if (!isForkJoin) return branches; + + // 1순위: elaprBranchList에서 SUPPLEMENT 상태인 브랜치들을 모두 수집 + for (const b of elaprBranchList) { + if (b.branchSttsCd === "SUPPLEMENT" && b.branchSeCd && ["SP", "SF"].includes(b.branchSeCd)) { + branches.add(b.branchSeCd); + } + } + + // 2순위: props로 전달된 supplementDmndBranch 추가 + if (supplementDmndBranch && ["SP", "SF"].includes(supplementDmndBranch)) { + branches.add(supplementDmndBranch); + } + + return branches; + }, [elaprBranchList, isForkJoin, supplementDmndBranch]); + const [currentBranchSeCd, setCurrentBranchSeCd] = useState(() => resolveDefaultBranchSeCd({ branchSeCd, @@ -128,6 +172,7 @@ function PwrkCnsltnDocMngDialog({ userRole, prgrsSttsComCd, supplementDmndBranch, + supplementBranches, }), ); @@ -140,10 +185,11 @@ function PwrkCnsltnDocMngDialog({ userRole, prgrsSttsComCd, supplementDmndBranch, + supplementBranches, }), ); } - }, [open, branchSeCd, isForkJoin, userRole, prgrsSttsComCd, supplementDmndBranch]); + }, [open, branchSeCd, isForkJoin, userRole, prgrsSttsComCd, supplementBranches, supplementDmndBranch]); const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); @@ -355,21 +401,43 @@ function PwrkCnsltnDocMngDialog({ const baseCanSave = !viewOnly && (canSaveProp ?? canSaveByAtrz); const canSaveCurrentBranch = useMemo(() => { + // 1. 지자체 사용자 (ROLE_USER05): 보완요청(SL) 및 지자체회신/조치결과작성(RB) 상태에서 보완 부서만 저장 허용 + if (userRole === "ROLE_USER05") { + if (prgrsSttsComCd === "SL" || prgrsSttsComCd === "RB") { + if (isForkJoin) { + // 보완요청 부서(supplementBranches)가 존재하는 경우 해당 부서만 저장 허용 + if (supplementBranches.size > 0) { + return supplementBranches.has(currentBranchSeCd); + } + return currentBranchSeCd === "SP" || currentBranchSeCd === "SF"; + } + return true; + } + return false; + } + + // 2. 공단 사용자 등 일반 사용자 (검토자, 결재자 등) if (!baseCanSave) return false; - if (userRole === "ROLE_USER05") return false; + if (isForkJoin) { + // 종합병합자: MAIN 탭만 저장 가능 if (branchSeCd === "TOTAL_AGG") { return currentBranchSeCd === "MAIN"; } + // 관로 검토자: 관로(SP) 탭만 저장 가능 (타 부서 보완요청 여부와 무관하게 본인 부서 작성/수정 가능) if (branchSeCd === "SP") { return currentBranchSeCd === "SP"; } + // 시설 검토자: 시설(SF) 탭만 저장 가능 (타 부서 보완요청 여부와 무관하게 본인 부서 작성/수정 가능) if (branchSeCd === "SF") { return currentBranchSeCd === "SF"; } + // 특정 부서가 지정되지 않은 일반 관리자/사용자 + return baseCanSave; } + return baseCanSave; - }, [baseCanSave, userRole, isForkJoin, branchSeCd, currentBranchSeCd]); + }, [baseCanSave, userRole, prgrsSttsComCd, isForkJoin, branchSeCd, currentBranchSeCd, supplementBranches]); return ( <> @@ -412,19 +480,31 @@ function PwrkCnsltnDocMngDialog({ - {branchSeCd === "TOTAL_AGG" - ? "종합병합자: SP/SF를 참조하여 MAIN 종합본을 수동 병합 저장합니다." - : branchSeCd === "SP" - ? currentBranchSeCd === "SP" - ? "관로 검토자: 관로 초안(SP) 편집 모드" - : "관로 검토자: 타 문서 열람 모드 (읽기 전용)" - : branchSeCd === "SF" - ? currentBranchSeCd === "SF" - ? "시설 검토자: 시설 초안(SF) 편집 모드" - : "시설 검토자: 타 문서 열람 모드 (읽기 전용)" - : userRole === "ROLE_USER05" - ? "지자체 사용자: 보완요청 부서 문서 열람 전용" - : "문서 열람 모드"} + {userRole === "ROLE_USER05" + ? isForkJoin && supplementBranches.size > 0 + ? supplementBranches.has(currentBranchSeCd) + ? supplementBranches.size > 1 + ? `[보완 수정 모드] 관로·시설 부서 모두 보완요청 상태이며, ${currentBranchSeCd === "SP" ? "관로" : "시설"} 초안을 수정 및 저장할 수 있습니다.` + : `[보완 수정 모드] ${supplementBranches.has("SP") ? "관로" : "시설"} 초안만 수정 및 저장 가능합니다.` + : `[읽기 전용 모드] ${currentBranchSeCd === "MAIN" ? "종합본" : currentBranchSeCd === "SP" ? "관로 초안" : "시설 초안"}은 보완 비대상으로 저장 불가합니다.` + : prgrsSttsComCd === "SL" || prgrsSttsComCd === "RB" + ? `지자체 담당자: ${currentBranchSeCd === "MAIN" ? "종합본 열람 전용" : `${currentBranchSeCd === "SP" ? "관로" : "시설"} 초안 보완 수정 모드 (저장 가능)`}` + : "지자체 사용자: 문서 열람 모드" + : branchSeCd === "TOTAL_AGG" + ? currentBranchSeCd === "MAIN" + ? "종합병합자: SP/SF를 참조하여 MAIN 종합본을 수동 병합 저장합니다." + : "종합병합자: 타 문서 열람 모드 (읽기 전용)" + : branchSeCd === "SP" + ? currentBranchSeCd === "SP" + ? "관로 검토자: 관로 초안(SP) 편집 모드 (저장 가능)" + : "관로 검토자: 타 문서 열람 모드 (읽기 전용)" + : branchSeCd === "SF" + ? currentBranchSeCd === "SF" + ? "시설 검토자: 시설 초안(SF) 편집 모드 (저장 가능)" + : "시설 검토자: 타 문서 열람 모드 (읽기 전용)" + : isForkJoin && supplementBranches.size > 0 + ? `[보완 진행 중] ${Array.from(supplementBranches).join(", ")} 부서 보완요청 진행 중` + : "문서 열람 모드"} ) : null} diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx index 15319f84c..7c0f47f5d 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx @@ -352,8 +352,8 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps) fnrsCnsltnDocNo={fnrsCnsltnDocNo} isForkJoin={isForkJoinTask(initialData?.detDto?.taskSeComCd)} userRole={userInfo?.dtlInstCd} - prgrsSttsComCd={initialData?.detDto?.prgrsSttsComCd} - canSave={!isSplmntPrgrsStts} + prgrsSttsComCd={initialData?.detDto?.prgrsSttsComCd ?? undefined} + canSave={isSplmntPrgrsStts && userInfo?.dtlInstCd === "ROLE_USER05" ? null : !isSplmntPrgrsStts} onClose={() => setIsPwrkCnsltnDocMngOpen(false)} /> ) : null} diff --git a/apps/as/src/shared/config/api.ts b/apps/as/src/shared/config/api.ts index 4fcf3ca42..b1be71d92 100644 --- a/apps/as/src/shared/config/api.ts +++ b/apps/as/src/shared/config/api.ts @@ -42,6 +42,7 @@ export const ENDPOINT = { ELAPR_APLN: "/as-elapr-service/api/v1/elapr-apln", ELAPR_DMND: "/as-elapr-service/api/v1/elapr-dmnd", ELAPR_PRGRS_STTS: "/as-elapr-service/api/v1/elapr-prgrs-stts", + ELAPR_BRANCH: "/as-elapr-service/api/v1/elapr-branch", }, CMS_SERVICE: { CODE: "/cms-service/api/v1/code/cache",