/** * GemEgg 予実管理 スプレッドシート * Google Apps Script 実装 v2 (修正版) * * 使用方法: * 1. スプレッドシートで「拡張機能」>「Apps Script」を開く * 2. このコードを貼り付けて保存 * 3. onOpen関数を実行(初回は権限承認が必要) * 4. スプレッドシートを再読み込み * 5. メニューから「GemEgg予実管理」を選択 */ // ============================================ // メニュー設定 // ============================================ function onOpen() { const ui = SpreadsheetApp.getUi(); ui.createMenu('🔷 GemEgg予実管理') .addItem('📈 経営ダッシュボードを更新', 'updateDashboard') .addItem('🔄 全て更新(予実+ダッシュボード)', 'updateAll') .addSeparator() .addSubMenu(ui.createMenu('初期設定') .addItem('予算シートを初期化', 'initBudgetSheet') .addItem('実績シートを初期化', 'initActualSheet') .addItem('予実シートを初期化', 'initOutputSheet')) .addSeparator() .addSubMenu(ui.createMenu('自動化') .addItem('毎日自動更新を設定', 'createDailyTrigger') .addItem('毎月自動更新を設定', 'createMonthlyTrigger') .addItem('トリガーを全削除', 'deleteAllTriggers')) .addSeparator() .addItem('ℹ️ 使い方', 'showHelp') .addToUi(); } // ============================================ // 定数 // ============================================ const MONTHS = [ '2025-04', '2025-05', '2025-06', '2025-07', '2025-08', '2025-09', '2025-10', '2025-11', '2025-12', '2026-01', '2026-02', '2026-03' ]; // 損益計算書項目(VLOOKUP用) const PL_ITEMS = [ { name: '売上高', level: 0 }, { name: '売上原価', level: 0 }, { name: '売上総損益金額', level: 0 }, { name: '販売管理費', level: 0 }, { name: '役員報酬', level: 1 }, { name: '給料手当', level: 1 }, { name: '法定福利費', level: 1 }, { name: '福利厚生費', level: 1 }, { name: '広告宣伝費', level: 1 }, { name: '交際費', level: 1 }, { name: '会議費', level: 1 }, { name: '旅費交通費', level: 1 }, { name: '通信費', level: 1 }, { name: '販売促進費', level: 1 }, { name: '消耗品費', level: 1 }, { name: '水道光熱費', level: 1 }, { name: '支払手数料', level: 1 }, { name: '地代家賃', level: 1 }, { name: '租税公課', level: 1 }, { name: '支払報酬料', level: 1 }, { name: '雑費', level: 1 }, { name: '販売管理費 計', level: 0 }, { name: '営業損益金額', level: 0 }, { name: '営業外収益', level: 1 }, { name: '営業外費用', level: 1 }, { name: '経常損益金額', level: 0 }, { name: '特別利益', level: 1 }, { name: '特別損失', level: 1 }, { name: '税引前当期純損益金額', level: 0 }, { name: '法人税等', level: 1 }, { name: '当期純損益金額', level: 0 }, ]; // ============================================ // メイン機能 // ============================================ /** * 予実シートを更新(メイン機能) */ function updateBudgetVsActual() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const ui = SpreadsheetApp.getUi(); const budgetSheet = ss.getSheetByName('予算入力'); const actualSheet = ss.getSheetByName('実績入力'); let outputSheet = ss.getSheetByName('予実出力'); if (!budgetSheet) { ui.alert('エラー', '予算入力シートが見つかりません。', ui.ButtonSet.OK); return; } if (!actualSheet) { ui.alert('エラー', '実績入力シートが見つかりません。', ui.ButtonSet.OK); return; } if (!outputSheet) { outputSheet = ss.insertSheet('予実出力'); } try { // 予実出力シートを更新(VLOOKUP使用) updateOutputSheetWithFormulas(outputSheet); const timestamp = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'); ui.alert('✅ 更新完了', `予実シートを更新しました。\n\n更新日時: ${timestamp}`, ui.ButtonSet.OK); } catch (error) { ui.alert('エラー', `更新中にエラーが発生しました:\n${error.message}`, ui.ButtonSet.OK); } } /** * 予実シートと経営ダッシュボードを両方更新 */ function updateAll() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const ui = SpreadsheetApp.getUi(); try { // 1. 予実シートを更新 const outputSheet = ss.getSheetByName('予実出力') || ss.insertSheet('予実出力'); updateOutputSheetWithFormulas(outputSheet); // 2. 経営ダッシュボードを更新 const dashboard = ss.getSheetByName('経営ダッシュボード'); if (dashboard) { updateDashboardInternal(dashboard); } const timestamp = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'); const message = dashboard ? '予実シートと経営ダッシュボードを更新しました。' : '予実シートを更新しました。'; ui.alert('✅ 更新完了', `${message}\n\n更新日時: ${timestamp}`, ui.ButtonSet.OK); } catch (error) { ui.alert('エラー', `更新中にエラーが発生しました:\n${error.message}`, ui.ButtonSet.OK); } } /** * 予実出力シートを更新(VLOOKUP数式使用) */ function updateOutputSheetWithFormulas(sheet) { // シートをクリア sheet.clear(); // ヘッダー設定 sheet.getRange('B1').setValue('予実管理').setFontSize(14).setFontWeight('bold'); sheet.getRange('B3').setValue('単位:円'); // 月ヘッダー(4行目) let col = 3; // C列から開始 MONTHS.forEach(month => { sheet.getRange(4, col).setValue(month); sheet.getRange(4, col, 1, 4).merge() .setHorizontalAlignment('center') .setBackground('#d9e7fd'); col += 4; }); // サブヘッダー(5行目) sheet.getRange(5, 2).setValue('項目').setFontWeight('bold'); col = 3; MONTHS.forEach(() => { sheet.getRange(5, col).setValue('予算'); sheet.getRange(5, col + 1).setValue('実績'); sheet.getRange(5, col + 2).setValue('差異'); sheet.getRange(5, col + 3).setValue('達成率'); col += 4; }); sheet.getRange(5, 3, 1, 48) .setBackground('#4a86e8') .setFontColor('white') .setFontWeight('bold') .setHorizontalAlignment('center'); // データ行(6行目以降) PL_ITEMS.forEach((item, index) => { const row = 6 + index; const indent = ' '.repeat(item.level); sheet.getRange(row, 2).setValue(indent + item.name); // 月別データ(VLOOKUP数式) col = 3; MONTHS.forEach((month, mIndex) => { // 予算: 予算入力シートからVLOOKUP // 予算入力シートの構造: B列=項目名, C~N列=2025-04~2026-03 const budgetCol = mIndex + 2; // VLOOKUP列番号: C=2, D=3, ... const budgetFormula = `=IFERROR(VLOOKUP($B${row},'予算入力'!$B:$O,${budgetCol},FALSE),0)`; // 実績: 実績入力シートからVLOOKUP // 実績入力シートの構造: B列=項目名, G~R列=2025-04~2026-03 // VLOOKUPのrange B:TでG列=6, H列=7, ... const actualCol = mIndex + 7; // G=7, H=8, ... const actualFormula = `=IFERROR(VLOOKUP($B${row},'実績入力'!$B:$T,${actualCol},FALSE),0)`; // 差異: 実績 - 予算 const budgetCellCol = columnToLetter(col); const actualCellCol = columnToLetter(col + 1); const diffFormula = `=${actualCellCol}${row}-${budgetCellCol}${row}`; // 達成率: 実績 / 予算 const rateFormula = `=IF(${budgetCellCol}${row}=0,"",${actualCellCol}${row}/${budgetCellCol}${row})`; // 数式を設定 sheet.getRange(row, col).setFormula(budgetFormula).setNumberFormat('#,##0'); sheet.getRange(row, col + 1).setFormula(actualFormula).setNumberFormat('#,##0'); sheet.getRange(row, col + 2).setFormula(diffFormula).setNumberFormat('#,##0'); sheet.getRange(row, col + 3).setFormula(rateFormula).setNumberFormat('0%'); // 差異の色分け(テキスト色) const diffCell = sheet.getRange(row, col + 2); diffCell.setFormulaR1C1( `=IF(R[0]C[0]>0,"✓",IF(R[0]C[0]<0,"✗",""))` // 実際の色は条件付き書式で設定 ); // 達成率の色分け(テキスト色) const rateCell = sheet.getRange(row, col + 3); col += 4; }); }); // 列幅調整 sheet.setColumnWidth(2, 180); for (let i = 3; i <= 50; i++) { sheet.setColumnWidth(i, 80); } // 条件付き書式を適用 applyConditionalFormattingToOutput(sheet); } /** * 列番号を列文字に変換(1=A, 2=B, ...) */ function columnToLetter(column) { let temp; let letter = ''; while (column > 0) { temp = (column - 1) % 26; letter = String.fromCharCode(temp + 65) + letter; column = (column - temp - 1) / 26; } return letter; } /** * 予実出力シートに条件付き書式を適用 */ function applyConditionalFormattingToOutput(sheet) { const lastRow = 6 + PL_ITEMS.length - 1; // 差異列(毎月の3列目)の条件付き書式 for (let m = 0; m < MONTHS.length; m++) { const diffCol = 3 + m * 4 + 2; // C列から4列ずつ、3列目が差異 const range = sheet.getRange(6, diffCol, lastRow - 5, 1); // 正の値(緑) const greenRule = SpreadsheetApp.newConditionalFormatRule() .whenNumberGreaterThan(0) .setFontColor('#38761d') .setRanges([range]) .build(); // 負の値(赤) const redRule = SpreadsheetApp.newConditionalFormatRule() .whenNumberLessThan(0) .setFontColor('#cc0000') .setRanges([range]) .build(); const rules = sheet.getConditionalFormatRules(); rules.push(greenRule); rules.push(redRule); sheet.setConditionalFormatRules(rules); } // 達成率列(毎月の4列目)の条件付き書式 for (let m = 0; m < MONTHS.length; m++) { const rateCol = 3 + m * 4 + 3; // C列から4列ずつ、4列目が達成率 const range = sheet.getRange(6, rateCol, lastRow - 5, 1); // 100%以上(緑) const greenRule = SpreadsheetApp.newConditionalFormatRule() .whenNumberGreaterThanOrEqualTo(1) .setFontColor('#38761d') .setRanges([range]) .build(); // 80%未満(赤) const redRule = SpreadsheetApp.newConditionalFormatRule() .whenNumberLessThan(0.8) .setFontColor('#cc0000') .setRanges([range]) .build(); const rules = sheet.getConditionalFormatRules(); rules.push(greenRule); rules.push(redRule); sheet.setConditionalFormatRules(rules); } } // ============================================ // 経営ダッシュボード // ============================================ /** * 経営ダッシュボードを更新 */ function updateDashboard() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const dashboard = ss.getSheetByName('経営ダッシュボード'); if (!dashboard) { SpreadsheetApp.getUi().alert('エラー', '経営ダッシュボードシートが見つかりません。', SpreadsheetApp.getUi().ButtonSet.OK); return; } try { updateDashboardInternal(dashboard); SpreadsheetApp.getUi().alert('✅ 完了', '経営ダッシュボードを更新しました。', SpreadsheetApp.getUi().ButtonSet.OK); } catch (error) { SpreadsheetApp.getUi().alert('エラー', `ダッシュボード更新中にエラーが発生しました:\n${error.message}`, SpreadsheetApp.getUi().ButtonSet.OK); } } /** * 経営ダッシュボードを内部的に更新 */ function updateDashboardInternal(dashboard) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const budgetSheet = ss.getSheetByName('予算入力'); const actualSheet = ss.getSheetByName('実績入力'); if (!budgetSheet || !actualSheet) { throw new Error('予算入力または実績入力シートが見つかりません'); } // 期首月と締月を取得 const startMonth = dashboard.getRange('C4').getValue(); // "2025/4" const endMonth = dashboard.getRange('C5').getValue(); // "2025/10" // 月数を計算 const monthsElapsed = getMonthsElapsed(startMonth, endMonth); // 各セクションを更新 updateAnnualBudgetVsActual(dashboard, budgetSheet, actualSheet, monthsElapsed); updateMonthlyBudgetVsActual(dashboard, budgetSheet, actualSheet, monthsElapsed); updateProfitabilityIndicators(dashboard); updateEfficiencyIndicators(dashboard, actualSheet, monthsElapsed); updateCashFlowMetrics(dashboard, monthsElapsed); updateOtherMetrics(dashboard); } /** * 期首から締月までの月数を計算 */ function getMonthsElapsed(startMonth, endMonth) { const start = String(startMonth).split('/').map(Number); const end = String(endMonth).split('/').map(Number); return (end[0] - start[0]) * 12 + (end[1] - start[1]) + 1; } /** * セクション1: 年間予算 vs 締月地点実績 */ function updateAnnualBudgetVsActual(dashboard, budgetSheet, actualSheet, monthsElapsed) { const startCol = 7; // G列 const endCol = startCol + monthsElapsed - 1; // 年間予算 const annualBudgetRevenue = budgetSheet.getRange('O6').getValue(); // 締月地点実績を計算 const actualRevenue = sumRange(actualSheet, 9, startCol, endCol); const actualGrossProfit = sumRange(actualSheet, 18, startCol, endCol); const actualOperatingProfit = sumRange(actualSheet, 32, startCol, endCol); const actualOrdinaryProfit = sumRange(actualSheet, 35, startCol, endCol); // 販管費の年間予算を取得 const annualSgaExpense = getBudgetSgaExpense(budgetSheet); // 年間予算を計算 const annualGrossProfit = annualBudgetRevenue; const annualOperatingProfit = annualGrossProfit - annualSgaExpense; const annualOrdinaryProfit = annualOperatingProfit; // ダッシュボードに書き込み dashboard.getRange('C8').setValue(annualBudgetRevenue).setNumberFormat('#,##0'); dashboard.getRange('C9').setValue(annualGrossProfit).setNumberFormat('#,##0'); dashboard.getRange('C10').setValue(annualOperatingProfit).setNumberFormat('#,##0'); dashboard.getRange('C11').setValue(annualOrdinaryProfit).setNumberFormat('#,##0'); dashboard.getRange('D8').setValue(actualRevenue).setNumberFormat('#,##0'); dashboard.getRange('D9').setValue(actualGrossProfit).setNumberFormat('#,##0'); dashboard.getRange('D10').setValue(actualOperatingProfit).setNumberFormat('#,##0'); dashboard.getRange('D11').setValue(actualOrdinaryProfit).setNumberFormat('#,##0'); dashboard.getRange('E8').setFormula('=IFERROR(D8/C8, 0)').setNumberFormat('0%'); dashboard.getRange('E9').setFormula('=IFERROR(D9/C9, 0)').setNumberFormat('0%'); dashboard.getRange('E10').setFormula('=IFERROR(D10/C10, 0)').setNumberFormat('0%'); dashboard.getRange('E11').setFormula('=IFERROR(D11/C11, 0)').setNumberFormat('0%'); } /** * セクション2: 締月地点予算 vs 実績 */ function updateMonthlyBudgetVsActual(dashboard, budgetSheet, actualSheet, monthsElapsed) { const budgetStartCol = 3; const budgetEndCol = budgetStartCol + monthsElapsed - 1; const budgetRevenue = sumRange(budgetSheet, 6, budgetStartCol, budgetEndCol); const budgetSgaExpense = sumRangeForSga(budgetSheet, budgetStartCol, budgetEndCol); const budgetGrossProfit = budgetRevenue; const budgetOperatingProfit = budgetGrossProfit - budgetSgaExpense; const budgetOrdinaryProfit = budgetOperatingProfit; dashboard.getRange('C14').setValue(budgetRevenue).setNumberFormat('#,##0'); dashboard.getRange('C15').setValue(budgetGrossProfit).setNumberFormat('#,##0'); dashboard.getRange('C16').setValue(budgetOperatingProfit).setNumberFormat('#,##0'); dashboard.getRange('C17').setValue(budgetOrdinaryProfit).setNumberFormat('#,##0'); dashboard.getRange('D14').setFormula('=D8'); dashboard.getRange('D15').setFormula('=D9'); dashboard.getRange('D16').setFormula('=D10'); dashboard.getRange('D17').setFormula('=D11'); dashboard.getRange('E14').setFormula('=D14-C14').setNumberFormat('#,##0'); dashboard.getRange('E15').setFormula('=D15-C15').setNumberFormat('#,##0'); dashboard.getRange('E16').setFormula('=D16-C16').setNumberFormat('#,##0'); dashboard.getRange('E17').setFormula('=D17-C17').setNumberFormat('#,##0'); dashboard.getRange('F14').setFormula('=IFERROR(D14/C14, 0)').setNumberFormat('0%'); dashboard.getRange('F15').setFormula('=IFERROR(D15/C15, 0)').setNumberFormat('0%'); dashboard.getRange('F16').setFormula('=IFERROR(D16/C16, 0)').setNumberFormat('0%'); dashboard.getRange('F17').setFormula('=IFERROR(D17/C17, 0)').setNumberFormat('0%'); } /** * セクション3: 収益性指標 */ function updateProfitabilityIndicators(dashboard) { dashboard.getRange('C20').setFormula('=IFERROR(D9/D8, 0)').setNumberFormat('0.0%'); dashboard.getRange('C21').setFormula('=IFERROR(D10/D8, 0)').setNumberFormat('0.0%'); dashboard.getRange('C22').setFormula('=IFERROR(D11/D8, 0)').setNumberFormat('0.0%'); } /** * セクション4: 効率性指標 */ function updateEfficiencyIndicators(dashboard, actualSheet, monthsElapsed) { const startCol = 7; const endCol = startCol + monthsElapsed - 1; const sgaExpense = sumRange(actualSheet, 31, startCol, endCol); const laborCost = sumRange(actualSheet, 20, startCol, endCol) + sumRange(actualSheet, 21, startCol, endCol) + sumRange(actualSheet, 22, startCol, endCol); dashboard.getRange('D25').setValue(sgaExpense).setNumberFormat('#,##0'); dashboard.getRange('C25').setFormula('=IFERROR(D25/D8, 0)').setNumberFormat('0.0%'); dashboard.getRange('D26').setValue(laborCost).setNumberFormat('#,##0'); dashboard.getRange('C26').setFormula('=IFERROR(D26/D8, 0)').setNumberFormat('0.0%'); } /** * セクション5: キャッシュフロー関連 */ function updateCashFlowMetrics(dashboard, monthsElapsed) { dashboard.getRange('E30').setValue(monthsElapsed + 'ヶ月'); dashboard.getRange('C30').setFormula(`=IFERROR(D25/${monthsElapsed}, 0)`).setNumberFormat('#,##0'); dashboard.getRange('C31').setFormula('=IFERROR(C29/C30, 0)').setNumberFormat('0.0'); dashboard.getRange('C32').setFormula(`=IFERROR(D11/${monthsElapsed}, 0)`).setNumberFormat('#,##0'); dashboard.getRange('C33').setFormula('=IF(C32<0, C29/ABS(C32), "∞")'); } /** * セクション6: その他指標 */ function updateOtherMetrics(dashboard) { dashboard.getRange('C36').setFormula('=D25').setNumberFormat('#,##0'); dashboard.getRange('C37').setFormula('=IFERROR(C36/D8, 0)').setNumberFormat('0.0%'); } /** * 指定範囲の合計を取得 */ function sumRange(sheet, row, startCol, endCol) { const range = sheet.getRange(row, startCol, 1, endCol - startCol + 1); const values = range.getValues()[0]; return values.reduce((sum, val) => { const num = typeof val === 'number' ? val : parseFloat(String(val).replace(/,/g, '')); return sum + (isNaN(num) ? 0 : num); }, 0); } /** * 予算入力シートから販管費の年間予算を取得 */ function getBudgetSgaExpense(budgetSheet) { const data = budgetSheet.getRange('B5:B50').getValues(); let sgaRow = -1; for (let i = 0; i < data.length; i++) { const cellValue = String(data[i][0]).trim(); if (cellValue.includes('販売管理費') && cellValue.includes('計')) { sgaRow = i + 5; break; } } if (sgaRow === -1) return 0; return budgetSheet.getRange(sgaRow, 15).getValue() || 0; } /** * 予算入力シートから販管費の締月地点予算を取得 */ function sumRangeForSga(budgetSheet, startCol, endCol) { const data = budgetSheet.getRange('B5:B50').getValues(); let sgaRow = -1; for (let i = 0; i < data.length; i++) { const cellValue = String(data[i][0]).trim(); if (cellValue.includes('販売管理費') && cellValue.includes('計')) { sgaRow = i + 5; break; } } if (sgaRow === -1) return 0; return sumRange(budgetSheet, sgaRow, startCol, endCol); } // ============================================ // 初期化機能 // ============================================ function initBudgetSheet() { const ss = SpreadsheetApp.getActiveSpreadsheet(); let sheet = ss.getSheetByName('予算入力'); if (sheet) { const ui = SpreadsheetApp.getUi(); const response = ui.alert('確認', '予算入力シートは既に存在します。初期化しますか?', ui.ButtonSet.YES_NO); if (response !== ui.Button.YES) return; sheet.clear(); } else { sheet = ss.insertSheet('予算入力'); } sheet.getRange('B1').setValue('予算入力シート').setFontSize(14).setFontWeight('bold'); sheet.getRange('B3').setValue('単位:円'); const headers = ['項目', ...MONTHS, '期間累計']; sheet.getRange(4, 2, 1, headers.length).setValues([headers]) .setBackground('#4a86e8').setFontColor('white').setFontWeight('bold'); PL_ITEMS.forEach((item, index) => { const indent = ' '.repeat(item.level); sheet.getRange(5 + index, 2).setValue(indent + item.name); const row = 5 + index; sheet.getRange(row, 15).setFormula(`=SUM(C${row}:N${row})`); }); sheet.setColumnWidth(2, 180); SpreadsheetApp.getUi().alert('✅ 完了', '予算入力シートを初期化しました。', SpreadsheetApp.getUi().ButtonSet.OK); } function initActualSheet() { const ss = SpreadsheetApp.getActiveSpreadsheet(); let sheet = ss.getSheetByName('実績入力'); if (sheet) { const ui = SpreadsheetApp.getUi(); const response = ui.alert('確認', '実績入力シートは既に存在します。初期化しますか?', ui.ButtonSet.YES_NO); if (response !== ui.Button.YES) return; sheet.clear(); } else { sheet = ss.insertSheet('実績入力'); } sheet.getRange('B1').setValue('実績入力シート').setFontSize(14).setFontWeight('bold'); sheet.getRange('B3').setValue('Money ForwardのCSVデータを下記に貼り付けてください:'); sheet.getRange('B5').setValue('↓ CSVデータ開始行 ↓').setFontWeight('bold'); sheet.getRange('B6:T50').setBackground('#fff2cc'); SpreadsheetApp.getUi().alert('✅ 完了', '実績入力シートを初期化しました。', SpreadsheetApp.getUi().ButtonSet.OK); } function initOutputSheet() { const ss = SpreadsheetApp.getActiveSpreadsheet(); let sheet = ss.getSheetByName('予実出力'); if (sheet) { const ui = SpreadsheetApp.getUi(); const response = ui.alert('確認', '予実出力シートは既に存在します。初期化しますか?', ui.ButtonSet.YES_NO); if (response !== ui.Button.YES) return; sheet.clear(); } else { sheet = ss.insertSheet('予実出力'); } sheet.getRange('B1').setValue('予実管理').setFontSize(14).setFontWeight('bold'); sheet.getRange('B3').setValue('「予実シートを更新」を実行してください。'); SpreadsheetApp.getUi().alert('✅ 完了', '予実出力シートを初期化しました。', SpreadsheetApp.getUi().ButtonSet.OK); } // ============================================ // 自動化(トリガー) // ============================================ function createDailyTrigger() { const triggers = ScriptApp.getProjectTriggers(); const existingDaily = triggers.find(t => t.getHandlerFunction() === 'autoUpdate' && t.getEventType() === ScriptApp.EventType.CLOCK ); if (existingDaily) { SpreadsheetApp.getUi().alert('既に毎日自動更新が設定されています。'); return; } ScriptApp.newTrigger('autoUpdate') .timeBased() .everyDays(1) .atHour(9) .create(); SpreadsheetApp.getUi().alert('✅ 設定完了', '毎日9時に自動更新するトリガーを設定しました。', SpreadsheetApp.getUi().ButtonSet.OK); } function createMonthlyTrigger() { const triggers = ScriptApp.getProjectTriggers(); const existingMonthly = triggers.find(t => t.getHandlerFunction() === 'autoUpdate' && t.getEventType() === ScriptApp.EventType.CLOCK ); if (existingMonthly) { SpreadsheetApp.getUi().alert('既に自動更新が設定されています。'); return; } ScriptApp.newTrigger('autoUpdate') .timeBased() .onMonthDay(1) .atHour(10) .create(); SpreadsheetApp.getUi().alert('✅ 設定完了', '毎月1日10時に自動更新するトリガーを設定しました。', SpreadsheetApp.getUi().ButtonSet.OK); } function deleteAllTriggers() { const triggers = ScriptApp.getProjectTriggers(); if (triggers.length === 0) { SpreadsheetApp.getUi().alert('削除するトリガーがありません。'); return; } const ui = SpreadsheetApp.getUi(); const response = ui.alert('確認', `${triggers.length}個のトリガーを削除しますか?`, ui.ButtonSet.YES_NO); if (response === ui.Button.YES) { triggers.forEach(trigger => ScriptApp.deleteTrigger(trigger)); ui.alert('✅ 完了', '全てのトリガーを削除しました。', ui.ButtonSet.OK); } } function autoUpdate() { const ss = SpreadsheetApp.openById(SpreadsheetApp.getActiveSpreadsheet().getId()); try { const outputSheet = ss.getSheetByName('予実出力') || ss.insertSheet('予実出力'); updateOutputSheetWithFormulas(outputSheet); const dashboard = ss.getSheetByName('経営ダッシュボード'); if (dashboard) { updateDashboardInternal(dashboard); } console.log('自動更新完了: ' + new Date().toISOString()); } catch (error) { console.log('自動更新エラー: ' + error.message); } } // ============================================ // ヘルプ // ============================================ function showHelp() { const help = ` 【GemEgg 予実管理 使い方】 1️⃣ 予算入力 「予算入力」シートに月別予算を入力 2️⃣ 実績入力 Money ForwardからCSVをダウンロード 「実績入力」シートのB6セルに貼り付け 3️⃣ 予実更新 メニュー「予実シートを更新」をクリック 4️⃣ ダッシュボード更新 メニュー「経営ダッシュボードを更新」をクリック 📅 自動化 「自動化」メニューからトリガー設定可能 - 毎日9時に自動更新 - 毎月1日10時に自動更新 ❓ 問題がある場合 各シートを「初期設定」メニューから再初期化 `; SpreadsheetApp.getUi().alert('📖 使い方', help, SpreadsheetApp.getUi().ButtonSet.OK); }