교육과정 부장님 필독! 3일 걸릴 검증 업무, 30분 만에 끝내는 법

카테고리
교육과정 상담 , 진로/진학/입시 담당보직업무 , 작성/관리 노하우
등록일자
2025-12-15 21:57
태그
바이브코딩 , 교육과정 , 과목선택지도
파일타입
문서 1개, 영상 1개
리뷰평점
( /5 )

10,000원

미리보기

상세내용

"선생님, 저 이거 못 들으면 졸업 못해요? 😭"

이런 말 나오지 않게 미리미리 챙겨야죠! 고등학교 교육과정 편성의 구세주, [과목 선택 검증 프로그램] 등판! 🦸‍♂️

수백 명 학생의 과목 선택, 일일이 엑셀 눈빠지게 검토하고 계신가요? 기초 교과 총점, 탐구 영역 필수 이수, 위계성 위반... 🤯 하나라도 놓치면 큰일 나는 교육과정 검증, 이제 프로그램에 맡기세요.

 

🛡️ 완벽한 검증 시스템

졸업 요건 자동 체크: 필수 이수 단위 부족 여부 즉시 확인

규정 위반 알림: 위계성, 영역별 선택 제한 등 복잡한 조건 완벽 필터링

클릭 한 번으로 끝: 학생 데이터만 넣으면 검증 결과가 1초 만에!

 

[구글 앱스크립트 코드 pdf에서 복사 안되면 아래에서 복사해서 쓰세요.]

/**
 * Google Apps Script for Curriculum Planner Backend
 * Updated with deleteResponse capability
 */

function setup() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  if (!ss.getSheetByName('Config')) ss.insertSheet('Config');
  if (!ss.getSheetByName('Settings')) ss.insertSheet('Settings');
  if (!ss.getSheetByName('Responses')) ss.insertSheet('Responses');
  if (!ss.getSheetByName('Registry')) ss.insertSheet('Registry');
  if (ss.getSheetByName('Sheet1')) ss.deleteSheet(ss.getSheetByName('Sheet1'));
}

function doGet(e) {
  const action = e.parameter.action;
  
  if (action === 'getConfig') {
    return getConfig();
  } else if (action === 'getResponses') {
    return getResponses();
  } else if (action === 'getSettings') {
    return getSettings();
  } else if (action === 'getRegistry') {
    return getRegistry();
  }
  
  return ContentService.createTextOutput(JSON.stringify({ status: 'error', message: 'Invalid action' }))
    .setMimeType(ContentService.MimeType.JSON);
}

function doPost(e) {
  try {
    const request = JSON.parse(e.postData.contents);
    const action = request.action;
    const data = request.data;
    
    if (action === 'saveConfig') {
      return saveConfig(data);
    } else if (action === 'submitResponse') {
      return submitResponse(data);
    } else if (action === 'saveSettings') {
      return saveSettings(data);
    } else if (action === 'saveRegistry') {
      return saveRegistry(data);
    } else if (action === 'deleteResponse') { // [NEW] Delete Handler
      return deleteResponse(data);
    }
    
    return createJSONOutput({ status: 'error', message: 'Invalid action' });
  } catch (error) {
    return createJSONOutput({ status: 'error', message: error.toString() });
  }
}

function getConfig() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Config');
  const data = sheet.getDataRange().getValues();
  
  if (data.length <= 1) return createJSONOutput([]);
  
  const headers = data[0];
  const rows = data.slice(1);
  const result = rows.map(row => {
    const obj = {};
    headers.forEach((header, index) => {
      obj[header] = row[index];
    });
    return obj;
  });
  
  return createJSONOutput(result);
}

function saveConfig(data) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Config');
  sheet.clear();
  
  if (!data || data.length === 0) return createJSONOutput({ status: 'success' });
  
  const headers = Object.keys(data[0]);
  sheet.appendRow(headers);
  
  const rows = data.map(obj => headers.map(header => obj[header]));
  // Batch write for performance
  if (rows.length > 0) {
    sheet.getRange(2, 1, rows.length, headers.length).setValues(rows);
  }
  
  return createJSONOutput({ status: 'success' });
}

function submitResponse(data) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Responses');
  
  // If sheet is empty, add headers
  if (sheet.getLastRow() === 0) {
    const headers = ['Timestamp', 'Grade', 'Class', 'Number', 'Name', 'Major', 'SelectedCourses', 'JointCourses', 'TotalCredits', 'ValidationResult', 'AiRecommendation'];
    sheet.appendRow(headers);
  } else {
    // Check if new headers exist, if not add them
    const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    const missingHeaders = [];
    if (!headers.includes('ValidationResult')) missingHeaders.push('ValidationResult');
    if (!headers.includes('AiRecommendation')) missingHeaders.push('AiRecommendation');
    
    if (missingHeaders.length > 0) {
      // Append missing headers
      sheet.getRange(1, headers.length + 1, 1, missingHeaders.length).setValues([missingHeaders]);
    }
  }
  
  // Re-fetch headers to ensure correct mapping
  const currentHeaders = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
  const row = [];
  
  // Construct row based on headers
  currentHeaders.forEach(header => {
    if (header === 'Timestamp') row.push(new Date());
    else if (header === 'Grade') row.push(data.Grade || data.grade);
    else if (header === 'Class') row.push(data.Class || data.classNum);
    else if (header === 'Number') row.push(data.Number || data.studentNum);
    else if (header === 'Name') row.push(data.Name || data.name);
    else if (header === 'Major') row.push(data.Major || data.major);
    else if (header === 'SelectedCourses') row.push(data.SelectedCourses || data.selectedCourses);
    else if (header === 'JointCourses') row.push(Array.isArray(data.JointCourses || data.jointCourses) ? (data.JointCourses || data.jointCourses).map(c => c.subjectName).join(', ') : (data.JointCourses || data.jointCourses || ''));
    else if (header === 'TotalCredits') row.push(data.TotalCredits || data.totalCredits);
    else if (header === 'ValidationResult') row.push(data.ValidationResult || data.validationResult || '');
    else if (header === 'AiRecommendation') row.push(data.AiRecommendation || data.aiRecommendation || '');
    else row.push(''); // Unknown header placeholder
  });
  
  sheet.appendRow(row);
  
  return createJSONOutput({ status: 'success' });
}

// [NEW] Delete Response Function
function deleteResponse(idsToDelete) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Responses');
  
  // If empty, nothing to delete
  if (sheet.getLastRow() <= 1) return createJSONOutput({ status: 'success', message: 'No data to delete' });

  const data = sheet.getDataRange().getValues();
  let rowsDeleted = 0;
  
  // Iterate backwards to safely delete rows
  // Skipping header (index 0), so stopping at i=1
  for (let i = data.length - 1; i >= 1; i--) {
    const row = data[i];
    
    // Column Index Assumptions based on submitResponse:
    // 0: Timestamp (Date object)
    // 1: Grade, 2: Class, 3: Number
    
    const timestamp = row[0];
    
    // Determine unique ID for this row to compare with idsToDelete
    // 1. Try Timestamp match
    let timestampStr = String(timestamp);
    let isoStr = "";
    try { isoStr = timestamp.toISOString(); } catch(e) {}
    
    // 2. Try Grade-Class-Number match (fallback)
    const uniqueId = row[1] + '-' + row[2] + '-' + row[3];
    
    // Check if this row matches any ID in the deletion list
    if (idsToDelete.includes(timestamp) || 
        idsToDelete.includes(timestampStr) || 
        (isoStr && idsToDelete.includes(isoStr)) || 
        idsToDelete.includes(uniqueId)) {
          
      sheet.deleteRow(i + 1); // deleteRow uses 1-based index
      rowsDeleted++;
    }
  }
  
  return createJSONOutput({ status: 'success', message: rowsDeleted + ' rows deleted' });
}

function getResponses() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Responses');
  const data = sheet.getDataRange().getValues();
  
  if (data.length <= 1) return createJSONOutput([]);
  
  const headers = data[0];
  const rows = data.slice(1);
  const result = rows.map(row => {
    const obj = {};
    headers.forEach((header, index) => {
      obj[header] = row[index];
    });
    return obj;
  });
  
  return createJSONOutput(result);
}

function getSettings() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Settings');
  
  if (sheet.getLastRow() === 0) return createJSONOutput({}); // Empty settings
  
  const settingsJson = sheet.getRange(1, 1).getValue();
  if (!settingsJson) return createJSONOutput({});
  
  try {
    return createJSONOutput(JSON.parse(settingsJson));
  } catch (e) {
    return createJSONOutput({});
  }
}

function saveSettings(data) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Settings');
  sheet.clear();
  
  // Save as JSON string in A1
  sheet.getRange(1, 1).setValue(JSON.stringify(data));
  
  return createJSONOutput({ status: 'success' });
}

function getRegistry() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Registry');
  const data = sheet.getDataRange().getValues();
  
  if (data.length <= 1) return createJSONOutput([]);
  
  const headers = data[0];
  const rows = data.slice(1);
  const result = rows.map(row => {
    const obj = {};
    headers.forEach((header, index) => {
      obj[header] = row[index];
    });
    return obj;
  });
  
  return createJSONOutput(result);
}

function saveRegistry(data) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Registry');
  sheet.clear();
  
  if (!data || data.length === 0) return createJSONOutput({ status: 'success' });
  
  const headers = Object.keys(data[0]);
  sheet.appendRow(headers);
  
  const rows = data.map(obj => headers.map(header => obj[header]));
  
  // Batch write
  if (rows.length > 0) {
    sheet.getRange(2, 1, rows.length, headers.length).setValues(rows);
  }
  
  return createJSONOutput({ status: 'success' });
}

function createJSONOutput(data) {
  return ContentService.createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}

콘텐츠평

0.0

문의하기

콘텐츠 문의

콘텐츠 문의와 관계없는 글, 광고성, 욕설, 비방 등의 글은 예고 없이 노출제한, 삭제 등의 조치가 취해질 수 있습니다.
공개 게시판이므로 문의 내용에는 연락처, 메일 등 소중한 개인정보는 절대 남기지 말아주세요.

등록
문의

유의사항

주문취소 및 환불요청에 대한 유의사항을 확인하실 수 있습니다. (유의사항 전체보기)