- clasp project bound to existing Google Sheet - syncPackageToDB: maps columns between 판매용SW and DB sheets, updates matching rows by 보여질순서 key - Handles merged 분야 column fill-down, 금액/연 calculation (단가×copy수) - Highlights updated cells in yellow - Includes listSheets utility and onOpen menu Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
201 lines
6.6 KiB
JavaScript
201 lines
6.6 KiB
JavaScript
/** 모든 시트 이름 출력 */
|
||
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();
|
||
}
|