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.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```bash
|
||||
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.
|
||||
Reference in New Issue
Block a user