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 052004649..a232a0639 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts @@ -228,7 +228,6 @@ const ROLE_USER01_FORK_JOIN_ATRZ_STATUS_ACTIONS: Record< ], ATRZ04: [ FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, - FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq, FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv, ], ATRZ99: [ 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 4c5166a84..cb42a6523 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 @@ -911,11 +911,15 @@ function FloatingActionButtons({ 조치 완료 )} - {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) && ( - setActiveDialog("rvwRsltInq")} disabled={isBusy}> - 검토 결과 조회 - - )} + {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) && + !( + isForkJoin && + (userPendingBranchSeCd === "TOTAL_AGG" || userPendingAtrzSeComCd === "ATRZ04") + ) && ( + setActiveDialog("rvwRsltInq")} disabled={isBusy}> + 검토 결과 조회 + + )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwStrg) && ( 저장 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 341b6ea3b..08da7e5d4 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 @@ -1,12 +1,14 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { useContext, useEffect, useMemo, useState } from "react"; import dynamic from "next/dynamic"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { PanelRightClose, PanelRightOpen } from "lucide-react"; +import { useStore } from "zustand"; import { getDefaultFileKey, useAlertStore, useTus } from "@/shared/lib"; +import { StoreContext } from "@/shared/providers"; import { AttachmentFileField } from "@/shared/ui"; import { attachService, @@ -18,7 +20,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 { useElaprBranchesByDocNo, useElaprDocPrgrsByDocNo } from "@/entities/elapr"; import { findCompletedPwrkDraft, pwrkCnsltnMngKeys, @@ -85,6 +87,7 @@ function resolveDefaultBranchSeCd({ prgrsSttsComCd, supplementDmndBranch, supplementBranches, + isFinalApproverAfterMerger, }: { branchSeCd?: string; isForkJoin?: boolean; @@ -92,7 +95,13 @@ function resolveDefaultBranchSeCd({ prgrsSttsComCd?: string; supplementDmndBranch?: string; supplementBranches?: Set; + isFinalApproverAfterMerger?: boolean; }): string { + // 0. 병합자 후의 최종 승인자는 무조건 MAIN 탭으로 고정 + if (isForkJoin && isFinalApproverAfterMerger) { + return "MAIN"; + } + // 1. 공단 사용자 등 본인 부서(branchSeCd)가 있는 경우 본인 부서 탭을 1순위로 지정 if (branchSeCd === "TOTAL_AGG") { return "MAIN"; @@ -134,6 +143,8 @@ function PwrkCnsltnDocMngDialog({ }: PwrkCnsltnDocMngDialogProps) { const queryClient = useQueryClient(); const alert = useAlertStore((state) => state.setMessage); + const storeContext = useContext(StoreContext); + const userInfo = storeContext ? useStore(storeContext.userStore, (state) => state.userInfo) : null; const [atchFileGroupId, setAtchFileGroupId] = useState(""); const [uploadedFiles, setUploadedFiles] = useState([]); const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false); @@ -145,6 +156,97 @@ function PwrkCnsltnDocMngDialog({ open && Boolean(isForkJoin), ); + // 전자결재 결재선 목록 직접 조회 (병합자 / 최종승인자 식별용) + const { data: elaprDocPrgrsList = [] } = useElaprDocPrgrsByDocNo( + open && Boolean(isForkJoin) ? fnrsCnsltnDocNo : null, + ); + + const elaprPrgrsForCycl = useMemo(() => { + if (!elaprDocPrgrsList.length) return []; + if (prgrsSttsComCd === "RK") { + const cycl2 = elaprDocPrgrsList.filter((row) => Number(row.elaprCycl) === 2); + return cycl2.length > 0 ? cycl2 : elaprDocPrgrsList; + } + return elaprDocPrgrsList; + }, [elaprDocPrgrsList, prgrsSttsComCd]); + + const currentUserPrgrs = useMemo(() => { + if (!userInfo?.userUnqId || !elaprPrgrsForCycl.length) return undefined; + return ( + elaprPrgrsForCycl.find( + (row) => row.aprvrUuid === userInfo.userUnqId && row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_002", + ) ?? elaprPrgrsForCycl.find((row) => row.aprvrUuid === userInfo.userUnqId) + ); + }, [userInfo?.userUnqId, elaprPrgrsForCycl]); + + const effectiveBranchSeCd = branchSeCd ?? currentUserPrgrs?.branchSeCd; + const effectiveAtrzSeList = useMemo(() => { + if (atrzSeComCdList && atrzSeComCdList.length > 0) return atrzSeComCdList; + if (currentUserPrgrs?.atrzSeComCd) return [currentUserPrgrs.atrzSeComCd]; + return []; + }, [atrzSeComCdList, currentUserPrgrs?.atrzSeComCd]); + + const isTeOrRk = prgrsSttsComCd === "TE" || prgrsSttsComCd === "RK"; + + // 1. 병합자 여부 (TE/RK 상태에서 TOTAL_AGG 브랜치이거나 ATRZ04 권한을 가진 병합자) + const isMerger = useMemo(() => { + if (!isForkJoin || !isTeOrRk) return false; + if (effectiveAtrzSeList.includes("ATRZ04")) return true; + if (effectiveBranchSeCd === "TOTAL_AGG" && !effectiveAtrzSeList.some((cd) => APPROVER_ATRZ_SE_COM_CDS.has(cd))) { + return true; + } + return false; + }, [isForkJoin, isTeOrRk, effectiveAtrzSeList, effectiveBranchSeCd]); + + // 2. 병합자 후의 최종 승인자 여부 (TE/RK 상태에서 TOTAL_AGG 브랜치이면서 ATRZ04가 아니고 ATRZ03 또는 ATRZ99 등 후속 결재/승인자) + const isFinalApproverAfterMerger = useMemo(() => { + if (!isForkJoin || !isTeOrRk) return false; + if (effectiveBranchSeCd === "TOTAL_AGG") { + if (effectiveAtrzSeList.includes("ATRZ04")) return false; + if (effectiveAtrzSeList.some((cd) => APPROVER_ATRZ_SE_COM_CDS.has(cd))) return true; + if (currentUserPrgrs?.atrzSeq && Number(currentUserPrgrs.atrzSeq) > 1) return true; + if (effectiveAtrzSeList.length > 0 && !effectiveAtrzSeList.includes("ATRZ04")) return true; + } + return false; + }, [isForkJoin, isTeOrRk, effectiveBranchSeCd, effectiveAtrzSeList, currentUserPrgrs?.atrzSeq]); + + // 3. 병합 완료(결재 후) 여부 + // 병합자가 이미 결재(승인)를 진행한 경우 (ATRZ_PRGRS_003 등) MAIN 탭 저장 불가 + const isMergerApproved = useMemo(() => { + if (!isForkJoin || !isMerger) return false; + + // 1순위: 현재 로그인 사용자의 결재선 행 확인 + if (userInfo?.userUnqId) { + const userRows = elaprPrgrsForCycl.filter((row) => row.aprvrUuid === userInfo.userUnqId); + const mergerRow = userRows.find( + (row) => + row.atrzSeComCd === "ATRZ04" || + (row.branchSeCd === "TOTAL_AGG" && Number(row.atrzSeq) === 1), + ); + if (mergerRow) { + return ( + mergerRow.atrzPrgrsSttsComCd === "ATRZ_PRGRS_003" || + mergerRow.atrzPrgrsSttsComCd === "ATRZ_PRGRS_999" + ); + } + } + + // 2순위: 결재선 상에서 TOTAL_AGG 브랜치 1순번(병합자)의 결재 완료 여부 확인 + const aggMergerRow = elaprPrgrsForCycl.find( + (row) => + row.branchSeCd === "TOTAL_AGG" && + (row.atrzSeComCd === "ATRZ04" || Number(row.atrzSeq) === 1), + ); + if (aggMergerRow) { + return ( + aggMergerRow.atrzPrgrsSttsComCd === "ATRZ_PRGRS_003" || + aggMergerRow.atrzPrgrsSttsComCd === "ATRZ_PRGRS_999" + ); + } + + return false; + }, [isForkJoin, isMerger, userInfo?.userUnqId, elaprPrgrsForCycl]); + // 전자결재 브랜치 테이블(tb_as_elapr_branch)에서 branchSttsCd === 'SUPPLEMENT'인 부서들을 모두 수집 (SP, SF) const supplementBranches = useMemo>(() => { const branches = new Set(); @@ -167,12 +269,13 @@ function PwrkCnsltnDocMngDialog({ const [currentBranchSeCd, setCurrentBranchSeCd] = useState(() => resolveDefaultBranchSeCd({ - branchSeCd, + branchSeCd: effectiveBranchSeCd, isForkJoin, userRole, prgrsSttsComCd, supplementDmndBranch, supplementBranches, + isFinalApproverAfterMerger, }), ); @@ -180,16 +283,26 @@ function PwrkCnsltnDocMngDialog({ if (open) { setCurrentBranchSeCd( resolveDefaultBranchSeCd({ - branchSeCd, + branchSeCd: effectiveBranchSeCd, isForkJoin, userRole, prgrsSttsComCd, supplementDmndBranch, supplementBranches, + isFinalApproverAfterMerger, }), ); } - }, [open, branchSeCd, isForkJoin, userRole, prgrsSttsComCd, supplementBranches, supplementDmndBranch]); + }, [ + open, + effectiveBranchSeCd, + isForkJoin, + userRole, + prgrsSttsComCd, + supplementBranches, + supplementDmndBranch, + isFinalApproverAfterMerger, + ]); const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); @@ -420,24 +533,44 @@ function PwrkCnsltnDocMngDialog({ if (!baseCanSave) return false; if (isForkJoin) { - // 종합병합자: MAIN 탭만 저장 가능 - if (branchSeCd === "TOTAL_AGG") { + // 2-1. 병합자 후의 최종 승인자 또는 병합 완료(결재 후)된 병합자: 저장 불가 (읽기 전용) + if (isFinalApproverAfterMerger || isMergerApproved) { + return false; + } + + // 2-2. 종합병합자 (ATRZ04 또는 TOTAL_AGG): + // 웹기안기의 한글문서 저장 버튼은 MAIN 탭의 것만 저장 버튼이 보이고 SP, SF 탭의 저장 버튼은 보이지 않음! + if (isMerger || effectiveBranchSeCd === "TOTAL_AGG") { return currentBranchSeCd === "MAIN"; } - // 관로 검토자: 관로(SP) 탭만 저장 가능 (타 부서 보완요청 여부와 무관하게 본인 부서 작성/수정 가능) - if (branchSeCd === "SP") { + + // 2-3. 관로 검토자: 관로(SP) 탭만 저장 가능 (타 부서 보완요청 여부와 무관하게 본인 부서 작성/수정 가능) + if (effectiveBranchSeCd === "SP") { return currentBranchSeCd === "SP"; } - // 시설 검토자: 시설(SF) 탭만 저장 가능 (타 부서 보완요청 여부와 무관하게 본인 부서 작성/수정 가능) - if (branchSeCd === "SF") { + + // 2-4. 시설 검토자: 시설(SF) 탭만 저장 가능 (타 부서 보완요청 여부와 무관하게 본인 부서 작성/수정 가능) + if (effectiveBranchSeCd === "SF") { return currentBranchSeCd === "SF"; } + // 특정 부서가 지정되지 않은 일반 관리자/사용자 return baseCanSave; } return baseCanSave; - }, [baseCanSave, userRole, prgrsSttsComCd, isForkJoin, branchSeCd, currentBranchSeCd, supplementBranches]); + }, [ + baseCanSave, + userRole, + prgrsSttsComCd, + isForkJoin, + isFinalApproverAfterMerger, + isMerger, + isMergerApproved, + effectiveBranchSeCd, + currentBranchSeCd, + supplementBranches, + ]); return ( <> @@ -454,22 +587,26 @@ function PwrkCnsltnDocMngDialog({ 부서별 문서 탭: - setCurrentBranchSeCd("SP")} - > - 관로 초안 (SP) - - setCurrentBranchSeCd("SF")} - > - 시설 초안 (SF) - + {!isFinalApproverAfterMerger && ( + <> + setCurrentBranchSeCd("SP")} + > + 관로 초안 (SP) + + setCurrentBranchSeCd("SF")} + > + 시설 초안 (SF) + + > + )} 0 - ? `[보완 진행 중] ${Array.from(supplementBranches).join(", ")} 부서 보완요청 진행 중` - : "문서 열람 모드"} + : isFinalApproverAfterMerger + ? "최종 승인자: MAIN 종합 실무협의서 열람 모드" + : isMerger || effectiveBranchSeCd === "TOTAL_AGG" + ? isMergerApproved + ? "종합병합자: 병합 결재 완료 (읽기 전용 모드)" + : currentBranchSeCd === "MAIN" + ? "종합병합자: SP/SF를 참조하여 MAIN 종합본을 수동 병합 저장합니다." + : "종합병합자: 타 문서 열람 모드 (읽기 전용)" + : effectiveBranchSeCd === "SP" + ? currentBranchSeCd === "SP" + ? "관로 검토자: 관로 초안(SP) 편집 모드 (저장 가능)" + : "관로 검토자: 타 문서 열람 모드 (읽기 전용)" + : effectiveBranchSeCd === "SF" + ? currentBranchSeCd === "SF" + ? "시설 검토자: 시설 초안(SF) 편집 모드 (저장 가능)" + : "시설 검토자: 타 문서 열람 모드 (읽기 전용)" + : isForkJoin && supplementBranches.size > 0 + ? `[보완 진행 중] ${Array.from(supplementBranches).join(", ")} 부서 보완요청 진행 중` + : "문서 열람 모드"} ) : null}