diff options
Diffstat (limited to 'lib/po/vendor-table/service.ts')
| -rw-r--r-- | lib/po/vendor-table/service.ts | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/lib/po/vendor-table/service.ts b/lib/po/vendor-table/service.ts new file mode 100644 index 00000000..88f6ddd5 --- /dev/null +++ b/lib/po/vendor-table/service.ts @@ -0,0 +1,417 @@ +"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 { 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"; + +/** + * 벤더 PO 목록 조회 + * contracts 테이블에서 실제 데이터를 조회합니다. + */ +export async function getVendorPOs(input: GetVendorPOSchema) { + try { + // 실제 데이터베이스 조회 + const offset = (input.page - 1) * input.perPage; + + // 검색 조건 구성 + let whereConditions = []; + 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) + ) + ); + } + + // 벤더 필터링 (partners 페이지에서 사용) + if (input.vendorId && input.vendorId > 0) { + whereConditions.push(eq(contracts.vendorId, input.vendorId)); + } + + // 필터 조건 추가 + if (input.filters && input.filters.length > 0) { + for (const filter of input.filters) { + if (filter.id && filter.value) { + switch (filter.id) { + case "contractStatus": + whereConditions.push(ilike(contracts.status, `%${filter.value}%`)); + break; + case "contractType": + whereConditions.push(ilike(contracts.purchaseDocType, `%${filter.value}%`)); + break; + case "currency": + whereConditions.push(eq(contracts.currency, filter.value)); + break; + // 추가 필터 조건들... + } + } + } + } + + const finalWhere = whereConditions.length > 0 ? and(...whereConditions) : undefined; + + // 정렬 조건 + const orderBy = input.sort && input.sort.length > 0 + ? input.sort.map((item) => { + switch (item.id) { + case "contractNo": + return item.desc ? desc(contracts.contractNo) : asc(contracts.contractNo); + case "contractName": + return item.desc ? desc(contracts.contractName) : asc(contracts.contractName); + case "lastModifiedDate": + return item.desc ? desc(contracts.updatedAt) : asc(contracts.updatedAt); + default: + return desc(contracts.updatedAt); + } + }) + : [desc(contracts.updatedAt)]; + + // 데이터 조회 (조인 포함) + const rawData = await db + .select({ + // contracts 테이블 필드들 + 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, + deliveryLocation: contracts.deliveryLocation, + shippmentPlace: contracts.shippmentPlace, + 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, + 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, + vendorName: vendors.vendorName, + }) + .from(contracts) + .leftJoin(projects, eq(contracts.projectId, projects.id)) + .leftJoin(vendors, eq(contracts.vendorId, vendors.id)) + .where(finalWhere) + .orderBy(...orderBy) + .offset(offset) + .limit(input.perPage); + + // 총 개수 조회 + const [{ totalCount }] = await db + .select({ totalCount: count() }) + .from(contracts) + .leftJoin(projects, eq(contracts.projectId, projects.id)) + .leftJoin(vendors, eq(contracts.vendorId, vendors.id)) + .where(finalWhere); + + const pageCount = Math.ceil(totalCount / input.perPage); + + // 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 + }; + + // 목업 데이터 사용 (개발/테스트용) + // 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; + } catch (err) { + console.error("Error in getVendorPOById:", err); + return null; + } +} + +/** + * 벤더 PO 액션 처리 + * PCR생성, 승인, 거절 등의 액션을 처리 + */ +export async function handleVendorPOAction( + poId: number, + action: string, + data?: any +): Promise<{ success: boolean; message: string }> { + try { + // 목업에서는 성공 응답만 반환 + // 실제 구현시에는 각 액션별로 비즈니스 로직 구현 + + switch (action) { + case "pcr-create": + return { success: true, message: "PCR이 성공적으로 생성되었습니다." }; + case "approve": + return { success: true, message: "계약이 승인되었습니다." }; + case "cancel-approve": + return { success: true, message: "승인이 취소되었습니다." }; + case "reject-contract": + return { success: true, message: "계약이 거절되었습니다." }; + case "print-contract": + return { success: true, message: "계약서 출력이 요청되었습니다." }; + default: + return { success: false, message: "알 수 없는 액션입니다." }; + } + } catch (err) { + console.error("Error in handleVendorPOAction:", err); + return { success: false, message: "액션 처리 중 오류가 발생했습니다." }; + } +} + +/** + * 특정 계약의 상세품목 조회 + * contract_items 테이블에서 실제 데이터를 조회합니다. + */ +export async function getVendorPOItems(contractId: number): Promise<VendorPOItem[]> { + try { + const rawItems = await db + .select({ + // contract_items 테이블 필드들 + id: contractItems.id, + contractId: contractItems.contractId, + itemId: contractItems.itemId, + description: contractItems.description, + quantity: contractItems.quantity, + unitPrice: contractItems.unitPrice, + taxRate: contractItems.taxRate, + taxAmount: contractItems.taxAmount, + totalLineAmount: contractItems.totalLineAmount, + remark: contractItems.remark, + + // contracts 테이블 필드들 + contractNo: contracts.contractNo, + + // items 테이블 필드들 + itemCode: items.itemCode, + itemName: items.itemName, + packageCode: items.packageCode, + unitOfMeasure: items.unitOfMeasure, + gradeMaterial: items.gradeMaterial, + steelType: items.steelType, + smCode: items.smCode, + }) + .from(contractItems) + .leftJoin(contracts, eq(contractItems.contractId, contracts.id)) + .leftJoin(items, eq(contractItems.itemId, items.id)) + .where(eq(contractItems.contractId, contractId)) + .orderBy(contractItems.id); + + // VendorPOItem 타입으로 변환 + const vendorPOItems: VendorPOItem[] = rawItems.map(row => ({ + contractNo: row.contractNo || '', + itemNo: row.itemCode || 'AUTO-ITEM', // mock 데이터용 + prNo: `PR-${new Date().getFullYear()}-${String(row.id).padStart(4, '0')}`, // mock 데이터용 + materialGroup: row.packageCode || 'Unknown Group', + priceStandard: 'FOB', // mock 데이터용 기본값 + materialNo: row.itemCode || '', + itemDescription: row.itemName || '', + materialSpec: row.description || '', + fittingNo: undefined, // contract_items에 없는 필드 + cert: undefined, // contract_items에 없는 필드 + material: row.gradeMaterial || undefined, + specification: row.description || '', + quantity: row.quantity || 1, + quantityUnit: row.unitOfMeasure || 'EA', + weight: undefined, // contract_items에 없는 필드 + weightUnit: undefined, // contract_items에 없는 필드 + totalWeight: undefined, // contract_items에 없는 필드 + unitPrice: row.unitPrice ? Number(row.unitPrice) : 0, + priceUnit: 'KRW', // 기본값 + priceUnitValue: '원/EA', // 기본값 + contractAmount: row.totalLineAmount ? Number(row.totalLineAmount) : 0, + adjustmentAmount: undefined, // contract_items에 없는 필드 + deliveryDate: new Date().toISOString().split('T')[0], // 기본값 (오늘 날짜) + vatType: row.taxRate && Number(row.taxRate) > 0 ? '과세' : '면세', + steelSpec: row.steelType || undefined, + prManager: 'AUTO-MANAGER', // mock 데이터용 기본값 + })); + + return vendorPOItems; + } catch (err) { + console.error("Error in getVendorPOItems:", err); + throw err; + } +} + +/** + * 계약번호로 상세품목 조회 (외부에서 contractNo로 호출할 때 사용) + */ +export async function getVendorPOItemsByContractNo(contractNo: string): Promise<VendorPOItem[]> { + try { + // 먼저 계약 ID 조회 + const contract = await db.query.contracts.findFirst({ + where: eq(contracts.contractNo, contractNo), + }); + + if (!contract) { + console.warn(`Contract not found: ${contractNo}`); + return []; + } + + return await getVendorPOItems(contract.id); + } catch (err) { + console.error("Error in getVendorPOItemsByContractNo:", err); + throw err; + } +} |
