summaryrefslogtreecommitdiff
path: root/lib/vendors
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors')
-rw-r--r--lib/vendors/mdg-actions.ts93
-rw-r--r--lib/vendors/mdg-service.ts598
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()