請求書PDFを1クリックで作る無料テンプレート(GAS・全文コピペ配布)
スプレッドシートに明細を入力してメニューを押すと、インボイス対応の請求書PDFがDriveに保存されます。コードは全文このページに置いています。実機で動作を確認した版です。
請求書を1枚作るのに、テンプレートを複製して、宛先を書き換えて、金額を打ち直して、消費税を電卓で出して、PDFに書き出して、ファイル名を付けて保存する。この一連の作業を、明細を入れてメニューを1回押すだけにするGoogle Apps Script(GAS)のコードを、このページに全文置いています。
無料です。ダウンロード先もメールアドレスの入力も要りません。下のコードをコピーして貼るだけです。
このテンプレートですること・しないこと
売り込みのために曖昧にしても意味がないので、最初に線を引いておきます。
できること
- 「請求書」シートに明細を入力 → メニュー「請求書 > この請求書をPDFにする」でPDFがDriveに保存される
- 標準税率10%と軽減税率8%が混ざった請求書に対応(端数処理は税率ごとに1回だけ切り捨て)
- 適格請求書(インボイス)の必須項目を印字する構成。登録番号・税率別の対価の額・税率別の消費税額を出す
- 発行日が空のままPDFにしようとすると止まる
- ファイル名は
20260817_取引先名_35160.pdfの形式で自動生成
できないこと
- 複数の取引先へまとめて発行すること(1枚ずつです)
- 請求書番号の自動採番(手入力欄になっています)
- 発行履歴の記録と、同じ月・同じ相手への二重発行の警告
- 源泉徴収10.21%の自動計算
後半の4つは、取引先が増えたときに効いてくる部分です。そこまで必要になった場合の選択肢はページの最後に書きます。まずは1枚分で足りるなら、この無料版で完結します。
導入手順
Googleアカウントがあれば5分ほどで動きます。
- Googleスプレッドシートを新規作成する
- メニュー「拡張機能 > Apps Script」を開く
- エディタに最初から入っている
function myFunction() {}をすべて消す(Cmd+A/Ctrl+A→ Delete) - このページ下部のコードを全文コピーして貼り付ける
Cmd+S/Ctrl+Sで保存する- スプレッドシートのタブに戻ってページを再読み込みする(メニューはここで初めて出ます)
- メニューバーに現れた「請求書」→「初期シートを作成」を押す
- 初回だけ承認画面が出ます。「詳細」→「(プロジェクト名)に移動」→「許可」の順に進む
- 「設定」シートに自分の事業者名・住所・登録番号・振込先を入力する
- 「請求書」シートに宛先と明細を入れて、「請求書 > この請求書をPDFにする」
承認画面で「このアプリは確認されていません」と出る場合
自分で貼ったコードなので、Googleの審査を受けていない状態です。表示されるのは正常です。「詳細」を開いて「安全ではないページに移動」に進むと承認できます。**心配なら、コードを読んでから実行してください。**外部へ通信しているのはPDF書き出しのためにGoogle自身のエクスポートURLを叩く1箇所だけで、他のサーバーへは何も送っていません。
承認するアカウントを間違えないこと
複数のGoogleアカウントにログインしている場合、スプレッドシートを所有しているアカウントと、承認画面で選ぶアカウントが一致していないと error=access_denied で弾かれます。所有アカウントが分からないときは、いったん myaccount.google.com を開いて確認してください。
入力の仕方
「請求書」シートは、色のついていない欄がすべて手入力です。
| 場所 | 入れるもの |
|---|---|
| A3 | 宛先(例: 株式会社サンプル商事 御中) |
| E3 | 発行日。初期シート作成時に当日の日付が入ります |
| E4 | 請求書番号(任意。無料版は手入力) |
| A13〜 | 品目・数量・単価・税区分 |
税区分はプルダウンで「標準10%」「軽減8%」「対象外」から選びます。金額の列と合計欄は触らなくて構いません。PDF化を押した時点でスクリプトが計算して書き込みます。
「対象外」を選んだ行は、小計には入りますが消費税の計算対象からは外れます。
インボイスの必須項目との対応
適格請求書として必要な項目は6つあります。このテンプレートがどこで満たしているかを並べます。
| 必須項目 | このテンプレートでの出力先 |
|---|---|
| 発行者の氏名または名称 | 「設定」シートの事業者名 → 右上に印字 |
| 登録番号 | 「設定」シートの登録番号 → 右上に印字 |
| 取引年月日 | 発行日(空欄のままだとPDF化が止まります) |
| 取引内容(軽減税率対象である旨) | 明細の品目 + 税区分「軽減8%」の表示 |
| 税率ごとに区分した対価の額と適用税率 | 「消費税10%(対象¥30,000)」の形で印字 |
| 税率ごとに区分した消費税額 | 上の各行の右側の金額 |
| 交付を受ける事業者の氏名または名称 | 宛先 |
端数処理は、税率ごとに1回だけ切り捨てています。明細1行ごとに消費税を出して足し上げる方式にはしていません。これは制度上、1つの適格請求書につき税率ごとに1回とされているためです。
登録番号を持っていない場合は、「設定」シートの登録番号欄を空にしてください。その行自体が印字されなくなります。
中身を自分で組みたい場合
このページのコードは、貼れば動くことだけを目的にしています。なぜその書き方なのか——結合セルへの差し込みでハマる箇所、PDF化の前に flush() が要る理由、請求書番号を実行をまたいで一意に保つ方法——は、スプレッドシートで請求書を自動発行する仕組みの作り方 に分解して書いています。自分の業務に合わせて作り変えるなら、そちらを先に読んでください。
実機で確認したこと
コードの動作は、実際にスプレッドシートを新規作成して確かめています(2026年8月17日)。
- 標準10%と軽減8%が混ざった請求書で、小計¥32,000 / 消費税10%(対象¥30,000)¥3,000 / 消費税8%(対象¥2,000)¥160 / 合計¥35,160 が出ること
- 発行日を空にしてPDF化を実行すると警告が出て、「いいえ」を選ぶとPDFが作られないこと
- 印字範囲の右端で登録番号や住所が切れないこと
列幅を変えるときは注意してください。ブラウザ上では全部見えていても、PDFでは印字範囲の外にはみ出した分が消えます。登録番号は13桁あるので、桁が欠けると請求書として成立しません。列幅を変えたら、PDFを開いて目で確かめてください。
コード
以下を全文コピーして、Apps Scriptエディタに貼り付けてください。
/**
* ============================================================
* 請求書PDF作成スプレッドシート(GAS)— 無料版
* フリーランス・個人事業主向け
*
* この無料版でできること:
* - 「請求書」シートに直接入力 → メニュー1クリックでPDF化してDriveへ保存
* - インボイス制度対応(登録番号・品目・適用税率・税率別内訳を印字)
* - 軽減税率8% と 標準税率10% の混在に対応(端数は税率ごとに1回切り捨て)
* - 合計金額の自動計算
*
* 無料版に入っていない機能(有料版で提供):
* - 取引先マスタと請求データからの自動集計(複数社を一括発行)
* - 請求書番号の月別自動採番
* - 発行履歴への自動記録
* - 同月・同宛先への二重発行の警告
* - 源泉徴収 10.21% の自動計算
* - 合計金額の二重計算による検証
* → https://jimu-jidoka.booth.pm/items/8644136
*
* 動作要件: Google Apps Script V8 ランタイム
* ============================================================
*/
// ------------------------------------------------------------
// 定数(シート名・セル番地はすべてここに集約)
// ------------------------------------------------------------
const SHEET_CONFIG = '設定';
const SHEET_TEMPLATE = '請求書';
const CONFIG_KEYS = {
OWN_NAME: '事業者名',
OWN_ADDRESS: '住所',
OWN_TEL: '電話番号',
REG_NUMBER: '適格請求書発行事業者登録番号',
BANK_INFO: '振込先',
TAX_RATE_STD: '消費税率(標準)',
TAX_RATE_RED: '消費税率(軽減)',
PDF_FOLDER_ID: 'PDF保存フォルダID',
REMARKS: '備考(請求書に印字)',
};
// 「請求書」シートのセル番地(結合範囲は必ず左上アンカーを指す)
const T = {
CLIENT_NAME: 'A3', // A3:C3 結合
CLIENT_ADDRESS: 'A4', // A4:C5 結合
ISSUE_DATE: 'E3', // E3:F3 結合
INVOICE_NO: 'E4', // E4:F4 結合
OWN_NAME: 'E6',
OWN_ADDRESS: 'E7',
OWN_TEL: 'E8',
REG_NUMBER: 'E9',
TOTAL_HEADLINE: 'B9', // B9:C9 結合
BILLING_MONTH: 'A11',
DETAIL_START_ROW: 13,
DETAIL_MAX_ROWS: 20,
SUBTOTAL: 'E34',
TAX_STD: 'E35',
TAX_RED: 'E36',
TOTAL: 'E38',
TAX_STD_LABEL: 'D35',
TAX_RED_LABEL: 'D36',
BANK_INFO: 'A35', // A35:C37 結合
REMARKS: 'A40', // A40:F41 結合
PRINT_RANGE: 'A1:F42',
};
// ------------------------------------------------------------
// メニュー
// ------------------------------------------------------------
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('請求書')
.addItem('この請求書をPDFにする', 'exportCurrentInvoice')
.addSeparator()
.addItem('初期シートを作成', 'setupSheets')
.addSeparator()
.addItem('有料版でできること', 'showUpgradeInfo')
.addToUi();
}
function showUpgradeInfo() {
const ui = SpreadsheetApp.getUi();
ui.alert(
'有料版でできること',
[
'この無料版は「1枚の請求書をPDFにする」ところまでです。',
'',
'有料版では次の作業がなくなります。',
'',
'・取引先マスタと明細を入力しておけば、月を指定するだけで',
' 該当する取引先すべての請求書を一括で発行します',
'・請求書番号を月別に自動採番します(重複しません)',
'・発行のたびに履歴シートへ自動で記録します',
'・同じ月・同じ宛先へ2回発行しようとすると警告します',
'・源泉徴収10.21%を設定1箇所でON/OFFできます',
'・合計金額をシートの実データから別ルートで再計算して照合し、',
' 一致しなければ発行を止めます',
'',
'https://jimu-jidoka.booth.pm/items/8644136',
].join('\n'),
ui.ButtonSet.OK
);
}
// ------------------------------------------------------------
// PDF出力(無料版の本体)
// ------------------------------------------------------------
function exportCurrentInvoice() {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActiveSpreadsheet();
try {
const config = loadConfig_(ss);
const sheet = mustGetSheet_(ss, SHEET_TEMPLATE);
// 自社情報を差し込む
sheet.getRange(T.OWN_NAME).setValue(config[CONFIG_KEYS.OWN_NAME] || '');
sheet.getRange(T.OWN_ADDRESS).setValue(config[CONFIG_KEYS.OWN_ADDRESS] || '');
sheet.getRange(T.OWN_TEL).setValue(config[CONFIG_KEYS.OWN_TEL] ? 'TEL: ' + config[CONFIG_KEYS.OWN_TEL] : '');
sheet.getRange(T.REG_NUMBER).setValue(config[CONFIG_KEYS.REG_NUMBER] ? '登録番号: ' + config[CONFIG_KEYS.REG_NUMBER] : '');
sheet.getRange(T.BANK_INFO).setValue(config[CONFIG_KEYS.BANK_INFO] || '');
sheet.getRange(T.REMARKS).setValue(config[CONFIG_KEYS.REMARKS] || '');
// 明細から合計を計算して書き込む
const totals = calcTotalsFromSheet_(sheet, config);
sheet.getRange(T.SUBTOTAL).setValue(totals.subtotal);
// 税率別の行は、その税率の対象がある時だけ印字する(適用税率と対価の額はセットで表示)
writeTaxRow_(sheet, T.TAX_STD_LABEL, T.TAX_STD, totals.pctStd, totals.baseStd, totals.taxStd);
writeTaxRow_(sheet, T.TAX_RED_LABEL, T.TAX_RED, totals.pctRed, totals.baseRed, totals.taxRed);
sheet.getRange(T.TOTAL).setValue(totals.total);
sheet.getRange(T.TOTAL_HEADLINE).setValue('¥' + formatNumber_(totals.total));
SpreadsheetApp.flush();
const clientName = String(sheet.getRange(T.CLIENT_NAME).getValue() || '').replace(/\s*様$|\s*御中$/, '').trim();
if (!clientName) {
ui.alert('宛先が空です', '「請求書」シートの ' + T.CLIENT_NAME + ' に宛先を入力してください。', ui.ButtonSet.OK);
return;
}
// 発行日(取引年月日)は適格請求書の必須項目。空のままPDFにしない
if (!hasIssueDate_(sheet)) {
const answer = ui.alert(
'発行日が入っていません',
[
'発行日(取引年月日)は、適格請求書に必ず必要な項目です。',
'このままPDFにすると、要件を満たさない請求書になります。',
'',
'本日の日付(' + todayText_() + ')を入れて続けますか?',
'',
'「いいえ」を選ぶと中止します。別の日付にしたい場合は',
'「請求書」シートの ' + T.ISSUE_DATE + ' を直接編集してください。',
].join('\n'),
ui.ButtonSet.YES_NO
);
if (answer !== ui.Button.YES) return;
sheet.getRange(T.ISSUE_DATE).setValue('発行日: ' + todayText_());
SpreadsheetApp.flush();
}
const folder = getOrCreatePdfFolder_(ss, config);
const fileName = buildPdfFileName_(clientName, totals.total);
const file = exportPdf_(ss, sheet, fileName, folder);
ui.alert(
'PDFを作成しました',
[
fileName,
'',
'保存先フォルダ: ' + folder.getName(),
'',
'合計: ¥' + formatNumber_(totals.total) + '(税込)',
'',
'※ 複数社の一括発行・請求書番号の自動採番・発行履歴・',
' 二重発行の警告・源泉徴収は有料版の機能です。',
' メニュー「請求書 > 有料版でできること」をご覧ください。',
].join('\n'),
ui.ButtonSet.OK
);
Logger.log('作成: ' + file.getUrl());
} catch (e) {
ui.alert('エラー', String(e && e.message ? e.message : e), ui.ButtonSet.OK);
throw e;
}
}
// ------------------------------------------------------------
// 計算
// ------------------------------------------------------------
/**
* 「請求書」シートの明細エリアから合計を計算する。
* 端数処理は税率ごとに1回だけ切り捨てる(インボイス制度の要件)。
*/
function calcTotalsFromSheet_(sheet, config) {
// 表示する税率は必ず計算に使った税率と同じものを返す(設定を変えたら印字も変わる)
const pctStd = Number(config[CONFIG_KEYS.TAX_RATE_STD] || 10);
const pctRed = Number(config[CONFIG_KEYS.TAX_RATE_RED] || 8);
const rateStd = pctStd / 100;
const rateRed = pctRed / 100;
const rows = sheet
.getRange(T.DETAIL_START_ROW, 1, T.DETAIL_MAX_ROWS, 5)
.getValues();
let baseStd = 0;
let baseRed = 0;
let baseExempt = 0;
rows.forEach((row, i) => {
const item = String(row[0] || '').trim();
if (!item) return;
const qtyRaw = row[1];
const priceRaw = row[2];
if (qtyRaw === '' || priceRaw === '') {
throw new Error('明細 ' + (T.DETAIL_START_ROW + i) + ' 行目の数量または単価が空欄です。');
}
const amount = Number(qtyRaw) * Number(priceRaw);
sheet.getRange(T.DETAIL_START_ROW + i, 5).setValue(amount);
const taxType = String(row[3] || '').trim();
if (taxType.indexOf('8') >= 0 || taxType.indexOf('軽減') >= 0) {
baseRed += amount;
} else if (taxType.indexOf('対象外') >= 0 || taxType.indexOf('非課税') >= 0) {
baseExempt += amount;
} else {
baseStd += amount;
}
});
const taxStd = Math.floor(baseStd * rateStd);
const taxRed = Math.floor(baseRed * rateRed);
const subtotal = baseStd + baseRed + baseExempt;
return {
baseStd: baseStd,
baseRed: baseRed,
pctStd: pctStd,
pctRed: pctRed,
subtotal: subtotal,
taxStd: taxStd,
taxRed: taxRed,
total: subtotal + taxStd + taxRed,
};
}
// ------------------------------------------------------------
// PDF
// ------------------------------------------------------------
function getOrCreatePdfFolder_(ss, config) {
const id = String(config[CONFIG_KEYS.PDF_FOLDER_ID] || '').trim();
if (id) return DriveApp.getFolderById(id);
const parents = DriveApp.getFileById(ss.getId()).getParents();
const parent = parents.hasNext() ? parents.next() : DriveApp.getRootFolder();
const name = '請求書PDF';
const existing = parent.getFoldersByName(name);
return existing.hasNext() ? existing.next() : parent.createFolder(name);
}
// 税率別の1行を書く。対象が0円の税率は行ごと空にする(¥0の行を印字しない)
function writeTaxRow_(sheet, labelCell, valueCell, pct, base, tax) {
if (base <= 0) {
sheet.getRange(labelCell).clearContent();
sheet.getRange(valueCell).clearContent();
return;
}
sheet.getRange(labelCell).setValue('消費税' + pct + '%(対象¥' + formatNumber_(base) + ')');
sheet.getRange(valueCell).setValue(tax);
}
function todayText_() {
return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy/MM/dd');
}
// 「発行日: 」というラベルだけの状態を「未入力」とみなす
function hasIssueDate_(sheet) {
const raw = sheet.getRange(T.ISSUE_DATE).getValue();
if (raw instanceof Date) return true;
const text = String(raw || '').replace(/^\s*発行日\s*[::]?\s*/, '').trim();
return text.length > 0;
}
function buildPdfFileName_(clientName, total) {
const tz = Session.getScriptTimeZone();
const ymd = Utilities.formatDate(new Date(), tz, 'yyyyMMdd');
return ymd + '_' + sanitizeFileName_(clientName) + '_' + total + '.pdf';
}
function sanitizeFileName_(name) {
return String(name).replace(/[\\\/:*?"<>|]/g, '_').trim();
}
function exportPdf_(ss, sheet, fileName, folder) {
SpreadsheetApp.flush();
const url = 'https://docs.google.com/spreadsheets/d/' + ss.getId() + '/export'
+ '?format=pdf&gid=' + sheet.getSheetId()
+ '&size=A4&portrait=true&fitw=true'
+ '&sheetnames=false&printtitle=false&pagenumbers=false&gridlines=false&fzr=false'
+ '&range=' + encodeURIComponent(T.PRINT_RANGE)
+ '&top_margin=0.50&bottom_margin=0.50&left_margin=0.50&right_margin=0.50';
const response = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
muteHttpExceptions: true,
});
if (response.getResponseCode() !== 200) {
throw new Error('PDFの生成に失敗しました(HTTP ' + response.getResponseCode() + ')。時間を置いて再実行してください。');
}
return folder.createFile(response.getBlob().setName(fileName));
}
// ------------------------------------------------------------
// 共通
// ------------------------------------------------------------
function loadConfig_(ss) {
const sheet = mustGetSheet_(ss, SHEET_CONFIG);
const values = sheet.getRange('A2:B' + sheet.getLastRow()).getValues();
const config = {};
values.forEach(function (row) {
const key = String(row[0] || '').trim();
if (key) config[key] = row[1];
});
return config;
}
function mustGetSheet_(ss, name) {
const sheet = ss.getSheetByName(name);
if (!sheet) {
throw new Error('シート「' + name + '」が見つかりません。メニュー「請求書 > 初期シートを作成」を実行してください。');
}
return sheet;
}
function formatNumber_(n) {
return Number(n).toLocaleString('ja-JP');
}
// ------------------------------------------------------------
// 初期シート作成
// ------------------------------------------------------------
function setupSheets() {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActiveSpreadsheet();
const config = ss.getSheetByName(SHEET_CONFIG) || ss.insertSheet(SHEET_CONFIG);
const template = ss.getSheetByName(SHEET_TEMPLATE) || ss.insertSheet(SHEET_TEMPLATE);
buildConfigSheet_(config);
buildTemplateSheet_(template);
ui.alert(
'初期シートを作成しました',
[
'「設定」と「請求書」の2枚を作成しました。',
'',
'1. 「設定」シートに自社情報を入力してください',
'2. 「請求書」シートに宛先と明細を入力してください',
'3. メニュー「請求書 > この請求書をPDFにする」を押してください',
].join('\n'),
ui.ButtonSet.OK
);
}
function buildConfigSheet_(sheet) {
sheet.clear();
const rows = [
['項目', '値', '説明'],
[CONFIG_KEYS.OWN_NAME, 'サンプル制作所', '請求書の発行者名(屋号または氏名)'],
[CONFIG_KEYS.OWN_ADDRESS, '〒000-0000 サンプル県サンプル市見本町0-0-0', '請求書に印字する住所'],
[CONFIG_KEYS.OWN_TEL, '000-0000-0000', '空欄可'],
[CONFIG_KEYS.REG_NUMBER, 'T0000000000000', 'インボイス登録番号。未登録なら空欄'],
[CONFIG_KEYS.BANK_INFO, 'サンプル銀行 本店 普通 0000000 サンプル セイサクジョ', '改行を入れる場合は Ctrl+Enter'],
[CONFIG_KEYS.TAX_RATE_STD, 10, '標準税率(%)'],
[CONFIG_KEYS.TAX_RATE_RED, 8, '軽減税率(%)'],
[CONFIG_KEYS.PDF_FOLDER_ID, '', '空欄ならシートと同じ場所に「請求書PDF」フォルダを自動作成'],
[CONFIG_KEYS.REMARKS, '恐れ入りますが、振込手数料は貴社にてご負担をお願いいたします。', '請求書の備考欄に印字'],
];
sheet.getRange(1, 1, rows.length, 3).setValues(rows);
sheet.getRange('A1:C1').setFontWeight('bold').setBackground('#efefef');
sheet.setColumnWidth(1, 220);
sheet.setColumnWidth(2, 320);
sheet.setColumnWidth(3, 380);
sheet.setFrozenRows(1);
}
function buildTemplateSheet_(sheet) {
sheet.clear();
sheet.getRange('A1:F42').clearFormat();
sheet.setColumnWidth(1, 200);
sheet.setColumnWidth(2, 60);
sheet.setColumnWidth(3, 80);
// D列は「消費税10%(対象¥1,234,567)」を折り返さずに収める必要がある
sheet.setColumnWidth(4, 160);
sheet.setColumnWidth(5, 100);
// 自社情報(住所・登録番号)はE列からF列へはみ出して表示される。
// F列が狭いと印字範囲の右端で文字が切れ、登録番号の桁が欠ける
sheet.setColumnWidth(6, 150);
// タイトル
sheet.getRange('A1:F1').merge().setValue('請 求 書')
.setFontSize(18).setFontWeight('bold').setHorizontalAlignment('center');
// 宛先
sheet.getRange('A3:C3').merge().setValue('(宛先を入力) 様')
.setFontSize(13).setFontWeight('bold').setBorder(false, false, true, false, false, false);
sheet.getRange('A4:C5').merge().setValue('(宛先の住所)').setVerticalAlignment('top');
// 発行日・請求書番号(無料版は手入力)
// 発行日は適格請求書の必須項目なので、空欄で放置されないよう本日の日付を入れておく
sheet.getRange('E3:F3').merge().setValue('発行日: ' + todayText_()).setHorizontalAlignment('right');
sheet.getRange('E4:F4').merge().setValue('請求書番号: ').setHorizontalAlignment('right');
// 自社情報(住所が長い事業者でも右端で切れないよう1段小さくする)
sheet.getRange('E6:E9').setFontSize(9);
sheet.getRange('E6').setValue('').setFontWeight('bold');
sheet.getRange('E7').setValue('');
sheet.getRange('E8').setValue('');
sheet.getRange('E9').setValue('');
sheet.getRange('A7').setValue('下記の通りご請求申し上げます。');
// 合計見出し
sheet.getRange('A9').setValue('ご請求金額').setFontWeight('bold');
sheet.getRange('B9:C9').merge().setFontSize(16).setFontWeight('bold').setHorizontalAlignment('center');
sheet.getRange('D9').setValue('(税込)');
sheet.getRange('A9:C9').setBorder(false, false, true, false, false, false);
sheet.getRange('A10').setValue('お支払期限: ');
sheet.getRange('A11').setValue('ご請求対象: ');
// 明細ヘッダ
const header = [['品目', '数量', '単価', '税区分', '金額']];
sheet.getRange('A12:E12').setValues(header)
.setFontWeight('bold').setBackground('#efefef').setHorizontalAlignment('center');
// 明細エリア
sheet.getRange('A12:E32').setBorder(true, true, true, true, true, true);
sheet.getRange('B13:B32').setHorizontalAlignment('center');
sheet.getRange('C13:C32').setNumberFormat('¥#,##0');
sheet.getRange('D13:D32').setHorizontalAlignment('center');
sheet.getRange('E13:E32').setNumberFormat('¥#,##0');
// 税区分のプルダウン
const rule = SpreadsheetApp.newDataValidation()
.requireValueInList(['標準10%', '軽減8%', '対象外'], true)
.setAllowInvalid(false)
.build();
sheet.getRange('D13:D32').setDataValidation(rule);
// サンプル明細
sheet.getRange('A13:D13').setValues([['Webサイト更新作業(7月分)', 1, 30000, '標準10%']]);
// 金額まとめ
sheet.getRange('D34').setValue('小計(税抜)');
sheet.getRange('D34:E38').setBorder(true, true, true, true, true, true);
sheet.getRange('D34:D36').setFontSize(9); // 税率別ラベルが長いので1段小さくする
sheet.getRange('E34:E38').setNumberFormat('¥#,##0');
sheet.getRange('D38').setValue('合計金額(税込)');
sheet.getRange('D38:E38').setFontWeight('bold').setBackground('#efefef');
// 振込先
sheet.getRange('A34').setValue('お振込先').setFontWeight('bold');
sheet.getRange('A35:C37').merge().setWrap(true).setVerticalAlignment('top');
sheet.getRange('A34:C37').setBorder(true, true, true, true, false, false);
// 備考
sheet.getRange('A39').setValue('備考').setFontWeight('bold');
sheet.getRange('A40:F41').merge().setWrap(true).setVerticalAlignment('top');
sheet.getRange('A39:F41').setBorder(true, true, true, true, false, false);
sheet.setHiddenGridlines(true);
}
取引先が増えてきたら
このテンプレートは1枚ずつ発行する前提です。取引先が5社を超えたあたりから、別の問題が出てきます。
- 毎月同じ内容を5回入力し直すことになる
- 請求書番号を手で管理していると、抜けや重複が起きる
- 「先月あの会社に出したかどうか」が分からなくなる
このうち番号の重複は、自分で仕組みを組むといちばん起きやすいところです。実行のたびにカウンタをメモリ上で持っていると、実行が終わった時点で消えるため、次に実行したときに1に戻ることがあります。防ぐには Document Properties への永続化とドキュメントロックが要ります。
下は、有料版で3回に分けて発行したときの履歴です。INV-202607-01 から -06 まで、実行をまたいでも番号が戻らず重複もしていません。

源泉徴収を設定でONにすると、税込金額から10.21%を差し引いた行が請求書に入ります。下は税抜20,000円の例で、消費税2,000円を足して源泉所得税2,042円を引いた19,958円が請求額になっています。

複数社の一括発行・月別の自動採番・発行履歴・二重発行の警告・源泉徴収10.21%まで組んだ版は、有料で配布しています。同梱物・できないこと・実機での検証記録は 請求書自動作成スプレッドシート(GAS)の詳細ページ に書いています。無料版で足りるならそのまま使ってください。
請求の手前、つまり問い合わせや申込の受付そのものを自動化したい場合は、Googleフォームの受付を自動化する に別のコードを置いています。受付番号付きの自動返信と、対応ステータス付きの台帳まで作るものです。