[as fr] 설치인가, 기본계획 병렬 Join 결재및 보완요청 관련 수정사항 적용

This commit is contained in:
2026-10-27 13:22:02 +09:00
parent 1317586f89
commit 9eab7fe981
8 changed files with 174 additions and 25 deletions
@@ -44,7 +44,7 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, InstlSncnViewBut
},
};
const ROLE_USER01_ATRZ_STATUS_CODES = ["ER", "TE"] as const;
const ROLE_USER01_ATRZ_STATUS_CODES = ["ER", "SL", "TE"] as const;
const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
string,
@@ -53,6 +53,7 @@ const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
ER: {
ATRZ01: [INSTL_SNCN_VIEW_BUTTON_ACTION.dsgntRvwr],
},
SL: {},
TE: {
ATRZ02: [
INSTL_SNCN_VIEW_BUTTON_ACTION.rvwStrg,
@@ -77,6 +78,7 @@ const ROLE_USER01_FORK_JOIN_ATRZ_STATUS_ACTIONS: Record<
ER: {
ATRZ01: [INSTL_SNCN_VIEW_BUTTON_ACTION.dsgntRvwr],
},
SL: {},
TE: {
ATRZ02: [
INSTL_SNCN_VIEW_BUTTON_ACTION.rvwStrg,
@@ -95,6 +97,8 @@ function resolveButtonConfigs(actions: InstlSncnViewButtonAction[]): InstlSncnVi
}
const PENDING_APPROVAL_ACTIONS = new Set<InstlSncnViewButtonAction>([
INSTL_SNCN_VIEW_BUTTON_ACTION.rvwStrg,
INSTL_SNCN_VIEW_BUTTON_ACTION.dmndSplmnt,
INSTL_SNCN_VIEW_BUTTON_ACTION.rvwCmptn,
INSTL_SNCN_VIEW_BUTTON_ACTION.atrzAprv,
INSTL_SNCN_VIEW_BUTTON_ACTION.atrzRply,
@@ -8,6 +8,7 @@ import {
} from "../model/view-types";
export function getProgressStepCount(prgrsSttsComCd?: string): number {
if (prgrsSttsComCd === "SL") return 3;
const idx = INSTL_PROGRESS_ORDER.indexOf(prgrsSttsComCd as (typeof INSTL_PROGRESS_ORDER)[number]);
if (idx <= INSTL_PROGRESS_ORDER.indexOf("SV")) return 1;
if (idx <= INSTL_PROGRESS_ORDER.indexOf("LE")) return 2;
@@ -54,7 +55,7 @@ export function getEditableReviewStep(
return "ENV";
}
if (dtlInstCd === "ROLE_USER01" && prgrsSttsComCd === "TE") {
if (dtlInstCd === "ROLE_USER01" && (prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL")) {
return "KECO";
}
@@ -79,6 +79,7 @@ export function buildSupplementReqPayload(
data: InstlSncnMngDetResponse,
payload: SplmntDmndDialogConfirmPayload,
checklistFormState: ChecklistFormState,
branchSeCd?: string,
): InstlSncnApplySaveRequestDto {
const existingItems: InstlSncnSplmntDmndSaveDto[] = (data.splmntDmndList ?? []).map((item) =>
toSplmntDmndSaveItem(item),
@@ -97,6 +98,7 @@ export function buildSupplementReqPayload(
return instlSncnApplySaveSchema.parse({
...buildSavePayload(data, checklistFormState, "LOCAL", "SL"),
splmntDmndList: [...existingItems, ...newItems],
branchSeCd,
});
}
@@ -91,12 +91,13 @@ function FloatingActionButtons({
const normalizedTaskSeComCd = normalizeTaskSeComCdForElapr(data.detDto.taskSeComCd);
const needsAtrzResolve = needsRoleUser01AtrzResolve(dtlInstCd, prgrsSttsComCd);
const needsTeApprovalCheck = needsAtrzResolve && prgrsSttsComCd === "TE";
const needsApprovalCheck =
needsAtrzResolve && (prgrsSttsComCd === "TE" || (isForkJoin && prgrsSttsComCd === "SL"));
const { data: elaprDocPrgrsList = [], isLoading: isElaprDocPrgrsLoading } =
useElaprDocPrgrsByDocNo(needsTeApprovalCheck ? docNo : null);
useElaprDocPrgrsByDocNo(needsApprovalCheck ? docNo : null);
const { data: elaprBranchList = [], isLoading: isElaprBranchLoading } = useElaprBranchesByDocNo(
isForkJoin && needsTeApprovalCheck ? docNo : null,
isForkJoin && (needsApprovalCheck || prgrsSttsComCd === "SL") ? docNo : null,
);
const hasPendingApprovalRequest = hasCurrentUserPendingApprovalRequest(
@@ -132,12 +133,61 @@ function FloatingActionButtons({
return atrzSeComCdList;
}, [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<string | undefined>(() => {
if (!isForkJoin || !userBranchSeCd) return undefined;
const matchedBranch = userBranchId
? elaprBranchList.find((b) => b.branchId === userBranchId)
: elaprBranchList.findLast((b) => b.branchSeCd === userBranchSeCd);
// 1. 이미 백엔드 브랜치 상태가 SUPPLEMENT이면 즉시 SUPPLEMENT
if (matchedBranch?.branchSttsCd === "SUPPLEMENT") {
return "SUPPLEMENT";
}
// 2. 문서 상태가 SL일 때: 보완요청 발생 부서(supplementDmndBranch 또는 본인 등록 건) 우선 판정
if (prgrsSttsComCd === "SL") {
const hasMyUncompletedSplmnt = data?.splmntDmndList?.some(
(item) => item.cmptnYn !== "Y" && item.rgtr?.toLowerCase() === userInfo?.userUnqId?.toLowerCase(),
);
if (hasMyUncompletedSplmnt || (supplementDmndBranch && userBranchSeCd === supplementDmndBranch)) {
return "SUPPLEMENT";
}
// 보완요청을 하지 않은 타 브랜치(시설부 등)는 결재 진행 중 상태 유지
const branchRows = elaprDocPrgrsList.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";
}
// 3. SL이 아닌 일반 상태(TE 등)에서는 matchedBranch 상태 적용
if (matchedBranch?.branchSttsCd) {
return matchedBranch.branchSttsCd;
}
@@ -150,14 +200,24 @@ function FloatingActionButtons({
}
return undefined;
}, [elaprBranchList, isForkJoin, prgrsSttsComCd, userBranchId, userBranchSeCd]);
}, [
data?.splmntDmndList,
elaprBranchList,
elaprDocPrgrsList,
isForkJoin,
prgrsSttsComCd,
supplementDmndBranch,
userBranchId,
userBranchSeCd,
userInfo?.userUnqId,
]);
const actionButtons = useMemo(() => {
if (needsAtrzResolve && isRvwLoginUserLoading) return [];
if (needsTeApprovalCheck && isElaprDocPrgrsLoading) return [];
if (needsApprovalCheck && isElaprDocPrgrsLoading) return [];
if (isForkJoin && isElaprBranchLoading) return [];
return getButtonConfigs(dtlInstCd, prgrsSttsComCd, effectiveAtrzSeComCdList, {
hasPendingApprovalRequest: needsTeApprovalCheck ? hasPendingApprovalRequest : undefined,
hasPendingApprovalRequest: needsApprovalCheck ? hasPendingApprovalRequest : undefined,
isForkJoin,
branchSeCd: userBranchSeCd,
branchSttsCd: userBranchSttsCd,
@@ -170,8 +230,8 @@ function FloatingActionButtons({
isElaprDocPrgrsLoading,
isForkJoin,
isRvwLoginUserLoading,
needsApprovalCheck,
needsAtrzResolve,
needsTeApprovalCheck,
prgrsSttsComCd,
userBranchSeCd,
userBranchSttsCd,
@@ -387,11 +447,12 @@ function FloatingActionButtons({
}
async function handleRvwStrgClick() {
const targetPrgrsStts = prgrsSttsComCd === "SL" ? "SL" : "TE";
const payload = buildTechReviewReplyPayload(
data,
checklistFormState,
"KECO",
"TE",
targetPrgrsStts,
"SV",
getReviewOpinionFormData(),
opnrNm,
@@ -406,13 +467,13 @@ function FloatingActionButtons({
}
async function handleRvwCmptnClick() {
const targetPrgrsStts = prgrsSttsComCd === "SL" ? "SL" : "TE";
const payload = {
...buildTechReviewReplyPayload(
data,
checklistFormState,
"KECO",
"TE",
targetPrgrsStts,
"SV",
getReviewOpinionFormData(),
opnrNm,
@@ -434,7 +495,13 @@ function FloatingActionButtons({
async function handleDmndSplmntConfirm(payload: SplmntDmndDialogConfirmPayload) {
setActiveDialog(null);
const requestPayload = buildSupplementReqPayload(data, payload, checklistFormState);
const branchSeCd = userBranchSeCd ?? userPendingBranchSeCd;
const requestPayload = buildSupplementReqPayload(
data,
payload,
checklistFormState,
branchSeCd,
);
await executeAction({
action: INSTL_SNCN_VIEW_BUTTON_ACTION.dmndSplmnt,
request: requestPayload,
@@ -34,7 +34,7 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, SewerBscPlanView
},
};
const ROLE_USER01_ATRZ_STATUS_CODES = ["ER", "TE"] as const;
const ROLE_USER01_ATRZ_STATUS_CODES = ["ER", "SL", "TE"] as const;
const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
string,
@@ -43,6 +43,7 @@ const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
ER: {
ATRZ01: [SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.dsgntRvwr],
},
SL: {},
TE: {
ATRZ02: [
SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.rvwStrg,
@@ -67,6 +68,7 @@ const ROLE_USER01_FORK_JOIN_ATRZ_STATUS_ACTIONS: Record<
ER: {
ATRZ01: [SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.dsgntRvwr],
},
SL: {},
TE: {
ATRZ02: [
SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.rvwStrg,
@@ -87,6 +89,8 @@ function resolveButtonConfigs(
}
const PENDING_APPROVAL_ACTIONS = new Set<SewerBscPlanViewButtonAction>([
SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.rvwStrg,
SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.dmndSplmnt,
SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.rvwCmptn,
SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.atrzAprv,
SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.atrzRply,
@@ -124,6 +124,7 @@ export function buildSupplementReqPayload(
data: SewerBscPlanDetResponseDto,
payload: SplmntDmndDialogConfirmPayload,
checklistFormState: ChecklistFormState,
branchSeCd?: string,
): SewerBscPlanApplySaveRequestDto {
const existingItems: SewerBscPlanSplmntDmndItem[] = (data.splmntDmndList ?? []).map((item) =>
toSplmntDmndSaveItem(item),
@@ -140,6 +141,7 @@ export function buildSupplementReqPayload(
return sewerBscPlanApplySaveSchema.parse({
...buildSavePayload(data, checklistFormState, "LOCAL", "SL"),
splmntDmndList: [...existingItems, ...newItems],
branchSeCd,
});
}
@@ -9,6 +9,7 @@ import {
} from "../model/view-types";
export function getProgressStepCount(prgrsSttsComCd?: string): number {
if (prgrsSttsComCd === "SL") return 3;
const idx = PBADMS_PROGRESS_ORDER.indexOf(
prgrsSttsComCd as (typeof PBADMS_PROGRESS_ORDER)[number],
);
@@ -57,7 +58,7 @@ export function getEditableReviewStep(
return "ENV";
}
if (dtlInstCd === "ROLE_USER01" && prgrsSttsComCd === "TE") {
if (dtlInstCd === "ROLE_USER01" && (prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL")) {
return "KECO";
}
@@ -94,11 +94,12 @@ function FloatingActionButtons({
const isForkJoin = isForkJoinTask(data.detDto?.taskSeComCd);
const needsAtrzResolve = needsRoleUser01AtrzResolve(dtlInstCd, prgrsSttsComCd);
const needsTeApprovalCheck = needsAtrzResolve && prgrsSttsComCd === "TE";
const needsApprovalCheck =
needsAtrzResolve && (prgrsSttsComCd === "TE" || (isForkJoin && prgrsSttsComCd === "SL"));
const { data: elaprDocPrgrsList = [], isLoading: isElaprDocPrgrsLoading } =
useElaprDocPrgrsByDocNo(needsTeApprovalCheck ? docNo : null);
useElaprDocPrgrsByDocNo(needsApprovalCheck ? docNo : null);
const { data: elaprBranchList = [], isLoading: isElaprBranchLoading } =
useElaprBranchesByDocNo(needsTeApprovalCheck && isForkJoin ? docNo : null);
useElaprBranchesByDocNo(isForkJoin && (needsApprovalCheck || prgrsSttsComCd === "SL") ? docNo : null);
const hasPendingApprovalRequest = hasCurrentUserPendingApprovalRequest(
elaprDocPrgrsList,
@@ -133,12 +134,61 @@ function FloatingActionButtons({
return atrzSeComCdList;
}, [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<string | undefined>(() => {
if (!isForkJoin || !userBranchSeCd) return undefined;
const matchedBranch = userBranchId
? elaprBranchList.find((b) => b.branchId === userBranchId)
: elaprBranchList.findLast((b) => b.branchSeCd === userBranchSeCd);
// 1. 이미 백엔드 브랜치 상태가 SUPPLEMENT이면 즉시 SUPPLEMENT
if (matchedBranch?.branchSttsCd === "SUPPLEMENT") {
return "SUPPLEMENT";
}
// 2. 문서 상태가 SL일 때: 보완요청 발생 부서(supplementDmndBranch 또는 본인 등록 건) 우선 판정
if (prgrsSttsComCd === "SL") {
const hasMyUncompletedSplmnt = data?.splmntDmndList?.some(
(item) => item.cmptnYn !== "Y" && item.rgtr?.toLowerCase() === userInfo?.userUnqId?.toLowerCase(),
);
if (hasMyUncompletedSplmnt || (supplementDmndBranch && userBranchSeCd === supplementDmndBranch)) {
return "SUPPLEMENT";
}
// 보완요청을 하지 않은 타 브랜치(시설부 등)는 결재 진행 중 상태 유지
const branchRows = elaprDocPrgrsList.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";
}
// 3. SL이 아닌 일반 상태(TE 등)에서는 matchedBranch 상태 적용
if (matchedBranch?.branchSttsCd) {
return matchedBranch.branchSttsCd;
}
@@ -151,14 +201,24 @@ function FloatingActionButtons({
}
return undefined;
}, [elaprBranchList, isForkJoin, prgrsSttsComCd, userBranchId, userBranchSeCd]);
}, [
data?.splmntDmndList,
elaprBranchList,
elaprDocPrgrsList,
isForkJoin,
prgrsSttsComCd,
supplementDmndBranch,
userBranchId,
userBranchSeCd,
userInfo?.userUnqId,
]);
const actionButtons = useMemo(() => {
if (needsAtrzResolve && isRvwLoginUserLoading) return [];
if (needsTeApprovalCheck && isElaprDocPrgrsLoading) return [];
if (needsApprovalCheck && isElaprDocPrgrsLoading) return [];
if (isForkJoin && isElaprBranchLoading) return [];
return getButtonConfigs(dtlInstCd, prgrsSttsComCd, effectiveAtrzSeComCdList, {
hasPendingApprovalRequest: needsTeApprovalCheck ? hasPendingApprovalRequest : undefined,
hasPendingApprovalRequest: needsApprovalCheck ? hasPendingApprovalRequest : undefined,
isForkJoin,
branchSeCd: userBranchSeCd,
branchSttsCd: userBranchSttsCd,
@@ -171,8 +231,8 @@ function FloatingActionButtons({
isElaprDocPrgrsLoading,
isForkJoin,
isRvwLoginUserLoading,
needsApprovalCheck,
needsAtrzResolve,
needsTeApprovalCheck,
prgrsSttsComCd,
userBranchSeCd,
userBranchSttsCd,
@@ -339,11 +399,12 @@ function FloatingActionButtons({
}
async function handleRvwStrgClick() {
const targetPrgrsStts = prgrsSttsComCd === "SL" ? "SL" : "TE";
const payload = buildTechReviewReplyPayload(
data,
checklistFormState,
"KECO",
"TE",
targetPrgrsStts,
"SV",
getReviewOpinionFormData(),
opnrNm,
@@ -369,12 +430,13 @@ function FloatingActionButtons({
return;
}
const targetPrgrsStts = prgrsSttsComCd === "SL" ? "SL" : "TE";
const payload = {
...buildTechReviewReplyPayload(
data,
checklistFormState,
"KECO",
"TE",
targetPrgrsStts,
"SV",
getReviewOpinionFormData(),
opnrNm,
@@ -396,7 +458,13 @@ function FloatingActionButtons({
async function handleDmndSplmntConfirm(payload: SplmntDmndDialogConfirmPayload) {
setActiveDialog(null);
const requestPayload = buildSupplementReqPayload(data, payload, checklistFormState);
const branchSeCd = userBranchSeCd ?? userPendingBranchSeCd;
const requestPayload = buildSupplementReqPayload(
data,
payload,
checklistFormState,
branchSeCd,
);
await executeAction({
action: SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.dmndSplmnt,
request: requestPayload,