diff options
Diffstat (limited to 'lib/po/vendor-table/service.ts')
| -rw-r--r-- | lib/po/vendor-table/service.ts | 573 |
1 files changed, 462 insertions, 111 deletions
diff --git a/lib/po/vendor-table/service.ts b/lib/po/vendor-table/service.ts index 195144a2..224dd2f1 100644 --- a/lib/po/vendor-table/service.ts +++ b/lib/po/vendor-table/service.ts @@ -1,14 +1,14 @@ "use server"; import { GetVendorPOSchema } from "./validations"; -import { getVendorPOsPage } from "./mock-data"; import { VendorPO, VendorPOItem } from "./types"; import db from "@/db/db"; -import { contracts, contractItems } from "@/db/schema/contract"; +import { contracts, contractItems, ContractStatus } from "@/db/schema/contract"; import { projects } from "@/db/schema/projects"; import { vendors } from "@/db/schema/vendors"; import { items } from "@/db/schema/items"; -import { eq, and, or, ilike, count, desc, asc } from "drizzle-orm"; +import { revalidatePath } from "next/cache"; +import { eq, and, or, ilike, count, desc, asc, SQL } from "drizzle-orm"; /** * 벤더 PO 목록 조회 @@ -20,17 +20,16 @@ export async function getVendorPOs(input: GetVendorPOSchema) { const offset = (input.page - 1) * input.perPage; // 검색 조건 구성 - let whereConditions = []; + const whereConditions: SQL<unknown>[] = []; if (input.search) { const searchTerm = `%${input.search}%`; - whereConditions.push( - or( - ilike(contracts.contractNo, searchTerm), - ilike(contracts.contractName, searchTerm), - ilike(projects.name, searchTerm), - ilike(vendors.vendorName, searchTerm) - ) + const searchCondition = or( + ilike(contracts.contractNo, searchTerm), + ilike(contracts.contractName, searchTerm), + ilike(projects.name, searchTerm), + ilike(vendors.vendorName, searchTerm) ); + if (searchCondition) whereConditions.push(searchCondition); } // 벤더 필터링 (partners 페이지에서 사용) @@ -44,13 +43,15 @@ export async function getVendorPOs(input: GetVendorPOSchema) { if (filter.id && filter.value) { switch (filter.id) { case "contractStatus": - whereConditions.push(ilike(contracts.status, `%${filter.value}%`)); + const statusCondition = ilike(contracts.status, `%${filter.value}%`); + if (statusCondition) whereConditions.push(statusCondition); break; case "contractType": - whereConditions.push(ilike(contracts.purchaseDocType, `%${filter.value}%`)); + const typeCondition = ilike(contracts.purchaseDocType, `%${filter.value}%`); + if (typeCondition) whereConditions.push(typeCondition); break; case "currency": - whereConditions.push(eq(contracts.currency, filter.value)); + whereConditions.push(eq(contracts.currency, filter.value as string)); break; // 추가 필터 조건들... } @@ -117,7 +118,11 @@ export async function getVendorPOs(input: GetVendorPOSchema) { priceIndexYn: contracts.priceIndexYn, writtenContractNo: contracts.writtenContractNo, contractVersion: contracts.contractVersion, - + + // 계약서 내용 및 노트 + contractContent: contracts.contractContent, + remarks: contracts.remarks, + createdAt: contracts.createdAt, updatedAt: contracts.updatedAt, @@ -145,34 +150,146 @@ export async function getVendorPOs(input: GetVendorPOSchema) { // VendorPO 타입으로 변환 const data: VendorPO[] = rawData.map(row => ({ + id: row.id, + contractNo: row.contractNo || '', + revision: 'Rev.01', // mock 데이터용 기본값 + itemNo: 'ITM-AUTO', // mock 데이터용 기본값 + contractStatus: row.status || '', + contractType: row.purchaseDocType || '', + details: '상세보기', // mock 데이터용 기본값 + projectName: row.projectName || '', + contractName: row.contractName || '', + contractPeriod: row.startDate && row.endDate + ? `${row.startDate} ~ ${row.endDate}` + : '', + contractQuantity: '1 LOT', // 기본값 (실제로는 contract_items에서 계산 필요) + currency: row.currency || 'KRW', + paymentTerms: row.paymentTerms || '', + tax: '10%', // 기본값 (실제로는 contract_items에서 계산 필요) + exchangeRate: row.exchangeRate?.toString() || '', + deliveryTerms: row.deliveryTerms || '', + purchaseManager: '', // 사용자 테이블 조인 필요 + poReceiveDate: row.createdAt?.toISOString().split('T')[0] || '', + contractDate: row.startDate || '', + lcNo: undefined, + priceIndexTarget: row.priceIndexYn === 'Y', + linkedContractNo: undefined, + lastModifiedDate: row.updatedAt?.toISOString().split('T')[0] || '', + lastModifiedBy: '', // 사용자 테이블 조인 필요 + + // SAP ECC 추가 필드들 + poVersion: row.poVersion || undefined, + purchaseDocType: row.purchaseDocType || undefined, + purchaseOrg: row.purchaseOrg || undefined, + purchaseGroup: row.purchaseGroup || undefined, + poConfirmStatus: row.poConfirmStatus || undefined, + contractGuaranteeCode: row.contractGuaranteeCode || undefined, + defectGuaranteeCode: row.defectGuaranteeCode || undefined, + guaranteePeriodCode: row.guaranteePeriodCode || undefined, + advancePaymentYn: row.advancePaymentYn || undefined, + budgetAmount: row.budgetAmount ? Number(row.budgetAmount) : undefined, + budgetCurrency: row.budgetCurrency || undefined, + totalAmount: row.totalAmount ? Number(row.totalAmount) : undefined, + totalAmountKrw: row.totalAmountKrw ? Number(row.totalAmountKrw) : undefined, + electronicContractYn: row.electronicContractYn || undefined, + electronicApprovalDate: row.electronicApprovalDate || undefined, + electronicApprovalTime: row.electronicApprovalTime || undefined, + ownerApprovalYn: row.ownerApprovalYn || undefined, + plannedInOutFlag: row.plannedInOutFlag || undefined, + settlementStandard: row.settlementStandard || undefined, + weightSettlementFlag: row.weightSettlementFlag || undefined, + priceIndexYn: row.priceIndexYn || undefined, + writtenContractNo: row.writtenContractNo || undefined, + contractVersion: row.contractVersion || undefined, + })); + + return { + data, + pageCount + }; + } catch (err) { + console.error("Error in getVendorPOs:", err); + return { data: [], pageCount: 0 }; + } +} + +/** + * 벤더 PO 상세 정보 조회 + */ +export async function getVendorPOById(id: number): Promise<VendorPO | null> { + try { + const [row] = await db + .select({ + id: contracts.id, + contractNo: contracts.contractNo, + contractName: contracts.contractName, + status: contracts.status, + startDate: contracts.startDate, + endDate: contracts.endDate, + currency: contracts.currency, + totalAmount: contracts.totalAmount, + totalAmountKrw: contracts.totalAmountKrw, + paymentTerms: contracts.paymentTerms, + deliveryTerms: contracts.deliveryTerms, + exchangeRate: contracts.exchangeRate, + poVersion: contracts.poVersion, + purchaseDocType: contracts.purchaseDocType, + purchaseOrg: contracts.purchaseOrg, + purchaseGroup: contracts.purchaseGroup, + poConfirmStatus: contracts.poConfirmStatus, + contractGuaranteeCode: contracts.contractGuaranteeCode, + defectGuaranteeCode: contracts.defectGuaranteeCode, + guaranteePeriodCode: contracts.guaranteePeriodCode, + advancePaymentYn: contracts.advancePaymentYn, + budgetAmount: contracts.budgetAmount, + budgetCurrency: contracts.budgetCurrency, + electronicContractYn: contracts.electronicContractYn, + electronicApprovalDate: contracts.electronicApprovalDate, + electronicApprovalTime: contracts.electronicApprovalTime, + ownerApprovalYn: contracts.ownerApprovalYn, + plannedInOutFlag: contracts.plannedInOutFlag, + settlementStandard: contracts.settlementStandard, + weightSettlementFlag: contracts.weightSettlementFlag, + priceIndexYn: contracts.priceIndexYn, + writtenContractNo: contracts.writtenContractNo, + contractVersion: contracts.contractVersion, + createdAt: contracts.createdAt, + updatedAt: contracts.updatedAt, + projectName: projects.name, + }) + .from(contracts) + .leftJoin(projects, eq(contracts.projectId, projects.id)) + .where(eq(contracts.id, id)) + .limit(1); + + if (!row) return null; + + // VendorPO 타입으로 변환 + const po: VendorPO = { id: row.id, contractNo: row.contractNo || '', - revision: 'Rev.01', // mock 데이터용 기본값 - itemNo: 'ITM-AUTO', // mock 데이터용 기본값 + revision: 'Rev.01', + itemNo: 'ITM-AUTO', contractStatus: row.status || '', contractType: row.purchaseDocType || '', - details: '상세보기', // mock 데이터용 기본값 + details: '상세보기', projectName: row.projectName || '', contractName: row.contractName || '', - contractPeriod: row.startDate && row.endDate - ? `${row.startDate} ~ ${row.endDate}` - : '', - contractQuantity: '1 LOT', // 기본값 (실제로는 contract_items에서 계산 필요) + contractPeriod: row.startDate && row.endDate ? `${row.startDate} ~ ${row.endDate}` : '', + contractQuantity: '1 LOT', currency: row.currency || 'KRW', paymentTerms: row.paymentTerms || '', - tax: '10%', // 기본값 (실제로는 contract_items에서 계산 필요) + tax: '10%', exchangeRate: row.exchangeRate?.toString() || '', deliveryTerms: row.deliveryTerms || '', - purchaseManager: '', // 사용자 테이블 조인 필요 + purchaseManager: '', poReceiveDate: row.createdAt?.toISOString().split('T')[0] || '', contractDate: row.startDate || '', lcNo: undefined, priceIndexTarget: row.priceIndexYn === 'Y', linkedContractNo: undefined, lastModifiedDate: row.updatedAt?.toISOString().split('T')[0] || '', - lastModifiedBy: '', // 사용자 테이블 조인 필요 - - // SAP ECC 추가 필드들 + lastModifiedBy: '', poVersion: row.poVersion || undefined, purchaseDocType: row.purchaseDocType || undefined, purchaseOrg: row.purchaseOrg || undefined, @@ -196,90 +313,9 @@ export async function getVendorPOs(input: GetVendorPOSchema) { priceIndexYn: row.priceIndexYn || undefined, writtenContractNo: row.writtenContractNo || undefined, contractVersion: row.contractVersion || undefined, - })); - - return { - data, - pageCount - }; - - // 목업 데이터 사용 (개발/테스트용) - // const result = getVendorPOsPage( - // input.page, - // input.perPage, - // input.search, - // input.filters - // ); - - // 실제 데이터베이스 연동시에는 아래와 같은 구조로 구현 - // const offset = (input.page - 1) * input.perPage; - // - // // 검색 조건 구성 - // let whereConditions = []; - // if (input.search) { - // const searchTerm = `%${input.search}%`; - // whereConditions.push( - // or( - // ilike(vendorPOTable.contractNo, searchTerm), - // ilike(vendorPOTable.contractName, searchTerm), - // ilike(vendorPOTable.projectName, searchTerm) - // ) - // ); - // } - // - // // 필터 조건 추가 - // if (input.contractStatus) { - // whereConditions.push(eq(vendorPOTable.contractStatus, input.contractStatus)); - // } - // - // const finalWhere = whereConditions.length > 0 ? and(...whereConditions) : undefined; - // - // // 정렬 조건 - // const orderBy = input.sort.length > 0 - // ? input.sort.map((item) => - // item.desc - // ? desc(vendorPOTable[item.id]) - // : asc(vendorPOTable[item.id]) - // ) - // : [desc(vendorPOTable.lastModifiedDate)]; - // - // // 데이터 조회 - // const data = await db - // .select() - // .from(vendorPOTable) - // .where(finalWhere) - // .orderBy(...orderBy) - // .offset(offset) - // .limit(input.perPage); - // - // // 총 개수 조회 - // const [{ count }] = await db - // .select({ count: count() }) - // .from(vendorPOTable) - // .where(finalWhere); - // - // const pageCount = Math.ceil(count / input.perPage); - - return { - data: result.data, - pageCount: result.pageCount }; - } catch (err) { - console.error("Error in getVendorPOs:", err); - return { data: [], pageCount: 0 }; - } -} -/** - * 벤더 PO 상세 정보 조회 - */ -export async function getVendorPOById(id: number): Promise<VendorPO | null> { - try { - // 목업 데이터에서 조회 - const result = getVendorPOsPage(1, 100); // 모든 데이터 가져오기 - const po = result.data.find(item => item.id === id); - - return po || null; + return po; } catch (err) { console.error("Error in getVendorPOById:", err); return null; @@ -292,8 +328,7 @@ export async function getVendorPOById(id: number): Promise<VendorPO | null> { */ export async function handleVendorPOAction( poId: number, - action: string, - data?: any + action: string ): Promise<{ success: boolean; message: string }> { try { // 목업에서는 성공 응답만 반환 @@ -415,3 +450,319 @@ export async function getVendorPOItemsByContractNo(contractNo: string): Promise< throw err; } } + +/** + * PCR 생성 요청: PCR 생성 요청 후, 상태 변경 + */ +export async function createPcrRequest(contractId: number) { + try { + // TODO PCR 생성 요청 로직 구현 + + // PCR 생성 요청 상태로 변경 + await db.update(contracts).set({ status: ContractStatus.PCR_REQUEST }).where(eq(contracts.id, contractId)); + + // 캐시 무효화하여 변경사항 즉시 반영 + revalidatePath("/partners/po"); + } catch (err) { + console.error("Error in createPcrRequest:", err); + throw err; + } + + return { + success: true, + message: "PCR 생성 요청이 성공적으로 완료되었습니다." + }; +} + +/** + * 계약 승인 처리: 상태만 변경 + */ +export async function acceptContract(contractId: number) { + try { + await db.update(contracts).set({ status: ContractStatus.COMPLETE_THE_CONTRACT }).where(eq(contracts.id, contractId)); + + // 캐시 무효화하여 변경사항 즉시 반영 + revalidatePath("/partners/po"); + } catch (err) { + console.error("Error in acceptContract:", err); + throw err; + } + + return { + success: true, + message: "계약이 성공적으로 승인되었습니다." + }; +} +/** + * 계약 승인 취소 처리: 상태만 변경 + */ +export async function cancelAcceptContract(contractId: number) { + try { + + // 계약 승인 상태에서만 취소 가능 + const contract = await db.query.contracts.findFirst({ + where: eq(contracts.id, contractId), + }); + if (!contract) { + throw new Error("계약을 찾을 수 없습니다."); + } + if (contract.status !== ContractStatus.COMPLETE_THE_CONTRACT) { + throw new Error("계약 승인 상태가 아닙니다."); + } + + // 취소 처리 + await db.update(contracts).set({ status: ContractStatus.CONTRACT_ACCEPT_REQUEST }).where(eq(contracts.id, contractId)); + + // 캐시 무효화하여 변경사항 즉시 반영 + revalidatePath("/partners/po"); + } catch (err) { + console.error("Error in cancelAcceptContract:", err); + throw err; + } + + return { + success: true, + message: "계약이 성공적으로 승인 취소되었습니다." + }; +} + +/** + * 계약 거절 처리: 거절 사유를 입력받고, 상태 변경 + */ +export async function rejectContract(contractId: number, rejectionReason: string) { + try { + await db.update(contracts).set({ status: ContractStatus.REJECT_TO_ACCEPT_CONTRACT, rejectionReason }).where(eq(contracts.id, contractId)); + + // 캐시 무효화하여 변경사항 즉시 반영 + revalidatePath("/partners/po"); + } catch (err) { + console.error("Error in rejectContract:", err); + throw err; + } + + return { + success: true, + message: "계약이 성공적으로 거절되었습니다." + }; +} + +/** + * 벤더 코멘트 저장 + */ +export async function saveVendorComment(contractId: number, vendorComment: string) { + try { + await db.update(contracts).set({ + vendorComment, + updatedAt: new Date() + }).where(eq(contracts.id, contractId)); + + // 캐시 무효화하여 변경사항 즉시 반영 + revalidatePath("/partners/po"); + } catch (err) { + console.error("Error in saveVendorComment:", err); + throw err; + } + + return { + success: true, + message: "의견이 성공적으로 저장되었습니다." + }; +} + +/** + * SHI 코멘트 저장 (EVCP용) + */ +export async function saveSHIComment(contractId: number, shiComment: string) { + try { + await db.update(contracts).set({ + shiComment, + updatedAt: new Date() + }).where(eq(contracts.id, contractId)); + + // 캐시 무효화하여 변경사항 즉시 반영 + revalidatePath("/evcp/po"); + revalidatePath(`/evcp/po/${contractId}`); + } catch (err) { + console.error("Error in saveSHIComment:", err); + throw err; + } + + return { + success: true, + message: "SHI 의견이 성공적으로 저장되었습니다." + }; +} + +/** + * 특정 계약의 상세 정보 조회 (EVCP/SHI용) + */ +export async function getContractDetail(contractId: number) { + try { + // contractId 유효성 검사 + if (!contractId || isNaN(contractId)) { + return { success: false, error: "유효하지 않은 계약 ID입니다." }; + } + + // 계약 기본 정보 조회 + const [contractData] = await db + .select({ + // contracts 테이블 필드들 + id: contracts.id, + contractNo: contracts.contractNo, + contractName: contracts.contractName, + status: contracts.status, + startDate: contracts.startDate, + endDate: contracts.endDate, + contractDate: contracts.createdAt, + currency: contracts.currency, + totalAmount: contracts.totalAmount, + totalAmountKrw: contracts.totalAmountKrw, + paymentTerms: contracts.paymentTerms, + deliveryTerms: contracts.deliveryTerms, + exchangeRate: contracts.exchangeRate, + rejectionReason: contracts.rejectionReason, + + // SAP ECC 추가 필드들 + poVersion: contracts.poVersion, + purchaseDocType: contracts.purchaseDocType, + purchaseOrg: contracts.purchaseOrg, + purchaseGroup: contracts.purchaseGroup, + poConfirmStatus: contracts.poConfirmStatus, + + // 계약/보증 관련 + contractGuaranteeCode: contracts.contractGuaranteeCode, + defectGuaranteeCode: contracts.defectGuaranteeCode, + guaranteePeriodCode: contracts.guaranteePeriodCode, + advancePaymentYn: contracts.advancePaymentYn, + + // 계약서 내용 및 노트 + contractContent: contracts.contractContent, + remarks: contracts.remarks, + vendorComment: contracts.vendorComment, + shiComment: contracts.shiComment, + + createdAt: contracts.createdAt, + updatedAt: contracts.updatedAt, + + // 조인된 테이블 필드들 + projectId: projects.id, + projectName: projects.name, + projectCode: projects.code, + vendorId: vendors.id, + vendorName: vendors.vendorName, + vendorCode: vendors.vendorCode, + }) + .from(contracts) + .leftJoin(projects, eq(contracts.projectId, projects.id)) + .leftJoin(vendors, eq(contracts.vendorId, vendors.id)) + .where(eq(contracts.id, contractId)) + .limit(1); + + if (!contractData) { + return { success: false, error: "계약 정보를 찾을 수 없습니다." }; + } + + // 계약 품목 조회 + const items = await getVendorPOItems(contractId); + + return { + success: true, + data: { + ...contractData, + items, + }, + }; + } catch (error) { + console.error("Error fetching contract detail:", error); + return { success: false, error: "계약 상세 정보 조회 중 오류가 발생했습니다." }; + } +} + +/** + * 특정 계약의 상세 정보 조회 (벤더용 계약 상세 페이지) + */ +export async function getVendorContractDetail(contractId: number, vendorId: number) { + try { + // contractId 유효성 검사 + if (!contractId || isNaN(contractId)) { + return { success: false, error: "유효하지 않은 계약 ID입니다." }; + } + + // 계약 기본 정보 조회 (벤더 필터링 포함) + const [contractData] = await db + .select({ + // contracts 테이블 필드들 + id: contracts.id, + contractNo: contracts.contractNo, + contractName: contracts.contractName, + status: contracts.status, + startDate: contracts.startDate, + endDate: contracts.endDate, + contractDate: contracts.createdAt, + currency: contracts.currency, + totalAmount: contracts.totalAmount, + totalAmountKrw: contracts.totalAmountKrw, + paymentTerms: contracts.paymentTerms, + deliveryTerms: contracts.deliveryTerms, + exchangeRate: contracts.exchangeRate, + + // SAP ECC 추가 필드들 + poVersion: contracts.poVersion, + purchaseDocType: contracts.purchaseDocType, + purchaseOrg: contracts.purchaseOrg, + purchaseGroup: contracts.purchaseGroup, + poConfirmStatus: contracts.poConfirmStatus, + + // 계약/보증 관련 + contractGuaranteeCode: contracts.contractGuaranteeCode, + defectGuaranteeCode: contracts.defectGuaranteeCode, + guaranteePeriodCode: contracts.guaranteePeriodCode, + advancePaymentYn: contracts.advancePaymentYn, + + // 계약서 내용 및 노트 + contractContent: contracts.contractContent, + remarks: contracts.remarks, + vendorComment: contracts.vendorComment, + shiComment: contracts.shiComment, + + createdAt: contracts.createdAt, + updatedAt: contracts.updatedAt, + + // 조인된 테이블 필드들 + projectId: projects.id, + projectName: projects.name, + projectCode: projects.code, + vendorId: vendors.id, + vendorName: vendors.vendorName, + vendorCode: vendors.vendorCode, + }) + .from(contracts) + .leftJoin(projects, eq(contracts.projectId, projects.id)) + .leftJoin(vendors, eq(contracts.vendorId, vendors.id)) + .where( + and( + eq(contracts.id, contractId), + eq(contracts.vendorId, vendorId) // 벤더 권한 체크 + ) + ) + .limit(1); + + if (!contractData) { + return { success: false, error: "계약 정보를 찾을 수 없거나 접근 권한이 없습니다." }; + } + + // 계약 품목 조회 + const items = await getVendorPOItems(contractId); + + return { + success: true, + data: { + ...contractData, + items, + }, + }; + } catch (error) { + console.error("Error fetching contract detail:", error); + return { success: false, error: "계약 상세 정보 조회 중 오류가 발생했습니다." }; + } +}
\ No newline at end of file |
