Ran the push flow against ERPNext 15.121.6 on a plain site and on a site with India Compliance 15.32.0 (podman, scripts/erpnext-e2e). 15 ignored live tests pass on both. Fixes, each covered by a mock-based test: - load_options no longer fails on a plain site: the India Compliance-only gstin field is dropped on a 417 and retried. - Code-less rows with fractional hours or minutes send stock_uom, since ERPNext defaults it to Nos. - A created document whose total differs from Voiced's (ERPNext's default Banker's Rounding on half-paise ties) is recorded as a conflict, kept as a draft, not submitted and not given a PDF. The message names Commercial Rounding as the fix. - An existing customer address is reused instead of creating a duplicate on every first push. A bare creation order-by is not used with a Dynamic Link filter. - A re-adopted document no longer gets a second copy of the PDF. - Reverse charge: India Compliance rejects is_reverse_charge unless tax rows are negative RCM amounts, so the flag is sent as 0 with a warning that the invoice lands as a normal taxed invoice. - The 16-character name rule is checked locally in mirror mode under India Compliance, and a TDS-only payment is refused locally with the reason. Payment Entry mapping verified: bank_account is the Account name (paid_to), paid and received amounts equal the cash received, allocated_amount is cash plus TDS, and TDS is one positive deductions row. The integration user needs the Accounts User and Sales User roles. Not verified: ERPNext v14 and v16, other India Compliance versions, the Tauri commands and UI against a live site, SEZ and overseas customers, UTGST supplier states, TLS sites, e-invoicing.
10 KiB
ERPNext live end-to-end (podman)
Runs Voiced's ERPNext integration against real ERPNext v15 sites: one plain site and one with India Compliance.
Everything is rootless podman (podman and podman-compose, never docker). All names are prefixed voiced-erp.
| File | Purpose |
|---|---|
compose.yml |
Lean stack: MariaDB, one Redis, backend (gunicorn on 127.0.0.1:8088), one worker. Project name voiced-erp. |
Containerfile.ic |
frappe/erpnext:v15.121.6 plus the india_compliance app files (version-15 branch). |
setup.sh |
Builds the image, starts the stack, creates both sites, prepares them, writes .env.a and .env.b. |
setup_site.py |
Runs inside the container: company, accounts, address, items, integration user and keys. |
../../src-tauri/src/integrations/erpnext/live_tests.rs |
The #[ignore] Rust tests. |
Reproduce
cd scripts/erpnext-e2e
./setup.sh # first run pulls ~2 GB of images and takes about 10 minutes
setup.sh creates two sites that one backend serves by Host header:
| Site | URL | Contents | Env file |
|---|---|---|---|
127.0.0.1 |
http://127.0.0.1:8088 |
ERPNext only | .env.a |
localhost |
http://localhost:8088 |
ERPNext + India Compliance | .env.b |
Both get the company "Voiced Test Co" (abbreviation VTC, INR, India, fiscal year 2026-27), the output tax accounts
Output Tax CGST|SGST|UTGST|IGST - VTC (India Compliance creates its own with the same names), TDS Receivable - VTC,
the bank account HDFC - VTC, a company address, the UOMs Hour/Minute/Second, the items VOICED-SERVICE (stock UOM Nos)
and VOICED-HOURLY (stock UOM Hour), and an integration user with a fresh API key and secret.
.env.* files hold those keys; they are git-ignored (.env.* in the root .gitignore) and must never be committed.
Run the Rust tests (they only touch the site named by the sourced env file):
set -a; . scripts/erpnext-e2e/.env.a; set +a # or .env.b for the India Compliance site
cargo test --manifest-path src-tauri/Cargo.toml live_ -- --ignored --test-threads=1
Environment variables the tests read:
| Variable | Meaning |
|---|---|
ERPNEXT_URL, ERPNEXT_KEY, ERPNEXT_SECRET |
Site address and the integration user's API key and secret. |
ERPNEXT_COMPANY (Voiced Test Co), ERPNEXT_ABBR (VTC) |
Company; account names derive from the abbreviation. |
ERPNEXT_IC |
1 on the India Compliance site; the live_ic_* tests skip themselves otherwise. |
ERPNEXT_ROUNDING |
banker (ERPNext default) or commercial; decides what the half-paise test expects. |
ERPNEXT_BANK_ACCOUNT (HDFC - VTC) |
Account that receives payments. |
Each test builds its own in-memory Voiced database and a fresh invoice series (L<time><n>/001), so reruns never collide
with documents from earlier runs. Switch the rounding method of a site with
podman exec -i voiced-erp-backend bash -c "cd /home/frappe/frappe-bench/sites && /home/frappe/frappe-bench/env/bin/python - 127.0.0.1 rounding=commercial" < setup_site.py
(re-run setup.sh or copy the printed ERPNEXT_* lines if you need the new key; the key is regenerated on every run).
Tear down (touches only the voiced-erp project; the cached images stay):
podman-compose -p voiced-erp -f compose.yml down -v
podman volume rm voiced-erp_voiced-erp-db voiced-erp_voiced-erp-sites voiced-erp_voiced-erp-logs # only if `down -v` left them
Notes on rootless podman: the compose file has no healthchecks (podman's healthcheck timers need systemd), and the
backend and worker are started by setup.sh after the configurator has exited.
Integration user (feeds the main README)
Create a dedicated user in ERPNext (User list, type System User) with the roles "Accounts User" and "Sales User". Then open the user, "Settings" tab, "API Access", "Generate Keys", and copy the secret once.
Why both (verified live on plain ERPNext and with India Compliance; setup_site.py creates exactly this user):
- "Accounts User": create and submit Sales Invoices (draft, insert-and-submit, submit-later) and Payment Entries; read Accounts, Items, UOMs, Cost Centers, Price Lists, Item Groups, Company, the Sales Invoice naming series and (with India Compliance) GST Settings; attach a private PDF to the invoice.
- "Sales User": look up and create Customers and Addresses, read Customer Groups, Territories and the tax templates. With "Accounts User" alone a fresh site answers HTTP 403 on Customer.
No tax template is used (Voiced always sends explicit tax rows), so the tax-template list is only a convenience.
Do not use Administrator or System Manager keys. Roles on a user are cached by Frappe: after changing them run
bench clear-cache or the next request may still use the old set.
Results (what was verified live)
Versions: Frappe 15.121.3, ERPNext 15.121.6, India Compliance 15.32.0 (the version-15 branch; commit db73b3e at the time of this run, so a later image build may differ), MariaDB 11.8,
Redis 8.6, API v2 naming available (Frappe 15.73 or newer).
Verified on both sites unless marked IC (India Compliance site only):
- Connection test: user, frappe/erpnext/india_compliance versions, India Compliance detection, no warnings when the accounts match. Option lists load (companies, addresses, accounts, UOMs, cost centers, groups, series).
- Sales Invoice push in mirror mode (API v2,
name= Voiced number): CGST+SGST, IGST, discount (apply_discount_on: Net Total), fractional hours, repeated descriptions on code-less rows, item-code rows from a preset, remarks. The grand total, the tax amounts per head and the posting date equal Voiced's;disable_rounded_totalworks. - PDF attachment: uploaded through
upload_file,is_privateis 1. - Idempotency: a second push is a no-op; with the local sync row deleted, the 409 path adopts the existing document (no duplicate, no second PDF).
- Submit: insert then submit in a second push (v2
method/submit), and series mode through the v1run_methodroute. - Series mode: ERPNext names the document, the Voiced number is in
remarks, and a lost sync row is recovered by the remarks lookup. - Payments:
get_payment_entryplus overrides.bank_accountis the Account name and lands inpaid_to;paid_amount = received_amount = cash; the reference'sallocated_amount = cash + TDS; TDS is onedeductionsrow{account, cost_center, amount: +TDS}; the invoice's outstanding falls by cash + TDS; partial payments leave the rest. - Customer and Address shape (IC:
gstin,gst_categoryon both; state exact name; link to the Customer). - IC:
place_of_supplyas29-Karnataka; Voiced's 38 state names equal India Compliance'sgst_statelist; HSN/SAC must be 6 or 8 digits (GST Settingsmin_hsn_digitswas 6) and is enforced at submit; the 16-character name rule; GST-account warnings (mismatching CGST account, company without GST accounts, a too-long next number).
Defects found and fixed (each has a mock-based test in the normal suite):
load_options: the company-address list asked for the India Compliance-onlygstinfield and failed with HTTP 417 on a plain site. It now retries without it.- Code-less rows with a fractional quantity of hours/minutes failed ("Quantity cannot be a fraction ... UOM Nos"):
ERPNext defaults the row's stock UOM to Nos without an item. Such rows now also send
stock_uom= the row UOM. - Rounding: ERPNext's default Rounding Method is Banker's Rounding; Voiced rounds half-paise up. On ties (for example
9% of 10.50) the totals differ by 1 paise. A created document whose total differs is now a
conflict: kept as a draft, never submitted, no PDF, with a message naming the fix (System Settings, Rounding Method, "Commercial Rounding"). The existing-document conflict message carries the same hint. With Commercial Rounding every case matched. - A lost link to a client's address created a new address on every first push. The customer's matching address (same
first line and PIN) is reused. (
creationin the order-by is ambiguous with a Dynamic Link filter and gives HTTP 500.) - A re-adopted document got a second copy of the PDF. A file of the same name is now looked up first.
- IC refuses
is_reverse_charge = 1unless the tax rows are negative amounts on separate "RCM" accounts, which would make ERPNext's total differ from Voiced's. It is sent as 0 and the push warns. - IC's 16-character rule is now checked before the POST in mirror mode (a readable, local refusal that points to series naming); ERPNext's own message is "Transaction Name must be 16 characters or fewer to meet GST requirements".
- A TDS-only payment (no cash) got ERPNext's bare "Paid Amount is mandatory"; it is refused locally with the reason.
- A fractional quantity on a whole-number UOM is refused by ERPNext with a readable message; Voiced appends what to change (unit mapping). No local pre-check: whether "Nos" is whole-number is a site setting.
Limits and notes:
- Rounding setting cannot be read by a restricted user, so it is detected by comparing the totals after creating the draft, not in the connection test.
- With India Compliance, an invoice with a missing or short HSN/SAC is created as a draft and fails at submit; the readable ERPNext message ("HSN/SAC must exist and should be 6 or 8 digits long for the following row numbers") is shown.
- A mirrored item row with an item code whose stock UOM differs from the row UOM works for whole quantities
(
conversion_factor1); a fractional quantity needs the item's stock UOM to allow fractions. - One intermittent failure was seen once in the half-paise test on the India Compliance site (the push of a non-tie case failed; three later full runs and a single-test run passed). The cause was not captured.
Not verified:
- ERPNext v14 and v16 (the v16 images that exist on some machines were not used), other India Compliance versions.
- Windows and WebView2; the Tauri commands and UI (the tests call the Rust push/payment functions directly).
- Overseas/SEZ customers, UTGST supplier states, multi-currency, GST Settings with "Round Off GST Values" on,
reverse charge booked on RCM accounts, e-invoice/e-waybill (India Compliance API features), a TLS or reverse-proxied
site (only plain
http://on a loopback address), Frappe Cloud or any site withallow_corsor rate limits.