Skip to content

About

Creates a CSV for importing batch customer invoices into GnuCash. Also batch creates statements for Customers.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

29 Commits

Folders and files

Repository files navigation

GnuCash Invoice Batch Creator

A local/internal-network web application for creating batch customer invoice CSV files that can be imported into GnuCash, and for generating batch customer report PDFs from a revised uploaded GnuCash book.

The initial goal is monthly or recurring invoices/dues: create an entity/profile, upload a copy of that entity's GnuCash book, select customers directly from the uploaded GnuCash book, choose shared invoice parameters once, preview the batch, optionally save the customer group and/or invoice template, then generate a GnuCash invoice import CSV. After the CSV is imported into GnuCash, upload the revised book copy and reuse the same group to generate Customer Report PDFs as a ZIP.

Repository: https://github.com/thystra/gnucash-invoice-batch-creator

Release notes are maintained in CHANGELOG.md.

What this tool does

  • Stores multiple entities/profiles, such as ORG A, ORG B, or ENTITY A LLC.
  • Stores multiple uploaded GnuCash book copies per entity/profile.
  • Scans the active uploaded GnuCash book copy for customers and existing invoice IDs.
  • Suggests the next available invoice number.
  • Presents customers from the uploaded GnuCash book in the GUI, using the customers.id column as the customer ID.
  • Filters inactive customers by default, based on customers.active where 1 is active and 0 is inactive.
  • Provides a Show inactive customers option for unusual or historical billing batches.
  • Allows customer selection to be sorted by customer ID or by name.
  • Provides a balance-aware Select non-zero visible shortcut in the Batch Wizard and Groups page when the uploaded book is SQLite and transaction-level A/R balances can be scanned.
  • Scans the uploaded book for valid income and Accounts Receivable accounts, then uses those scanned accounts in account selection fields.
  • Still supports optional CSV, text, or XLSX customer-ID uploads as a fallback for external rosters.
  • Matches selected or uploaded IDs against GnuCash customers.
  • Walks the user through a local web wizard to create batch invoices.
  • Saves reusable customer groups under each entity/profile, either during invoice generation or directly from the Groups page without generating invoices.
  • Saves reusable invoice templates under each entity/profile.
  • Generates a GnuCash invoice import CSV under each entity/profile.
  • Generates GnuCash-like Customer Report PDFs in batch using saved groups and Chromium PDF rendering.
  • Exports existing invoices as individual PDF files, selected by date range, saved customer group, single customer, or by individual invoice selection.
  • Supports multiple A/R accounts in customer reports.
  • Stores report appearance settings, logo/banner uploads, custom CSS, and optional exported GnuCash HTML style references per entity/profile.
  • Supports posted invoices by default, with a configuration option to generate unposted invoices.
  • Includes a root index.php local front controller and index.html redirect for convenience when browsing the repo directory directly.

Important safety notes

Run this on localhost or a trusted internal network only. This tool reads customer data from your GnuCash book copies and temporarily stores uploaded files and generated CSV files.

Do not point a public web server at this repository. The preferred NGINX root is still the public/ directory. The root index.html is only a convenience redirect for local use; it is not a substitute for keeping config/, var/, and source files out of the web root.

Upload a copy of your GnuCash file for testing before importing generated CSV files into your live book. For report generation, upload the revised copy after importing invoices into GnuCash; the report generator reads the uploaded copy and does not modify it.

GnuCash import behavior

GnuCash imports customer invoices from rows of invoice entry data. The import file contains no explicit customer-invoice-vs-vendor-bill marker; that choice is made in GnuCash's import dialog. GnuCash takes invoice header data from the first row for each invoice ID, and if any row for an invoice has an error, the whole invoice is ignored.

GnuCash's invoice import format also does not support automatic invoice numbering, so this tool scans existing invoice IDs and generates IDs before import.

In GnuCash, import the generated file with:

File -> Import -> Import Bills & Invoices...

Then choose:

  • Import type: Invoice
  • CSV format: normally Comma separated with quotes
  • Date format: match the date format generated by this tool and your GnuCash preferences
  • Afterwards: choose not to open imported documents in tabs for large batches

Customer groups

Open Groups to create or update a reusable customer group directly from the active uploaded GnuCash book. This does not create invoices and does not require a generated CSV. Active customers are selected by default, inactive customers are hidden unless you choose Show inactive customers, and the list can be sorted by name or by customer ID.

This is the preferred clean-install path when you only want to generate customer statements/reports from an uploaded book. Create the group first, then open Reports and select that group.

In-app Help tab

The web UI includes a Help / Instructions tab with the condensed import workflow. The key GnuCash choices are:

  • File -> Import -> Import Bills & Invoices...
  • Import type: Invoice
  • CSV format: normally Comma separated with quotes when the tool is configured for comma output
  • Date format: this tool's GnuCash import date format setting must match the date format configured in GnuCash Preferences
  • Afterwards: choose Do not open imported documents in tabs, especially for large batches

Date format

GnuCash validates invoice import dates against the date format set in GnuCash Preferences. The tool's Settings page has a GnuCash import date format value for CSV generation. Common values are:

  • m/d/Y for MM/DD/YYYY
  • d/m/Y for DD/MM/YYYY
  • Y-m-d for YYYY-MM-DD

The browser form still uses ISO dates internally, but the generated CSV converts date_opened, line date, date_posted, and due_date to the configured import format.

Prices and discounts are exported as explicit fixed-point decimal values with two fractional digits, such as 15.00 instead of 15. Quantity is exported as an explicit decimal, such as 1.0. This avoids GnuCash importing whole-dollar or whole-quantity values as cents/hundredths when decimal/locale import settings are not what the user expects.

Requirements

Designed for:

  • Ubuntu 26.04
  • NGINX
  • PHP 8.5 / PHP-FPM
  • Python 3
  • GnuCash book saved as SQLite or XML/gzipped XML

Install baseline packages:

sudo apt update
sudo apt install nginx php8.5-fpm php8.5-cli php8.5-sqlite3 python3 python3-openpyxl unzip zip git chromium

python3-openpyxl is optional but enables XLSX customer-list uploads. CSV and plain text uploads work without it. chromium is used for Customer Report PDF rendering. On Ubuntu desktop systems, Chromium may be installed through snap at /snap/bin/chromium; the Settings page checks the configured path, tries /snap/bin/chromium, then common manual install paths and command -v chromium.

Check PHP modules:

php -m | grep -Ei 'json|session|sqlite3'

Directory setup

Recommended development path:

mkdir -p /home/$USER/public_html/gnucash-invoice-batch-creator
cd /home/$USER/public_html/gnucash-invoice-batch-creator

Clone or initialize the repository:

git clone https://github.com/thystra/gnucash-invoice-batch-creator.git .

Create writable runtime directories and repair ownership/ACLs:

cd /home/$USER/public_html/gnucash-invoice-batch-creator
bash bin/setup-local-permissions.sh

The helper script reclaims existing runtime files that may have been created by www-data, sets the project owner/group to $USER:publicweb, restores setgid directories, and applies default ACLs to config/ and var/. This is the best repair when recursive chmod fails on a generated file owned by www-data.

If PHP-FPM continues to run this app as www-data, new generated CSV/PDF/ZIP files will still be owned by www-data. ACLs and the publicweb group can make those files writable by your shell user, but Linux file ownership follows the process that creates the file.

For a single-user local workstation setup where generated files should be owned by your user, use a dedicated PHP-FPM pool for this app:

cd /home/$USER/public_html/gnucash-invoice-batch-creator
sudo bash bin/install-local-fpm-pool.sh "$USER" publicweb

Then update your nginx location for this app to use:

fastcgi_pass unix:/run/php/gnucash-invoice-batch-creator.sock;

See config/nginx-local-example.conf for examples. After changing nginx, run:

sudo nginx -t
sudo systemctl reload nginx

The important bits are: dedicated PHP-FPM pool user = your shell user, project group = publicweb, setgid directories, and default ACLs on var/ and config/.

Choosing the PHP-FPM open_basedir scope

The default bin/install-local-fpm-pool.sh behavior is intentionally strict: it limits PHP to the current clone path. That is safest for a single app, but it is awkward when you test multiple local GnuCash tools side by side.

For a local/trusted workstation, you can broaden the allowed root to all of public_html:

cd /home/$USER/public_html/invoices
sudo bash bin/install-local-fpm-pool.sh "$USER" publicweb 8.5 "$(pwd)" public_html

That makes the pool allow:

/home/$USER/public_html

and all subdirectories.

A cleaner middle ground for the invoice tool plus the vendor/bill import tool is a shared parent directory:

/home/$USER/public_html/gnucashtools/invoices
/home/$USER/public_html/gnucashtools/bills

For that layout, install one shared PHP-FPM pool:

mkdir -p /home/$USER/public_html/gnucashtools/{invoices,bills}
cd /home/$USER/public_html/gnucashtools/invoices
sudo bash bin/install-gnucash-tools-fpm-pool.sh "$USER" publicweb 8.5 /home/$USER/public_html/gnucashtools

Then both GnuCash tools can use:

fastcgi_pass unix:/run/php/gnucash-tools.sock;

You still need separate nginx location blocks for each URL path, because nginx must map /gnucashtools/invoices/ and /gnucashtools/bills/ to different filesystem directories. The PHP-FPM pool/socket can be shared; the nginx URL-to-file mapping cannot be shared unless both tools have the same document root.

Profile/entity storage layout

Each entity/profile is stored under:

var/profiles/<profile-slug>/
  profile.json
  books/
  uploads/
  generated/
  groups/
  templates/
  report-assets/
  report-templates/
  reports/
    customer-reports/

This keeps ORG A's uploaded book copies, groups, templates, uploads, and generated CSVs separate from ORG B's.

The legacy top-level var/uploads, var/generated, var/groups, and var/templates directories remain for early development compatibility, but new wizard data is profile-specific.

NGINX example

Use a server block with root pointing at public/:

server {
    listen 127.0.0.1:8088;
    server_name gnucash-invoice-batch-creator.local localhost;

    root /home/<YOUR USER NAME>/public_html/gnucash-invoice-batch-creator/public;
    index index.php;

    client_max_body_size 100M;

    location / {
        try_files $uri /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.5-fpm.sock;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }
}

Enable and reload:

sudo nginx -t
sudo systemctl reload nginx

For LAN use, bind to your LAN IP instead of 127.0.0.1, and firewall it to trusted machines only.

First-run setup

Open the app in a browser. If there are no profiles, the app opens the first-entity setup wizard.

The first-run wizard asks for:

  • Entity/profile name, such as ORG A
  • An initial GnuCash book copy upload

After creating the profile, the app activates it and sends you to the batch wizard. The wizard scans the uploaded book and lists active customers directly from the GnuCash customers table.

Settings page

The Settings page lets you:

  • Switch the active entity/profile.
  • Create additional entities/profiles.
  • Upload additional GnuCash book copies for the active entity.
  • Choose which uploaded book copy is active.
  • Delete stored book copies.
  • Delete an entire entity/profile and its stored data.
  • Configure global defaults such as Python path, Chromium path, scanned income/A/R account defaults, invoice ID prefix, posting default, tax defaults, GnuCash import date format, and timezone.
  • View runtime writable checks for config/, var/, and active profile directories.

The app writes local settings to config/config.php, which is intentionally ignored by git.

Batch invoice wizard workflow

The primary workflow is now fully GUI-driven:

  1. Upload or select an active GnuCash book copy for the current entity/profile.
  2. Open Batch Wizard.
  3. Review the customer list scanned from the book. Active customers are checked by default. Sort by ID or by name as needed.
  4. Use Select non-zero visible when you want to bill or review only customers whose current A/R balance is not zero. This shortcut is available when the uploaded book is SQLite and transaction-level balances can be scanned.
  5. Use Show inactive customers only when you deliberately need to include inactive customers. Inactive customers are visible but unchecked by default.
  6. Continue with the selected customers. Once a selection exists, the large customer list collapses so the invoice fields are easier to reach.
  7. Enter the shared invoice values, such as description, dates, scanned income account, scanned A/R account, price, posting option, and tax settings.
  8. Optionally save the selected customers as a reusable group and save the invoice values as a reusable template.
  9. Generate and download the GnuCash invoice CSV.

The old customer-ID upload workflow remains under Saved groups and alternate input for external rosters, but it is no longer required for normal use.

Batch Customer Reports

The report workflow is:

  1. Generate a GnuCash invoice CSV from a saved group/template.
  2. Import the CSV into GnuCash.
  3. Save a revised copy of the GnuCash book.
  4. Upload that revised copy into the same entity/profile.
  5. Open Reports, select the same saved customer group, choose the date range and A/R accounts, and generate PDFs.
  6. Download the generated ZIP.

The report generator intentionally uses a tool-owned HTML template rendered by Chromium rather than trying to automate GnuCash's Scheme report engine for each customer. This is more reliable for bulk output and allows better pagination controls. The Settings page includes a Chromium sanity check and will detect /snap/bin/chromium when Ubuntu installs Chromium as a snap.

For brand continuity, each profile can store:

  • Header organization/entity name
  • Logo or banner image
  • Footer text
  • Custom CSS
  • Optional exported GnuCash Customer Report HTML as a style reference

To use the style reference option, generate one Customer Report manually in GnuCash, export it as HTML, and upload that HTML on the Reports page. The tool extracts CSS from the exported HTML and layers it into the GnuCash-like report template.

Report generation currently requires a SQLite GnuCash book copy for transaction-level data. XML book scanning still works for customer/invoice ID discovery, but the transaction-level Customer Report generator expects the SQLite schema.

Command-line report metadata check:

python3 bin/gnc_batch_invoice.py report-metadata --book /path/to/book.gnucash

Command-line report generation expects JSON on stdin and is normally driven by the web UI:

python3 bin/gnc_batch_invoice.py customer-reports --book /path/to/book.gnucash --out-dir /tmp/customer-reports < report-batch.json

Per-invoice PDF exports

Open Invoice PDFs to export existing GnuCash invoices as separate PDF files. The workflow is:

  1. Upload/select a revised SQLite GnuCash book copy for the entity/profile.
  2. Open Invoice PDFs.
  3. Choose an invoice date range and, optionally, a saved customer group filter or a single customer from the active book.
  4. Click Show available invoices.
  5. Review invoices grouped by customer and deselect any invoices you do not want. For one-off exports, use the Single customer filter dropdown rather than creating a saved group.
  6. Generate the batch. Each selected invoice is rendered as its own PDF and all PDFs are packaged into invoice-pdfs.zip.

Invoice PDF export currently requires a SQLite GnuCash book copy because it reads invoice, customer, entry, transaction, split, and account tables directly. XML book scanning still works for customer/invoice ID discovery, but invoice PDF rendering expects SQLite table data.

The PDF layout is tool-owned HTML rendered by Chromium. It reuses report appearance settings where practical, including organization name, logo, page size, style-reference CSS, custom CSS, filename customer source, filename date format, and page-number rendering. The default filename template is:

{customer} - {invoice_id} - invoice

Supported invoice filename variables include {customer}, {customer_id}, {customer_number}, {company_name}, {billing_name}, {invoice_id}, {invoice_date}, {date}, {date_from}, {date_to}, and {text}.

Upload file formats

Optional fallback customer-ID upload formats:

  • CSV with a column named one of: customer_id, customer, gnucash_customer_id, owner_id, id
  • CSV without a recognized header, where the scanner searches cells for exact GnuCash customer IDs
  • Plain text, one customer ID per line
  • XLSX if python3-openpyxl is installed

Only customer IDs that exist in the active GnuCash book copy are included in the generated CSV. Unmatched IDs are shown in the preview. For the normal GUI workflow, the customer list is read directly from the uploaded book and no separate customer-ID file is needed.

Supported GnuCash book-copy upload extensions:

  • .gnucash
  • .sqlite, .sqlite3
  • .db, .db3
  • .xml, .xac, .gz

Generated CSV columns

The generated file follows GnuCash's invoice/bill import column order:

id,date_opened,owner_id,billingid,notes,date,desc,action,account,quantity,price,disc_type,disc_how,discount,taxable,taxincluded,tax_table,date_posted,due_date,account_posted,memo_posted,accu_splits

The generated file intentionally omits a header row because GnuCash expects invoice entry rows. The quantity field is emitted as an explicit decimal, for example 1.0, and the price and discount fields are emitted with two decimal places, for example 15.00, to make amounts explicit for GnuCash import.

Command-line checks

Scan a GnuCash book:

python3 bin/gnc_batch_invoice.py scan-book --book /path/to/book.gnucash

Scan an uploaded customer file:

python3 bin/gnc_batch_invoice.py scan-upload --book /path/to/book.gnucash --input /path/to/customers.csv

Generate from JSON on stdin:

python3 bin/gnc_batch_invoice.py generate --book /path/to/book.gnucash --out /tmp/invoices.csv < batch.json

Suggested first test

  1. Create an entity/profile from a copy of your GnuCash book.
  2. Open the Batch Wizard and select two or three active customers from the scanned book list.
  3. Generate the invoice CSV.
  4. Import it into a test GnuCash file as invoices.
  5. Verify invoice IDs, customers, dates, accounts, posting state, and amounts.

Runtime ownership notes

Patch scripts from v0.1.6 onward are designed not to reset existing var/ and config/ permissions. They also remind you to run bin/setup-local-permissions.sh if runtime checks fail. v0.1.7 also improved Chromium detection for snap-based Ubuntu installs.

A web application cannot create files as $USER while PHP-FPM is running as www-data. To make generated CSV files owned by $USER, the PHP-FPM worker for this app must run as $USER. Use bin/install-local-fpm-pool.sh for that local/trusted deployment model.

Fixing open_basedir restriction in effect after cloning to another directory

If nginx shows No input file specified and the error log contains a message like:

open_basedir restriction in effect. File(/home/$USER/public_html/invoices/index.php) is not within the allowed path(s): (/home/$USER/public_html/gnucash-invoice-batch-creator:/tmp:/usr/bin:/run/php)

then the PHP-FPM pool is still restricted to the old clone path. Regenerate the local pool from inside the new clone, explicitly passing the current directory:

cd /home/$USER/public_html/invoices
sudo bash bin/install-local-fpm-pool.sh $USER publicweb 8.5 "$(pwd)"
sudo nginx -t
sudo systemctl reload nginx

For local/trusted development where multiple tools live under public_html, you can instead broaden the pool to /home/$USER/public_html:

cd /home/$USER/public_html/invoices
sudo bash bin/install-local-fpm-pool.sh $USER publicweb 8.5 "$(pwd)" public_html
sudo nginx -t
sudo systemctl reload nginx

For the recommended shared GnuCash tools layout, use:

cd /home/$USER/public_html/gnucashtools/invoices
sudo bash bin/install-gnucash-tools-fpm-pool.sh $USER publicweb 8.5 /home/$USER/public_html/gnucashtools
sudo nginx -t
sudo systemctl reload nginx

For a quick manual repair, edit /etc/php/8.5/fpm/pool.d/gnucash-invoice-batch-creator.conf and make the first path in open_basedir match the actual clone path or its trusted parent directory:

php_admin_value[open_basedir] = /home/YOUR_USER_NAME/public_html/invoices:/tmp:/usr/bin:/bin:/snap/bin:/var/lib/snapd/snap/bin:/run/php

Then restart PHP-FPM and reload nginx:

sudo systemctl restart php8.5-fpm
sudo systemctl reload nginx

The /snap/bin entries are included so the Settings page can detect Ubuntu's snap Chromium path.

Shared GnuCash tools layout

For a local/trusted workstation that hosts this invoice tool and the vendor/bill import tool together, the recommended layout is:

/home/$USER/public_html/gnucashtools/invoices
/home/$USER/public_html/gnucashtools/bills

Use one shared PHP-FPM pool only if both tools are trusted and should run as the same local user. The shared helper creates /run/php/gnucash-tools.sock and sets open_basedir to the shared parent directory, not to only one clone.

cd /home/$USER/public_html/gnucashtools/invoices
sudo bash bin/install-gnucash-tools-fpm-pool.sh "$USER" publicweb 8.5 /home/$USER/public_html/gnucashtools

The nginx config still needs separate location blocks for /gnucashtools/invoices/ and /gnucashtools/bills/. See config/nginx-local-example.conf.

License

GPL-3.0-or-later.

Fixing “No input file specified” on a public_html/subdirectory install

If a fresh clone works for static files but PHP shows No input file specified, the issue is usually nginx/PHP-FPM path mapping rather than config/config.php. PHP-FPM is being handed a SCRIPT_FILENAME that does not match the real file on disk.

Version 0.1.8 added a root-level index.php shim so a simple clone at:

/home/$USER/public_html/invoices

can be opened as:

http://localhost/invoices/index.php

or, through the root redirect:

http://localhost/invoices/

The preferred production-style deployment is still to point nginx at public/, but the root shim is useful for local testing and simple git pull installs.

Quick checks:

cd ~/public_html/invoices
ls -l index.php public/index.php public/assets/app.css
php -l index.php
php -l public/index.php

If PHP syntax checks pass but the browser still reports No input file specified, update nginx so SCRIPT_FILENAME points at the real path. See config/nginx-local-example.conf. For a clone at /home/$USER/public_html/invoices, the important part is:

location /invoices/ {
    alias /home/YOUR_USER_NAME/public_html/invoices/;
    index index.php;
    try_files $uri $uri/ /invoices/index.php?$query_string;
}

location ~ ^/invoices/(.+\.php)$ {
    alias /home/YOUR_USER_NAME/public_html/invoices/$1;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /home/YOUR_USER_NAME/public_html/invoices/$1;
    fastcgi_param DOCUMENT_ROOT /home/YOUR_USER_NAME/public_html/invoices;
    fastcgi_pass unix:/run/php/gnucash-invoice-batch-creator.sock;
}

location ~ ^/invoices/(?:app|bin|config|data|var|\.git|\.github)/ {
    deny all;
}

Then reload services:

sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart php8.5-fpm

About

Creates a CSV for importing batch customer invoices into GnuCash. Also batch creates statements for Customers.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Contributors

Languages