关于 Invoice Generator
Invoice Generator 是面向自由职业者、代理机构和企业的技能,可通过程序生成品牌化发票。它涵盖金额计算、模板、批量与定期生成,并支持 PDF、HTML 或 DOCX 格式。
主要功能
- 01生成 PDF、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