Invoice Generation skill for Claude Code

Gapless numbers, exact cents, immutable invoices. Claude models invoices as drafts that become immutable when issued, assigns gapless numbers per series inside the issuing transaction, and computes totals with Decimal under one written rounding policy shared by PDF, XML and ledger. Corrections go through credit notes, e-invoice formats come from the same frozen data, and archived files carry a hash in write-once storage.

Name
invoice-generation
Size
190 lines · ≈2,300 tokens
Files
1
License
MIT
Updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Builds invoice and receipt generation that accountants and tax authorities accept - gapless sequential numbering per series assigned at finalization, immutable issued invoices with credit notes for corrections, required fields and VAT/GST/sales-tax lines, Decimal arithmetic with an explicit rounding policy, multi-currency, HTML-to-PDF templates, structured e-invoice formats (EN 16931, UBL/Peppol, Factur-X/ZUGFeRD), idempotent generation, and tamper-evident archiving with retention. Use when adding invoicing or receipts to an app, totals are off by a cent, numbering has gaps or duplicates, a customer needs a corrected invoice, or e-invoicing requirements apply.

Try asking

  • Add invoice generation with PDF output to our B2B SaaS
  • Our invoice totals are off by one cent against the accounting export
  • We need to correct an invoice we already sent - how should the system handle it?

Use it when

  • Adding invoices, credit notes, or receipts to an application
  • Totals and tax lines do not reconcile to the cent
  • Invoice numbers have gaps, duplicates, or race conditions
  • Structured e-invoicing formats are required for some customers

Skip it when

  • Stripe Invoicing already issues your invoices - use stripe-payments
  • You only need PDF layout and page breaks fixed - use html-to-pdf

How to check the result

Property tests show subtotal plus tax equals total for random lines, 50 parallel issues yield consecutive unique numbers, and archived PDF hashes match the stored values.

The common mistake it prevents

Using a database sequence or auto-increment id as the invoice number, which skips values on rolled-back transactions and leaves gaps auditors question.

~/.claude/skills/invoice-generation/SKILL.md

--- name: invoice-generation description: Builds invoice and receipt generation that accountants and tax authorities accept - gapless sequential numbering per series assigned at… license: MIT ---

An invoice system is right when numbers are unique, sequential, and never reused, an issued invoice never changes (corrections are credit notes), every amount is computed with decimal arithmetic under a written rounding rule so line totals, tax lines, and the grand total reconcile to the cent, the PDF and any structured e-invoice carry identical data, and the archived copy can be produced years later byte for byte. Tax rules vary by jurisdiction; confirm requirements with the user’s accountant - this is not tax advice.

Workflow

- [ ] 1. Collect requirements: seller entities, jurisdictions, tax regimes, currencies, B2B/B2C, e-invoicing mandates
- [ ] 2. Model drafts, issued invoices, credit notes, and number series
- [ ] 3. Implement totals with Decimal and the chosen rounding policy
- [ ] 4. Assign numbers atomically at finalization
- [ ] 5. Render PDF (and XML e-invoice where required) from the frozen invoice data
- [ ] 6. Archive immutably with a hash; deliver; record delivery
- [ ] 7. Test edge cases and reconcile against the ledger

2. Data model

invoice_series   (id, seller_entity_id, prefix, next_number, kind: invoice|credit_note)
invoice          (id, series_id, number NULL until issued, status: draft|issued|void_by_credit|paid,
                  issue_date, supply_date, due_date, currency, seller_snapshot jsonb, buyer_snapshot jsonb,
                  subtotal, tax_total, total, rounding_policy, pdf_sha256, issued_at)
invoice_line     (invoice_id, position, description, quantity, unit_price, discount, tax_rate_code,
                  tax_rate, line_net, line_tax)
invoice_tax_summary (invoice_id, tax_rate_code, rate, taxable_base, tax_amount)
credit_note      -> same shape, references original invoice_id and reason
  • Snapshot seller and buyer details (legal name, address, tax IDs) onto the invoice at issue time. Later customer edits must not change old invoices.
  • Store amounts as numeric(18, 4) or integer minor units with the currency’s exponent (JPY 0, USD 2, KWD 3). Never floats.
  • Status transitions: draft -> issued -> paid / partially paid; corrections create a credit note (full or partial) and, if needed, a new invoice. Never edit or delete an issued invoice.
  • Receipts (proof of payment) are separate documents generated from payments, not a relabeled invoice.

3. Totals and rounding

from decimal import Decimal, ROUND_HALF_UP

CENT = Decimal("0.01")

def q(x: Decimal) -> Decimal:
    return x.quantize(CENT, rounding=ROUND_HALF_UP)

lines = [
    {"qty": Decimal("3"), "unit": Decimal("19.99"), "rate": Decimal("0.20")},
    {"qty": Decimal("1"), "unit": Decimal("4.15"),  "rate": Decimal("0.05")},
]
for l in lines:
    l["net"] = q(l["qty"] * l["unit"])

# Policy A (common in EU): round tax per rate on the summed base
by_rate = {}
for l in lines:
    by_rate.setdefault(l["rate"], Decimal("0"))
    by_rate[l["rate"]] += l["net"]
tax_lines = {rate: q(base * rate) for rate, base in by_rate.items()}

subtotal = sum(l["net"] for l in lines)
tax_total = sum(tax_lines.values())
total = subtotal + tax_total
PolicyRuleTypical
Per-rate on base (A)sum net per tax rate, then compute and round tax once per ratemany EU VAT invoices, EN 16931
Per-linecompute and round tax on each line, sum the rounded taxessome sales-tax systems, POS receipts
Tax-inclusive prices (B2C)prices include tax; extract tax = gross x rate / (1 + rate) per rateretail, consumer apps

Pick one per seller entity with the accountant, store it on the invoice (rounding_policy), and use the same rule in the PDF, the e-invoice XML, and the ledger export. Mixed policies are the usual source of one-cent mismatches.

  • Use ROUND_HALF_UP unless the jurisdiction or accountant specifies otherwise; Python’s default ROUND_HALF_EVEN differs on .5 cases.
  • Discounts: decide whether they apply before tax per line or as a separate line with its own tax treatment; document it.
  • Multi-currency: invoice in the customer’s currency; store the exchange rate, its source, and date when tax must also be reported in the local currency.
  • US sales tax depends on destination address and product taxability; use a tax engine (Stripe Tax, Avalara, TaxJar) rather than hardcoding rates.

4. Numbering

Requirements in many jurisdictions: unique, sequential within a series, no unexplained gaps, assigned when the invoice is issued (not when a draft is created).

-- Atomic assignment inside the issue transaction
UPDATE invoice_series SET next_number = next_number + 1
WHERE id = $1
RETURNING prefix, next_number - 1 AS number;      -- row lock serializes concurrent issuers
-- then: UPDATE invoice SET number = ..., status = 'issued', issued_at = now() WHERE id = $2 AND status = 'draft'
  • Do not use database sequences or auto-increment IDs for invoice numbers; they skip values on rollback and leave gaps.
  • Separate series per legal entity and per document type (credit notes often have their own prefix); a yearly reset (2026-000142) is allowed in many places, check locally.
  • Draft invoices have no number. Deleting drafts never creates gaps.
  • Add a unique constraint on (series_id, number).
  • Make issuing idempotent: an issue request with the same idempotency key returns the already-issued invoice instead of consuming another number.

Required content checklist

Confirm per jurisdiction; common elements for B2B tax invoices:

  • “Invoice” (or “Tax invoice”) label, unique number, issue date, supply/delivery date or period
  • Seller legal name, address, tax/VAT ID; company registration details where required
  • Buyer legal name, address, and VAT ID for B2B (required for reverse charge)
  • Line description, quantity, unit price, net amount, tax rate per line
  • Tax summary per rate: taxable base and tax amount; totals net, tax, gross; currency
  • Exemption or reverse-charge wording when tax is not charged (with the legal basis text the accountant provides)
  • Payment terms, due date, bank details or payment link, late payment terms
  • For credit notes: reference to the original invoice number and date, reason

5. Rendering

  • Generate the PDF from the frozen invoice data with an HTML template and an HTML-to-PDF engine (see html-to-pdf). Template versions are code: changing a template must not alter archived PDFs, so archive the rendered file, not just the data.
  • Locale-aware formatting for display only (1.234,56 EUR vs EUR 1,234.56); keep calculations locale-free.
  • Include the seller’s logo as a local asset; embed fonts.
  • Show amounts that sum visibly: line nets add to the subtotal; tax lines add to tax total.

E-invoicing

Structured invoices are mandatory or becoming mandatory in many countries (EU B2G under EN 16931, Italy SDI, Poland KSeF, phased B2B mandates in Germany, France, Belgium and others; Mexico CFDI, India GST e-invoicing). Requirements and dates change; check the current rules for each seller entity before building.

FormatNotes
UBL 2.1 / Peppol BIS Billing 3.0XML exchanged over the Peppol network via an access point provider
CII (UN/CEFACT)XML syntax also allowed under EN 16931
Factur-X / ZUGFeRDPDF/A-3 with embedded CII XML - one file readable by humans and machines
XRechnungGerman CIUS of EN 16931 for public sector

Generate XML from the same frozen data as the PDF, validate it against the official schematron rules for the profile, and usually send through a certified provider rather than building network connectivity.

6. Archiving

  • Store the final PDF (and XML) in write-once storage (S3 Object Lock, WORM bucket) with its SHA-256 recorded on the invoice row.
  • Retention periods are commonly 5-10 years depending on country; confirm and set lifecycle rules accordingly. Never let routine cleanup jobs touch issued documents.
  • Log issue, send, view, and payment events (audit trail).
  • Regenerating a “copy” must return the archived file, not a fresh render.

Gotchas

  • Changing a customer’s address updates old invoices if you join live customer data; use snapshots.
  • Emailing a PDF with the invoice number in the filename and subject reduces “which invoice?” support tickets; include a stable download link that serves the archived file.
  • Partial refunds need partial credit notes with the tax split per rate, not a negative free-form line.
  • Time zones: issue date is the seller’s local date, not UTC midnight.
  • Stripe and similar billing platforms number and archive invoices themselves; if already using Stripe Invoicing, do not build a parallel numbering system (see stripe-payments).

Verification

  • Property test: for random line sets, subtotal + tax_total = total exactly and per-rate tax lines equal the policy formula.
  • Concurrency test: 50 parallel issue calls produce 50 consecutive unique numbers, no gaps.
  • Idempotency test: repeated issue with the same key returns the same number.
  • Credit note test: original stays unchanged; net of invoice plus credit note equals the intended amount.
  • Archived PDF hash matches the stored hash; e-invoice XML passes schematron validation.

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc