Invoice Generator について
Invoice Generatorは、フリーランサー、代理店、企業向けにブランドに合わせた請求書をプログラムで作成するスキルです。金額計算、テンプレート、バッチ生成、定期生成に対応し、PDF、HTML、DOCX形式で出力します。
主な機能
- 01PDF、HTML、DOCX形式で請求書を生成
- 02税額、割引、合計額の計算
- 03ブランド付き請求書テンプレートへのデータ反映
- 04請求書の一括作成
- 05定期請求書の生成
活用シーン
- 01顧客向けブランド付き請求書の作成
- 02明細項目の税額と割引の計算
- 03複数顧客分の請求書をまとめて生成
- 04予定に基づく定期請求書の準備
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ eddiebe147/ claude-settings --skill invoice-generatorインストール後、Claude Code で/invoice-generatorを入力して読み込まれたか確認します。
詳細
請求書ジェネレーター
請求書ジェネレーターのスキルを使うと、プロフェッショナルなブランド入り請求書を複数の形式(PDF、HTML、DOCX)で自動生成できます。明細項目、税額計算、割引、支払い条件、企業ブランディングなど、請求書の標準的な構成要素をすべて処理します。プログラムから請求書を生成する必要があるフリーランサー、代理店、企業に最適です。
テンプレートから請求書を作成し、定期請求を自動化し、複数のクライアント向けに請求書を一括生成して、すべての請求書類で一貫したブランディングを維持できます。
主なワークフロー
ワークフロー 1:標準請求書の生成(PDF)
目的: 標準的な要素をすべて含む、プロフェッショナルな PDF 請求書を作成します
手順:
- 請求書データ(クライアント情報、明細項目、金額)を収集します
- 小計、税額、割引、合計を計算します
- 請求書テンプレートを読み込むか、レイアウトを作成します
- 企業ブランディング(ロゴ、色、連絡先情報)を追加します
- 請求書の詳細(番号、日付、支払期日)を入力します
- 説明と金額を記載した明細項目の表を追加します
- 支払い条件と方法を表示します
- 適切な書式で PDF を生成します
実装:
const PDFDocument = require('pdfkit');
const fs = require('fs');
function generateInvoice(invoiceData, outputPath) {
const doc = new PDFDocument({ margin: 50 });
const stream = fs.createWriteStream(outputPath);
doc.pipe(stream);
// Company header
doc.fontSize(20)
.text(invoiceData.company.name, 50, 50)
.fontSize(10)
.text(invoiceData.company.address, 50, 80)
.text(invoiceData.company.email, 50, 95)
.text(invoiceData.company.phone, 50, 110);
// Company logo (if exists)
if (invoiceData.company.logo) {
doc.image(invoiceData.company.logo, 450, 50, { width: 100 });
}
// Invoice title and number
doc.fontSize(24)
.text('INVOICE', 400, 150, { align: 'right' })
.fontSize(12)
.text(`Invoice #: ${invoiceData.invoiceNumber}`, 400, 180, { align: 'right' })
.text(`Date: ${invoiceData.date}`, 400, 195, { align: 'right' })
.text(`Due: ${invoiceData.dueDate}`, 400, 210, { align: 'right' });
// Bill to section
doc.fontSize(12)
.text('BILL TO:', 50, 150)
.fontSize(10)
.text(invoiceData.client.name, 50, 170)
.text(invoiceData.client.address, 50, 185)
.text(invoiceData.client.email, 50, 200);
// Line items table
const tableTop = 250;
doc.fontSize(10);
// Table header
doc.fillColor('#2C3E50')
.rect(50, tableTop, 500, 25)
.fill()
.fillColor('#FFFFFF')
.text('Description', 60, tableTop + 8)
.text('Quantity', 320, tableTop + 8)
.text('Rate', 390, tableTop + 8)
.text('Amount', 470, tableTop + 8);
// Table rows
let currentY = tableTop + 30;
doc.fillColor('#000000');
invoiceData.lineItems.forEach((item, index) => {
const y = currentY + (index * 25);
const bgColor = index % 2 === 0 ? '#FFFFFF' : '#F8F9FA';
doc.fillColor(bgColor)
.rect(50, y, 500, 25)
.fill()
.fillColor('#000000')
.text(item.description, 60, y + 8, { width: 240 })
.text(item.quantity, 320, y + 8)
.text(`$${item.rate.toFixed(2)}`, 390, y + 8)
.text(`$${(item.quantity * item.rate).toFixed(2)}`, 470, y + 8);
});
// Totals section
const totalsY = currentY + (invoiceData.lineItems.length * 25) + 30;
doc.text('Subtotal:', 380, totalsY)
.text(`$${invoiceData.subtotal.toFixed(2)}`, 470, totalsY);
if (invoiceData.tax) {
doc.text(`Tax (${invoiceData.taxRate}%):`, 380, totalsY + 20)
.text(`$${invoiceData.tax.toFixed(2)}`, 470, totalsY + 20);
}
if (invoiceData.discount) {
doc.text('Discount:', 380, totalsY + 40)
.text(`-$${invoiceData.discount.toFixed(2)}`, 470, totalsY + 40);
}
// Total
doc.fontSize(14)
.fillColor('#2C3E50')
.rect(380, totalsY + 60, 170, 30)
.fill()
.fillColor('#FFFFFF')
.text('TOTAL:', 390, totalsY + 68)
.text(`$${invoiceData.total.toFixed(2)}`, 470, totalsY + 68);
// Payment terms
doc.fillColor('#000000')
.fontSize(10)
.text('Payment Terms:', 50, totalsY + 120)
.fontSize(9)
.text(invoiceData.paymentTerms, 50, totalsY + 135, { width: 500 });
// Footer
doc.fontSize(8)
.fillColor('#7F8C8D')
.text('Thank you for your business!', 50, doc.page.height - 80, {
align: 'center',
width: 500
});
doc.end();
return new Promise((resolve, reject) => {
stream.on('finish', () => resolve(outputPath));
stream.on('error', reject);
});
}
ワークフロー 2:請求書の合計額の計算
目的: 小計、税額、割引、最終合計を自動計算します
手順:
- すべての明細項目の金額を合計して小計を算出します
- 割引(割合または固定額)を適用します
- 割引後の小計に対する税額を計算します
- 最終合計を算出します
- 小数点以下 2 桁に丸めます
- 計算結果を検証します
実装:
function calculateInvoiceTotals(lineItems, options = {}) {
// Calculate subtotal
const subtotal = lineItems.reduce((sum, item) => {
return sum + (item.quantity * item.rate);
}, 0);
// Apply discount
let discount = 0;
if (options.discountPercent) {
discount = subtotal * (options.discountPercent / 100);
} else if (options.discountAmount) {
discount = options.discountAmount;
}
const afterDiscount = subtotal - discount;
// Calculate tax
let tax = 0;
if (options.taxRate) {
tax = afterDiscount * (options.taxRate / 100);
}
// Calculate total
const total = afterDiscount + tax;
return {
subtotal: parseFloat(subtotal.toFixed(2)),
discount: parseFloat(discount.toFixed(2)),
discountedSubtotal: parseFloat(afterDiscount.toFixed(2)),
tax: parseFloat(tax.toFixed(2)),
taxRate: options.taxRate || 0,
total: parseFloat(total.toFixed(2)),
lineItemCount: lineItems.length
};
}
ワークフロー 3:テンプレートからの生成
目的: 一貫性を保つため、ブランド入り請求書テンプレートを使用します
手順:
- プレースホルダーを含む HTML/Word テンプレートを読み込みます
- テンプレート変数にデータを割り当てます
- プレースホルダーを実際のデータに置き換えます
- 明細項目のループを処理します
- 合計を計算して挿入します
- PDF に変換するか、Word ドキュメントとして保存します
- 企業ブランディングを自動的に適用します
実装:
const Handlebars = require('handlebars');
const puppeteer = require('puppeteer');
const fs = require('fs');
async function generateFromTemplate(templatePath, invoiceData, outputPath) {
// Load template
const templateHtml = fs.readFileSync(templatePath, 'utf8');
const template = Handlebars.compile(templateHtml);
// Register helpers
Handlebars.registerHelper('currency', function(value) {
return `$${parseFloat(value).toFixed(2)}`;
});
Handlebars.registerHelper('multiply', function(a, b) {
return (a * b).toFixed(2);
});
// Prepare data
const data = {
...invoiceData,
currentYear: new Date().getFullYear(),
formattedDate: new Date(invoiceData.date).toLocaleDateString(),
formattedDueDate: new Date(invoiceData.dueDate).toLocaleDateString()
};
// Render template
const html = template(data);
// Convert to PDF
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html);
await page.pdf({
path: outputPath,
format: 'A4',
margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' },
printBackground: true
});
await browser.close();
return outputPath;
}
// Template example (invoice-template.html):
/*
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.header { background: #2C3E50; color: white; padding: 20px; }
.line-items { width: 100%; border-collapse: collapse; }
.line-items th { background: #34495E; color: white; padding: 10px; }
.line-items td { border-bottom: 1px solid #ddd; padding: 8px; }
.totals { text-align: right; margin-top: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>{{company.name}}</h1>
<p>{{company.address}}</p>
</div>
<h2>Invoice #{{invoiceNumber}}</h2>
<p>Date: {{formattedDate}} | Due: {{formattedDueDate}}</p>
<h3>Bill To:</h3>
<p>{{client.name}}<br>{{client.address}}</p>
<table class="line-items">
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th>Rate</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{{#each lineItems}}
<tr>
<td>{{description}}</td>
<td>{{quantity}}</td>
<td>{{currency rate}}</td>
<td>{{currency (multiply quantity rate)}}</td>
</tr>
{{/each}}
</tbody>
</table>
<div class="totals">
<p>Subtotal: {{currency subtotal}}</p>
{{#if tax}}
<p>Tax ({{taxRate}}%): {{currency tax}}</p>
{{/if}}
<h3>Total: {{currency total}}</h3>
</div>
<p>{{paymentTerms}}</p>
</body>
</html>
*/
ワークフロー 4:請求書の一括生成
目的: 異なるクライアント向けの複数の請求書を一度に生成します
手順:
- 請求書の詳細を含むクライアントリストを読み込みます
- 各クライアントについて、次の処理を行います:
- 請求書データを準備します
- 合計を計算します
- 請求書(PDF またはその他の形式)を生成します
- 一意のファイル名で保存します
- 生成の成功/失敗を記録します
- 生成したすべての請求書の概要レポートを作成します
- 必要に応じて、請求書をメールで送信します
実装:
async function batchGenerateInvoices(invoices, outputDir) {
const results = [];
for (const invoice of invoices) {
try {
// Calculate totals
const totals = calculateInvoiceTotals(invoice.lineItems, {
taxRate: invoice.taxRate,
discountPercent: invoice.discountPercent
});
const invoiceData = {
...invoice,
...totals
};
// Generate filename
const filename = `${outputDir}/invoice-${invoice.invoiceNumber}-${invoice.client.name.replace(/\s+/g, '-')}.pdf`;
// Generate invoice
await generateInvoice(invoiceData, filename);
results.push({
success: true,
invoiceNumber: invoice.invoiceNumber,
client: invoice.client.name,
total: totals.total,
filename: filename
});
} catch (error) {
results.push({
success: false,
invoiceNumber: invoice.invoiceNumber,
client: invoice.client.name,
error: error.message
});
}
}
// Generate summary report
const summary = {
totalInvoices: invoices.length,
successful: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length,
totalAmount: results
.filter(r => r.success)
.reduce((sum, r) => sum + r.total, 0),
results: results
};
fs.writeFileSync(
`${outputDir}/batch-summary.json`,
JSON.stringify(summary, null, 2)
);
return summary;
}
ワークフロー 5:定期請求書の生成
目的: 月次/週次の定期請求書を自動化します
手順:
- 定期請求書のテンプレートを読み込みます
- 請求書生成の期日を確認します
- 期日を迎えた各請求書について、次の処理を行います:
- テンプレートデータを複製します
- 請求書番号を更新します(加算)
- 日付(請求日、支払期日)を更新します
- 新しい請求書を生成します
- 次回請求日を更新します
- 請求書の記録を保存します
- 必要に応じて、メール通知を送信します
実装:
function generateRecurringInvoices(recurringInvoices) {
const today = new Date();
const generated = [];
recurringInvoices.forEach(template => {
const nextBillingDate = new Date(template.nextBillingDate);
if (nextBillingDate <= today) {
// Generate invoice
const invoiceData = {
...template,
invoiceNumber: generateNextInvoiceNumber(template.invoiceNumberPrefix),
date: today.toISOString().split('T')[0],
dueDate: calculateDueDate(today, template.paymentTermsDays)
};
const totals = calculateInvoiceTotals(invoiceData.lineItems, {
taxRate: template.taxRate
});
generateInvoice({ ...invoiceData, ...totals }, `invoices/invoice-${invoiceData.invoiceNumber}.pdf`);
// Update next billing date
template.nextBillingDate = calculateNextBillingDate(
nextBillingDate,
template.frequency // 'monthly', 'weekly', 'quarterly'
);
generated.push(invoiceData);
}
});
return generated;
}
function calculateDueDate(fromDate, days) {
const dueDate = new Date(fromDate);
dueDate.setDate(dueDate.getDate() + days);
return dueDate.toISOString().split('T')[0];
}
function calculateNextBillingDate(currentDate, frequency) {
const nextDate = new Date(currentDate);
switch (frequency) {
case 'weekly':
nextDate.setDate(nextDate.getDate() + 7);
break;
case 'monthly':
nextDate.setMonth(nextDate.getMonth() + 1);
break;
case 'quarterly':
nextDate.setMonth(nextDate.getMonth() + 3);
break;
case 'yearly':
nextDate.setFullYear(nextDate.getFullYear() + 1);
break;
}
return nextDate.toISOString().split('T')[0];
}
クイックリファレンス
| アクション | コマンド/トリガー |
|---|---|
| 請求書を生成 | 「[client] の請求書を作成」 |
| テンプレートから作成 | 「[client] に請求書テンプレートを使用」 |
| 合計を計算 | 「請求書の合計を計算」 |
| 一括生成 | 「[clients] の請求書を作成」 |
| 定期請求書 | 「定期請求書を生成」 |
| 明細項目を追加 | 「請求書に項目を追加」 |
| 割引を適用 | 「[%] の割引を適用」 |
| 支払い済みにする | 「請求書 [number] を支払い済みにする」 |
ベストプラクティス
- 請求書番号の付与: 接頭辞を付けた連番を使用します(例:INV-2025-001)
- 支払期限: 支払条件(Net 30、Net 15 など)を明確に記載します
- 明細: 各明細項目の詳細な説明を記載します
- 税務コンプライアンス: 管轄区域に応じた適切な税率を適用します
- 支払方法: 利用可能なすべての支払方法を明確に記載します
- 連絡先情報: 請求に関する問い合わせ先として、メールアドレスと電話番号を記載します
- 通貨: 通貨(USD、EUR など)を必ず明記します
- バックアップ: 生成したすべての請求書のコピーを保管します
- バージョン管理: 変更が必要な場合に備え、請求書の改訂履歴を記録します
- ブランディング: すべての請求書で会社のブランドを一貫させます
- 法的情報: 必要に応じて、事業者登録番号や納税者番号を記載します
- 延滞料金: 該当する場合は、支払いが遅れた際の違約金を明記します
- アクセシビリティ: PDF がスクリーンリーダーで読み取れることを確認します
よくあるパターン
フリーランス向け請求書:
const freelanceInvoice = {
company: {
name: 'Your Name',
address: '123 Main St, City, State 12345',
email: '[email protected]',
phone: '(555) 123-4567'
},
client: {
name: 'Client Co.',
address: '456 Client Ave, City, State 67890',
email: '[email protected]'
},
invoiceNumber: 'INV-2025-001',
date: '2025-01-15',
dueDate: '2025-02-14', // Net 30
lineItems: [
{ description: 'Website Design - January 2025', quantity: 1, rate: 3000 },
{ description: 'Consultation (5 hours)', quantity: 5, rate: 150 }
],
taxRate: 0, // No tax for services in some jurisdictions
paymentTerms: 'Payment due within 30 days. Accepted methods: Bank transfer, PayPal, Check.'
};
プロジェクトベースの請求書:
const projectInvoice = {
lineItems: [
{ description: 'Phase 1: Discovery & Planning', quantity: 1, rate: 5000 },
{ description: 'Phase 2: Design Mockups', quantity: 1, rate: 7500 },
{ description: 'Phase 3: Development', quantity: 1, rate: 15000 },
{ description: 'Additional Revisions (8 hours)', quantity: 8, rate: 150 }
],
discountPercent: 10, // Early payment discount
taxRate: 8.5
};
依存関係
必要なパッケージをインストールします:
npm install pdfkit # For PDF generation
npm install handlebars # For templating
npm install puppeteer # For HTML to PDF conversion
npm install nodemailer # For email sending (optional)
エラー処理
- データの不足: 生成前に必須フィールドがすべて入力されていることを検証します
- 無効な金額: すべての金額が有効な数値であることを確認します
- 日付の検証: 日付形式と整合性(支払期限が請求日より後であること)を確認します
- ファイル権限: 請求書の保存時に発生する書き込みエラーに対処します
- テンプレートエラー: テンプレートのレンダリングエラーを適切に捕捉します
- 通貨の精度: 通貨は必ず小数点以下 2 桁に丸めます
パフォーマンスのヒント
- 会社のロゴやブランディング素材をキャッシュします
- 一括生成では PDF/テンプレートのインスタンスを再利用します
- 処理前にデータを事前検証します
- 大規模な一括処理ではストリーミングを使用します
- メール送信に備えて PDF を圧縮します
高度な機能
請求書のメール送信:
const nodemailer = require('nodemailer');
async function emailInvoice(invoiceData, pdfPath) {
const transporter = nodemailer.createTransport({ /* config */ });
await transporter.sendMail({
from: invoiceData.company.email,
to: invoiceData.client.email,
subject: `Invoice ${invoiceData.invoiceNumber} from ${invoiceData.company.name}`,
html: `
<p>Dear ${invoiceData.client.name},</p>
<p>Please find attached invoice ${invoiceData.invoiceNumber} for $${invoiceData.total}.</p>
<p>Payment is due by ${invoiceData.dueDate}.</p>
<p>Thank you for your business!</p>
`,
attachments: [
{
filename: `invoice-${invoiceData.invoiceNumber}.pdf`,
path: pdfPath
}
]
});
}
支払いの追跡:
const invoiceStatus = {
invoiceNumber: 'INV-2025-001',
status: 'unpaid', // 'unpaid', 'paid', 'overdue', 'cancelled'
paidDate: null,
paidAmount: 0,
paymentMethod: null
};
コメント
Markdown 対応