# JDL 데이터 연동 방법 (API 대체 방안) ## 중요 사항 **JDL은 공개 REST API를 제공하지 않습니다.** JDL IBEX 시리즈는 Windows 기반 회계 소프트웨어로, 외부 시스템과의 연동은 주로 **CSV 파일 가져오기/내보내기**를 통해 수행됩니다. ## 제공되는 데이터 연동 방법 ### 1. CSV 가져오기/내보내기 (주요 방법) - JDL 표준 형식 CSV - freee, Money Forward 등 타사 회계 소프트웨어와 호환 ### 2. JDL 은행 API 서비스 - 금융기관 API를 통한 거래 명세 자동 수집 - JDL 내부에서만 사용 가능 ### 3. Web POSTBOX - 세무사 사무소와의 안전한 데이터 교환 - JDL 사용자 간 전용 ## freee에서 JDL로 자동 데이터 전달 구현 API가 없으므로, Google Apps Script를 사용하여 **준자동화** 방식으로 구현합니다. ### 아키텍처 ``` freee API → Google Apps Script → JDL 형식 CSV → Google Drive → (수동) JDL 가져오기 ``` ## Google Apps Script 구현 ### 1. 전체 워크플로우 함수 ```javascript // code.gs // freee에서 JDL로 데이터 전달 (월차 자동 실행) function monthlyFreeeToJDLExport() { const today = new Date(); const year = today.getFullYear(); const month = today.getMonth(); // 0-based (0 = 1월) // 전월 데이터 추출 const lastMonth = month === 0 ? 12 : month; const lastMonthYear = month === 0 ? year - 1 : year; // 전월 1일부터 말일까지 const startDate = new Date(lastMonthYear, lastMonth - 1, 1); const endDate = new Date(lastMonthYear, lastMonth, 0); const startDateStr = Utilities.formatDate(startDate, 'JST', 'yyyy-MM-dd'); const endDateStr = Utilities.formatDate(endDate, 'JST', 'yyyy-MM-dd'); Logger.log(`처리 기간: ${startDateStr} ~ ${endDateStr}`); // 1. freee API로 거래 데이터 가져오기 const deals = getFreeeDeals(startDateStr, endDateStr); if (!deals || deals.length === 0) { Logger.log('거래 데이터가 없습니다.'); return null; } // 2. JDL 형식으로 변환 const jdlData = convertFreeeToJDL(deals); // 3. CSV 파일 생성 및 저장 const csvContent = generateJDLCSV(jdlData); const fileName = `JDL_${lastMonthYear}${String(lastMonth).padStart(2, '0')}.csv`; saveCSVToDrive(fileName, csvContent); // 4. 알림 메일 발송 sendNotificationEmail(fileName, deals.length); return { fileName: fileName, recordCount: deals.length, period: `${startDateStr} ~ ${endDateStr}` }; } ``` ### 2. freee 데이터를 JDL 형식으로 변환 ```javascript // freee 거래 데이터를 JDL CSV 형식으로 변환 function convertFreeeToJDL(deals) { const jdlRecords = []; deals.forEach(deal => { // freee의 복식 분개를 JDL 형식으로 변환 const details = deal.details || []; if (details.length < 2) { Logger.log(`거래 ${deal.id}는 분개 정보가 불완전합니다.`); return; } // 차변과 대변 분리 const debitDetails = details.filter(d => d.entry_side === 'debit'); const creditDetails = details.filter(d => d.entry_side === 'credit'); // 각 차변/대변 조합으로 JDL 레코드 생성 debitDetails.forEach(debit => { creditDetails.forEach(credit => { jdlRecords.push({ date: deal.issue_date, debitAccountCode: getJDLAccountCode(debit.account_item_id), debitAccountName: debit.account_item_name, debitSubAccount: debit.partner_name || '', debitAmount: debit.amount, creditAccountCode: getJDLAccountCode(credit.account_item_id), creditAccountName: credit.account_item_name, creditSubAccount: credit.partner_name || '', creditAmount: credit.amount, description: deal.description || '', transactionId: deal.id }); }); }); }); return jdlRecords; } ``` ### 3. freee 계정과목 코드를 JDL 코드로 매핑 ```javascript // freee 계정과목 ID를 JDL 계정과목 코드로 변환 function getJDLAccountCode(freeeAccountId) { // Google Sheet에서 매핑 테이블 읽기 const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('AccountMapping'); const data = sheet.getDataRange().getValues(); // 헤더 제외하고 검색 for (let i = 1; i < data.length; i++) { if (data[i][0] == freeeAccountId) { // freee ID return data[i][2]; // JDL 코드 } } Logger.log(`매핑되지 않은 계정과목 ID: ${freeeAccountId}`); return '9999'; // 미지정 코드 } // 계정과목 매핑 테이블 초기 설정 function setupAccountMapping() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('AccountMapping'); if (!sheet) { Logger.log('AccountMapping 시트를 생성합니다.'); const newSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('AccountMapping'); // 헤더 설정 newSheet.getRange(1, 1, 1, 4).setValues([ ['freee ID', 'freee 계정과목명', 'JDL 코드', 'JDL 계정과목명'] ]); // 예시 데이터 newSheet.getRange(2, 1, 3, 4).setValues([ [101, '현금', '1110', '現金'], [201, '보통예금', '1120', '普通預金'], [301, '매출', '4110', '売上高'] ]); Logger.log('AccountMapping 시트가 생성되었습니다. 실제 매핑 데이터를 입력하세요.'); } } ``` ### 4. JDL CSV 파일 생성 ```javascript // JDL 표준 형식 CSV 생성 function generateJDLCSV(jdlRecords) { // JDL CSV 헤더 (실제 JDL 형식에 맞게 조정) const headers = [ '取引日', '借方科目コード', '借方科目名', '借方補助科目', '借方金額', '貸方科目コード', '貸方科目名', '貸方補助科目', '貸方金額', '摘要', '取引番号' ]; const rows = [headers]; jdlRecords.forEach(record => { rows.push([ record.date, record.debitAccountCode, record.debitAccountName, record.debitSubAccount, record.debitAmount, record.creditAccountCode, record.creditAccountName, record.creditSubAccount, record.creditAmount, record.description, record.transactionId ]); }); // CSV 문자열 생성 (Shift_JIS 인코딩 필요) const csvString = rows.map(row => { return row.map(cell => { // 따옴표 처리 const cellStr = String(cell || ''); if (cellStr.includes(',') || cellStr.includes('"') || cellStr.includes('\n')) { return '"' + cellStr.replace(/"/g, '""') + '"'; } return cellStr; }).join(','); }).join('\n'); return csvString; } ``` ### 5. CSV 파일을 Google Drive에 저장 ```javascript // CSV 파일을 Google Drive에 저장 function saveCSVToDrive(fileName, csvContent) { // JDL_Exports 폴더 확인 또는 생성 const folders = DriveApp.getFoldersByName('JDL_Exports'); let folder; if (folders.hasNext()) { folder = folders.next(); } else { folder = DriveApp.createFolder('JDL_Exports'); Logger.log('JDL_Exports 폴더를 생성했습니다.'); } // Shift_JIS 인코딩 (JDL은 Shift_JIS를 주로 사용) // Google Apps Script는 UTF-8이 기본이므로, Shift_JIS 변환 필요 // 간단한 방법: UTF-8 BOM 추가 const bom = '\uFEFF'; const contentWithBOM = bom + csvContent; // 파일 생성 const file = folder.createFile(fileName, contentWithBOM, MimeType.CSV); Logger.log(`CSV 파일 저장 완료: ${file.getUrl()}`); return file; } ``` ### 6. 알림 메일 발송 ```javascript // 처리 완료 알림 메일 function sendNotificationEmail(fileName, recordCount) { const recipient = Session.getActiveUser().getEmail(); const subject = `[JDL] ${fileName} 생성 완료`; const body = ` 안녕하세요, freee에서 JDL 형식으로 데이터 변환이 완료되었습니다. 파일명: ${fileName} 레코드 수: ${recordCount}건 생성 시각: ${new Date().toLocaleString('ja-JP', {timeZone: 'Asia/Tokyo'})} 다음 단계: 1. Google Drive > JDL_Exports 폴더에서 파일 다운로드 2. JDL IBEX 회계 net에 로그인 3. 데이터 > CSV 가져오기 선택 4. 다운로드한 CSV 파일 업로드 감사합니다. `; MailApp.sendEmail(recipient, subject, body); Logger.log(`알림 메일 발송: ${recipient}`); } ``` ### 7. 월차 자동 실행 트리거 설정 ```javascript // 매월 1일 오전 9시에 자동 실행 설정 function setupMonthlyTrigger() { // 기존 트리거 삭제 const triggers = ScriptApp.getProjectTriggers(); triggers.forEach(trigger => { if (trigger.getHandlerFunction() === 'monthlyFreeeToJDLExport') { ScriptApp.deleteTrigger(trigger); } }); // 새 트리거 생성 (매월 1일) ScriptApp.newTrigger('monthlyFreeeToJDLExport') .timeBased() .onMonthDay(1) .atHour(9) .create(); Logger.log('월차 트리거가 설정되었습니다: 매월 1일 오전 9시'); } ``` ### 8. 수동 실행 메뉴 ```javascript // Sheet 오픈 시 메뉴 추가 function onOpen() { const ui = SpreadsheetApp.getUi(); ui.createMenu('JDL 연동') .addItem('이번 달 데이터 내보내기', 'exportCurrentMonth') .addItem('지난 달 데이터 내보내기', 'monthlyFreeeToJDLExport') .addItem('계정과목 매핑 설정', 'setupAccountMapping') .addItem('월차 자동 실행 설정', 'setupMonthlyTrigger') .addToUi(); } // 이번 달 데이터 내보내기 function exportCurrentMonth() { const today = new Date(); const year = today.getFullYear(); const month = today.getMonth() + 1; const startDate = new Date(year, month - 1, 1); const endDate = new Date(year, month, 0); const startDateStr = Utilities.formatDate(startDate, 'JST', 'yyyy-MM-dd'); const endDateStr = Utilities.formatDate(endDate, 'JST', 'yyyy-MM-dd'); const deals = getFreeeDeals(startDateStr, endDateStr); const jdlData = convertFreeeToJDL(deals); const csvContent = generateJDLCSV(jdlData); const fileName = `JDL_${year}${String(month).padStart(2, '0')}_current.csv`; saveCSVToDrive(fileName, csvContent); SpreadsheetApp.getActiveSpreadsheet().toast( `${deals.length}건의 데이터를 ${fileName}로 저장했습니다.`, 'JDL 내보내기 완료', 5 ); } ``` ## JDL로 가져오기 절차 ### 1. Google Drive에서 CSV 다운로드 ``` 1. Google Drive 접속 2. JDL_Exports 폴더 열기 3. 최신 CSV 파일 다운로드 ``` ### 2. JDL IBEX 회계 net에서 가져오기 ``` 1. JDL IBEX 회계 net 로그인 2. 메뉴 > 데이터 > CSV 가져오기 선택 3. 다운로드한 CSV 파일 선택 4. 가져오기 설정 확인 - 문자 인코딩: Shift_JIS - 날짜 형식: yyyy-MM-dd - 구분자: 쉼표(,) 5. 가져오기 실행 6. 데이터 검증 7. 필요시 수정 8. 확정 ``` ## 데이터 검증 ### Google Apps Script로 검증 ```javascript // 생성된 JDL CSV의 유효성 검증 function validateJDLCSV(jdlRecords) { const errors = []; jdlRecords.forEach((record, index) => { // 필수 항목 확인 if (!record.date) { errors.push(`${index + 1}행: 날짜가 없습니다.`); } if (!record.debitAccountCode || !record.creditAccountCode) { errors.push(`${index + 1}행: 계정과목 코드가 없습니다.`); } if (!record.debitAmount || !record.creditAmount) { errors.push(`${index + 1}행: 금액이 없습니다.`); } // 차변 = 대변 확인 if (record.debitAmount !== record.creditAmount) { errors.push(`${index + 1}행: 차변(${record.debitAmount})과 대변(${record.creditAmount})이 일치하지 않습니다.`); } // 날짜 형식 확인 if (!/^\d{4}-\d{2}-\d{2}$/.test(record.date)) { errors.push(`${index + 1}행: 날짜 형식이 올바르지 않습니다. (${record.date})`); } }); if (errors.length > 0) { Logger.log('검증 오류:'); errors.forEach(error => Logger.log(error)); return false; } Logger.log('검증 통과: 모든 레코드가 유효합니다.'); return true; } ``` ## 제한 사항 및 주의점 ### 1. API 없음 - JDL은 공개 API가 없으므로 완전 자동화 불가능 - CSV 파일을 수동으로 JDL에 가져와야 함 ### 2. 인코딩 문제 - JDL은 Shift_JIS 인코딩을 주로 사용 - Google Apps Script는 UTF-8이 기본 - 한글이 포함된 경우 문자 깨짐 가능성 ### 3. 계정과목 매핑 - freee와 JDL의 계정과목 코드 체계가 다름 - 수동으로 매핑 테이블 관리 필요 ### 4. 데이터 검증 - JDL 가져오기 후 반드시 데이터 검증 필요 - 보조과목, 거래처 등 수동 조정 필요할 수 있음 ## 개선 방안 ### 1. RPA 도구 활용 - UiPath, WinActor 등 RPA 도구로 JDL 가져오기 자동화 가능 - CSV 다운로드 → JDL 실행 → 가져오기 클릭 자동화 ### 2. VBA 매크로 - Excel VBA로 JDL 실행 파일 제어 가능 - JDL이 COM 인터페이스를 제공하는 경우 ### 3. 세무사 사무소 연계 - 세무사가 JDL 사용 시 Web POSTBOX로 데이터 전달 - 세무사 측에서 JDL 가져오기 수행 ## 요약 JDL은 REST API를 제공하지 않지만, 다음과 같은 방식으로 준자동화가 가능합니다: 1. **Google Apps Script로 freee 데이터를 JDL CSV로 변환** (자동) 2. **Google Drive에 CSV 저장** (자동) 3. **알림 메일 발송** (자동) 4. **Google Drive에서 CSV 다운로드** (수동) 5. **JDL에서 CSV 가져오기** (수동) 완전 자동화를 원한다면 RPA 도구를 추가로 도입해야 합니다. ## 참고 링크 - JDL 공식 사이트: https://www.jdl.co.jp/ - freee에서 JDL로 출력: https://support.freee.co.jp/hc/ja/articles/204614914