[as fr] 설계자문에 병렬조인 결재로직 변경 적용

This commit is contained in:
2026-10-26 18:38:27 +09:00
parent bda556b808
commit 1dd86b2008
4 changed files with 198 additions and 19 deletions
@@ -43,14 +43,48 @@ 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, DesignCnstnViewButtonAction[]>
> = {
CT: {
ATRZ01: [DESIGN_CNSTN_VIEW_BUTTON_ACTION.rqstRvw],
},
CS: {
ATRZ02: [
DESIGN_CNSTN_VIEW_BUTTON_ACTION.rvwStrg,
DESIGN_CNSTN_VIEW_BUTTON_ACTION.rvwCmptn,
],
ATRZ03: [DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzRply], // Stage 2 부서승인 ➔ 병합에 회신
ATRZ04: [DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzAprv], // Stage 3 정책총괄 ➔ 병합
ATRZ99: [DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzRply], // Stage 3 정책총괄 ➔ 최종 회신
},
CP: {},
};
function resolveButtonConfigs(
actions: DesignCnstnViewButtonAction[],
): DesignCnstnViewButtonAction[] {
return actions.map((action) => DESIGN_CNSTN_VIEW_BUTTON_ACTION[action]);
}
interface GetButtonConfigsOptions {
const PENDING_APPROVAL_ACTIONS = new Set<DesignCnstnViewButtonAction>([
DESIGN_CNSTN_VIEW_BUTTON_ACTION.rvwCmptn,
DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzAprv,
DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzRply,
]);
export interface GetButtonConfigsOptions {
hasPendingApprovalRequest?: boolean;
isForkJoin?: boolean;
branchSeCd?: string;
branchSttsCd?: string;
}
function resolveRoleUser01AtrzButtonActions(
@@ -58,11 +92,25 @@ function resolveRoleUser01AtrzButtonActions(
atrzSeComCdList: string[],
options?: GetButtonConfigsOptions,
): DesignCnstnViewButtonAction[] {
if (prgrsSttsComCd === "CS" && options?.hasPendingApprovalRequest === false) {
return [];
let effectiveStatus = prgrsSttsComCd;
if (options?.isForkJoin && options?.branchSttsCd) {
switch (options.branchSttsCd) {
case "SUPPLEMENT":
return [];
case "COMPLETED":
return [];
case "WAITING":
return [];
case "IN_PROGRESS":
effectiveStatus = "CS";
break;
}
}
const atrzStatusMap = ROLE_USER01_ATRZ_STATUS_ACTIONS[prgrsSttsComCd];
const atrzStatusMap = options?.isForkJoin
? ROLE_USER01_FORK_JOIN_ATRZ_STATUS_ACTIONS[effectiveStatus]
: ROLE_USER01_ATRZ_STATUS_ACTIONS[effectiveStatus];
if (!atrzStatusMap || atrzSeComCdList.length === 0) return [];
const seen = new Set<DesignCnstnViewButtonAction>();
@@ -79,7 +127,11 @@ function resolveRoleUser01AtrzButtonActions(
}
}
return actions;
return actions.filter((action) => {
const needsPendingApproval = PENDING_APPROVAL_ACTIONS.has(action);
if (needsPendingApproval && options?.hasPendingApprovalRequest === false) return false;
return true;
});
}
export function needsRoleUser01AtrzResolve(
@@ -62,3 +62,26 @@ export function parseTaskSeComCdFromApi(taskSeComCd?: string | null): string[] {
if (!taskSeComCd) return [];
return taskSeComCd.split(",").filter(Boolean);
}
/**
* 설계자문 저장 시 코드가 "SF,SP", "SP,SF" 등의 복수 코드로 분리 저장된 경우,
* 전자결재 복합병렬결재(Fork-Join) 화면 및 프로세스 규격에 맞춰 "SF_SP"로 정규화합니다.
*/
export function normalizeTaskSeComCdForElapr(taskSeComCd?: string | null): string {
if (!taskSeComCd) return "";
const trimmed = taskSeComCd.trim();
if (trimmed === "SF_SP") return "SF_SP";
const codes = trimmed.split(",").map((c) => c.trim()).filter(Boolean);
if (codes.includes("SF") && codes.includes("SP")) {
return "SF_SP";
}
return trimmed;
}
/**
* 전자결재 복합병렬결재(Fork-Join) 대상 업무 여부 판단
*/
export function isForkJoinTask(taskSeComCd?: string | null): boolean {
return normalizeTaskSeComCdForElapr(taskSeComCd) === "SF_SP";
}
@@ -23,6 +23,7 @@ import {
type ElaprTmpltPickerConfirmPayload,
ElaprTmpltPickerDialog,
hasCurrentUserPendingApprovalRequest,
useElaprBranchesByDocNo,
useElaprDocPrgrsByDocNo,
} from "@/entities/elapr";
import { Button } from "@common/components";
@@ -33,6 +34,10 @@ import {
getButtonConfigs,
needsRoleUser01AtrzResolve,
} from "../../lib/button-config";
import {
isForkJoinTask,
normalizeTaskSeComCdForElapr,
} from "../../lib/design-cnstn-regist-schema";
import { buildSavePayload, buildTechReviewPayload } from "../../lib/save-payload-utils";
import type { TechReviewOpinionSectionHandle } from "../sections/tech-review-opinion-section";
@@ -72,31 +77,92 @@ function FloatingActionButtons({
const docNo = data.detDto?.designCnstnDocNo ?? designCnstnDocNo;
const isRegister = data.detDto?.rgtr === userInfo?.userUnqId;
const normalizedTaskSeComCd = normalizeTaskSeComCdForElapr(data.detDto?.taskSeComCd);
const isForkJoin = isForkJoinTask(data.detDto?.taskSeComCd);
const needsAtrzResolve = needsRoleUser01AtrzResolve(dtlInstCd, prgrsSttsComCd);
const needsCsApprovalCheck = needsAtrzResolve && prgrsSttsComCd === "CS";
const { data: elaprDocPrgrsList = [], isLoading: isElaprDocPrgrsLoading } =
useElaprDocPrgrsByDocNo(needsCsApprovalCheck ? docNo : null);
const { data: elaprBranchList = [], isLoading: isElaprBranchLoading } =
useElaprBranchesByDocNo(needsCsApprovalCheck && isForkJoin ? docNo : null);
const hasPendingApprovalRequest = hasCurrentUserPendingApprovalRequest(
elaprDocPrgrsList,
userInfo?.userUnqId,
);
const currentUserPrgrsRow = elaprDocPrgrsList.find(
(row) => row.aprvrUuid?.toLowerCase() === userInfo?.userUnqId?.toLowerCase(),
);
const userPendingPrgrs = elaprDocPrgrsList.find(
(row) =>
row.aprvrUuid?.toLowerCase() === userInfo?.userUnqId?.toLowerCase() &&
row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_002",
);
const userBranchId = userPendingPrgrs?.branchId ?? currentUserPrgrsRow?.branchId;
const userBranchSeCd = (userPendingPrgrs?.branchSeCd ?? currentUserPrgrsRow?.branchSeCd) as
| "SP"
| "SF"
| "TOTAL_AGG"
| "MAIN"
| undefined;
const userPendingBranchSeCd = userPendingPrgrs?.branchSeCd ?? userBranchSeCd;
const userPendingAtrzSeComCd = userPendingPrgrs?.atrzSeComCd;
const effectiveAtrzSeComCdList = useMemo(() => {
if (isForkJoin && userPendingAtrzSeComCd) {
return [userPendingAtrzSeComCd];
}
if (isForkJoin && currentUserPrgrsRow?.atrzSeComCd) {
return [currentUserPrgrsRow.atrzSeComCd];
}
return atrzSeComCdList;
}, [atrzSeComCdList, currentUserPrgrsRow?.atrzSeComCd, isForkJoin, userPendingAtrzSeComCd]);
const userBranchSttsCd = useMemo<string | undefined>(() => {
if (!isForkJoin || !userBranchSeCd) return undefined;
const matchedBranch = userBranchId
? elaprBranchList.find((b) => b.branchId === userBranchId)
: elaprBranchList.findLast((b) => b.branchSeCd === userBranchSeCd);
if (matchedBranch?.branchSttsCd) {
return matchedBranch.branchSttsCd;
}
if (prgrsSttsComCd === "CS") {
return "IN_PROGRESS";
}
if (prgrsSttsComCd === "CP") {
return "COMPLETED";
}
return undefined;
}, [elaprBranchList, isForkJoin, prgrsSttsComCd, userBranchId, userBranchSeCd]);
const actionButtons = useMemo(() => {
if (needsAtrzResolve && isRvwLoginUserLoading) return [];
if (needsCsApprovalCheck && isElaprDocPrgrsLoading) return [];
return getButtonConfigs(dtlInstCd, prgrsSttsComCd, atrzSeComCdList, {
if (isForkJoin && isElaprBranchLoading) return [];
return getButtonConfigs(dtlInstCd, prgrsSttsComCd, effectiveAtrzSeComCdList, {
hasPendingApprovalRequest: needsCsApprovalCheck ? hasPendingApprovalRequest : undefined,
isForkJoin,
branchSeCd: userBranchSeCd,
branchSttsCd: userBranchSttsCd,
});
}, [
atrzSeComCdList,
dtlInstCd,
effectiveAtrzSeComCdList,
hasPendingApprovalRequest,
isElaprBranchLoading,
isElaprDocPrgrsLoading,
isForkJoin,
isRvwLoginUserLoading,
needsAtrzResolve,
needsCsApprovalCheck,
prgrsSttsComCd,
userBranchSeCd,
userBranchSttsCd,
]);
const canEdit =
@@ -191,6 +257,9 @@ function FloatingActionButtons({
dmndCn: data.detDto?.bizCn ?? "",
elaprCycl: 1,
docPrgrsSttsComCd: data.detDto?.prgrsSttsComCd,
taskSeComCd: normalizedTaskSeComCd,
forkJoinSeCd: isForkJoin ? "FORK_JOIN" : "NONE",
crntStageNo: isForkJoin ? 2 : 1,
elaprPrgrsSttsList: buildElaprPrgrsSttsListFromAplnRows(aplnRows),
};
await executeAction({
@@ -201,6 +270,11 @@ function FloatingActionButtons({
await queryClient.invalidateQueries({
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", docNo],
});
if (isForkJoin) {
await queryClient.invalidateQueries({
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_BRANCH, "findBranchesByDocNo", docNo],
});
}
}
async function handleRvwStrgClick() {
@@ -281,16 +355,33 @@ function FloatingActionButtons({
검토완료
</Button>
)}
{shouldShowButton(DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzAprv) && (
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isProcessing}>
결재(승인)
</Button>
)}
{shouldShowButton(DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzRply) && (
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isProcessing}>
결재(회신)
</Button>
)}
{shouldShowButton(DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzAprv) &&
(!isForkJoin ||
(userPendingBranchSeCd === "TOTAL_AGG" && userPendingAtrzSeComCd === "ATRZ04")) && (
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isProcessing}>
{isForkJoin ? "결재 (병합)" : "결재(승인)"}
</Button>
)}
{shouldShowButton(DESIGN_CNSTN_VIEW_BUTTON_ACTION.atrzRply) &&
(!isForkJoin ||
(userPendingBranchSeCd !== "TOTAL_AGG" && userPendingAtrzSeComCd === "ATRZ03") ||
(userPendingBranchSeCd === "TOTAL_AGG" && userPendingAtrzSeComCd === "ATRZ99")) && (
<Button
color={
isForkJoin && userPendingBranchSeCd !== "TOTAL_AGG"
? "green"
: undefined
}
onClick={() => setActiveDialog("elaprApproval")}
disabled={isProcessing}
>
{isForkJoin
? userPendingBranchSeCd === "TOTAL_AGG"
? "결재 (환경청에 회신)"
: "결재 (병합에 회신)"
: "결재(회신)"}
</Button>
)}
</div>
</div>
@@ -298,18 +389,27 @@ function FloatingActionButtons({
open={activeDialog === "elaprTmplt"}
onClose={() => setActiveDialog(null)}
docSeComCd={docSeComCd}
taskSeComCd={normalizedTaskSeComCd}
onConfirm={handleElaprTmpltConfirm}
/>
<ElaprApprovalDialog
open={activeDialog === "elaprApproval"}
onOpenChange={(open) => setActiveDialog(open ? "elaprApproval" : null)}
elaprDocNo={data.detDto?.designCnstnDocNo ?? designCnstnDocNo}
elaprDocNo={docNo}
currentUserUuid={userInfo?.userUnqId ?? ""}
onSuccess={() => {
queryClient.invalidateQueries({
queryKey: designCnstnKeys.detail(designCnstnDocNo),
});
queryClient.invalidateQueries({
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", docNo],
});
if (isForkJoin) {
queryClient.invalidateQueries({
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_BRANCH, "findBranchesByDocNo", docNo],
});
}
}}
/>
</>
@@ -13,6 +13,7 @@ import { AttachmentsViewSection, ProgressStatusSection } from "@/widgets/pbadms-
import { Button, cn, Skeleton } from "@common/components";
import { needsRoleUser01AtrzResolve } from "../lib/button-config";
import { normalizeTaskSeComCdForElapr } from "../lib/design-cnstn-regist-schema";
import { getEditableReviewStep } from "../lib/review-step-utils";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
@@ -108,7 +109,10 @@ function DesignViewPage({ id }: DesignViewPageProps) {
/>
<BasicInfoSection det={detDto} />
<ContactInfoSection det={detDto} />
<ElaprDocPrgrsStatus elaprDocNo={detDto.designCnstnDocNo} />
<ElaprDocPrgrsStatus
elaprDocNo={detDto.designCnstnDocNo}
taskSeComCd={normalizeTaskSeComCdForElapr(detDto.taskSeComCd)}
/>
<BusinessScaleSection bizSclList={bizSclList} />
<AttachmentsViewSection esntlFileList={esntlFileList} etcFileList={etcFileList} />
<TechReviewOpinionSection