/** 모든 시트 이름 출력 */ function listSheets() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheets = ss.getSheets(); var names = []; for (var i = 0; i < sheets.length; i++) { names.push(sheets[i].getName()); } Logger.log("시트 목록: " + JSON.stringify(names)); SpreadsheetApp.getUi().alert("시트 목록:\n" + names.join("\n")); } /** * "판매용 SW" 시트의 Pakage 데이터(Row 7~12)를 "DB" 시트로 동기화 * - 매칭 키: 보여질순서 (A01, B01, C02 등) * - 업데이트된 셀은 노란색 배경으로 표시 * - DB의 데이터 유효성 검사가 있는 셀은 유효한 값만 업데이트 */ function syncPackageToDB() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var swSheet = ss.getSheetByName("판매용 SW"); var dbSheet = ss.getSheetByName("DB"); if (!swSheet || !dbSheet) { SpreadsheetApp.getUi().alert("시트를 찾을 수 없습니다."); return; } // 판매용 SW 시트에서 Row 7~12 Pakage 데이터 읽기 var swData = swSheet.getRange("A1:X12").getValues(); // Row 1~12만 읽기 var swRows = extractPackageRows(swData, 6, 11); // 0-indexed: Row 7=index 6, Row 12=index 11 // DB 시트 데이터 읽기 (헤더: Row 1, 데이터: Row 2~) var dbData = dbSheet.getDataRange().getValues(); // DB에서 보여질순서 → 행번호(1-indexed) 매핑 구축 var dbKeyCol = 4; // DB의 "보여질순서" 컬럼 인덱스 (0-based) var dbTypeCol = 3; // DB의 "Type" 컬럼 인덱스 var dbRowMap = {}; for (var i = 1; i < dbData.length; i++) { var key = String(dbData[i][dbKeyCol]).trim(); var type = String(dbData[i][dbTypeCol]).trim(); if (key && type === "Pakage") { dbRowMap[key] = i + 1; // 시트의 실제 행번호 (1-indexed) } } // SW → DB 컬럼 매핑 (SW컬럼인덱스 → DB컬럼인덱스) var colMap = { 0: 0, // Lv1 → Lv1 1: 1, // 배포범위 → 배포범위 2: 2, // 서비스형태 → 서비스형태 3: 4, // 보여질순서 → 보여질순서 // 4: skip (빈 컬럼) 5: 5, // 분야 → 분야 6: 6, // 명칭 → 명칭 7: 3, // Type → Type 8: 7, // 사용 목적 → 사용 목적 9: 9, // 기획팀(협업) → 기획팀(협업) 10: 8, // 기획/개발/영업 → 담당자 11: 10, // 기획 → 기획담당자 12: 11, // 개발팀 → 개발팀 13: 12, // 개발 → 개발담당자 14: 13, // 영업 → 영업담당자 15: 14, // 진행상태 → 진행상태 16: 15, // 판매단가/연 → 판매단가/연 17: 16, // copy수 → copy수 // 18: 금액/연은 별도 계산 (판매단가 × copy수) 19: 18, // 매입 or 사용료 → 매입 or 사용료 20: 19, // 프로젝트상태 → 프로젝트상태 21: 20, // 버전 → 버전 22: 21, // PM → PM 23: 22 // 참조 → 참조 }; var dbTotalCols = 23; var updatedCount = 0; var addedCount = 0; var skippedCells = []; var yellow = "#FFFF00"; for (var r = 0; r < swRows.length; r++) { var swRow = swRows[r]; var orderKey = String(swRow[3]).trim(); // 보여질순서 if (!orderKey) continue; var dbRowNum = dbRowMap[orderKey]; if (dbRowNum) { // 기존 행 업데이트 var dbRowData = dbSheet.getRange(dbRowNum, 1, 1, dbTotalCols).getValues()[0]; var updatedCells = []; for (var swCol in colMap) { var dbCol = colMap[swCol]; var newVal = swRow[parseInt(swCol)]; var oldVal = dbRowData[dbCol]; if (String(newVal).trim() !== String(oldVal).trim()) { try { dbSheet.getRange(dbRowNum, dbCol + 1).setValue(newVal); updatedCells.push(dbCol + 1); } catch (e) { skippedCells.push(orderKey + " 컬럼" + (dbCol + 1) + ": \"" + newVal + "\" → 유효성 검사 위반"); Logger.log("SKIP: " + orderKey + " DB컬럼" + (dbCol + 1) + " 값: " + newVal + " (" + e.message + ")"); } } } // 금액/연 계산 (판매단가 × copy수) var unitPrice = swRow[16] || 0; var copies = swRow[17] || 0; var calcAmount = unitPrice * copies; var dbAmountCol = 17; // DB의 금액/연 (0-indexed) if (calcAmount !== dbRowData[dbAmountCol]) { dbSheet.getRange(dbRowNum, dbAmountCol + 1).setValue(calcAmount); updatedCells.push(dbAmountCol + 1); } // 업데이트된 셀 노란색 표시 if (updatedCells.length > 0) { for (var c = 0; c < updatedCells.length; c++) { dbSheet.getRange(dbRowNum, updatedCells[c]).setBackground(yellow); } updatedCount++; } } else { // 새 행 추가 var newRow = new Array(dbTotalCols).fill(""); for (var swCol in colMap) { newRow[colMap[swCol]] = swRow[parseInt(swCol)]; } newRow[17] = (swRow[16] || 0) * (swRow[17] || 0); dbSheet.appendRow(newRow); var lastRow = dbSheet.getLastRow(); dbSheet.getRange(lastRow, 1, 1, dbTotalCols).setBackground(yellow); addedCount++; } } var msg = "동기화 완료!\n업데이트: " + updatedCount + "건\n새로 추가: " + addedCount + "건"; if (skippedCells.length > 0) { msg += "\n\n⚠ 유효성 검사로 건너뛴 셀:\n" + skippedCells.join("\n"); } Logger.log(msg); SpreadsheetApp.getUi().alert(msg); } /** * 판매용 SW 시트에서 지정 범위의 Pakage 타입 행만 추출 * 머지된 분야 컬럼의 빈값을 이전 값으로 채움 */ function extractPackageRows(swData, startIdx, endIdx) { var typeCol = 7; var fieldCol = 5; var rows = []; var lastField = ""; // startIdx 이전 행들에서 분야 값 미리 추적 (머지 대비) for (var i = 0; i < startIdx; i++) { if (String(swData[i][fieldCol]).trim()) { lastField = swData[i][fieldCol]; } } // 지정 범위 행만 처리 for (var i = startIdx; i <= endIdx && i < swData.length; i++) { var row = swData[i]; if (String(row[fieldCol]).trim()) { lastField = row[fieldCol]; } if (String(row[typeCol]).trim() === "Pakage") { var rowCopy = row.slice(); if (!String(rowCopy[fieldCol]).trim()) { rowCopy[fieldCol] = lastField; } rows.push(rowCopy); } } Logger.log("추출된 Pakage 행 수: " + rows.length); for (var j = 0; j < rows.length; j++) { Logger.log(" " + rows[j][3] + " | " + rows[j][5] + " | " + rows[j][6] + " | " + rows[j][7]); } return rows; } function onOpen() { var ui = SpreadsheetApp.getUi(); ui.createMenu("DB 동기화") .addItem("Pakage → DB 업데이트", "syncPackageToDB") .addItem("시트 목록 보기", "listSheets") .addToUi(); }