acumatica-cli 0.30.0__tar.gz → 0.31.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/PKG-INFO +25 -24
  2. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/README.md +24 -23
  3. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/pyproject.toml +1 -1
  4. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/pyproject.toml.orig +1 -1
  5. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/cli.py +41 -21
  6. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/reconcile.py +2 -2
  7. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/README.md +2 -2
  8. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/__init__.py +0 -0
  9. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/bootstrap.py +0 -0
  10. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  11. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/bootstrap_project.xml +0 -0
  12. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/client.py +0 -0
  13. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/config.py +0 -0
  14. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/extract.py +0 -0
  15. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/firstlogin.py +0 -0
  16. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/inventory.py +0 -0
  17. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/models.py +0 -0
  18. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/output.py +0 -0
  19. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/run.py +0 -0
  20. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/seed.py +0 -0
  21. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/seed_catalog.yaml +0 -0
  22. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/snapshot_map.yaml +0 -0
  23. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/state.py +0 -0
  24. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
  25. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
  26. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
  27. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
  28. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
  29. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
  30. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/91-company-packaging.yaml +0 -0
  31. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
  32. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
  33. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
  34. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/bootstrap/segmented-key.yaml +0 -0
  35. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/05-numbering-sequences.yaml +0 -0
  36. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
  37. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
  38. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
  39. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
  40. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
  41. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
  42. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
  43. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
  44. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
  45. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
  46. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
  47. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
  48. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
  49. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
  50. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
  51. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
  52. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
  53. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
  54. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
  55. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
  56. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
  57. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
  58. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
  59. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
  60. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
  61. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/90-roles.yaml +0 -0
  62. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/91-users.yaml +0 -0
  63. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/92-role-users.yaml +0 -0
  64. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
  65. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
  66. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
  67. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
  68. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/env +0 -0
  69. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/gitignore +0 -0
  70. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/overlays/README.md +0 -0
  71. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/overlays/default-24.200.001/README.md +0 -0
  72. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/overlays/default-24.200.001/scenario/30-build.yaml +0 -0
  73. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
  74. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
  75. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
  76. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
  77. {acumatica_cli-0.30.0 → acumatica_cli-0.31.0}/src/acumatica_cli/tenant.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acumatica-cli
3
- Version: 0.30.0
3
+ Version: 0.31.0
4
4
  Summary: Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data
5
5
  Author: Konstantin Borovik
6
6
  Author-email: Konstantin Borovik <kb@lab5.ca>
@@ -40,21 +40,21 @@ YAML in git is the source of truth; the live tenant is the target. `apply` is th
40
40
  | ------- | ------- | ------ |
41
41
  | Seed a tenant from YAML | `apply` | Idempotent PUT of `config/` into the live tenant |
42
42
  | Detect drift | `diff` | Compare seed YAML to live (exit 2 when they diverge) |
43
- | Pull live config into YAML | `extract` | Inverse of `apply` — GET into `config/{bootstrap,baseline,setup,master}/` |
43
+ | Pull live config into YAML | `survey extract` | Inverse of `apply` — GET into `config/{bootstrap,baseline,setup,master}/` |
44
44
  | Run transaction scripts | `run` | Forward documents from `scenario/` (capital, buy, build, sell) |
45
45
  | Capture derived balances | `state` | Inquire trial-balance etc. into `state/` (not seed) |
46
46
  | Create and destroy tenants | `tenant` | SSH control plane: list / create / delete / recycle |
47
47
  | Publish the Bootstrap contract | `bootstrap` | REST publish of AcuBootstrap (or `--export` zip for the UI) |
48
48
  | Prove a cold rebuild | `tenant create` then `apply` then `run` then `diff` | Compose on a fresh tenant. No wrap command. |
49
- | Snapshot a site offline | `inventory` | SM203520 XML ZIP or `ac.exe export xml` writes `inventory/` |
50
- | Cross-check snapshot vs seed | `reconcile` | `inventory/` plus optional `config/` writes `findings/` only |
49
+ | Snapshot a site offline | `survey inventory` | SM203520 XML ZIP or `ac.exe export xml` writes `inventory/` |
50
+ | Cross-check snapshot vs seed | `survey reconcile` | `inventory/` plus optional `config/` writes `findings/` only |
51
51
  | Dump the contract schema | `schema` | OpenAPI `swagger.json` for the pinned endpoint |
52
52
  | Scaffold a data repo | `config init` | Full `config/`, `scenario/`, `.env` tree |
53
53
  | Preflight a target | `config check` | Discovery, secrets, REST, endpoints, SSH |
54
54
 
55
55
  Seed YAML covers features, company, credit terms, subaccounts, chart of accounts, ledger, UOMs, financial year / calendar / periods, numbering sequences, inventory and distribution masters, roles, and users.
56
56
 
57
- REST is the data plane (`apply`, `diff`, `run`, `extract`, `state`, `bootstrap`, `schema`).
57
+ REST is the data plane (`apply`, `diff`, `run`, `survey extract`, `state`, `bootstrap`, `schema`).
58
58
  SSH is the control plane (`tenant *`).
59
59
 
60
60
  Hosted sites skip SSH.
@@ -116,11 +116,12 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
116
116
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
117
117
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
118
118
  │ capture derived state into state/ (not seed)
119
- ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
120
- │ inverse of apply into config/{bootstrap,baseline,setup,master}/
121
- ├── inventory [--out DIR] [--force] [--dry-run] ARTIFACT
122
- │ offline snapshot artifact → inventory/ (not seed)
123
- ├── reconcile [--inventory DIR] [--config DIR] [--out DIR] [--force] [--dry-run]
119
+ ├── survey existing-tenant dual-reader (never L1 extract/inventory/reconcile)
120
+ │ ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
121
+ │ │ inverse of apply into config/{bootstrap,baseline,setup,master}/
122
+ │ ├── inventory [--out DIR] [--force] [--dry-run] ARTIFACT
123
+ │ │ offline snapshot artifact → inventory/ (not seed)
124
+ │ └── reconcile [--inventory DIR] [--config DIR] [--out DIR] [--force] [--dry-run]
124
125
  │ inventory/ + optional config/ → findings/ only
125
126
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
126
127
  │
@@ -141,12 +142,12 @@ When you omit FILES:
141
142
  Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
142
143
  `config/views` and `state` keep Period pinned — see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views).
143
144
 
144
- `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
145
+ `survey extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
145
146
 
146
- `inventory` and `reconcile` are offline (no REST, SSH, or password).
147
- `inventory` turns an SM203520 Settings XML ZIP or `ac.exe export xml` folder into `inventory/`.
147
+ `survey inventory` and `survey reconcile` are offline (no REST, SSH, or password).
148
+ `survey inventory` turns an SM203520 Settings XML ZIP or `ac.exe export xml` folder into `inventory/`.
148
149
 
149
- `reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
150
+ `survey reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
150
151
  It never writes seed.
151
152
 
152
153
  Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
@@ -168,9 +169,9 @@ Do not confuse them with each other or with `state`:
168
169
 
169
170
  | Command | Plane | Input | Writes | Role |
170
171
  | ------- | ----- | ----- | ------ | ---- |
171
- | `extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
172
- | `inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
173
- | `reconcile` | Offline | `inventory/` + optional `config/` | `findings/` only | Cross-check gaps/deltas — never mutates seed or tenant |
172
+ | `survey extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
173
+ | `survey inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
174
+ | `survey reconcile` | Offline | `inventory/` + optional `config/` | `findings/` only | Cross-check gaps/deltas — never mutates seed or tenant |
174
175
  | `state` | REST (live) | `config/views/` | `state/` | Derived balances/totals — not seed, not inventory |
175
176
  | `apply` | REST (live) | seed YAML under `config/` | tenant | **Sole** tenant writer (keyed PUT) |
176
177
 
@@ -198,8 +199,8 @@ There is no `--flavor`.
198
199
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
199
200
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
200
201
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
201
- | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
202
- | `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
202
+ | `inventory/` | engagement: offline snapshot tables from `acu survey inventory` (not seed; not SEED_DIRS) |
203
+ | `findings/` | engagement: `acu survey reconcile` cross-check output (never apply path) |
203
204
  | `.env` | secrets + where (`ACU_BASE_URL`) + pin (`ACU_API_VERSION`) |
204
205
 
205
206
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
@@ -208,7 +209,7 @@ Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-
208
209
  Commit seed YAML; keep `.env` out of git (scaffold `.gitignore` already lists it).
209
210
 
210
211
  Seed YAML is state: `apply` upserts it, `diff` proves it.
211
- `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
212
+ `acu survey extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
212
213
 
213
214
  Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
214
215
  The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
@@ -220,7 +221,7 @@ Row failures continue (exit 1 only if any row failed).
220
221
  Drift stays with `diff`.
221
222
 
222
223
  ```sh
223
- acu --tenant DEV extract --out . --force # refresh config/** from live tenant
224
+ acu --tenant DEV survey extract --out . --force # refresh config/** from live tenant
224
225
  git diff config/ # review extract delta before commit
225
226
  acu --tenant DEV apply config/ # replay extracted seed
226
227
  acu --tenant DEV diff config/ # expect exit 0
@@ -377,9 +378,9 @@ gmake release patch # or minor | major
377
378
  2. Fail if `## Unreleased` has no bullets
378
379
  3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
379
380
  4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
380
- 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`
381
- 6. `gmake check` again on the promoted tree
382
- 7. Push tag + branch — GitHub Actions publishes
381
+ 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together
382
+ 6. `gmake check` again on the promoted tree — fail keeps the commit, creates no tag, pushes nothing
383
+ 7. Tag `vX.Y.Z` and push branch + that tag — GitHub Actions publishes
383
384
 
384
385
  GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via OIDC trusted publishing, and creates a GitHub Release whose notes are the promoted CHANGELOG section for that tag (plus the artifacts).
385
386
 
@@ -22,21 +22,21 @@ YAML in git is the source of truth; the live tenant is the target. `apply` is th
22
22
  | ------- | ------- | ------ |
23
23
  | Seed a tenant from YAML | `apply` | Idempotent PUT of `config/` into the live tenant |
24
24
  | Detect drift | `diff` | Compare seed YAML to live (exit 2 when they diverge) |
25
- | Pull live config into YAML | `extract` | Inverse of `apply` — GET into `config/{bootstrap,baseline,setup,master}/` |
25
+ | Pull live config into YAML | `survey extract` | Inverse of `apply` — GET into `config/{bootstrap,baseline,setup,master}/` |
26
26
  | Run transaction scripts | `run` | Forward documents from `scenario/` (capital, buy, build, sell) |
27
27
  | Capture derived balances | `state` | Inquire trial-balance etc. into `state/` (not seed) |
28
28
  | Create and destroy tenants | `tenant` | SSH control plane: list / create / delete / recycle |
29
29
  | Publish the Bootstrap contract | `bootstrap` | REST publish of AcuBootstrap (or `--export` zip for the UI) |
30
30
  | Prove a cold rebuild | `tenant create` then `apply` then `run` then `diff` | Compose on a fresh tenant. No wrap command. |
31
- | Snapshot a site offline | `inventory` | SM203520 XML ZIP or `ac.exe export xml` writes `inventory/` |
32
- | Cross-check snapshot vs seed | `reconcile` | `inventory/` plus optional `config/` writes `findings/` only |
31
+ | Snapshot a site offline | `survey inventory` | SM203520 XML ZIP or `ac.exe export xml` writes `inventory/` |
32
+ | Cross-check snapshot vs seed | `survey reconcile` | `inventory/` plus optional `config/` writes `findings/` only |
33
33
  | Dump the contract schema | `schema` | OpenAPI `swagger.json` for the pinned endpoint |
34
34
  | Scaffold a data repo | `config init` | Full `config/`, `scenario/`, `.env` tree |
35
35
  | Preflight a target | `config check` | Discovery, secrets, REST, endpoints, SSH |
36
36
 
37
37
  Seed YAML covers features, company, credit terms, subaccounts, chart of accounts, ledger, UOMs, financial year / calendar / periods, numbering sequences, inventory and distribution masters, roles, and users.
38
38
 
39
- REST is the data plane (`apply`, `diff`, `run`, `extract`, `state`, `bootstrap`, `schema`).
39
+ REST is the data plane (`apply`, `diff`, `run`, `survey extract`, `state`, `bootstrap`, `schema`).
40
40
  SSH is the control plane (`tenant *`).
41
41
 
42
42
  Hosted sites skip SSH.
@@ -98,11 +98,12 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
98
98
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
99
99
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
100
100
  │ capture derived state into state/ (not seed)
101
- ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
102
- │ inverse of apply into config/{bootstrap,baseline,setup,master}/
103
- ├── inventory [--out DIR] [--force] [--dry-run] ARTIFACT
104
- │ offline snapshot artifact → inventory/ (not seed)
105
- ├── reconcile [--inventory DIR] [--config DIR] [--out DIR] [--force] [--dry-run]
101
+ ├── survey existing-tenant dual-reader (never L1 extract/inventory/reconcile)
102
+ │ ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
103
+ │ │ inverse of apply into config/{bootstrap,baseline,setup,master}/
104
+ │ ├── inventory [--out DIR] [--force] [--dry-run] ARTIFACT
105
+ │ │ offline snapshot artifact → inventory/ (not seed)
106
+ │ └── reconcile [--inventory DIR] [--config DIR] [--out DIR] [--force] [--dry-run]
106
107
  │ inventory/ + optional config/ → findings/ only
107
108
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
108
109
  │
@@ -123,12 +124,12 @@ When you omit FILES:
123
124
  Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
124
125
  `config/views` and `state` keep Period pinned — see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views).
125
126
 
126
- `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
127
+ `survey extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
127
128
 
128
- `inventory` and `reconcile` are offline (no REST, SSH, or password).
129
- `inventory` turns an SM203520 Settings XML ZIP or `ac.exe export xml` folder into `inventory/`.
129
+ `survey inventory` and `survey reconcile` are offline (no REST, SSH, or password).
130
+ `survey inventory` turns an SM203520 Settings XML ZIP or `ac.exe export xml` folder into `inventory/`.
130
131
 
131
- `reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
132
+ `survey reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
132
133
  It never writes seed.
133
134
 
134
135
  Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
@@ -150,9 +151,9 @@ Do not confuse them with each other or with `state`:
150
151
 
151
152
  | Command | Plane | Input | Writes | Role |
152
153
  | ------- | ----- | ----- | ------ | ---- |
153
- | `extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
154
- | `inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
155
- | `reconcile` | Offline | `inventory/` + optional `config/` | `findings/` only | Cross-check gaps/deltas — never mutates seed or tenant |
154
+ | `survey extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
155
+ | `survey inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
156
+ | `survey reconcile` | Offline | `inventory/` + optional `config/` | `findings/` only | Cross-check gaps/deltas — never mutates seed or tenant |
156
157
  | `state` | REST (live) | `config/views/` | `state/` | Derived balances/totals — not seed, not inventory |
157
158
  | `apply` | REST (live) | seed YAML under `config/` | tenant | **Sole** tenant writer (keyed PUT) |
158
159
 
@@ -180,8 +181,8 @@ There is no `--flavor`.
180
181
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
181
182
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
182
183
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
183
- | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
184
- | `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
184
+ | `inventory/` | engagement: offline snapshot tables from `acu survey inventory` (not seed; not SEED_DIRS) |
185
+ | `findings/` | engagement: `acu survey reconcile` cross-check output (never apply path) |
185
186
  | `.env` | secrets + where (`ACU_BASE_URL`) + pin (`ACU_API_VERSION`) |
186
187
 
187
188
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
@@ -190,7 +191,7 @@ Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-
190
191
  Commit seed YAML; keep `.env` out of git (scaffold `.gitignore` already lists it).
191
192
 
192
193
  Seed YAML is state: `apply` upserts it, `diff` proves it.
193
- `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
194
+ `acu survey extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
194
195
 
195
196
  Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
196
197
  The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
@@ -202,7 +203,7 @@ Row failures continue (exit 1 only if any row failed).
202
203
  Drift stays with `diff`.
203
204
 
204
205
  ```sh
205
- acu --tenant DEV extract --out . --force # refresh config/** from live tenant
206
+ acu --tenant DEV survey extract --out . --force # refresh config/** from live tenant
206
207
  git diff config/ # review extract delta before commit
207
208
  acu --tenant DEV apply config/ # replay extracted seed
208
209
  acu --tenant DEV diff config/ # expect exit 0
@@ -359,9 +360,9 @@ gmake release patch # or minor | major
359
360
  2. Fail if `## Unreleased` has no bullets
360
361
  3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
361
362
  4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
362
- 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`
363
- 6. `gmake check` again on the promoted tree
364
- 7. Push tag + branch — GitHub Actions publishes
363
+ 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together
364
+ 6. `gmake check` again on the promoted tree — fail keeps the commit, creates no tag, pushes nothing
365
+ 7. Tag `vX.Y.Z` and push branch + that tag — GitHub Actions publishes
365
366
 
366
367
  GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via OIDC trusted publishing, and creates a GitHub Release whose notes are the promoted CHANGELOG section for that tag (plus the artifacts).
367
368
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.30.0"
3
+ version = "0.31.0"
4
4
  description = "Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data"
5
5
  license = "PolyForm-Noncommercial-1.0.0"
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.30.0"
3
+ version = "0.31.0"
4
4
  description = "Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data"
5
5
  authors = [{ name = "Konstantin Borovik", email = "kb@lab5.ca" }]
6
6
  license = "PolyForm-Noncommercial-1.0.0"
@@ -5,13 +5,13 @@ Configure Acumatica ERP from YAML in a git data repo. No UI wizards.
5
5
  \b
6
6
  MENTAL MODEL
7
7
  Data repo = directory with .env (walk-up from cwd). Holds:
8
- config/{bootstrap,baseline,setup,master}/ seed YAML (apply/diff/extract)
8
+ config/{bootstrap,baseline,setup,master}/ seed YAML (apply/diff/survey extract)
9
9
  config/views/ observer views (state)
10
10
  scenario/ transaction scripts (run)
11
11
  .env secrets + where + API pin
12
12
  state/ inventory/ findings/ schemas/ command outputs (not seed)
13
13
  Two planes (do not mix):
14
- REST data plane — apply diff run bootstrap extract state schema
14
+ REST data plane — apply diff run bootstrap survey extract state schema
15
15
  needs: base_url, tenant, password (SSH optional)
16
16
  SSH control plane — tenant list|create|delete|recycle
17
17
  needs: non-empty ACU_SSH (user@host)
@@ -56,9 +56,9 @@ COMMAND MAP (pick by intent)
56
56
  seed check diff [FILES...] (exit 2 on drift)
57
57
  txns run [--dry-run] [FILES...]
58
58
  cold rebuild tenant create then apply then run then diff (no wrap cmd)
59
- pull seed extract [--only ENTITY]... (inverse of apply)
59
+ pull seed survey extract [--only ENTITY]... (inverse of apply)
60
60
  observe state [--diff|--assert-unchanged]
61
- offline inventory ARTIFACT | reconcile
61
+ offline survey inventory ARTIFACT | survey reconcile
62
62
  reference schema
63
63
 
64
64
  \b
@@ -73,7 +73,7 @@ DEFAULT PATHS (when FILES omitted)
73
73
  then overlays/default-<api>/ when present
74
74
  run -> scenario/ (overlay same-basename wins)
75
75
  state -> config/views/ -> writes state/
76
- extract -> always writes config/{bootstrap,baseline,setup,master}/
76
+ survey extract -> always writes config/{bootstrap,baseline,setup,master}/
77
77
 
78
78
  Run `acu <command> --help` for flags, examples, and prerequisites.
79
79
  See package README and docs/demo-seed.md for seed YAML shape.
@@ -1027,7 +1027,7 @@ def apply_cmd(inst: Instance, files: tuple[Path, ...], dry_run: bool) -> None:
1027
1027
  acu --tenant DEV apply --dry-run config/master/
1028
1028
  acu --tenant DEV apply config/baseline/20-accounts.yaml
1029
1029
 
1030
- Related: `diff` (drift) · `extract` (inverse pull) · `run` (txns).
1030
+ Related: `diff` (drift) · `survey extract` (inverse pull) · `run` (txns).
1031
1031
  """
1032
1032
  total_ok = 0
1033
1033
  all_errors: list[str] = []
@@ -1106,7 +1106,7 @@ def diff_cmd(inst: Instance, files: tuple[Path, ...]) -> None:
1106
1106
  acu --tenant DEV diff config/
1107
1107
  acu --tenant DEV diff config/master/90-roles.yaml
1108
1108
 
1109
- Related: `apply` (fix) · `extract` (pull) · `state` (balances).
1109
+ Related: `apply` (fix) · `survey extract` (pull) · `state` (balances).
1110
1110
  """
1111
1111
  paths = expand_files(files or default_apply_dirs(inst))
1112
1112
  drifts: list[str] = []
@@ -1189,7 +1189,27 @@ def _complete_only(
1189
1189
  ]
1190
1190
 
1191
1191
 
1192
- @cli.command("extract")
1192
+ @cli.group("survey", context_settings=_HELP_CTX)
1193
+ def survey_group() -> None:
1194
+ """Existing-tenant dual-reader (REST extract + offline inventory/reconcile).
1195
+
1196
+ Noun owns extract|inventory|reconcile. Never L1 those verbs. No aliases.
1197
+
1198
+ \b
1199
+ Subcommands
1200
+ extract [--only ENTITY]... live GET -> config/ SEED_DIRS (REST)
1201
+ inventory ARTIFACT snapshot ZIP/folder -> inventory/ (offline)
1202
+ reconcile inventory/ + optional config/ -> findings/ (offline)
1203
+
1204
+ \b
1205
+ Examples
1206
+ acu --tenant DEV survey extract
1207
+ acu survey inventory ./export-xml/
1208
+ acu survey reconcile
1209
+ """
1210
+
1211
+
1212
+ @survey_group.command("extract")
1193
1213
  @click.option(
1194
1214
  "--out",
1195
1215
  "out_dir",
@@ -1230,11 +1250,11 @@ def extract_cmd(
1230
1250
 
1231
1251
  \b
1232
1252
  Examples
1233
- acu --tenant DEV extract
1234
- acu --tenant DEV extract --only Account --only Vendor
1235
- acu --tenant DEV extract --force --dry-run
1253
+ acu --tenant DEV survey extract
1254
+ acu --tenant DEV survey extract --only Account --only Vendor
1255
+ acu --tenant DEV survey extract --force --dry-run
1236
1256
 
1237
- Related: `apply` · `diff` · `inventory` · `state`.
1257
+ Related: `apply` · `diff` · `survey inventory` · `state`.
1238
1258
  """
1239
1259
  with AcumaticaClient(inst) as client:
1240
1260
  failed = extract.run(
@@ -1258,7 +1278,7 @@ def _exit_on_drift(inst: Instance, drifts: list[str], files: int) -> None:
1258
1278
  output.success(f"no drift on {inst.tenant} ({inst.base_url}, {files} file(s))")
1259
1279
 
1260
1280
 
1261
- @cli.command("inventory")
1281
+ @survey_group.command("inventory")
1262
1282
  @click.argument(
1263
1283
  "artifact",
1264
1284
  type=click.Path(exists=True, path_type=Path),
@@ -1290,15 +1310,15 @@ def inventory_cmd(
1290
1310
  Existing files skip unless --force.
1291
1311
 
1292
1312
  Not extract (REST → config/ seed). Not state (balances). Never writes
1293
- config/ or state/. Feed inventory/ into `acu reconcile`.
1313
+ config/ or state/. Feed inventory/ into `acu survey reconcile`.
1294
1314
 
1295
1315
  \b
1296
1316
  Exit 0 = clean · 1 = parse/format/version fail · never 2
1297
1317
 
1298
1318
  \b
1299
1319
  Examples
1300
- acu inventory ./export-xml/
1301
- acu inventory Settings.zip --out inventory/ --force
1320
+ acu survey inventory ./export-xml/
1321
+ acu survey inventory Settings.zip --out inventory/ --force
1302
1322
  """
1303
1323
  # V9 long single-op: artifact parse (ZIP/folder IR) via step; banner +
1304
1324
  # per-table write/skip emit stay multi-unit stdout after.
@@ -1312,13 +1332,13 @@ def inventory_cmd(
1312
1332
  inventory.emit(art, dest, force=force, dry_run=dry_run)
1313
1333
 
1314
1334
 
1315
- @cli.command("reconcile")
1335
+ @survey_group.command("reconcile")
1316
1336
  @click.option(
1317
1337
  "--inventory",
1318
1338
  "inventory_dir",
1319
1339
  type=click.Path(file_okay=False, path_type=Path),
1320
1340
  default=None,
1321
- help="Inventory tree from `acu inventory` (default: inventory/)",
1341
+ help="Inventory tree from `acu survey inventory` (default: inventory/)",
1322
1342
  )
1323
1343
  @click.option(
1324
1344
  "--config",
@@ -1361,8 +1381,8 @@ def reconcile_cmd(
1361
1381
 
1362
1382
  \b
1363
1383
  Examples
1364
- acu inventory export.zip && acu reconcile
1365
- acu reconcile --inventory inv/ --config config/ --out findings/
1384
+ acu survey inventory export.zip && acu survey reconcile
1385
+ acu survey reconcile --inventory inv/ --config config/ --out findings/
1366
1386
  """
1367
1387
  inv = (
1368
1388
  inventory_dir
@@ -1440,7 +1460,7 @@ def state_cmd(
1440
1460
  acu --tenant DEV state --assert-unchanged
1441
1461
  acu --tenant DEV state config/views/10-trial-balance.yaml
1442
1462
 
1443
- Related: `run` (moves balances) · `diff` (seed drift) · `extract`.
1463
+ Related: `run` (moves balances) · `diff` (seed drift) · `survey extract`.
1444
1464
  """
1445
1465
  if not files:
1446
1466
  default = data_root() / "config" / "views"
@@ -287,7 +287,7 @@ class FindingsBundle(Model):
287
287
 
288
288
 
289
289
  def load_inventory_tree(path: Path | str) -> InventoryTree:
290
- """Load an ``inventory/`` tree written by ``acu inventory`` (I.data)."""
290
+ """Load an ``inventory/`` tree written by ``acu survey inventory`` (I.data)."""
291
291
  root = Path(path)
292
292
  if not root.is_dir():
293
293
  raise SystemExit(f"{root}: inventory directory not found")
@@ -295,7 +295,7 @@ def load_inventory_tree(path: Path | str) -> InventoryTree:
295
295
  if not summary_path.is_file():
296
296
  raise SystemExit(
297
297
  f"{root}: missing {inventory.SUMMARY_NAME} "
298
- "(run `acu inventory` first, or pass --inventory DIR)"
298
+ "(run `acu survey inventory` first, or pass --inventory DIR)"
299
299
  )
300
300
  try:
301
301
  raw = yaml.safe_load(summary_path.read_text(encoding="utf-8"))
@@ -33,11 +33,11 @@ acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged
33
33
  # SSH cold rebuild: acu tenant create --login NAME, then apply, run, diff
34
34
 
35
35
  # Optional: re-seed from live (inverse of apply; always under config/)
36
- # acu extract --out . --force
36
+ # acu survey extract --out . --force
37
37
  ```
38
38
 
39
39
  Bare `acu apply` / `acu diff` also prefer `config/` when those trees exist.
40
- `acu extract` hard-cuts emit to `config/{bootstrap,baseline,setup,master}/` (never root SEED_DIRS).
40
+ `acu survey extract` hard-cuts emit to `config/{bootstrap,baseline,setup,master}/` (never root SEED_DIRS).
41
41
 
42
42
  ## Layout
43
43