summaryrefslogtreecommitdiff
path: root/lib/po/vendor-table/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/po/vendor-table/service.ts')
-rw-r--r--lib/po/vendor-table/service.ts573
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