diff options
| author | joonhoekim <26rote@gmail.com> | 2025-07-01 10:45:37 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-07-01 10:45:37 +0000 |
| commit | 4fb273b7fc85352183113f1240fc33f7d6c98328 (patch) | |
| tree | 420302b9edec72cd153b3fa1791c4a55e041616b /lib | |
| parent | 6e25ab8da8a90a6d9bf40ccc83e36f119fb27568 (diff) | |
(김준회) 벤더 기본 정보 UI 및 서비스 - NONSAP 에서 데이터 페칭
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/vendors/mdg-actions.ts | 93 | ||||
| -rw-r--r-- | lib/vendors/mdg-service.ts | 598 |
2 files changed, 691 insertions, 0 deletions
diff --git a/lib/vendors/mdg-actions.ts b/lib/vendors/mdg-actions.ts new file mode 100644 index 00000000..ac57aec4 --- /dev/null +++ b/lib/vendors/mdg-actions.ts @@ -0,0 +1,93 @@ +"use server" + +/** + * MDG 마이그레이션 진행되지 않은 상태라 PLM DB를 싱크해 사용했으므로, 추후 수정 필요 + * PLM 쪽으로는 업데이트 불가능하므로, 최초 1회 마이그레이션한 데이터만 사용할 것임 + * node-cron 으로 PLM 데이터 동기화할 필요도 없다는 얘기 + */ + +import { revalidateTag } from "next/cache" +import { vendorMdgService, type VendorUpdateData } from "./mdg-service" +import { z } from "zod" + +// 벤더 업데이트 데이터 스키마 +const vendorUpdateSchema = z.object({ + vendorId: z.string().min(1, "Vendor ID is required"), + updateData: z.object({ + VNDRNM_1: z.string().optional(), + VNDRNM_2: z.string().optional(), + VNDRNM_ABRV_1: z.string().optional(), + BIZR_NO: z.string().optional(), + CO_REG_NO: z.string().optional(), + CO_VLM: z.string().optional(), + REPR_NM: z.string().optional(), + REP_TEL_NO: z.string().optional(), + REPR_RESNO: z.string().optional(), + REPRESENTATIVE_EMAIL: z.string().optional(), + BIZTP: z.string().optional(), + BIZCON: z.string().optional(), + NTN_CD: z.string().optional(), + ADR_1: z.string().optional(), + ADR_2: z.string().optional(), + POSTAL_CODE: z.string().optional(), + ADDR_DETAIL_1: z.string().optional(), + }) +}) + +export type VendorUpdateInput = z.infer<typeof vendorUpdateSchema> + +/** + * MDG 벤더 기본 정보 업데이트 서버 액션 + */ +export async function updateMdgVendorBasicInfo(input: VendorUpdateInput) { + try { + // 입력 데이터 검증 + const validatedData = vendorUpdateSchema.parse(input) + + // 벤더 ID로 벤더 코드 조회 + const vendorCode = await vendorMdgService.getVendorCodeByVendorId(validatedData.vendorId) + + if (!vendorCode) { + return { + success: false, + error: "벤더를 찾을 수 없습니다." + } + } + + // MDG 서비스를 통해 벤더 정보 업데이트 + const success = await vendorMdgService.updateVendorBasicInfo( + vendorCode, + validatedData.updateData + ) + + if (success) { + // 캐시 무효화 + revalidateTag(`vendor-details-${validatedData.vendorId}`) + revalidateTag("vendors") + + return { + success: true, + message: "벤더 정보가 성공적으로 업데이트되었습니다." + } + } else { + return { + success: false, + error: "벤더 정보 업데이트에 실패했습니다." + } + } + } catch (error) { + console.error("MDG 벤더 정보 업데이트 중 오류:", error) + + if (error instanceof z.ZodError) { + return { + success: false, + error: `입력 데이터 오류: ${error.errors[0].message}` + } + } + + return { + success: false, + error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다." + } + } +}
\ No newline at end of file diff --git a/lib/vendors/mdg-service.ts b/lib/vendors/mdg-service.ts new file mode 100644 index 00000000..27372a1e --- /dev/null +++ b/lib/vendors/mdg-service.ts @@ -0,0 +1,598 @@ +/** + * MDG 마이그레이션 진행되지 않은 상태라 PLM DB를 싱크해 사용했으므로, 추후 수정 필요 + * PLM 쪽으로는 업데이트 불가능하므로, 최초 1회 마이그레이션한 데이터만 사용할 것임 + * node-cron 으로 PLM 데이터 동기화할 필요도 없다는 얘기 + */ + +import { eq } from 'drizzle-orm' +import { + cmctbVendorGeneral, + cmctbVendorAddr, + cmctbVendorCompny, + cmctbVendorPorg, + cmctbVendorRepremail, + cmctbVendorInco, + type CmctbVendorGeneral, + type CmctbVendorAddr +} from '@/db/schema/NONSAP/nonsap' +import { vendors } from '@/db/schema/vendors' +import db from '@/db/db' +import { debugLog, debugError, debugWarn, debugSuccess } from '@/lib/debug-utils' + +// 구매조직별 정보 타입 정의 +export interface PurchasingOrgInfo { + PUR_ORG_CD: string // 구매조직 코드 + PUR_ORD_CUR: string | null // 오더통화 + SPLY_COND: string | null // 지급조건 + DL_COND_1: string | null // 인도조건1 + DL_COND_2: string | null // 인도조건2 + GR_BSE_INVC_VR: string | null // GR기준송장검증 + ORD_CNFM_REQ_ORDR: string | null // P/O 확인요청 + CNFM_CTL_KEY: string | null // 확정제어키 + PUR_HOLD_ORDR: string | null // 구매보류지시자 + DEL_ORDR: string | null // 삭제지시자 + AT_PUR_ORD_ORDR: string | null // 자동구매오더지시자 + SALE_CHRGR_NM: string | null // 영업담당자명 + VNDR_TELNO: string | null // VENDOR전화번호 + PUR_HOLD_DT: string | null // 구매보류일자 + PUR_HOLD_CAUS: string | null // 구매보류사유 +} + +// 벤더 상세 정보 타입 정의 +export interface VendorDetails { + // 기본 정보 + VNDRCD: string + VNDRNM_1: string | null + VNDRNM_2: string | null + VNDRNM_ABRV_1: string | null + BIZR_NO: string | null + CO_REG_NO: string | null + CO_VLM: string | null + + // 대표자 정보 + REPR_NM: string | null + REP_TEL_NO: string | null + REPR_RESNO: string | null + REPRESENTATIVE_EMAIL: string | null + + // 사업 정보 + BIZTP: string | null + BIZCON: string | null + NTN_CD: string | null + REG_DT: string | null + + // 주소 정보 + ADR_1: string | null + ADR_2: string | null + POSTAL_CODE: string | null + ADDR_DETAIL_1: string | null + + // 이전업체코드 + PREVIOUS_VENDOR_CODE: string | null + + // 내외자구분 (사내협력사 정보) << 정확하지 않음. 추후 확인 필요 + PRTNR_GB: string | null + + // 구매조직별 정보 (배열) + PURCHASING_ORGS: PurchasingOrgInfo[] + + // 상태 정보 + DEL_ORDR: string | null + PUR_HOLD_ORDR: string | null +} + +// 벤더 수정 데이터 타입 +export interface VendorUpdateData { + // 기본 정보 + VNDRNM_1?: string + VNDRNM_2?: string + VNDRNM_ABRV_1?: string + BIZR_NO?: string + CO_REG_NO?: string + CO_VLM?: string + + // 대표자 정보 + REPR_NM?: string + REP_TEL_NO?: string + REPR_RESNO?: string + REPRESENTATIVE_EMAIL?: string + + // 사업 정보 + BIZTP?: string + BIZCON?: string + NTN_CD?: string + + // 주소 정보 + ADR_1?: string + ADR_2?: string + POSTAL_CODE?: string + ADDR_DETAIL_1?: string +} + +// 벤더 목록 아이템 타입 +export interface VendorListItem { + VNDRCD: string + VNDRNM_1: string | null + VNDRNM_2: string | null + BIZR_NO: string | null + REG_DT: string | null + DEL_ORDR: string | null + PUR_HOLD_ORDR: string | null +} + +export class VendorMdgService { + /** + * 벤더 ID로 벤더 코드 조회 + * @param vendorId 벤더 ID + * @returns 벤더 코드 (VNDRCD) + */ + async getVendorCodeByVendorId(vendorId: string): Promise<string | null> { + debugLog(`벤더 코드 조회 시작: ID ${vendorId}`) + + try { + const vendor = await db + .select({ vendorCode: vendors.vendorCode }) + .from(vendors) + .where(eq(vendors.id, parseInt(vendorId))) + .limit(1) + + debugLog(`vendors 테이블 조회 결과:`, { + found: vendor.length > 0, + vendorCode: vendor[0]?.vendorCode || null + }) + + if (vendor.length === 0) { + debugWarn(`벤더 ID ${vendorId}에 해당하는 벤더를 찾을 수 없습니다.`) + return null + } + + const vendorCode = vendor[0].vendorCode + if (!vendorCode) { + debugWarn(`벤더 ID ${vendorId}의 vendor_code가 null입니다.`) + return null + } + + debugSuccess(`벤더 코드 조회 성공: ID ${vendorId} -> 코드 ${vendorCode}`) + return vendorCode + } catch (error) { + debugError('벤더 코드 조회 중 오류 발생:', error) + return null + } + } + + /** + * 벤더 ID로 벤더 상세 정보 조회 + * @param vendorId 벤더 ID + * @returns 벤더 상세 정보 (데이터가 없어도 기본 구조 반환) + */ + async getVendorDetailsByVendorId(vendorId: string): Promise<VendorDetails | null> { + debugLog(`벤더 ID로 상세 정보 조회 시작: ${vendorId}`) + + // 1. 벤더 코드 조회 + const vendorCode = await this.getVendorCodeByVendorId(vendorId) + + if (!vendorCode) { + debugWarn(`벤더 ID ${vendorId}에 대한 벤더 코드를 찾을 수 없습니다.`) + return null + } + + // 2. 벤더 코드로 상세 정보 조회 + debugLog(`벤더 코드 ${vendorCode}로 상세 정보 조회 시작`) + return await this.getVendorDetails(vendorCode) + } + + /** + * 벤더 코드로 벤더 상세 정보 조회 + * @param vendorCode 벤더 코드 + * @returns 벤더 상세 정보 (데이터가 없어도 기본 구조 반환) + */ + async getVendorDetails(vendorCode: string): Promise<VendorDetails> { + debugLog(`벤더 정보 조회 시작: ${vendorCode}`) + + try { + // 메인 쿼리: 벤더 일반 정보 + debugLog(`CMCTB_VENDOR_GENERAL 테이블에서 ${vendorCode} 조회 중...`) + const vendorGeneral = await db + .select() + .from(cmctbVendorGeneral) + .where(eq(cmctbVendorGeneral.VNDRCD, vendorCode)) + .limit(1) + + debugLog(`CMCTB_VENDOR_GENERAL 조회 결과:`, { + found: vendorGeneral.length > 0, + count: vendorGeneral.length, + data: vendorGeneral.length > 0 ? vendorGeneral[0] : null + }) + + const vendor = vendorGeneral[0] || null + + // 주소 정보 조회 + debugLog(`CMCTB_VENDOR_ADDR 테이블에서 ${vendorCode} 조회 중...`) + const vendorAddr = await db + .select() + .from(cmctbVendorAddr) + .where(eq(cmctbVendorAddr.VNDRCD, vendorCode)) + .limit(1) + + debugLog(`CMCTB_VENDOR_ADDR 조회 결과:`, { + found: vendorAddr.length > 0, + count: vendorAddr.length, + data: vendorAddr.length > 0 ? vendorAddr[0] : null + }) + + // 회사 정보 조회 (첫 번째 회사 코드) + debugLog(`CMCTB_VENDOR_COMPNY 테이블에서 ${vendorCode} 조회 중...`) + const vendorCompany = await db + .select() + .from(cmctbVendorCompny) + .where(eq(cmctbVendorCompny.VNDRCD, vendorCode)) + .limit(1) + + debugLog(`CMCTB_VENDOR_COMPNY 조회 결과:`, { + found: vendorCompany.length > 0, + count: vendorCompany.length, + data: vendorCompany.length > 0 ? vendorCompany[0] : null + }) + + // 모든 구매조직 정보 조회 + debugLog(`CMCTB_VENDOR_PORG 테이블에서 ${vendorCode}의 모든 구매조직 조회 중...`) + const vendorPorgs = await db + .select() + .from(cmctbVendorPorg) + .where(eq(cmctbVendorPorg.VNDRCD, vendorCode)) + + debugLog(`CMCTB_VENDOR_PORG 조회 결과:`, { + found: vendorPorgs.length > 0, + count: vendorPorgs.length, + data: vendorPorgs + }) + + // 사내협력사 정보 조회 (내외자구분) + debugLog(`CMCTB_VENDOR_INCO 테이블에서 ${vendorCode} 조회 중...`) + const vendorInco = await db + .select() + .from(cmctbVendorInco) + .where(eq(cmctbVendorInco.VNDRCD, vendorCode)) + .limit(1) + + debugLog(`CMCTB_VENDOR_INCO 조회 결과:`, { + found: vendorInco.length > 0, + count: vendorInco.length, + data: vendorInco.length > 0 ? vendorInco[0] : null + }) + + // 대표자 이메일 조회 + debugLog(`CMCTB_VENDOR_REPREMAIL 테이블에서 ${vendorCode} 조회 중...`) + const vendorEmail = await db + .select() + .from(cmctbVendorRepremail) + .where(eq(cmctbVendorRepremail.VNDRCD, vendorCode)) + .limit(1) + + debugLog(`CMCTB_VENDOR_REPREMAIL 조회 결과:`, { + found: vendorEmail.length > 0, + count: vendorEmail.length, + data: vendorEmail.length > 0 ? vendorEmail[0] : null + }) + + const addr = vendorAddr[0] || null + const company = vendorCompany[0] || null + const inco = vendorInco[0] || null + const email = vendorEmail[0] || null + + // 구매조직 정보 배열 구성 + const purchasingOrgs: PurchasingOrgInfo[] = vendorPorgs.map(porg => ({ + PUR_ORG_CD: porg.PUR_ORG_CD, + PUR_ORD_CUR: porg.PUR_ORD_CUR, + SPLY_COND: porg.SPLY_COND, + DL_COND_1: porg.DL_COND_1, + DL_COND_2: porg.DL_COND_2, + GR_BSE_INVC_VR: porg.GR_BSE_INVC_VR, + ORD_CNFM_REQ_ORDR: porg.ORD_CNFM_REQ_ORDR, + CNFM_CTL_KEY: porg.CNFM_CTL_KEY, + PUR_HOLD_ORDR: porg.PUR_HOLD_ORDR, + DEL_ORDR: porg.DEL_ORDR, + AT_PUR_ORD_ORDR: porg.AT_PUR_ORD_ORDR, + SALE_CHRGR_NM: porg.SALE_CHRGR_NM, + VNDR_TELNO: porg.VNDR_TELNO, + PUR_HOLD_DT: porg.PUR_HOLD_DT, + PUR_HOLD_CAUS: porg.PUR_HOLD_CAUS + })) + + // 데이터 존재 여부 확인 + const hasAnyData = vendor || addr || company || purchasingOrgs.length > 0 || inco || email + if (!hasAnyData) { + debugWarn(`벤더 ${vendorCode}에 대한 데이터가 전혀 없습니다. 기본 구조만 반환합니다.`) + } else { + debugSuccess(`벤더 ${vendorCode} 데이터 조회 완료`, { + general: !!vendor, + addr: !!addr, + company: !!company, + purchasingOrgs: purchasingOrgs.length, + inco: !!inco, + email: !!email + }) + } + + // 벤더 상세 정보 구성 (데이터가 없어도 기본 구조 제공) + const vendorDetails: VendorDetails = { + // 기본 정보 (General 테이블) + VNDRCD: vendorCode, // 항상 요청된 벤더 코드 반환 + VNDRNM_1: vendor?.VNDRNM_1 || null, + VNDRNM_2: vendor?.VNDRNM_2 || null, + VNDRNM_ABRV_1: vendor?.VNDRNM_ABRV_1 || null, + BIZR_NO: vendor?.BIZR_NO || null, + CO_REG_NO: vendor?.CO_REG_NO || null, + CO_VLM: vendor?.CO_VLM || null, + + // 대표자 정보 + REPR_NM: vendor?.REPR_NM || null, + REP_TEL_NO: vendor?.REP_TEL_NO || null, + REPR_RESNO: vendor?.REPR_RESNO || null, + REPRESENTATIVE_EMAIL: email?.EMAIL_ADR || null, + + // 사업 정보 + BIZTP: vendor?.BIZTP || null, + BIZCON: vendor?.BIZCON || null, + NTN_CD: vendor?.NTN_CD || null, + REG_DT: vendor?.REG_DT || null, + + // 주소 정보 (Address 테이블 우선, 없으면 General 테이블) + ADR_1: addr?.ADR_1 || vendor?.ADR_1 || null, + ADR_2: addr?.ADR_2 || vendor?.ADR_2 || null, + POSTAL_CODE: addr?.CITY_ZIP_NO || null, + ADDR_DETAIL_1: addr?.ETC_ADR_1 || null, + + // 이전업체코드 + PREVIOUS_VENDOR_CODE: company?.BF_VNDRCD || null, + + // 내외자구분 (사내협력사 정보) + PRTNR_GB: inco?.PRTNR_GB || null, + + // 구매조직별 정보 배열 + PURCHASING_ORGS: purchasingOrgs, + + // 상태 정보 (기본값 제공) + DEL_ORDR: vendor?.DEL_ORDR || 'N', // 기본값: 활성 + PUR_HOLD_ORDR: vendor?.PUR_HOLD_ORDR || null + } + + debugLog(`최종 벤더 정보 구성 완료:`, vendorDetails) + + return vendorDetails + } catch (error) { + debugError('벤더 정보 조회 중 오류 발생:', error) + + // 오류가 발생해도 기본 구조는 제공 + debugWarn(`오류로 인해 ${vendorCode}의 기본 구조만 반환합니다.`) + return { + VNDRCD: vendorCode, + VNDRNM_1: null, + VNDRNM_2: null, + VNDRNM_ABRV_1: null, + BIZR_NO: null, + CO_REG_NO: null, + CO_VLM: null, + REPR_NM: null, + REP_TEL_NO: null, + REPR_RESNO: null, + REPRESENTATIVE_EMAIL: null, + BIZTP: null, + BIZCON: null, + NTN_CD: null, + REG_DT: null, + ADR_1: null, + ADR_2: null, + POSTAL_CODE: null, + ADDR_DETAIL_1: null, + PREVIOUS_VENDOR_CODE: null, + PRTNR_GB: null, + PURCHASING_ORGS: [], + DEL_ORDR: 'N', // 기본값: 활성 + PUR_HOLD_ORDR: null + } + } + } + + /** + * 벤더 기본 정보 수정 + * @param vendorCode 벤더 코드 + * @param updateData 수정할 데이터 + * @returns 성공 여부 + */ + async updateVendorBasicInfo( + vendorCode: string, + updateData: VendorUpdateData + ): Promise<boolean> { + try { + // 트랜잭션으로 여러 테이블 업데이트 + await db.transaction(async (tx) => { + // 1. General 테이블 업데이트 + const generalUpdateData: Partial<CmctbVendorGeneral> = {} + if (updateData.VNDRNM_1 !== undefined) generalUpdateData.VNDRNM_1 = updateData.VNDRNM_1 + if (updateData.VNDRNM_2 !== undefined) generalUpdateData.VNDRNM_2 = updateData.VNDRNM_2 + if (updateData.VNDRNM_ABRV_1 !== undefined) generalUpdateData.VNDRNM_ABRV_1 = updateData.VNDRNM_ABRV_1 + if (updateData.BIZR_NO !== undefined) generalUpdateData.BIZR_NO = updateData.BIZR_NO + if (updateData.CO_REG_NO !== undefined) generalUpdateData.CO_REG_NO = updateData.CO_REG_NO + if (updateData.CO_VLM !== undefined) generalUpdateData.CO_VLM = updateData.CO_VLM + if (updateData.REPR_NM !== undefined) generalUpdateData.REPR_NM = updateData.REPR_NM + if (updateData.REP_TEL_NO !== undefined) generalUpdateData.REP_TEL_NO = updateData.REP_TEL_NO + if (updateData.REPR_RESNO !== undefined) generalUpdateData.REPR_RESNO = updateData.REPR_RESNO + if (updateData.BIZTP !== undefined) generalUpdateData.BIZTP = updateData.BIZTP + if (updateData.BIZCON !== undefined) generalUpdateData.BIZCON = updateData.BIZCON + if (updateData.NTN_CD !== undefined) generalUpdateData.NTN_CD = updateData.NTN_CD + if (updateData.ADR_1 !== undefined) generalUpdateData.ADR_1 = updateData.ADR_1 + if (updateData.ADR_2 !== undefined) generalUpdateData.ADR_2 = updateData.ADR_2 + + // 현재 시간 설정 + generalUpdateData.CHG_DT = new Date().toISOString().slice(0, 10).replace(/-/g, '') + generalUpdateData.CHG_TM = new Date().toTimeString().slice(0, 8).replace(/:/g, '') + + if (Object.keys(generalUpdateData).length > 2) { // CHG_DT, CHG_TM 외에 다른 필드가 있는 경우만 업데이트 + await tx + .update(cmctbVendorGeneral) + .set(generalUpdateData) + .where(eq(cmctbVendorGeneral.VNDRCD, vendorCode)) + } + + // 2. Address 테이블 업데이트 (있는 경우) + if (updateData.ADR_1 || updateData.ADR_2 || updateData.POSTAL_CODE || updateData.ADDR_DETAIL_1) { + const addrUpdateData: Partial<CmctbVendorAddr> = {} + if (updateData.ADR_1 !== undefined) addrUpdateData.ADR_1 = updateData.ADR_1 + if (updateData.ADR_2 !== undefined) addrUpdateData.ADR_2 = updateData.ADR_2 + if (updateData.POSTAL_CODE !== undefined) addrUpdateData.CITY_ZIP_NO = updateData.POSTAL_CODE + if (updateData.ADDR_DETAIL_1 !== undefined) addrUpdateData.ETC_ADR_1 = updateData.ADDR_DETAIL_1 + + // 주소 레코드가 있는지 확인 + const existingAddr = await tx + .select() + .from(cmctbVendorAddr) + .where(eq(cmctbVendorAddr.VNDRCD, vendorCode)) + .limit(1) + + if (existingAddr.length > 0) { + // 기존 주소 업데이트 + await tx + .update(cmctbVendorAddr) + .set(addrUpdateData) + .where(eq(cmctbVendorAddr.VNDRCD, vendorCode)) + } else { + // 새 주소 레코드 생성 + await tx + .insert(cmctbVendorAddr) + .values({ + VNDRCD: vendorCode, + ADR_NO: '0001', + INTL_ADR_VER_ID: '1', + ...addrUpdateData + }) + } + } + + // 3. 대표자 이메일 업데이트 (있는 경우) + if (updateData.REPRESENTATIVE_EMAIL !== undefined) { + // 기존 이메일 레코드가 있는지 확인 + const existingEmail = await tx + .select() + .from(cmctbVendorRepremail) + .where(eq(cmctbVendorRepremail.VNDRCD, vendorCode)) + .limit(1) + + const currentDate = new Date().toISOString().slice(0, 10).replace(/-/g, '') + + if (existingEmail.length > 0) { + // 기존 이메일 업데이트 + await tx + .update(cmctbVendorRepremail) + .set({ + EMAIL_ADR: updateData.REPRESENTATIVE_EMAIL, + IF_DT: currentDate, + IF_TM: new Date().toTimeString().slice(0, 8).replace(/:/g, ''), + IF_STAT: '1' + }) + .where(eq(cmctbVendorRepremail.VNDRCD, vendorCode)) + } else if (updateData.REPRESENTATIVE_EMAIL) { + // 새 이메일 레코드 생성 (빈 문자열이 아닌 경우만) + await tx + .insert(cmctbVendorRepremail) + .values({ + VNDRCD: vendorCode, + ADR_NO: '0001', + REPR_SER: '001', + VLD_ST_DT: currentDate, + EMAIL_ADR: updateData.REPRESENTATIVE_EMAIL, + IF_DT: currentDate, + IF_TM: new Date().toTimeString().slice(0, 8).replace(/:/g, ''), + IF_STAT: '1' + }) + } + } + }) + + return true + } catch (error) { + console.error('벤더 정보 수정 중 오류 발생:', error) + throw new Error('벤더 정보를 수정할 수 없습니다.') + } + } + + /** + * 벤더 목록 조회 (페이징) + * @param page 페이지 번호 (1부터 시작) + * @param limit 페이지당 개수 + * @param searchTerm 검색어 (업체명 검색) + * @returns 벤더 목록과 총 개수 + */ + async getVendorList( + page: number = 1, + limit: number = 20, + searchTerm?: string + ): Promise<{ vendors: VendorListItem[], total: number }> { + try { + const offset = (page - 1) * limit + + // 기본 조건: 활성 벤더만 조회 + const whereCondition = eq(cmctbVendorGeneral.DEL_ORDR, 'N') + + // 검색어가 있는 경우 업체명으로 필터링 (추후 구현 시 사용) + void searchTerm // 현재는 미사용 + + // 총 개수 조회 + const totalResult = await db + .select({ count: cmctbVendorGeneral.VNDRCD }) + .from(cmctbVendorGeneral) + .where(whereCondition) + + // 벤더 목록 조회 + const vendors = await db + .select({ + VNDRCD: cmctbVendorGeneral.VNDRCD, + VNDRNM_1: cmctbVendorGeneral.VNDRNM_1, + VNDRNM_2: cmctbVendorGeneral.VNDRNM_2, + BIZR_NO: cmctbVendorGeneral.BIZR_NO, + REG_DT: cmctbVendorGeneral.REG_DT, + DEL_ORDR: cmctbVendorGeneral.DEL_ORDR, + PUR_HOLD_ORDR: cmctbVendorGeneral.PUR_HOLD_ORDR + }) + .from(cmctbVendorGeneral) + .where(whereCondition) + .limit(limit) + .offset(offset) + + return { + vendors, + total: totalResult.length + } + } catch (error) { + console.error('벤더 목록 조회 중 오류 발생:', error) + throw new Error('벤더 목록을 조회할 수 없습니다.') + } + } + + /** + * 벤더 상태 변경 (활성/비활성) + * @param vendorCode 벤더 코드 + * @param isActive 활성 상태 여부 + * @returns 성공 여부 + */ + async updateVendorStatus(vendorCode: string, isActive: boolean): Promise<boolean> { + try { + await db + .update(cmctbVendorGeneral) + .set({ + DEL_ORDR: isActive ? 'N' : 'Y', + CHG_DT: new Date().toISOString().slice(0, 10).replace(/-/g, ''), + CHG_TM: new Date().toTimeString().slice(0, 8).replace(/:/g, '') + }) + .where(eq(cmctbVendorGeneral.VNDRCD, vendorCode)) + + return true + } catch (error) { + console.error('벤더 상태 변경 중 오류 발생:', error) + throw new Error('벤더 상태를 변경할 수 없습니다.') + } + } +} + +// 싱글톤 인스턴스 생성 +export const vendorMdgService = new VendorMdgService() |
