[as]병렬결재에서 병합자 - 웹기안기 MAIN만 수정가능하게 하고 최종승인자의 경우에는 MAIN 탭만 보이도록 수정함

This commit is contained in:
2026-10-19 14:59:52 +09:00
parent 14803b2625
commit 42dc2dc027
3 changed files with 193 additions and 49 deletions
@@ -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: [
@@ -911,11 +911,15 @@ function FloatingActionButtons({
조치 완료
</Button>
)}
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) && (
<Button color="sky" onClick={() => setActiveDialog("rvwRsltInq")} disabled={isBusy}>
검토 결과 조회
</Button>
)}
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) &&
!(
isForkJoin &&
(userPendingBranchSeCd === "TOTAL_AGG" || userPendingAtrzSeComCd === "ATRZ04")
) && (
<Button color="sky" onClick={() => setActiveDialog("rvwRsltInq")} disabled={isBusy}>
검토 결과 조회
</Button>
)}
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwStrg) && (
<Button onClick={handleRvwStrgClick} disabled={isBusy}>
저장
@@ -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<string>;
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<UploadedAtchFile[]>([]);
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<Set<string>>(() => {
const branches = new Set<string>();
@@ -167,12 +269,13 @@ function PwrkCnsltnDocMngDialog({
const [currentBranchSeCd, setCurrentBranchSeCd] = useState<string>(() =>
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({
<div className="flex flex-wrap items-center justify-between gap-2 rounded-md border border-border bg-muted/40 px-3 py-2">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-muted-foreground">부서별 문서 탭:</span>
<Button
type="button"
size="sm"
color={currentBranchSeCd === "SP" ? "primary" : "white"}
onClick={() => setCurrentBranchSeCd("SP")}
>
관로 초안 (SP)
</Button>
<Button
type="button"
size="sm"
color={currentBranchSeCd === "SF" ? "primary" : "white"}
onClick={() => setCurrentBranchSeCd("SF")}
>
시설 초안 (SF)
</Button>
{!isFinalApproverAfterMerger && (
<>
<Button
type="button"
size="sm"
color={currentBranchSeCd === "SP" ? "primary" : "white"}
onClick={() => setCurrentBranchSeCd("SP")}
>
관로 초안 (SP)
</Button>
<Button
type="button"
size="sm"
color={currentBranchSeCd === "SF" ? "primary" : "white"}
onClick={() => setCurrentBranchSeCd("SF")}
>
시설 초안 (SF)
</Button>
</>
)}
<Button
type="button"
size="sm"
@@ -490,21 +627,25 @@ function PwrkCnsltnDocMngDialog({
: 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(", ")} 부서 보완요청 진행 중`
: "문서 열람 모드"}
: 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(", ")} 부서 보완요청 진행 중`
: "문서 열람 모드"}
</span>
</div>
) : null}