acumatica-cli 0.29.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.
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/PKG-INFO +27 -30
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/README.md +26 -29
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/pyproject.toml +1 -1
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/pyproject.toml.orig +1 -1
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/cli.py +45 -213
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/reconcile.py +2 -2
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/README.md +3 -4
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/__init__.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/bootstrap.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/bootstrap_project.xml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/client.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/config.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/extract.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/firstlogin.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/inventory.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/models.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/output.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/run.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/seed.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/seed_catalog.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/snapshot_map.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/state.py +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/baseline/91-company-packaging.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/bootstrap/segmented-key.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/05-numbering-sequences.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/90-roles.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/91-users.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/master/92-role-users.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/env +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/gitignore +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/overlays/README.md +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/overlays/default-24.200.001/README.md +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/overlays/default-24.200.001/scenario/30-build.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
- {acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
- {acumatica_cli-0.29.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.
|
|
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
|
-
| Prove a cold rebuild | `
|
|
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 |
|
|
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 | `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.
|
|
@@ -76,7 +76,6 @@ acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup
|
|
|
76
76
|
acu --tenant DEV run scenario/ # once capital → buy → build → sell
|
|
77
77
|
acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
|
|
78
78
|
acu --tenant DEV state # capture state/ trial-balance
|
|
79
|
-
acu check --yes --tenant DEV # cold lifecycle create→apply→run→diff (leave tenant)
|
|
80
79
|
```
|
|
81
80
|
|
|
82
81
|
Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
|
|
@@ -115,14 +114,14 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
|
|
|
115
114
|
├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
|
|
116
115
|
├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
|
|
117
116
|
├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
|
|
118
|
-
├── check [--yes] [--tenant L] cold lifecycle create→apply→run→diff; leave tenant (V47)
|
|
119
117
|
├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
|
|
120
118
|
│ capture derived state into state/ (not seed)
|
|
121
|
-
├──
|
|
122
|
-
│
|
|
123
|
-
|
|
124
|
-
│
|
|
125
|
-
|
|
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]
|
|
126
125
|
│ inventory/ + optional config/ → findings/ only
|
|
127
126
|
├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
|
|
128
127
|
│
|
|
@@ -143,12 +142,12 @@ When you omit FILES:
|
|
|
143
142
|
Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
|
|
144
143
|
`config/views` and `state` keep Period pinned — see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views).
|
|
145
144
|
|
|
146
|
-
`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).
|
|
147
146
|
|
|
148
|
-
`inventory` and `reconcile` are offline (no REST, SSH, or password).
|
|
149
|
-
`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/`.
|
|
150
149
|
|
|
151
|
-
`reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
|
|
150
|
+
`survey reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
|
|
152
151
|
It never writes seed.
|
|
153
152
|
|
|
154
153
|
Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
|
|
@@ -170,9 +169,9 @@ Do not confuse them with each other or with `state`:
|
|
|
170
169
|
|
|
171
170
|
| Command | Plane | Input | Writes | Role |
|
|
172
171
|
| ------- | ----- | ----- | ------ | ---- |
|
|
173
|
-
| `extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
|
|
174
|
-
| `inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
|
|
175
|
-
| `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 |
|
|
176
175
|
| `state` | REST (live) | `config/views/` | `state/` | Derived balances/totals — not seed, not inventory |
|
|
177
176
|
| `apply` | REST (live) | seed YAML under `config/` | tenant | **Sole** tenant writer (keyed PUT) |
|
|
178
177
|
|
|
@@ -200,8 +199,8 @@ There is no `--flavor`.
|
|
|
200
199
|
| `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
|
|
201
200
|
| `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
|
|
202
201
|
| `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
|
|
203
|
-
| `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
|
|
204
|
-
| `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) |
|
|
205
204
|
| `.env` | secrets + where (`ACU_BASE_URL`) + pin (`ACU_API_VERSION`) |
|
|
206
205
|
|
|
207
206
|
Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
|
|
@@ -210,7 +209,7 @@ Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-
|
|
|
210
209
|
Commit seed YAML; keep `.env` out of git (scaffold `.gitignore` already lists it).
|
|
211
210
|
|
|
212
211
|
Seed YAML is state: `apply` upserts it, `diff` proves it.
|
|
213
|
-
`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).
|
|
214
213
|
|
|
215
214
|
Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
|
|
216
215
|
The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
|
|
@@ -222,7 +221,7 @@ Row failures continue (exit 1 only if any row failed).
|
|
|
222
221
|
Drift stays with `diff`.
|
|
223
222
|
|
|
224
223
|
```sh
|
|
225
|
-
acu --tenant DEV extract --out . --force # refresh config/** from live tenant
|
|
224
|
+
acu --tenant DEV survey extract --out . --force # refresh config/** from live tenant
|
|
226
225
|
git diff config/ # review extract delta before commit
|
|
227
226
|
acu --tenant DEV apply config/ # replay extracted seed
|
|
228
227
|
acu --tenant DEV diff config/ # expect exit 0
|
|
@@ -285,7 +284,6 @@ Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
|
|
|
285
284
|
`base_url` resolves as `--url`, else `ACU_BASE_URL`, else a hard error naming sources.
|
|
286
285
|
|
|
287
286
|
`acu config show` prints the resolved `.env` (password excluded; includes `ACU_API_VERSION`).
|
|
288
|
-
`acu check` (lifecycle) uses the same resolved instance.
|
|
289
287
|
|
|
290
288
|
### Multi-host overlays (V44)
|
|
291
289
|
|
|
@@ -317,9 +315,6 @@ acu diff
|
|
|
317
315
|
# explicit path args (no auto) — later path wins same keys
|
|
318
316
|
acu apply config/ overlays/default-24.200.001/
|
|
319
317
|
acu diff config/ overlays/default-24.200.001/
|
|
320
|
-
|
|
321
|
-
# cold lifecycle vs the resolved .env instance (SSH + tenant required)
|
|
322
|
-
acu check --yes --tenant LAB5
|
|
323
318
|
```
|
|
324
319
|
|
|
325
320
|
Add a future half by creating `overlays/default-<new-half>/` with the
|
|
@@ -335,7 +330,7 @@ Worth knowing:
|
|
|
335
330
|
- Without a `.env`, global flags plus the process environment supply the configuration.
|
|
336
331
|
- When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the resolved base_url hostname.
|
|
337
332
|
A **present blank** `ACU_SSH=` is the hosted opt-out.
|
|
338
|
-
Only `acu tenant`
|
|
333
|
+
Only `acu tenant` requires a non-empty value post-default.
|
|
339
334
|
- `acu config show` prints the resolved `.env` (password excluded).
|
|
340
335
|
- Redirect it to turn resolved state into a working config: `acu config show > .env`.
|
|
341
336
|
|
|
@@ -383,7 +378,9 @@ gmake release patch # or minor | major
|
|
|
383
378
|
2. Fail if `## Unreleased` has no bullets
|
|
384
379
|
3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
|
|
385
380
|
4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
|
|
386
|
-
5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together
|
|
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
|
|
387
384
|
|
|
388
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).
|
|
389
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
|
-
| Prove a cold rebuild | `
|
|
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 |
|
|
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 | `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.
|
|
@@ -58,7 +58,6 @@ acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup
|
|
|
58
58
|
acu --tenant DEV run scenario/ # once capital → buy → build → sell
|
|
59
59
|
acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
|
|
60
60
|
acu --tenant DEV state # capture state/ trial-balance
|
|
61
|
-
acu check --yes --tenant DEV # cold lifecycle create→apply→run→diff (leave tenant)
|
|
62
61
|
```
|
|
63
62
|
|
|
64
63
|
Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
|
|
@@ -97,14 +96,14 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
|
|
|
97
96
|
├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
|
|
98
97
|
├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
|
|
99
98
|
├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
|
|
100
|
-
├── check [--yes] [--tenant L] cold lifecycle create→apply→run→diff; leave tenant (V47)
|
|
101
99
|
├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
|
|
102
100
|
│ capture derived state into state/ (not seed)
|
|
103
|
-
├──
|
|
104
|
-
│
|
|
105
|
-
|
|
106
|
-
│
|
|
107
|
-
|
|
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]
|
|
108
107
|
│ inventory/ + optional config/ → findings/ only
|
|
109
108
|
├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
|
|
110
109
|
│
|
|
@@ -125,12 +124,12 @@ When you omit FILES:
|
|
|
125
124
|
Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
|
|
126
125
|
`config/views` and `state` keep Period pinned — see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views).
|
|
127
126
|
|
|
128
|
-
`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).
|
|
129
128
|
|
|
130
|
-
`inventory` and `reconcile` are offline (no REST, SSH, or password).
|
|
131
|
-
`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/`.
|
|
132
131
|
|
|
133
|
-
`reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
|
|
132
|
+
`survey reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
|
|
134
133
|
It never writes seed.
|
|
135
134
|
|
|
136
135
|
Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
|
|
@@ -152,9 +151,9 @@ Do not confuse them with each other or with `state`:
|
|
|
152
151
|
|
|
153
152
|
| Command | Plane | Input | Writes | Role |
|
|
154
153
|
| ------- | ----- | ----- | ------ | ---- |
|
|
155
|
-
| `extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
|
|
156
|
-
| `inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
|
|
157
|
-
| `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 |
|
|
158
157
|
| `state` | REST (live) | `config/views/` | `state/` | Derived balances/totals — not seed, not inventory |
|
|
159
158
|
| `apply` | REST (live) | seed YAML under `config/` | tenant | **Sole** tenant writer (keyed PUT) |
|
|
160
159
|
|
|
@@ -182,8 +181,8 @@ There is no `--flavor`.
|
|
|
182
181
|
| `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
|
|
183
182
|
| `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
|
|
184
183
|
| `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
|
|
185
|
-
| `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
|
|
186
|
-
| `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) |
|
|
187
186
|
| `.env` | secrets + where (`ACU_BASE_URL`) + pin (`ACU_API_VERSION`) |
|
|
188
187
|
|
|
189
188
|
Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
|
|
@@ -192,7 +191,7 @@ Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-
|
|
|
192
191
|
Commit seed YAML; keep `.env` out of git (scaffold `.gitignore` already lists it).
|
|
193
192
|
|
|
194
193
|
Seed YAML is state: `apply` upserts it, `diff` proves it.
|
|
195
|
-
`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).
|
|
196
195
|
|
|
197
196
|
Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
|
|
198
197
|
The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
|
|
@@ -204,7 +203,7 @@ Row failures continue (exit 1 only if any row failed).
|
|
|
204
203
|
Drift stays with `diff`.
|
|
205
204
|
|
|
206
205
|
```sh
|
|
207
|
-
acu --tenant DEV extract --out . --force # refresh config/** from live tenant
|
|
206
|
+
acu --tenant DEV survey extract --out . --force # refresh config/** from live tenant
|
|
208
207
|
git diff config/ # review extract delta before commit
|
|
209
208
|
acu --tenant DEV apply config/ # replay extracted seed
|
|
210
209
|
acu --tenant DEV diff config/ # expect exit 0
|
|
@@ -267,7 +266,6 @@ Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
|
|
|
267
266
|
`base_url` resolves as `--url`, else `ACU_BASE_URL`, else a hard error naming sources.
|
|
268
267
|
|
|
269
268
|
`acu config show` prints the resolved `.env` (password excluded; includes `ACU_API_VERSION`).
|
|
270
|
-
`acu check` (lifecycle) uses the same resolved instance.
|
|
271
269
|
|
|
272
270
|
### Multi-host overlays (V44)
|
|
273
271
|
|
|
@@ -299,9 +297,6 @@ acu diff
|
|
|
299
297
|
# explicit path args (no auto) — later path wins same keys
|
|
300
298
|
acu apply config/ overlays/default-24.200.001/
|
|
301
299
|
acu diff config/ overlays/default-24.200.001/
|
|
302
|
-
|
|
303
|
-
# cold lifecycle vs the resolved .env instance (SSH + tenant required)
|
|
304
|
-
acu check --yes --tenant LAB5
|
|
305
300
|
```
|
|
306
301
|
|
|
307
302
|
Add a future half by creating `overlays/default-<new-half>/` with the
|
|
@@ -317,7 +312,7 @@ Worth knowing:
|
|
|
317
312
|
- Without a `.env`, global flags plus the process environment supply the configuration.
|
|
318
313
|
- When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the resolved base_url hostname.
|
|
319
314
|
A **present blank** `ACU_SSH=` is the hosted opt-out.
|
|
320
|
-
Only `acu tenant`
|
|
315
|
+
Only `acu tenant` requires a non-empty value post-default.
|
|
321
316
|
- `acu config show` prints the resolved `.env` (password excluded).
|
|
322
317
|
- Redirect it to turn resolved state into a working config: `acu config show > .env`.
|
|
323
318
|
|
|
@@ -365,7 +360,9 @@ gmake release patch # or minor | major
|
|
|
365
360
|
2. Fail if `## Unreleased` has no bullets
|
|
366
361
|
3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
|
|
367
362
|
4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
|
|
368
|
-
5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together
|
|
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
|
|
369
366
|
|
|
370
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).
|
|
371
368
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acumatica-cli"
|
|
3
|
-
version = "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)
|
|
@@ -55,10 +55,10 @@ COMMAND MAP (pick by intent)
|
|
|
55
55
|
seed write apply [--dry-run] [FILES...] (sole mutator)
|
|
56
56
|
seed check diff [FILES...] (exit 2 on drift)
|
|
57
57
|
txns run [--dry-run] [FILES...]
|
|
58
|
-
cold
|
|
59
|
-
pull seed extract [--only ENTITY]...
|
|
58
|
+
cold rebuild tenant create then apply then run then diff (no wrap cmd)
|
|
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
|
|
@@ -66,7 +66,6 @@ EXIT CODES (common)
|
|
|
66
66
|
0 success
|
|
67
67
|
1 operational / parse / expectation failure
|
|
68
68
|
2 seed drift (diff) or state moved (state --assert-unchanged)
|
|
69
|
-
check (lifecycle) never exits 2 — drift fails the run as exit 1
|
|
70
69
|
|
|
71
70
|
\b
|
|
72
71
|
DEFAULT PATHS (when FILES omitted)
|
|
@@ -74,7 +73,7 @@ DEFAULT PATHS (when FILES omitted)
|
|
|
74
73
|
then overlays/default-<api>/ when present
|
|
75
74
|
run -> scenario/ (overlay same-basename wins)
|
|
76
75
|
state -> config/views/ -> writes state/
|
|
77
|
-
extract
|
|
76
|
+
survey extract -> always writes config/{bootstrap,baseline,setup,master}/
|
|
78
77
|
|
|
79
78
|
Run `acu <command> --help` for flags, examples, and prerequisites.
|
|
80
79
|
See package README and docs/demo-seed.md for seed YAML shape.
|
|
@@ -674,8 +673,8 @@ def config_group() -> None:
|
|
|
674
673
|
show print resolved config as a complete .env (password redacted)
|
|
675
674
|
check read-only live preflight (REST + optional SSH)
|
|
676
675
|
|
|
677
|
-
|
|
678
|
-
|
|
676
|
+
Start here on a new machine: init → edit .env → config check →
|
|
677
|
+
tenant create / bootstrap.
|
|
679
678
|
"""
|
|
680
679
|
|
|
681
680
|
|
|
@@ -721,7 +720,6 @@ def config_init(host: str | None, directory: Path | None) -> None:
|
|
|
721
720
|
output.data(" 5. acu run scenario/")
|
|
722
721
|
output.data(" 6. acu diff config/")
|
|
723
722
|
output.data(" 7. acu state")
|
|
724
|
-
output.data(" 8. acu check # cold lifecycle (SSH + tenant)")
|
|
725
723
|
|
|
726
724
|
|
|
727
725
|
@config_group.command("show")
|
|
@@ -751,7 +749,7 @@ def config_check(ctx: click.Context) -> None:
|
|
|
751
749
|
"""Read-only preflight of the resolved target (ok/fail/warn/skip lines).
|
|
752
750
|
|
|
753
751
|
Proves connectivity before apply/run. Writes nothing (no PUT, no tenant
|
|
754
|
-
CRUD).
|
|
752
|
+
CRUD).
|
|
755
753
|
|
|
756
754
|
\b
|
|
757
755
|
Probe order
|
|
@@ -875,192 +873,6 @@ def _probe_endpoints(endpoints: list[tuple[str, str]], inst: Instance) -> bool:
|
|
|
875
873
|
return False
|
|
876
874
|
|
|
877
875
|
|
|
878
|
-
@cli.command("check")
|
|
879
|
-
@click.option(
|
|
880
|
-
"--yes",
|
|
881
|
-
is_flag=True,
|
|
882
|
-
help="Accepted for muscle-memory (pre-clean delete is always unattended)",
|
|
883
|
-
)
|
|
884
|
-
@click.option(
|
|
885
|
-
"--tenant",
|
|
886
|
-
"tenant_login",
|
|
887
|
-
default=None,
|
|
888
|
-
help="Tenant login for pre-clean create (else ACU_TENANT / global --tenant)",
|
|
889
|
-
)
|
|
890
|
-
@click.pass_context
|
|
891
|
-
def check_cmd(ctx: click.Context, yes: bool, tenant_login: str | None) -> None:
|
|
892
|
-
"""Cold lifecycle: delete → create → apply → run → diff (leave tenant).
|
|
893
|
-
|
|
894
|
-
Destructive rebuild for CI against the resolved .env instance. Not
|
|
895
|
-
`acu config check` (read-only preflight). Requires non-empty ACU_SSH
|
|
896
|
-
and a tenant login (--tenant / ACU_TENANT). Pre-clean delete is
|
|
897
|
-
always unattended; rebuilt tenant is left on the host for inspect /
|
|
898
|
-
state. --yes is accepted for muscle-memory only.
|
|
899
|
-
|
|
900
|
-
\b
|
|
901
|
-
Flow
|
|
902
|
-
delete tenant (if present) → create+bootstrap → apply → run → diff
|
|
903
|
-
Diff drift fails the run as exit 1 (never exit 2 from this command).
|
|
904
|
-
|
|
905
|
-
\b
|
|
906
|
-
Examples
|
|
907
|
-
acu check --tenant DEV
|
|
908
|
-
acu check --yes --tenant DEV
|
|
909
|
-
|
|
910
|
-
Prefer `config check` for "can I connect?" Prefer this for "rebuild
|
|
911
|
-
green from empty."
|
|
912
|
-
"""
|
|
913
|
-
_ = yes
|
|
914
|
-
overrides: dict[str, str] = dict(ctx.obj or {})
|
|
915
|
-
tenant = _resolve_check_tenant(tenant_login, overrides)
|
|
916
|
-
try:
|
|
917
|
-
inst = load_instance({**overrides, "tenant": tenant})
|
|
918
|
-
except SystemExit as exc:
|
|
919
|
-
raise SystemExit(str(exc)) from exc
|
|
920
|
-
if not inst.ssh:
|
|
921
|
-
raise SystemExit(
|
|
922
|
-
"ACU_SSH not set (pass --ssh, or put ACU_SSH in .env or the environment)"
|
|
923
|
-
)
|
|
924
|
-
output.data(f"check {inst.base_url} (api={inst.api_version})")
|
|
925
|
-
if _run_lifecycle(inst, tenant) != 0:
|
|
926
|
-
raise SystemExit(1)
|
|
927
|
-
output.success("check: green")
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
def _resolve_check_tenant(tenant_login: str | None, overrides: dict[str, str]) -> str:
|
|
931
|
-
"""Tenant login from flag, global, env, or .env; hard-error if unset."""
|
|
932
|
-
tenant = tenant_login or overrides.get("tenant") or os.environ.get("ACU_TENANT")
|
|
933
|
-
if not tenant and (root := find_data_root()) is not None:
|
|
934
|
-
tenant = read_env_values(root / ".env").get("tenant") or None
|
|
935
|
-
if not tenant:
|
|
936
|
-
raise SystemExit(
|
|
937
|
-
"tenant not set (pass --tenant, "
|
|
938
|
-
"or put ACU_TENANT in .env or the environment)"
|
|
939
|
-
)
|
|
940
|
-
return tenant
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
def _run_lifecycle(inst: Instance, tenant: str) -> int:
|
|
944
|
-
"""pre-clean→create→apply→run→diff; leave tenant (V47); return 0 or 1."""
|
|
945
|
-
mgr = TenantManager(inst)
|
|
946
|
-
_lifecycle_delete_best_effort(mgr, tenant, label="pre-clean")
|
|
947
|
-
try:
|
|
948
|
-
_lifecycle_create_and_bootstrap(inst, mgr, tenant)
|
|
949
|
-
data_inst = inst.model_copy(update={"tenant": tenant})
|
|
950
|
-
_lifecycle_apply(data_inst)
|
|
951
|
-
_lifecycle_run(data_inst)
|
|
952
|
-
_lifecycle_diff(data_inst)
|
|
953
|
-
except SystemExit as exc:
|
|
954
|
-
code = exc.code if isinstance(exc.code, int) else 1
|
|
955
|
-
if code == 2:
|
|
956
|
-
code = 1 # V47: check never exits 2
|
|
957
|
-
output.error(f"lifecycle: {exc}")
|
|
958
|
-
# Leave tenant for manual inspect (no post-clean — V47).
|
|
959
|
-
return 1 if code != 0 else 0
|
|
960
|
-
except (RuntimeError, httpx.HTTPError) as exc:
|
|
961
|
-
output.error(f"lifecycle: {_format_failure(exc, target=inst.base_url)}")
|
|
962
|
-
# Leave tenant for manual inspect (no post-clean — V47).
|
|
963
|
-
return 1
|
|
964
|
-
output.success(
|
|
965
|
-
f"lifecycle green on {inst.base_url} tenant {tenant} (left for inspection)"
|
|
966
|
-
)
|
|
967
|
-
return 0
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
def _lifecycle_create_and_bootstrap(
|
|
971
|
-
inst: Instance, mgr: TenantManager, tenant: str
|
|
972
|
-
) -> None:
|
|
973
|
-
"""Create (or exists-skip) tenant + init + bootstrap chain."""
|
|
974
|
-
tenants = mgr.list()
|
|
975
|
-
existing = next((t for t in tenants if t.login_name == tenant), None)
|
|
976
|
-
if existing is not None:
|
|
977
|
-
company_id = existing.company_id
|
|
978
|
-
output.data(f"skip create: tenant {tenant} exists (id {company_id})")
|
|
979
|
-
else:
|
|
980
|
-
company_id = max((t.company_id for t in tenants), default=0) + 1
|
|
981
|
-
with output.step(f"creating tenant {company_id} ({tenant}) on {inst.base_url}"):
|
|
982
|
-
mgr.create(company_id, tenant, 1, True, "")
|
|
983
|
-
output.data("created")
|
|
984
|
-
with output.step(f"aligning CompanyCD to login ({tenant})"):
|
|
985
|
-
if mgr.set_company_cd(company_id, tenant):
|
|
986
|
-
output.data(f"CompanyCD set to {tenant}")
|
|
987
|
-
else:
|
|
988
|
-
output.data(f"CompanyCD already {tenant}")
|
|
989
|
-
_init_tenant(inst, mgr, tenant)
|
|
990
|
-
_bootstrap_tenant(inst, mgr, tenant)
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
def _lifecycle_delete_best_effort(
|
|
994
|
-
mgr: TenantManager, tenant: str, *, label: str
|
|
995
|
-
) -> None:
|
|
996
|
-
try:
|
|
997
|
-
tenants = mgr.list()
|
|
998
|
-
if any(t.login_name == tenant for t in tenants):
|
|
999
|
-
with output.step(f"{label} delete tenant {tenant}"):
|
|
1000
|
-
mgr.delete(login_name=tenant)
|
|
1001
|
-
with output.step(f"recycling app pool after {label}"):
|
|
1002
|
-
mgr.recycle_app_pool()
|
|
1003
|
-
output.data(f"{label}: deleted {tenant}")
|
|
1004
|
-
else:
|
|
1005
|
-
output.data(f"{label}: tenant {tenant} absent")
|
|
1006
|
-
except (RuntimeError, SystemExit) as exc:
|
|
1007
|
-
output.warn(f"{label}: {exc}")
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
def _lifecycle_apply(inst: Instance) -> None:
|
|
1011
|
-
"""Bare apply (pin overlay auto-compose); SystemExit 1 on failure."""
|
|
1012
|
-
total_ok = 0
|
|
1013
|
-
all_errors: list[str] = []
|
|
1014
|
-
with AcumaticaClient(inst) as client:
|
|
1015
|
-
for path in expand_files(default_apply_dirs(inst)):
|
|
1016
|
-
baseline = seed.load_baseline(path)
|
|
1017
|
-
output.data(
|
|
1018
|
-
f"{path} -> {inst.tenant} on {inst.base_url} ({baseline.entity})"
|
|
1019
|
-
)
|
|
1020
|
-
n, errors = seed.apply(client, baseline, dry_run=False)
|
|
1021
|
-
total_ok += n
|
|
1022
|
-
all_errors.extend(f"{path}: {e}" for e in errors)
|
|
1023
|
-
output.data(f" {n} record(s)")
|
|
1024
|
-
if all_errors:
|
|
1025
|
-
output.error(f"apply: {len(all_errors)} error(s), {total_ok} record(s) applied")
|
|
1026
|
-
for err in all_errors:
|
|
1027
|
-
output.error(err)
|
|
1028
|
-
raise SystemExit(1)
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
def _lifecycle_run(inst: Instance) -> None:
|
|
1032
|
-
"""Bare run (pin overlay); SystemExit 1 on failure."""
|
|
1033
|
-
paths = list(default_scenario_files(inst))
|
|
1034
|
-
scenarios = [run.load_scenario(path) for path in paths]
|
|
1035
|
-
ok = True
|
|
1036
|
-
with AcumaticaClient(inst) as client:
|
|
1037
|
-
for scenario in scenarios:
|
|
1038
|
-
ok = run.run(client, scenario) and ok
|
|
1039
|
-
if not ok:
|
|
1040
|
-
raise SystemExit(1)
|
|
1041
|
-
output.success(f"{len(scenarios)} scenario(s) passed on {inst.tenant}")
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
def _lifecycle_diff(inst: Instance) -> None:
|
|
1045
|
-
"""Bare diff; SystemExit 2 on drift (caller maps to 1 for check)."""
|
|
1046
|
-
paths = expand_files(default_apply_dirs(inst))
|
|
1047
|
-
drifts: list[str] = []
|
|
1048
|
-
with AcumaticaClient(inst) as client:
|
|
1049
|
-
for path in paths:
|
|
1050
|
-
baseline = seed.load_baseline(path)
|
|
1051
|
-
output.data(
|
|
1052
|
-
f"{path} -> {inst.tenant} on {inst.base_url} ({baseline.entity})"
|
|
1053
|
-
)
|
|
1054
|
-
file_drifts = seed.diff(client, baseline)
|
|
1055
|
-
drifts += file_drifts
|
|
1056
|
-
n = 1 if isinstance(baseline, seed.ActionFile) else len(baseline.records)
|
|
1057
|
-
if file_drifts:
|
|
1058
|
-
output.data(f" {n} record(s), {len(file_drifts)} drift(s)")
|
|
1059
|
-
else:
|
|
1060
|
-
output.data(f" {n} record(s) ok")
|
|
1061
|
-
_exit_on_drift(inst, drifts, len(paths))
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
876
|
SEED_DIRS = ("bootstrap", "baseline", "setup", "master")
|
|
1065
877
|
|
|
1066
878
|
|
|
@@ -1215,7 +1027,7 @@ def apply_cmd(inst: Instance, files: tuple[Path, ...], dry_run: bool) -> None:
|
|
|
1215
1027
|
acu --tenant DEV apply --dry-run config/master/
|
|
1216
1028
|
acu --tenant DEV apply config/baseline/20-accounts.yaml
|
|
1217
1029
|
|
|
1218
|
-
Related: `diff` (drift) · `extract` (inverse pull) · `run` (txns).
|
|
1030
|
+
Related: `diff` (drift) · `survey extract` (inverse pull) · `run` (txns).
|
|
1219
1031
|
"""
|
|
1220
1032
|
total_ok = 0
|
|
1221
1033
|
all_errors: list[str] = []
|
|
@@ -1294,7 +1106,7 @@ def diff_cmd(inst: Instance, files: tuple[Path, ...]) -> None:
|
|
|
1294
1106
|
acu --tenant DEV diff config/
|
|
1295
1107
|
acu --tenant DEV diff config/master/90-roles.yaml
|
|
1296
1108
|
|
|
1297
|
-
Related: `apply` (fix) · `extract` (pull) · `state` (balances).
|
|
1109
|
+
Related: `apply` (fix) · `survey extract` (pull) · `state` (balances).
|
|
1298
1110
|
"""
|
|
1299
1111
|
paths = expand_files(files or default_apply_dirs(inst))
|
|
1300
1112
|
drifts: list[str] = []
|
|
@@ -1377,7 +1189,27 @@ def _complete_only(
|
|
|
1377
1189
|
]
|
|
1378
1190
|
|
|
1379
1191
|
|
|
1380
|
-
@cli.
|
|
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")
|
|
1381
1213
|
@click.option(
|
|
1382
1214
|
"--out",
|
|
1383
1215
|
"out_dir",
|
|
@@ -1418,11 +1250,11 @@ def extract_cmd(
|
|
|
1418
1250
|
|
|
1419
1251
|
\b
|
|
1420
1252
|
Examples
|
|
1421
|
-
acu --tenant DEV extract
|
|
1422
|
-
acu --tenant DEV extract --only Account --only Vendor
|
|
1423
|
-
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
|
|
1424
1256
|
|
|
1425
|
-
Related: `apply` · `diff` · `inventory` · `state`.
|
|
1257
|
+
Related: `apply` · `diff` · `survey inventory` · `state`.
|
|
1426
1258
|
"""
|
|
1427
1259
|
with AcumaticaClient(inst) as client:
|
|
1428
1260
|
failed = extract.run(
|
|
@@ -1446,7 +1278,7 @@ def _exit_on_drift(inst: Instance, drifts: list[str], files: int) -> None:
|
|
|
1446
1278
|
output.success(f"no drift on {inst.tenant} ({inst.base_url}, {files} file(s))")
|
|
1447
1279
|
|
|
1448
1280
|
|
|
1449
|
-
@
|
|
1281
|
+
@survey_group.command("inventory")
|
|
1450
1282
|
@click.argument(
|
|
1451
1283
|
"artifact",
|
|
1452
1284
|
type=click.Path(exists=True, path_type=Path),
|
|
@@ -1478,15 +1310,15 @@ def inventory_cmd(
|
|
|
1478
1310
|
Existing files skip unless --force.
|
|
1479
1311
|
|
|
1480
1312
|
Not extract (REST → config/ seed). Not state (balances). Never writes
|
|
1481
|
-
config/ or state/. Feed inventory/ into `acu reconcile`.
|
|
1313
|
+
config/ or state/. Feed inventory/ into `acu survey reconcile`.
|
|
1482
1314
|
|
|
1483
1315
|
\b
|
|
1484
1316
|
Exit 0 = clean · 1 = parse/format/version fail · never 2
|
|
1485
1317
|
|
|
1486
1318
|
\b
|
|
1487
1319
|
Examples
|
|
1488
|
-
acu inventory ./export-xml/
|
|
1489
|
-
acu inventory Settings.zip --out inventory/ --force
|
|
1320
|
+
acu survey inventory ./export-xml/
|
|
1321
|
+
acu survey inventory Settings.zip --out inventory/ --force
|
|
1490
1322
|
"""
|
|
1491
1323
|
# V9 long single-op: artifact parse (ZIP/folder IR) via step; banner +
|
|
1492
1324
|
# per-table write/skip emit stay multi-unit stdout after.
|
|
@@ -1500,13 +1332,13 @@ def inventory_cmd(
|
|
|
1500
1332
|
inventory.emit(art, dest, force=force, dry_run=dry_run)
|
|
1501
1333
|
|
|
1502
1334
|
|
|
1503
|
-
@
|
|
1335
|
+
@survey_group.command("reconcile")
|
|
1504
1336
|
@click.option(
|
|
1505
1337
|
"--inventory",
|
|
1506
1338
|
"inventory_dir",
|
|
1507
1339
|
type=click.Path(file_okay=False, path_type=Path),
|
|
1508
1340
|
default=None,
|
|
1509
|
-
help="Inventory tree from `acu inventory` (default: inventory/)",
|
|
1341
|
+
help="Inventory tree from `acu survey inventory` (default: inventory/)",
|
|
1510
1342
|
)
|
|
1511
1343
|
@click.option(
|
|
1512
1344
|
"--config",
|
|
@@ -1549,8 +1381,8 @@ def reconcile_cmd(
|
|
|
1549
1381
|
|
|
1550
1382
|
\b
|
|
1551
1383
|
Examples
|
|
1552
|
-
acu inventory export.zip && acu reconcile
|
|
1553
|
-
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/
|
|
1554
1386
|
"""
|
|
1555
1387
|
inv = (
|
|
1556
1388
|
inventory_dir
|
|
@@ -1628,7 +1460,7 @@ def state_cmd(
|
|
|
1628
1460
|
acu --tenant DEV state --assert-unchanged
|
|
1629
1461
|
acu --tenant DEV state config/views/10-trial-balance.yaml
|
|
1630
1462
|
|
|
1631
|
-
Related: `run` (moves balances) · `diff` (seed drift) · `extract`.
|
|
1463
|
+
Related: `run` (moves balances) · `diff` (seed drift) · `survey extract`.
|
|
1632
1464
|
"""
|
|
1633
1465
|
if not files:
|
|
1634
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"))
|
|
@@ -30,15 +30,14 @@ acu state
|
|
|
30
30
|
# warm gate: once-capital only — additive buy/sell moves numeric observations
|
|
31
31
|
acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged
|
|
32
32
|
|
|
33
|
-
#
|
|
34
|
-
# acu check --yes
|
|
33
|
+
# SSH cold rebuild: acu tenant create --login NAME, then apply, run, diff
|
|
35
34
|
|
|
36
35
|
# Optional: re-seed from live (inverse of apply; always under config/)
|
|
37
|
-
# acu extract --out . --force
|
|
36
|
+
# acu survey extract --out . --force
|
|
38
37
|
```
|
|
39
38
|
|
|
40
39
|
Bare `acu apply` / `acu diff` also prefer `config/` when those trees exist.
|
|
41
|
-
`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).
|
|
42
41
|
|
|
43
42
|
## Layout
|
|
44
43
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/overlays/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/20-buy.yaml
RENAMED
|
File without changes
|
{acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/30-build.yaml
RENAMED
|
File without changes
|
{acumatica_cli-0.29.0 → acumatica_cli-0.31.0}/src/acumatica_cli/templates/scenario/40-sell.yaml
RENAMED
|
File without changes
|
|
File without changes
|