From a78f26bc52a2ff032a8103f36a2a660cefa9fb64 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Fri, 7 Nov 2025 15:29:36 +0900 Subject: (김준회) 정규벤더 등록요청 결재 연결, 공통컴포넌트(미리보기) 연결, MDG 송신 공통함수 사용처리 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/approval/approval-saga.ts | 1 + lib/approval/types.ts | 1 + .../mdg/send/vendor-master/send-single-vendor.ts | 4 +- .../approval-actions.ts | 34 ++++- lib/vendor-regular-registrations/handlers.ts | 11 +- lib/vendor-regular-registrations/service.ts | 154 +++------------------ ...regular-registrations-table-toolbar-actions.tsx | 5 + 7 files changed, 64 insertions(+), 146 deletions(-) diff --git a/lib/approval/approval-saga.ts b/lib/approval/approval-saga.ts index 0bbf7836..e04c7e24 100644 --- a/lib/approval/approval-saga.ts +++ b/lib/approval/approval-saga.ts @@ -215,6 +215,7 @@ export class ApprovalSubmissionSaga { approvalLines, { contentsType: 'HTML', // HTML 템플릿 사용 + attachments: this.approvalConfig.attachments, // 첨부파일 전달 } ); diff --git a/lib/approval/types.ts b/lib/approval/types.ts index 7f3fe52b..9b9f0d7b 100644 --- a/lib/approval/types.ts +++ b/lib/approval/types.ts @@ -15,6 +15,7 @@ export interface ApprovalConfig { epId: string | null; email?: string; }; + attachments?: File[]; // 첨부파일 (선택사항) } export interface ApprovalResult { diff --git a/lib/soap/mdg/send/vendor-master/send-single-vendor.ts b/lib/soap/mdg/send/vendor-master/send-single-vendor.ts index d10792b7..bf040b36 100644 --- a/lib/soap/mdg/send/vendor-master/send-single-vendor.ts +++ b/lib/soap/mdg/send/vendor-master/send-single-vendor.ts @@ -194,8 +194,8 @@ function mapVendorToMDGFormat( NATION: '', // 빈 문자열 (보내면 안됨) COUNTRY: vendor.country || 'KR', // 국가코드 (기본값 KR) POST_CODE1: vendor.postalCode || '00000', // 우편번호 (없으면 00000) - CITY1: vendor.addressDetail || '', // 상세주소 - STREET: vendor.address || '', // 기본주소 + CITY1: vendor.addressDetail || '상세주소 정보가 없습니다', // 상세주소 (없으면 fallback) + STREET: vendor.address || '주소 정보가 없습니다', // 기본주소 (없으면 fallback) // === 연락처 정보 === TEL_NUMBER: vendor.phone || '', // 전화번호 diff --git a/lib/vendor-regular-registrations/approval-actions.ts b/lib/vendor-regular-registrations/approval-actions.ts index b90ef2b6..2bd5ec0f 100644 --- a/lib/vendor-regular-registrations/approval-actions.ts +++ b/lib/vendor-regular-registrations/approval-actions.ts @@ -14,6 +14,7 @@ import type { RegistrationRequestData } from '@/components/vendor-regular-regist import db from '@/db/db'; import { eq } from 'drizzle-orm'; import { vendorRegularRegistrations } from '@/db/schema/vendorRegistrations'; +import { users } from '@/db/schema'; /** * 결재를 거쳐 정규업체 등록을 처리하는 서버 액션 @@ -24,7 +25,8 @@ import { vendorRegularRegistrations } from '@/db/schema/vendorRegistrations'; * registrationId: 123, * requestData: registrationData, * currentUser: { id: 1, epId: 'EP001', email: 'user@example.com' }, - * approvers: ['EP002', 'EP003'] + * approvers: ['EP002', 'EP003'], + * attachments: [file1, file2] // 선택사항 * }); * * if (result.status === 'pending_approval') { @@ -38,6 +40,8 @@ export async function registerVendorWithApproval(data: { vendorId?: number; // vendors 테이블에서 정보를 가져오기 위한 vendorId currentUser: { id: number; epId: string | null; email?: string }; approvers?: string[]; // Knox EP ID 배열 (결재선) + attachments?: File[]; // 첨부파일 (선택사항) + title?: string; // 결재 제목 (선택사항, 미지정 시 자동 생성) }) { debugLog('[VendorRegistrationApproval] 정규업체 등록 결재 서버 액션 시작', { registrationId: data.registrationId, @@ -58,7 +62,16 @@ export async function registerVendorWithApproval(data: { throw new Error('등록 ID가 필요합니다'); } - // 1. 템플릿 변수 매핑 + // 1. 유저의 nonsapUserId 조회 (Cronjob 환경을 위해) + debugLog('[VendorRegistrationApproval] nonsapUserId 조회'); + const userResult = await db.query.users.findFirst({ + where: eq(users.id, data.currentUser.id), + columns: { nonsapUserId: true } + }); + const nonsapUserId = userResult?.nonsapUserId || null; + debugLog('[VendorRegistrationApproval] nonsapUserId 조회 완료', { nonsapUserId }); + + // 2. 템플릿 변수 매핑 debugLog('[VendorRegistrationApproval] 템플릿 변수 매핑 시작'); const requestedAt = new Date(); const variables = await mapRegistrationToTemplateVariables({ @@ -70,8 +83,11 @@ export async function registerVendorWithApproval(data: { variableKeys: Object.keys(variables), }); - // 2. 결재 워크플로우 시작 (Saga 패턴) - debugLog('[VendorRegistrationApproval] ApprovalSubmissionSaga 생성'); + // 3. 결재 워크플로우 시작 (Saga 패턴) + debugLog('[VendorRegistrationApproval] ApprovalSubmissionSaga 생성', { + hasAttachments: !!data.attachments, + attachmentCount: data.attachments?.length || 0, + }); const saga = new ApprovalSubmissionSaga( // actionType: 핸들러를 찾을 때 사용할 키 'vendor_regular_registration', @@ -80,23 +96,29 @@ export async function registerVendorWithApproval(data: { { registrationId: data.registrationId, requestData: data.requestData, + currentUser: { // ⚠️ Cronjob 환경을 위한 유저 정보 포함 (headers() 대신) + id: data.currentUser.id, + email: data.currentUser.email, + nonsapUserId: nonsapUserId, // ⚠️ 미리 조회한 nonsapUserId 전달 + }, }, // approvalConfig: 결재 상신 정보 (템플릿 포함) { - title: `정규업체 등록 - ${data.requestData.companyNameKor}`, + title: data.title || `정규업체 등록 - ${data.requestData.companyNameKor}`, description: `${data.requestData.companyNameKor} 정규업체 등록 요청`, templateName: '정규업체 등록', // 한국어 템플릿명 variables, // 치환할 변수들 approvers: data.approvers, currentUser: data.currentUser, + attachments: data.attachments, // 첨부파일 전달 } ); debugLog('[VendorRegistrationApproval] Saga 실행 시작'); const result = await saga.execute(); - // 3. 결재 상신 성공 시 상태를 pending_approval로 변경 + // 4. 결재 상신 성공 시 상태를 pending_approval로 변경 if (result.status === 'pending_approval') { debugLog('[VendorRegistrationApproval] 상태를 pending_approval로 변경'); await db.update(vendorRegularRegistrations) diff --git a/lib/vendor-regular-registrations/handlers.ts b/lib/vendor-regular-registrations/handlers.ts index 7490d81a..262d37ab 100644 --- a/lib/vendor-regular-registrations/handlers.ts +++ b/lib/vendor-regular-registrations/handlers.ts @@ -25,18 +25,25 @@ import { vendorAdditionalInfo, vendorRegularRegistrations } from '@/db/schema/ve export async function registerVendorInternal(payload: { registrationId: number; requestData: RegistrationRequestData; + currentUser?: { // Cronjob 환경을 위한 유저 정보 + id: string | number; + name?: string | null; + email?: string | null; + nonsapUserId?: string | null; + }; }) { debugLog('[VendorRegistrationHandler] 정규업체 등록 핸들러 시작', { registrationId: payload.registrationId, companyName: payload.requestData.companyNameKor, + hasCurrentUser: !!payload.currentUser, }); try { // 1. MDG로 정규업체 등록 요청 데이터 전송 + // sendSingleVendorToMDG 함수를 사용하여 vendors 테이블에서 데이터를 자동으로 조회 debugLog('[VendorRegistrationHandler] sendRegistrationRequestToMDG 호출'); const mdgResult = await sendRegistrationRequestToMDG( - payload.registrationId, - payload.requestData + payload.registrationId ); if (!mdgResult.success) { diff --git a/lib/vendor-regular-registrations/service.ts b/lib/vendor-regular-registrations/service.ts index 2a6695fa..ae6ba2a2 100644 --- a/lib/vendor-regular-registrations/service.ts +++ b/lib/vendor-regular-registrations/service.ts @@ -11,7 +11,6 @@ import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { headers } from "next/headers"; import { sendEmail } from "@/lib/mail/sendEmail"; -import type { RegistrationRequestData } from "@/components/vendor-regular-registrations/registration-request-dialog"; import { vendors, vendorRegularRegistrations, @@ -22,12 +21,11 @@ import { vendorPQSubmissions, vendorBusinessContacts, vendorAdditionalInfo, - basicContractTemplates, - users + basicContractTemplates } from "@/db/schema"; import db from "@/db/db"; import { inArray, eq, desc, and, lt } from "drizzle-orm"; -import { sendTestVendorDataToMDG } from "@/lib/soap/mdg/send/vendor-master/action"; +import { sendSingleVendorToMDG } from "@/lib/soap/mdg/send/vendor-master/send-single-vendor"; // 3개월 이상 정규등록검토 상태인 등록을 장기미등록으로 변경 async function updatePendingApprovals() { @@ -996,21 +994,11 @@ export async function fetchRegistrationRequestData(registrationId: number) { // MDG로 정규업체 등록 요청 데이터를 보내는 함수 export async function sendRegistrationRequestToMDG( - registrationId: number, - requestData: RegistrationRequestData + registrationId: number ) { try { console.log('🚀 MDG로 정규업체 등록 요청 데이터 송신 시작'); - // 세션 사용자 정보 가져오기 - const session = await getServerSession(authOptions); - const userId = session?.user?.id || 'EVCP_USER'; - //users table에서 userid로 nonsapuserid 찾기 - const userResult = await db.query.users.findFirst({ - where: eq(users.id, Number(userId)), - columns: { nonsapUserId: true } - }); - const nonsapUserId = userResult?.nonsapUserId || 'EVCP_USER'; // 등록 정보 조회 const registration = await db .select() @@ -1022,134 +1010,28 @@ export async function sendRegistrationRequestToMDG( return { success: false, error: "등록 정보를 찾을 수 없습니다." }; } - // registration[0].vendorId를 이용해 벤더 정보 조회 - const vendor = await db - .select() - .from(vendors) - .where(eq(vendors.id, registration[0].vendorId)) - .limit(1); - - if (!vendor[0]) { - return { success: false, error: "벤더 정보를 찾을 수 없습니다." }; - } - - // MDG 필수 필드 매핑 (이메일 내용 기반) - const mdgData: Record = { - // 1. BP_HEADER: 벤더코드가 있으면 벤더코드를, 없으면 eVCP에서 관리번호를 보내드리겠습니다. (필수) - BP_HEADER: vendor[0].vendorCode || vendor[0].id.toString(), - - // 2. ZZSRMCD: eVCP에서 내부관리번호를 보내드리겠습니다. (필수) - ZZSRMCD: vendor[0].id.toString(), - - // 3. SORT1: 벤더명 보내드립니다. (필수) - SORT1: requestData.companyNameKor, - - // 4. NAME1: 벤더명 보내드립니다. (필수) - NAME1: requestData.companyNameKor, - - // 5. NAME2: 벤더 영문명 (있는 경우) (선택) - NAME2: requestData.companyNameEng || '', - - // 6. KTOKK: 셈플로 받은자료에는 "LIEF"로 되어 있습니다. 고정값 (필수) - KTOKK: 'LIEF', - - // 7. J_1KFREPRE: 대표자명 (필수) - J_1KFREPRE: requestData.representativeNameKor, - - // 8. MASTERFLAG: 지시자 같은데, 어떤값을 보내면 되나요? -> V (필수) - MASTERFLAG: 'V', - - // 9. IBND_TYPE: 입력가능한 값을 알려주시기 바랍니다. -> 생성 : I, 변경: U (필수) - IBND_TYPE: 'I', - - // 10. ZZREQID: SAP의 USER ID를 보내드리겠습니다. (필수) - ZZREQID: nonsapUserId, - - // 11. ADDRNO: I/F정의서에는 필수입력으로 되어 있습니다. -> 빈값으로 처리 (필수) - ADDRNO: '', - - // 12. COUNTRY: ISO 3166-1의 규약에 따른 국가코드를 보내드릴 예정입니다. (필수) - COUNTRY: vendor[0].country || 'KR', - - // 13. POST_CODE1: 우편번호를 송부하겠습니다. (필수) - POST_CODE1: vendor[0].postalCode || '', - - // 14. CITY1: 상세 주소를 송부하겠습니다. (필수) - 샘플에서는 상세주소가 들어감 - CITY1: vendor[0].addressDetail || '', - - // 15. STREET: 주소를 송부하겠습니다. (필수) - 샘플에서는 기본주소가 들어감 - STREET: vendor[0].address || '', - - // 16. TEL_NUMBER: 전화번호 (필수) - TEL_NUMBER: vendor[0].phone || '', - - // 17. R3_USER: 전화/휴대폰 구분자로 해석됩니다. 0이면 전화, 1이면 휴대폰 (필수) - R3_USER: '0', // 일반 전화번호로 가정 - - // 18. TAXTYPE: 국가 코드에 맞게 하면 됩니다. 국가 KR -> TAXTYPE KR2 (필수) - TAXTYPE: (vendor[0].country || 'KR') + '2', - - // 19. TAXNUM: 사업자번호 (필수) - TAXNUM: vendor[0].taxId || '', - - // 20. BP_TX_TYP: 대표자 주민번호 YYMMDD + 0000000 (YYMMDD0000000) - 대표자 생년월일 기준으로 생성 - BP_TX_TYP: requestData.representativeBirthDate ? - requestData.representativeBirthDate.replace(/-/g, '') + '0000000' : '', - - // 21. STCD3: 법인등록번호 (선택) - STCD3: requestData.corporateNumber || '', - - // 22. CONSNUMBER: 순번 (샘플에서는 1, 2로 설정됨) - CONSNUMBER: '1', - - // 23. ZZIND03: 기업규모 (A,B,C,D 값을 넣는 것으로 알고 있습니다.) (선택) - ZZIND03: 'B', // 기본값으로 B 설정 - - // 24. J_1KFTBUS: 사업유형 (샘플에서는 "건설업외") - J_1KFTBUS: '', - - // 25. J_1KFTIND: 산업유형 (샘플에서는 "제조업") - J_1KFTIND: '', - - // 26. SMTP_ADDR: 대표 이메일 주소 (필수) - SMTP_ADDR: requestData.representativeEmail || vendor[0].email || '', - - // 27. URI_ADDR: 웹사이트 주소 (선택) - URI_ADDR: vendor[0].website || '', - - // 28. ZZCNAME1: 해당 벤더의 첫번째 유저의 이름 (영문) (선택) - ZZCNAME1: requestData.representativeNameEng || '', - - // 29. ZZCNAME2: 해당 벤더의 첫번째 유저의 이름 (한글) (선택) - ZZCNAME2: requestData.representativeNameKor || '', - - // 30. ZZTELF1_C: 해당 벤더의 첫번째 유저의 전화번호 (선택) - ZZTELF1_C: requestData.representativeContact || '', - }; - - // MDG로 데이터 전송 - console.log('📤 MDG 송신 데이터:', mdgData); - const result = await sendTestVendorDataToMDG(mdgData); + // sendSingleVendorToMDG 함수를 사용하여 MDG로 전송 + // 정규 벤더 모드로 전송 + console.log('📤 sendSingleVendorToMDG 호출 (정규 벤더 모드)'); + const result = await sendSingleVendorToMDG({ + vendorId: registration[0].vendorId, + mode: 'REGULAR_VENDOR' + }); console.log('📤 MDG 송신 결과:', result); if (!result.success) { - // 필수 필드 누락 에러인 경우 더 자세한 메시지 제공 - if (result.message.includes('필수 필드가 누락되었습니다')) { - return { - success: false, - error: `MDG 송신 실패: ${result.message}\n\n누락된 필수 필드들을 확인하고 다시 시도해주세요.` - }; - } + return { + success: false, + error: `MDG 송신 실패: ${result.message}` + }; } return { - success: result.success, - message: result.success ? - 'MDG로 정규업체 등록 요청이 성공적으로 전송되었습니다.' : - `MDG 송신 실패: ${result.message}`, - responseData: result.responseData, - generatedXML: result.generatedXML + success: true, + message: 'MDG로 정규업체 등록 요청이 성공적으로 전송되었습니다.', + responseData: result.responseText, + generatedXML: result.requestXml }; } catch (error) { diff --git a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx index f879f065..20cd3427 100644 --- a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx +++ b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx @@ -218,6 +218,8 @@ export function VendorRegularRegistrationsTableToolbarActions({ email: session.user.email || undefined, }, approvers: approvers, + attachments: attachments, // 첨부파일 전달 + title: title, // 미리보기에서 수정한 제목 전달 }); if (result.status === 'pending_approval') { @@ -322,6 +324,9 @@ export function VendorRegularRegistrationsTableToolbarActions({ email: session.user.email || undefined, }} onConfirm={handleApprovalSubmit} + enableAttachments={true} + maxAttachments={10} + maxFileSize={100 * 1024 * 1024} // 100MB /> )} -- cgit v1.2.3