Files
Voiced/scripts/erpnext-e2e/README.md
T
xavierk 0f27e5ddc1 Verify the ERPNext integration live and fix the defects it found (Phase F4)
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.
2026-10-04 22:09:03 +05:30

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_total works.
  • PDF attachment: uploaded through upload_file, is_private is 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 v1 run_method route.
  • 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_entry plus overrides. bank_account is the Account name and lands in paid_to; paid_amount = received_amount = cash; the reference's allocated_amount = cash + TDS; TDS is one deductions row {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_category on both; state exact name; link to the Customer).
  • IC: place_of_supply as 29-Karnataka; Voiced's 38 state names equal India Compliance's gst_state list; HSN/SAC must be 6 or 8 digits (GST Settings min_hsn_digits was 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):

  1. load_options: the company-address list asked for the India Compliance-only gstin field and failed with HTTP 417 on a plain site. It now retries without it.
  2. 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.
  3. 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.
  4. 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. (creation in the order-by is ambiguous with a Dynamic Link filter and gives HTTP 500.)
  5. A re-adopted document got a second copy of the PDF. A file of the same name is now looked up first.
  6. IC refuses is_reverse_charge = 1 unless 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.
  7. 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".
  8. A TDS-only payment (no cash) got ERPNext's bare "Paid Amount is mandatory"; it is refused locally with the reason.
  9. 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_factor 1); 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 with allow_cors or rate limits.