diff --git a/apps/as/package.json b/apps/as/package.json index 0ca386f38..b30e4e45e 100644 --- a/apps/as/package.json +++ b/apps/as/package.json @@ -28,6 +28,7 @@ "lucide-react": "^0.474.0", "next": "15.3.8", "react-hook-form": "^7.54.2", + "tus-js-client": "^4.3.1", "zod": "^3.24.2" }, "devDependencies": { diff --git a/apps/as/src/entities/cklst/api/cklst-mng-service.ts b/apps/as/src/entities/cklst/api/cklst-mng-service.ts index 67f22d3b1..d3b6713e8 100644 --- a/apps/as/src/entities/cklst/api/cklst-mng-service.ts +++ b/apps/as/src/entities/cklst/api/cklst-mng-service.ts @@ -7,6 +7,8 @@ import type { CklstMngSaveRequest, CklstMngSearchParams, CklstMngService, + CklstTreeNode, + CklstTreeSearchParams, } from "../model/cklst-mng-interface"; async function findPage(params: CklstMngSearchParams): Promise> { @@ -21,6 +23,13 @@ async function findById(cklstSn: number): Promise { return response.data; } +async function findTree(params: CklstTreeSearchParams): Promise { + const response = await client.get(`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/tree`, { + params, + }); + return response.data; +} + async function create(body: CklstMngSaveRequest): Promise { const response = await client.post(ENDPOINT.AS_CHECKLIST_SERVICE.CKLST, body); return response.data; @@ -42,6 +51,7 @@ async function remove(cklstSn: number): Promise { export const cklstMngService = { findPage, findById, + findTree, create, update, remove, diff --git a/apps/as/src/entities/cklst/index.ts b/apps/as/src/entities/cklst/index.ts index bb14bc48f..12796cd18 100644 --- a/apps/as/src/entities/cklst/index.ts +++ b/apps/as/src/entities/cklst/index.ts @@ -33,6 +33,8 @@ export type { CklstMngSaveRequest, CklstMngSearchParams, CklstMngService, + CklstTreeNode, + CklstTreeSearchParams, } from "./model/cklst-mng-interface"; export type { CklstRvw, diff --git a/apps/as/src/entities/cklst/model/cklst-mng-interface.ts b/apps/as/src/entities/cklst/model/cklst-mng-interface.ts index ad1302b00..7e3187ce7 100644 --- a/apps/as/src/entities/cklst/model/cklst-mng-interface.ts +++ b/apps/as/src/entities/cklst/model/cklst-mng-interface.ts @@ -13,6 +13,24 @@ export interface CklstMng { mdfcnDt?: string; } +export interface CklstTreeNode { + cklstSn: number; + cklstNm: string; + cklstArtclSn: number; + cklstArtclNm: string; + cklstRvwSn: number; + upCklstRvwSn: number | null; + treeLevel: number; + rvwCn: string; + bchckUseYn: "Y" | "N"; + sortSeq: number; +} + +export interface CklstTreeSearchParams { + docSeComCd?: string; + taskSeComCd?: string; +} + export interface CklstMngSearchParams extends PageableParams { cklstNm?: string; taskSeComCd?: string; @@ -30,6 +48,7 @@ export interface CklstMngSaveRequest { export interface CklstMngService { findPage(params: CklstMngSearchParams): Promise>; findById(cklstSn: number): Promise; + findTree(params: CklstTreeSearchParams): Promise; create(body: CklstMngSaveRequest): Promise; update(cklstSn: number, body: CklstMngSaveRequest): Promise; remove(cklstSn: number): Promise; diff --git a/apps/as/src/entities/cnstp/api/cnstp-service.ts b/apps/as/src/entities/cnstp/api/cnstp-service.ts new file mode 100644 index 000000000..00eb80371 --- /dev/null +++ b/apps/as/src/entities/cnstp/api/cnstp-service.ts @@ -0,0 +1,15 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +import type { CnstpSeMngTreeDto, CnstpService } from "../model/cnstp-interface"; + +async function findTree(): Promise { + const response = await client.get( + `${ENDPOINT.AS_COMMON_SERVICE.CNSTP_SE}/tree`, + ); + return response.data; +} + +export const cnstpService = { + findTree, +} satisfies CnstpService; diff --git a/apps/as/src/entities/cnstp/index.ts b/apps/as/src/entities/cnstp/index.ts new file mode 100644 index 000000000..1415c59bd --- /dev/null +++ b/apps/as/src/entities/cnstp/index.ts @@ -0,0 +1,2 @@ +export type { CnstpSeMngTreeDto, CnstpService } from "./model/cnstp-interface"; +export { cnstpService } from "./api/cnstp-service"; diff --git a/apps/as/src/entities/cnstp/model/cnstp-interface.ts b/apps/as/src/entities/cnstp/model/cnstp-interface.ts new file mode 100644 index 000000000..d68f0625b --- /dev/null +++ b/apps/as/src/entities/cnstp/model/cnstp-interface.ts @@ -0,0 +1,23 @@ +/** 공종구분 트리 노드 DTO */ +export interface CnstpSeMngTreeDto { + /** 들여쓰기 포함 표시명 */ + indCnstpSeCdNm: string; + /** 공종구분코드 */ + cnstpSeCd: string; + /** 공종구분코드명 */ + cnstpSeCdNm: string; + /** 상위 공종구분코드 (null = 최상위) */ + upCnstpCd: string | null; + /** 사용여부 */ + useYn: string; + /** 트리 레벨 */ + treeLevel: number; + /** 최상위~현재 노드까지 "|" 구분 경로 */ + treePath: string; +} + +/** 공종구분 서비스 인터페이스 */ +export interface CnstpService { + /** 공종구분 트리 전체 조회 */ + findTree(): Promise; +} diff --git a/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts b/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts index 29d15fcf0..dd7dccd7a 100644 --- a/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts +++ b/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts @@ -1,6 +1,7 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; import type { + InstlSncnApplySaveRequestDto, InstlSncnMng, InstlSncnMngSearchParams, InstlSncnMngService, @@ -19,6 +20,14 @@ export class InstlSncnApiService implements InstlSncnMngService { const response = await client.get(`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/${id}`); return response.data; } + + public async saveApply(payload: InstlSncnApplySaveRequestDto): Promise { + const response = await client.post( + `${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/save-apply`, + payload, + ); + return response.data; + } } export const InstlSncnApi = new InstlSncnApiService(); diff --git a/apps/as/src/entities/instl-sncn/index.ts b/apps/as/src/entities/instl-sncn/index.ts index 8fdbb442f..5da39b459 100644 --- a/apps/as/src/entities/instl-sncn/index.ts +++ b/apps/as/src/entities/instl-sncn/index.ts @@ -1,6 +1,11 @@ export { InstlSncnApi } from "./api/instl-sncn-service"; -export type { - InstlSncnMng, - InstlSncnMngSearchParams, - SpringPage, +export { + INSTL_SNCN_REGISTER_SELECTION_KEY, + type InstlSncnApplySaveRequestDto, + type InstlSncnBizCstSaveRequestDto, + type InstlSncnBzScaleSaveDto, + type InstlSncnCklstResultSaveDto, + type InstlSncnMng, + type InstlSncnMngSearchParams, + type SpringPage, } from "./model/instl-sncn-interface"; diff --git a/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts b/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts index 00a51b76b..63d280d86 100644 --- a/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts +++ b/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts @@ -1,3 +1,5 @@ +export const INSTL_SNCN_REGISTER_SELECTION_KEY = "instlSncnRegisterSelection"; + export interface SpringPage { content: T[]; totalElements: number; @@ -52,7 +54,57 @@ export interface InstlSncnMng { rgtrNm: string; } +/** 설치인가 신청 - 사업규모 저장 DTO(배열 원소) */ +export interface InstlSncnBzScaleSaveDto { + /** 공종구분코드 */ + cnstpSeCd: string; + /** 기본계획(A) */ + bscPl: number; + /** 예산반영(B) */ + budgetReflct: number; + /** 금회설계시(C) */ + thtiDsgn: number; + /** 증감(C-B) */ + diff: number; +} + +/** 설치인가 신청 - 총사업비 저장 DTO(배열 원소) */ +export interface InstlSncnBizCstSaveRequestDto { + /** 회계구분코드 */ + acntgSeCd: string; + /** 기본계획금액 */ + bscPlanAmt: number; + /** 예산반영금액 */ + bgtRfltAmt: number; + /** 금회설계금액 */ + tstmDesignAmt: number; +} + +/** 설치인가 신청 - 체크리스트 결과 저장 DTO(배열 원소) */ +export interface InstlSncnCklstResultSaveDto { + cklstSn: number; + cklstArtclSn: number; + cklstRvwSn: number; + selected: "Y" | "N" | null; +} + +/** 설치인가 신청 - 필수첨부파일 저장 DTO(배열 원소) */ +export interface InstlSncnEsntlAtchFileSaveDto { + esntlAtchFileId: string; + atchFileGroupId: string; +} + +/** 설치인가 신청 - 통합 저장 요청 DTO */ +export interface InstlSncnApplySaveRequestDto { + instlSncnMng: InstlSncnMng; + bzScaleList: InstlSncnBzScaleSaveDto[]; + bizCstList: InstlSncnBizCstSaveRequestDto[]; + cklstResultList: InstlSncnCklstResultSaveDto[]; + esntlAtchFileList: InstlSncnEsntlAtchFileSaveDto[]; +} + export interface InstlSncnMngService { getPage: (params: InstlSncnMngSearchParams) => Promise>; getById: (id: string) => Promise; + saveApply: (payload: InstlSncnApplySaveRequestDto) => Promise; } diff --git a/apps/as/src/pages/approval-line-management/lib/validators.ts b/apps/as/src/pages/approval-line-management/lib/validators.ts index a136d07d7..c5fbe0b71 100644 --- a/apps/as/src/pages/approval-line-management/lib/validators.ts +++ b/apps/as/src/pages/approval-line-management/lib/validators.ts @@ -15,11 +15,17 @@ export function validateAtrzOrder(rows: ElaprAplnRow[]): string | null { const visibleRows = getVisibleRows(rows); const receiverCount = visibleRows.filter((row) => row.atrzSeComCd === ATRZ_RECEIVER_CD).length; + if (receiverCount === 0) { + return "접수자는 반드시 등록해야 합니다."; + } if (receiverCount > 1) { return "접수자는 한 명만 등록할 수 있습니다."; } const finalCount = visibleRows.filter((row) => row.atrzSeComCd === ATRZ_FINAL_CD).length; + if (finalCount === 0) { + return "최종승인자는 반드시 등록해야 합니다."; + } if (finalCount > 1) { return "최종승인자는 한 명만 등록할 수 있습니다."; } diff --git a/apps/as/src/pages/approval-line-management/ui/elapr-apln-grid.tsx b/apps/as/src/pages/approval-line-management/ui/elapr-apln-grid.tsx index 3111673de..1dd757ff9 100644 --- a/apps/as/src/pages/approval-line-management/ui/elapr-apln-grid.tsx +++ b/apps/as/src/pages/approval-line-management/ui/elapr-apln-grid.tsx @@ -98,7 +98,6 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) { }; const handleRowDelete = async (row: ElaprAplnRow) => { - if (!(await confirm("선택한 결재선을 삭제하시겠습니까?"))) return; markDeleted(row.rowKey); }; @@ -129,6 +128,8 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) { return; } + if (!(await confirm("결재선을 저장하시겠습니까?"))) return; + await saveMultiMutation.mutateAsync({ body: payload, elaprTmpltId }); alert("결재선이 저장되었습니다."); await aplnQuery.refetch(); @@ -158,12 +159,7 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) {