mcpField

Invoice Generator

github.com/eddiebe147文档处理47 次安装
代码仓库

关于 Invoice Generator

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

主要功能

  1. 01生成 PDF、HTML 或 DOCX 发票
  2. 02计算税额、折扣和总额
  3. 03填充品牌化发票模板
  4. 04批量创建发票
  5. 05生成定期发票

适用场景

  1. 01为客户制作品牌化发票
  2. 02计算明细项目的税额与折扣
  3. 03一次为多个客户生成发票
  4. 04准备按计划生成的定期账单

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/eddiebe147/claude-settings --skill invoice-generator

安装后在 Claude Code 中输入/invoice-generator确认已加载。

详细介绍

发票生成器

发票生成器技能可自动生成多种格式(PDF、HTML、DOCX)的专业品牌发票。它支持所有标准发票组成部分,包括明细项、税费计算、折扣、付款条款和公司品牌元素。非常适合需要以编程方式生成发票的自由职业者、代理机构和企业。

使用模板创建发票、自动处理周期性账单、为多个客户批量生成发票,并确保所有账单文档的品牌风格一致。

核心工作流

工作流 1:生成标准发票(PDF)

目的: 创建包含所有标准元素的专业 PDF 发票

步骤:

  1. 收集发票数据(客户信息、明细项、金额)
  2. 计算小计、税费、折扣和总计
  3. 加载发票模板或创建版式
  4. 添加公司品牌元素(徽标、颜色、联系信息)
  5. 填写发票详情(编号、日期、到期日)
  6. 添加包含描述和金额的明细项表格
  7. 显示付款条款和方式
  8. 以适当的格式生成 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:计算发票总额

目的: 自动计算小计、税费、折扣和最终总额

步骤:

  1. 汇总所有明细项金额,得出小计
  2. 应用折扣(百分比或固定金额)
  3. 计算折后小计的税费
  4. 计算最终总额
  5. 处理舍入,保留 2 位小数
  6. 验证计算结果

实现:

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:使用模板生成

目的: 使用品牌发票模板,保持风格一致

步骤:

  1. 加载带占位符的 HTML/Word 模板
  2. 定义模板变量的数据映射
  3. 将占位符替换为实际数据
  4. 处理明细项循环
  5. 计算并填入总额
  6. 转换为 PDF 或保存为 Word 文档
  7. 自动应用公司品牌元素

实现:

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:批量生成发票

目的: 一次性为不同客户生成多张发票

步骤:

  1. 加载包含发票详情的客户列表
  2. 对每位客户:
    • 准备发票数据
    • 计算总额
    • 生成发票(PDF 或其他格式)
    • 使用唯一文件名保存
  3. 跟踪生成成功或失败的情况
  4. 创建所有已生成发票的汇总报告
  5. 可选择通过电子邮件发送发票

实现:

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:生成周期性发票

目的: 自动处理每月/每周的周期性发票

步骤:

  1. 加载周期性发票模板
  2. 检查发票生成到期日
  3. 对每张到期发票:
    • 复制模板数据
    • 更新发票编号(递增)
    • 更新日期(发票日期、到期日)
    • 生成新发票
  4. 更新下一个账单日期
  5. 保存发票记录
  6. 可选择发送电子邮件通知

实现:

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