PDF保存後に範囲ずれへ気づく前に|GASで出力設定をdry-run監査

PDF保存後に範囲ずれへ気づく前に|GASで出力設定をdry-run監査

ツールおじ|GoogleアカウントをAIエージェント化

ツールおじ|GoogleアカウントをAIエージェント化

月次レポートをPDFにしてから「右端の列が切れた」「対象シートを間違えた」「ファイル名に使えない文字が入っていた」と気づくと、確認と作り直しが発生します。

この記事では、PDFを作成する前に、対象シート・出力範囲・用紙・向き・余白・ファイル名を検査し、確認計画だけを別シートへ出す無料GASを紹介します。コードはPDF作成やDrive保存を行わず、元シートも変更しません。

先に知っておきたいこと

  • できること: PDF出力設定の空欄、存在しないシート、範囲外指定、用紙や向きの表記ミス、危険なファイル名、余白の上限超過を事前に確認できます。
  • できないこと: PDFの見た目を完全に再現したプレビュー、改ページ位置の保証、PDF作成、Drive保存、メール送信は行いません。
  • 安全設計: dry-run固定です。入力元を更新せず、個別のセル値もログへ出しません。
  • 必要な権限: 対象スプレッドシートの閲覧・編集権限が必要です。DriveやGmailの権限は使いません。

準備するシート

スプレッドシートに「PDF設定」シートを作り、1行目へ次の見出しを置きます。

  • 対象シート
  • 出力範囲
  • ファイル名
  • 用紙
  • 向き
  • 上余白mm
  • 下余白mm
  • 左余白mm
  • 右余白mm
  • 有効

入力例は「売上サマリ」「A1:H45」「月次売上_2026-09」「A4」「横」「10」「10」「8」「8」「TRUE」です。出力範囲を空欄にすると、そのシートのデータ範囲を確認対象にします。

コピーして使える最小コード

function previewPdfExportPlanFree() {
  const config = {
    dryRun: true,
    settingsSheetName: 'PDF設定',
    resultSheetName: 'PDF_確認計画',
    maxRows: 200,
    allowedPaperSizes: ['A4', 'A3', 'LETTER'],
    allowedOrientations: ['縦', '横'],
    maxMarginMm: 30
  };

  if (config.dryRun !== true) {
    throw new Error('安全のためdryRun=true以外では実行できません。');
  }

  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const settingsSheet = spreadsheet.getSheetByName(config.settingsSheetName);
  if (!settingsSheet) {
    throw new Error('PDF設定シートが見つかりません。');
  }

  const values = settingsSheet.getDataRange().getDisplayValues();
  if (values.length < 2) {
    throw new Error('PDF設定シートに確認対象の行がありません。');
  }
  if (values.length - 1 > config.maxRows) {
    throw new Error('一度に確認できるのは' + config.maxRows + '行までです。');
  }

  const requiredHeaders = [
    '対象シート', '出力範囲', 'ファイル名', '用紙', '向き',
    '上余白mm', '下余白mm', '左余白mm', '右余白mm', '有効'
  ];
  const headerIndexes = buildHeaderIndexes_(values[0], requiredHeaders);
  const plan = [];

  values.slice(1).forEach(function(row, offset) {
    const rowNumber = offset + 2;
    const enabled = parseBoolean_(row[headerIndexes['有効']]);
    if (!enabled) {
      plan.push([rowNumber, 'SKIP', '', '', '', '有効がTRUEではありません']);
      return;
    }

    const sheetName = row[headerIndexes['対象シート']].trim();
    const requestedRange = row[headerIndexes['出力範囲']].trim();
    const fileName = row[headerIndexes['ファイル名']].trim();
    const paperSize = row[headerIndexes['用紙']].trim().toUpperCase();
    const orientation = row[headerIndexes['向き']].trim();
    const issues = [];

    if (!sheetName) issues.push('対象シートが空欄');
    const targetSheet = sheetName ? spreadsheet.getSheetByName(sheetName) : null;
    if (sheetName && !targetSheet) issues.push('対象シートが存在しない');

    if (!fileName) {
      issues.push('ファイル名が空欄');
    } else if (/[\\/:*?"<>|]/.test(fileName)) {
      issues.push('ファイル名に使用しにくい記号を含む');
    } else if (fileName.length > 120) {
      issues.push('ファイル名が120文字を超える');
    }

    if (config.allowedPaperSizes.indexOf(paperSize) === -1) {
      issues.push('用紙はA4・A3・LETTERのいずれか');
    }
    if (config.allowedOrientations.indexOf(orientation) === -1) {
      issues.push('向きは縦または横');
    }

    const marginLabels = ['上余白mm', '下余白mm', '左余白mm', '右余白mm'];
    marginLabels.forEach(function(label) {
      const raw = row[headerIndexes[label]].trim();
      const margin = Number(raw);
      if (raw === '' || !Number.isFinite(margin) || margin < 0 || margin > config.maxMarginMm) {
        issues.push(label + 'は0〜' + config.maxMarginMm + 'の数値');
      }
    });

    let checkedRange = '';
    let sizeSummary = '';
    if (targetSheet) {
      try {
        const range = requestedRange
          ? targetSheet.getRange(requestedRange)
          : targetSheet.getDataRange();
        checkedRange = range.getA1Notation();
        sizeSummary = range.getNumRows() + '行 × ' + range.getNumColumns() + '列';
        if (range.getNumRows() === 1 && range.getNumColumns() === 1 && range.isBlank()) {
          issues.push('出力範囲が空白セルのみ');
        }
      } catch (error) {
        issues.push('出力範囲をA1形式で確認');
      }
    }

    plan.push([
      rowNumber,
      issues.length ? 'HOLD' : 'READY',
      sheetName,
      checkedRange,
      sizeSummary,
      issues.join(' / ') || 'PDF作成前の確認へ進めます'
    ]);
  });

  writePdfPlan_(spreadsheet, config.resultSheetName, plan);
  const readyCount = plan.filter(function(row) { return row[1] === 'READY'; }).length;
  const holdCount = plan.filter(function(row) { return row[1] === 'HOLD'; }).length;
  const skipCount = plan.filter(function(row) { return row[1] === 'SKIP'; }).length;

  console.log(JSON.stringify({
    dryRun: true,
    checkedRows: plan.length,
    readyCount: readyCount,
    holdCount: holdCount,
    skipCount: skipCount
  }));
}

function buildHeaderIndexes_(headerRow, requiredHeaders) {
  const normalized = headerRow.map(function(value) { return String(value).trim(); });
  const duplicateHeaders = normalized.filter(function(value, index, all) {
    return value && all.indexOf(value) !== index;
  });
  if (duplicateHeaders.length) {
    throw new Error('同名の見出しがあります。');
  }

  const missingHeaders = requiredHeaders.filter(function(header) {
    return normalized.indexOf(header) === -1;
  });
  if (missingHeaders.length) {
    throw new Error('不足している見出し: ' + missingHeaders.join(', '));
  }

  return requiredHeaders.reduce(function(indexes, header) {
    indexes[header] = normalized.indexOf(header);
    return indexes;
  }, {});
}

function parseBoolean_(value) {
  return ['TRUE', '1', 'はい', '有効'].indexOf(String(value).trim().toUpperCase()) !== -1;
}

function writePdfPlan_(spreadsheet, sheetName, rows) {
  let resultSheet = spreadsheet.getSheetByName(sheetName);
  if (!resultSheet) resultSheet = spreadsheet.insertSheet(sheetName);
  resultSheet.clearContents();

  const output = [[
    '設定行', '判定', '対象シート', '確認範囲', '範囲サイズ', '確認事項'
  ]].concat(rows);
  resultSheet.getRange(1, 1, output.length, output[0].length).setValues(output);
  resultSheet.setFrozenRows(1);
}

実行手順

  1. 元ファイルではなく、まずコピーしたスプレッドシートで試します。
  2. 拡張機能からApps Scriptを開き、コードを貼り付けて保存します。
  3. 「PDF設定」へ少量のサンプルを入力します。
  4. previewPdfExportPlanFreeを実行します。
  5. 初回の権限画面を確認し、対象スプレッドシートへの権限だけであることを確かめます。
  6. 「PDF_確認計画」でREADY・HOLD・SKIPと確認事項を読みます。
  7. HOLDが0件になってから、実際のPDF作成処理を別工程として検討します。

このコードが勝手に行わないこと

  • PDFファイルの作成
  • Google Driveへの保存や既存ファイルの上書き
  • メール送信や外部サービスへの通信
  • 元データの削除、書式変更、並べ替え
  • 時間主導トリガーの作成

結果用の「PDF_確認計画」だけは毎回クリアして書き直します。既存の同名シートを使っている場合は、実行前に名前を変えてください。

失敗しやすい点と制限

  • 出力範囲はA1形式で入力します。名前付き範囲や複数の離れた範囲は、この無料例では扱いません。
  • 画面上の列幅、改ページ、画像、グラフ、結合セルによって、実際のPDFの見え方は変わります。
  • 200行を超える設定は停止します。分割して確認してください。
  • ログには件数だけを出します。シート名、ファイル名、セル値をログへ追加しない方が安全です。
  • Apps Scriptやスプレッドシートの実行上限・仕様は変更される場合があります。
  • 実際のPDF作成を追加する場合も、既存ファイルの上書きや自動送信を初期値にしないでください。

公開前チェックリスト

  • 元ファイルのコピーで試した
  • PDF設定の見出しに不足や重複がない
  • 出力範囲が意図した行列数になっている
  • ファイル名へ機密情報や個人情報を入れていない
  • HOLDが0件になるまでPDF作成へ進まない
  • 自動送信・自動削除・上書きを追加していない
  • 実ファイルを少数だけ作り、目視確認する工程を別に用意する

まとめ

PDF出力の失敗は、保存処理そのものより、対象・範囲・用紙・向き・余白・ファイル名の組み合わせで起こりやすくなります。先に設定だけをdry-run監査し、HOLDを人が直してから作成工程へ進むと、元データやDriveを変更せずに確認できます。

作成後のPDFを含むDriveファイルを一覧で整理したい方は、関連する有料Tipsも確認できます。

DriveFileList Pro|Google Driveのファイル一覧化GASテンプレ

GasおじのTips商品一覧

※この記事は学習・検証用の無料例です。Google公式・Google公認の商品ではありません。実データへ適用する前に、コピー環境と少量データで確認してください。


あなたも記事の投稿・販売を
始めてみませんか?

Tipsなら簡単に記事を販売できます!
登録無料で始められます!

Tipsなら、無料ですぐに記事の販売をはじめることができます Tipsの詳細はこちら
 

この記事の販売者

ツールおじ|GoogleアカウントをAIエージェント化

おう、Gasおじだ。 AIツールとSaaSに散々突っ込んで遠回りした結果、最後に強かったのは「今あるGoogleアカウント」だった。 今ではAIエージェントであらゆるタスクを自動化して無双してるぞ。 GAS、スプレッドシート、フォーム、Gmail、AIエージェントを組み合わせれば、個人でも小さな会社でも、手作業だらけの業務はかなり自動化できる。 ここでは机上のAI論じゃなく、現場で動くテンプレ、コード、手順だけを置いていくぞ。 高いツールを増やす前に、まずお前さんのGoogleアカウントを仕事するエージェントに変えようじゃないか。

この販売者が書いた他の記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    1 %獲得
    (29 円相当)
  • Gmailの自動ラベルが競合する前に確認|GASで仕分けルールをdry-run監査

関連のおすすめ記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化

  • USBメモリで持ち運べるLinux Mint 22.1と永続的な書き込み領域の作成法【Rufus だけで作成する方法を初心者向けに徹底解説】

    ¥500
    1 %獲得
    (5 円相当)
    SmartSenior

    SmartSenior

  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化