[as elapr] 공공하수도 재원협의 복합사업(SF+SP) Fork-Join 전자결재 프론트엔드 병합자(ATRZ04) 결재 버튼 연동 및 순차결재 분리 개선

This commit is contained in:
2026-10-13 15:25:42 +09:00
parent b088918cd9
commit 9a33663f1e
6 changed files with 158 additions and 12 deletions
@@ -17,7 +17,7 @@ export type AtrzSeComCd =
| "ATRZ02" // 검토 (실무 기술검토)
| "ATRZ03" // 승인 (부서 승인 / 병합에 회신)
| "ATRZ04" // 병합 (정책총괄 AI 병합의견 종합 심의 - 10-12 신설)
| "ATRZ99"; // 최종승인(회신) (환경청에 최종 회신)
| "ATRZ99"; // 최종승인(회신) (환경청에 회신)
/** 브랜치 구분코드 */
export type BranchSeCd = "MAIN" | "PIPE" | "FACILITY" | "TOTAL_AGG";
@@ -123,7 +123,7 @@ function ElaprApprovalDialog({
const approveButtonLabel = useMemo(() => {
if (!isForkJoin) return "결재";
if (branchSeCd === "TOTAL_AGG" && isLastInBranch) return "결재 (환경청에 최종회신)";
if (branchSeCd === "TOTAL_AGG" && isLastInBranch) return "결재 (환경청에 회신)";
if (branchSeCd === "TOTAL_AGG") return "결재 (병합)";
if ((branchSeCd === "PIPE" || branchSeCd === "FACILITY") && isLastInBranch)
return "결재 (병합에 회신)";
@@ -200,7 +200,7 @@ export function ElaprDocPrgrsForkJoinStep({ steps }: ElaprDocPrgrsForkJoinStepPr
</div>
</div>
{/* Stage 3: 정책총괄부 종합취합 및 환경청 최종회신 트랙 */}
{/* Stage 3: 정책총괄부 종합취합 및 환경청 회신 트랙 */}
<div className="flex flex-1 flex-col gap-4 rounded-xl border border-purple-100 bg-purple-50/40 p-4">
<div className="flex items-center gap-2 border-b border-purple-200/60 pb-2">
<CheckCircle2 className="size-5 text-purple-600" />
@@ -58,7 +58,7 @@ const TRACK_CONFIGS: TrackConfig[] = [
key: "TOTAL_AGG",
label: "정책부 종합병합",
defaultDeptNm: "정책총괄부",
guideText: "ℹ️ 두 부서(관로+시설) Join 완료 후 정책취합 및 환경청 최종회신 단계로 진행됩니다.",
guideText: "ℹ️ 두 부서(관로+시설) Join 완료 후 정책취합 및 환경청 회신 단계로 진행됩니다.",
minCount: 2,
maxCount: 5,
},
@@ -107,7 +107,7 @@ export function ElaprForkJoinPickerGrid({
],
TOTAL_AGG: [
createDefaultRow("TOTAL_AGG", "정책총괄부", 1, "ATRZ04"), // 병합 (10-12 신설)
createDefaultRow("TOTAL_AGG", "정책총괄부", 2, "ATRZ99"), // 최종승인/전결 (환경청 최종회신)
createDefaultRow("TOTAL_AGG", "정책총괄부", 2, "ATRZ99"), // 최종승인/전결 (환경청 회신)
],
}));
@@ -188,16 +188,149 @@ const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
},
};
/**
* Fork-Join (병렬 결재 6인 체제: 관로2 + 시설2 + 정책병합2) 전용 상태별 버튼 액션 매핑
* - Stage 2: 관로/시설 실무검토자(ATRZ02), 부서승인자(ATRZ03)
* - Stage 3: 정책총괄 종합병합자(ATRZ04), 최종회신자(ATRZ99)
* - 일반 순차 결재 라인과 분리하여 독립적으로 관리
*/
const ROLE_USER01_FORK_JOIN_ATRZ_STATUS_ACTIONS: Record<
string,
Record<string, FnrsCnsltnViewButtonAction[]>
> = {
ER: {
ATRZ01: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileVrfc,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr,
],
},
SL: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
TE: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwStrg,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt,
],
ATRZ03: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply,
],
ATRZ04: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv,
],
ATRZ99: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply,
],
},
TP: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
RB: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
RA: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
AV: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
EV: {
ATRZ01: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.costVrfc,
],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
RK: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt,
],
ATRZ03: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply,
],
ATRZ04: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv,
],
ATRZ99: [
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply,
],
},
RR: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
EP: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
TA: {
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ04: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
},
};
function resolveButtonConfigs(actions: FnrsCnsltnViewButtonAction[]): FnrsCnsltnViewButtonAction[] {
return actions.map((action) => FNRS_CNSLTN_VIEW_BUTTON_ACTION[action]);
}
interface GetButtonConfigsOptions {
export interface GetButtonConfigsOptions {
hasPendingApprovalRequest?: boolean;
hasCompletedDraft?: boolean;
canRetryFileVrfc?: boolean;
canRetryDraftGen?: boolean;
canRetryCostVrfc?: boolean;
isForkJoin?: boolean;
}
const PENDING_APPROVAL_ACTIONS = new Set<FnrsCnsltnViewButtonAction>([
@@ -214,7 +347,9 @@ function resolveRoleUser01AtrzButtonActions(
atrzSeComCdList: string[],
options?: GetButtonConfigsOptions,
): FnrsCnsltnViewButtonAction[] {
const atrzStatusMap = ROLE_USER01_ATRZ_STATUS_ACTIONS[prgrsSttsComCd];
const atrzStatusMap = options?.isForkJoin
? ROLE_USER01_FORK_JOIN_ATRZ_STATUS_ACTIONS[prgrsSttsComCd]
: ROLE_USER01_ATRZ_STATUS_ACTIONS[prgrsSttsComCd];
if (!atrzStatusMap || atrzSeComCdList.length === 0) return [];
const seen = new Set<FnrsCnsltnViewButtonAction>();
@@ -194,6 +194,15 @@ function FloatingActionButtons({
const normalizedTaskSeComCd = normalizeTaskSeComCdForElapr(data.detDto?.taskSeComCd);
const isForkJoin = isForkJoinTask(data.detDto?.taskSeComCd);
const effectiveAtrzSeComCdList = useMemo(() => {
if (isForkJoin && userPendingAtrzSeComCd) {
if (!atrzSeComCdList.includes(userPendingAtrzSeComCd)) {
return [...atrzSeComCdList, userPendingAtrzSeComCd];
}
}
return atrzSeComCdList;
}, [atrzSeComCdList, isForkJoin, userPendingAtrzSeComCd]);
const viewOnlyPwrkCnsltnDoc =
prgrsSttsComCd === "SL" ||
(prgrsSttsComCd === "RB" && dtlInstCd !== "ROLE_USER05") ||
@@ -204,23 +213,25 @@ function FloatingActionButtons({
if (needsElaprPrgrsCheck && isElaprDocPrgrsLoading) return [];
if (needsPwrkDraftCheck && isPwrkCnsltnLoading) return [];
if (needsAiAutoLatest && isAiAutoLatestLoading) return [];
return getButtonConfigs(dtlInstCd, prgrsSttsComCd, atrzSeComCdList, {
return getButtonConfigs(dtlInstCd, prgrsSttsComCd, effectiveAtrzSeComCdList, {
hasPendingApprovalRequest: needsElaprPrgrsCheck ? hasPendingApprovalRequest : undefined,
hasCompletedDraft: needsPwrkDraftCheck ? hasCompletedDraft : undefined,
canRetryFileVrfc: needsAiAutoLatest ? canRetryFileVrfc : undefined,
canRetryDraftGen: needsAiAutoLatest ? canRetryDraftGen : undefined,
canRetryCostVrfc: needsAiAutoLatest ? canRetryCostVrfc : undefined,
isForkJoin,
});
}, [
atrzSeComCdList,
canRetryCostVrfc,
canRetryDraftGen,
canRetryFileVrfc,
dtlInstCd,
effectiveAtrzSeComCdList,
hasCompletedDraft,
hasPendingApprovalRequest,
isAiAutoLatestLoading,
isElaprDocPrgrsLoading,
isForkJoin,
isPwrkCnsltnLoading,
isRvwLoginUserLoading,
needsAiAutoLatest,
@@ -234,7 +245,7 @@ function FloatingActionButtons({
isRegister && actionButtons.some((button) => button === FNRS_CNSLTN_VIEW_BUTTON_ACTION.strg);
const canDmndSplmntInPwrkCnsltnDoc =
prgrsSttsComCd === "RK" &&
atrzSeComCdList.includes("ATRZ02") &&
effectiveAtrzSeComCdList.includes("ATRZ02") &&
actionButtons.some((button) => button === FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt);
function handleGoToList() {
@@ -817,7 +828,7 @@ function FloatingActionButtons({
>
{isForkJoin
? userPendingBranchSeCd === "TOTAL_AGG"
? "결재 (환경청에 최종회신)"
? "결재 (환경청에 회신)"
: "결재 (병합에 회신)"
: "결재(회신)"}
</Button>
@@ -877,7 +888,7 @@ function FloatingActionButtons({
<PwrkCnsltnDocMngDialog
open={activeDialog === "pwrkCnsltnDocMng"}
fnrsCnsltnDocNo={docNo}
atrzSeComCdList={atrzSeComCdList}
atrzSeComCdList={effectiveAtrzSeComCdList}
prgrsSttsComCd={prgrsSttsComCd}
canSave={
(needsElaprPrgrsCheck && !hasPendingApprovalRequest) || viewOnlyPwrkCnsltnDoc