acumatica-cli 0.13.1__tar.gz → 0.15.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.13.1 → acumatica_cli-0.15.0}/PKG-INFO +24 -12
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/README.md +23 -11
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/pyproject.toml +1 -1
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/cli.py +34 -35
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/client.py +1 -1
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/config.py +2 -6
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/extract.py +194 -52
- acumatica_cli-0.15.0/src/acumatica_cli/seed_catalog.yaml +450 -0
- acumatica_cli-0.13.1/src/acumatica_cli/snapshot.py → acumatica_cli-0.15.0/src/acumatica_cli/state.py +9 -9
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/README.md +9 -5
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +2 -21
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +2 -9
- {acumatica_cli-0.13.1/src/acumatica_cli/templates/config/snapshot → acumatica_cli-0.15.0/src/acumatica_cli/templates/config/views}/10-trial-balance.yaml +2 -2
- acumatica_cli-0.13.1/src/acumatica_cli/extract_manifest.yaml +0 -152
- acumatica_cli-0.13.1/src/acumatica_cli/templates/config/baseline/91-company-packaging.yaml +0 -14
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/__init__.py +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/bootstrap.py +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/bootstrap_project.xml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/firstlogin.py +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/models.py +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/output.py +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/run.py +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/seed.py +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/target.py +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/env +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/gitignore +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/target +0 -0
- {acumatica_cli-0.13.1 → acumatica_cli-0.15.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.15.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>
|
|
@@ -48,7 +48,7 @@ acu tenant create --id 3 --login DEV # create the tenant + bootstrap it (nee
|
|
|
48
48
|
acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup,master}/
|
|
49
49
|
acu --tenant DEV run scenario/ # once capital → buy → build → sell
|
|
50
50
|
acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
|
|
51
|
-
acu --tenant DEV
|
|
51
|
+
acu --tenant DEV state # capture state/ trial-balance
|
|
52
52
|
# warm: capital once-skips; Owner Capital stays 50000 (not 100000)
|
|
53
53
|
acu --tenant DEV run scenario/
|
|
54
54
|
```
|
|
@@ -85,10 +85,10 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
|
|
|
85
85
|
├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
|
|
86
86
|
├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
|
|
87
87
|
├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
|
|
88
|
-
├──
|
|
88
|
+
├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
|
|
89
89
|
│ capture derived state into state/ (not seed)
|
|
90
90
|
├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
|
|
91
|
-
│
|
|
91
|
+
│ inverse of apply into config/{bootstrap,baseline,setup,master}/
|
|
92
92
|
├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
|
|
93
93
|
│
|
|
94
94
|
└── config configuration ops
|
|
@@ -100,23 +100,24 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
|
|
|
100
100
|
`apply` and `diff` without FILES prefer `config/<name>/` when any seed child exists under `config/`; otherwise root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
|
|
101
101
|
A path like `config/` expands nested seed dirs in that fixed order.
|
|
102
102
|
`run` without FILES defaults to `scenario/`.
|
|
103
|
-
`
|
|
103
|
+
`state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
|
|
104
|
+
`extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
|
|
104
105
|
`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
|
|
105
106
|
Run `acu <command> --help` for details on any command.
|
|
106
107
|
|
|
107
108
|
## The data repo
|
|
108
109
|
|
|
109
110
|
Your configuration lives in its own git repo.
|
|
110
|
-
`acu config init` scaffolds a **single full seed** under `config/` (Bootstrap `project.xml` at `Bootstrap/1.0.0`, expanded COA, masters) plus lifecycle `scenario/`, observer `config/
|
|
111
|
+
`acu config init` scaffolds a **single full seed** under `config/` (Bootstrap `project.xml` at `Bootstrap/1.0.0`, expanded COA, masters) plus lifecycle `scenario/`, observer `config/views/`, and README. There is no `--flavor`.
|
|
111
112
|
|
|
112
113
|
| Path | What it holds |
|
|
113
114
|
| ---- | ------------- |
|
|
114
115
|
| `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
|
|
115
|
-
| `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs
|
|
116
|
+
| `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
|
|
116
117
|
| `config/setup/` | one-time actions: financial year, master calendar, open periods |
|
|
117
118
|
| `config/master/` | inventory/distribution masters (prefs, warehouse, items, parties) |
|
|
118
119
|
| `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
|
|
119
|
-
| `config/
|
|
120
|
+
| `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
|
|
120
121
|
| `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
|
|
121
122
|
| `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
|
|
122
123
|
| `.env` | where to apply and who signs in, every key an `ACU_*` variable |
|
|
@@ -128,13 +129,24 @@ The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for
|
|
|
128
129
|
Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
|
|
129
130
|
|
|
130
131
|
Seed YAML is state: `apply` upserts it, `diff` proves it.
|
|
131
|
-
|
|
132
|
+
`acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut). Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split); the demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths. Features synthesize to `config/bootstrap/features.yaml`. Existing files skip unless `--force`; empty live sets skip; row failures continue (exit 1 only if any row failed — drift stays with `diff`).
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
acu --tenant DEV extract --out . --force # refresh config/** from live tenant
|
|
136
|
+
git diff config/ # review extract delta before commit
|
|
137
|
+
acu --tenant DEV apply config/ # replay extracted seed
|
|
138
|
+
acu --tenant DEV diff config/ # expect exit 0
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Migration (T115–T120 extract hard-cut):** extract no longer writes root `bootstrap/` / `baseline/` / `setup/` / `master/`. Paths are always `config/…`. There is no `--layout`. Move any root-layout extract output under `config/`, or re-extract into a modern data repo. Catalog rename: `extract_manifest.yaml` becomes `seed_catalog.yaml` (package data only; operators do not author it).
|
|
142
|
+
|
|
143
|
+
Scenario YAML is different — it describes transactions that flow forward (not extractable seed).
|
|
132
144
|
`acu run` executes each step in order (`put`, `action`, `wait`, `get`), captures server-assigned document numbers into `${var}` references for later steps, and checks `expect:` assertions as deltas against a pre-run snapshot, so additive scenarios re-run safely on a warm tenant.
|
|
133
145
|
`once: true` scenarios declare a `present` inquire-absolute gate; when the probe already holds, the CLI prints `skip <path> (once: already present)` and runs neither steps nor expects (Owner Capital does not restack).
|
|
134
146
|
|
|
135
|
-
`acu
|
|
147
|
+
`acu state` is the third observation path: it captures live derived state (balances) into `state/` for git review. It is not `extract` (config seed) and not `diff` (desired vs actual config). Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`). After a cold `acu run scenario/ && acu state`, warm `acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
|
|
136
148
|
|
|
137
|
-
**Migration (
|
|
149
|
+
**Migration (T112–T114 hard-cut):** CLI verb is `state` only (no `snapshot` alias). Bare defaults are `config/views/` (views) and `state/` (observations). Prior `config/snapshot/` and root `snapshot/` / `snapshots/` are not fallbacks — move views to `config/views/` or pass explicit path args.
|
|
138
150
|
|
|
139
151
|
### Seed `endpoint:` symbols
|
|
140
152
|
|
|
@@ -308,7 +320,7 @@ The packaged full `config init` seed (under `config/`) is the only scaffold.
|
|
|
308
320
|
```sh
|
|
309
321
|
gmake e2e # whole tier, about 20 minutes
|
|
310
322
|
gmake e2e FILE=test_provision_lifecycle # apply/diff focus
|
|
311
|
-
gmake e2e FILE=test_scenario_lifecycle # scenario +
|
|
323
|
+
gmake e2e FILE=test_scenario_lifecycle # scenario + state focus
|
|
312
324
|
```
|
|
313
325
|
|
|
314
326
|
## License
|
|
@@ -30,7 +30,7 @@ acu tenant create --id 3 --login DEV # create the tenant + bootstrap it (nee
|
|
|
30
30
|
acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup,master}/
|
|
31
31
|
acu --tenant DEV run scenario/ # once capital → buy → build → sell
|
|
32
32
|
acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
|
|
33
|
-
acu --tenant DEV
|
|
33
|
+
acu --tenant DEV state # capture state/ trial-balance
|
|
34
34
|
# warm: capital once-skips; Owner Capital stays 50000 (not 100000)
|
|
35
35
|
acu --tenant DEV run scenario/
|
|
36
36
|
```
|
|
@@ -67,10 +67,10 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
|
|
|
67
67
|
├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
|
|
68
68
|
├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
|
|
69
69
|
├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
|
|
70
|
-
├──
|
|
70
|
+
├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
|
|
71
71
|
│ capture derived state into state/ (not seed)
|
|
72
72
|
├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
|
|
73
|
-
│
|
|
73
|
+
│ inverse of apply into config/{bootstrap,baseline,setup,master}/
|
|
74
74
|
├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
|
|
75
75
|
│
|
|
76
76
|
└── config configuration ops
|
|
@@ -82,23 +82,24 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
|
|
|
82
82
|
`apply` and `diff` without FILES prefer `config/<name>/` when any seed child exists under `config/`; otherwise root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
|
|
83
83
|
A path like `config/` expands nested seed dirs in that fixed order.
|
|
84
84
|
`run` without FILES defaults to `scenario/`.
|
|
85
|
-
`
|
|
85
|
+
`state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
|
|
86
|
+
`extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
|
|
86
87
|
`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
|
|
87
88
|
Run `acu <command> --help` for details on any command.
|
|
88
89
|
|
|
89
90
|
## The data repo
|
|
90
91
|
|
|
91
92
|
Your configuration lives in its own git repo.
|
|
92
|
-
`acu config init` scaffolds a **single full seed** under `config/` (Bootstrap `project.xml` at `Bootstrap/1.0.0`, expanded COA, masters) plus lifecycle `scenario/`, observer `config/
|
|
93
|
+
`acu config init` scaffolds a **single full seed** under `config/` (Bootstrap `project.xml` at `Bootstrap/1.0.0`, expanded COA, masters) plus lifecycle `scenario/`, observer `config/views/`, and README. There is no `--flavor`.
|
|
93
94
|
|
|
94
95
|
| Path | What it holds |
|
|
95
96
|
| ---- | ------------- |
|
|
96
97
|
| `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
|
|
97
|
-
| `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs
|
|
98
|
+
| `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
|
|
98
99
|
| `config/setup/` | one-time actions: financial year, master calendar, open periods |
|
|
99
100
|
| `config/master/` | inventory/distribution masters (prefs, warehouse, items, parties) |
|
|
100
101
|
| `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
|
|
101
|
-
| `config/
|
|
102
|
+
| `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
|
|
102
103
|
| `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
|
|
103
104
|
| `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
|
|
104
105
|
| `.env` | where to apply and who signs in, every key an `ACU_*` variable |
|
|
@@ -110,13 +111,24 @@ The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for
|
|
|
110
111
|
Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
|
|
111
112
|
|
|
112
113
|
Seed YAML is state: `apply` upserts it, `diff` proves it.
|
|
113
|
-
|
|
114
|
+
`acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut). Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split); the demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths. Features synthesize to `config/bootstrap/features.yaml`. Existing files skip unless `--force`; empty live sets skip; row failures continue (exit 1 only if any row failed — drift stays with `diff`).
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
acu --tenant DEV extract --out . --force # refresh config/** from live tenant
|
|
118
|
+
git diff config/ # review extract delta before commit
|
|
119
|
+
acu --tenant DEV apply config/ # replay extracted seed
|
|
120
|
+
acu --tenant DEV diff config/ # expect exit 0
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Migration (T115–T120 extract hard-cut):** extract no longer writes root `bootstrap/` / `baseline/` / `setup/` / `master/`. Paths are always `config/…`. There is no `--layout`. Move any root-layout extract output under `config/`, or re-extract into a modern data repo. Catalog rename: `extract_manifest.yaml` becomes `seed_catalog.yaml` (package data only; operators do not author it).
|
|
124
|
+
|
|
125
|
+
Scenario YAML is different — it describes transactions that flow forward (not extractable seed).
|
|
114
126
|
`acu run` executes each step in order (`put`, `action`, `wait`, `get`), captures server-assigned document numbers into `${var}` references for later steps, and checks `expect:` assertions as deltas against a pre-run snapshot, so additive scenarios re-run safely on a warm tenant.
|
|
115
127
|
`once: true` scenarios declare a `present` inquire-absolute gate; when the probe already holds, the CLI prints `skip <path> (once: already present)` and runs neither steps nor expects (Owner Capital does not restack).
|
|
116
128
|
|
|
117
|
-
`acu
|
|
129
|
+
`acu state` is the third observation path: it captures live derived state (balances) into `state/` for git review. It is not `extract` (config seed) and not `diff` (desired vs actual config). Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`). After a cold `acu run scenario/ && acu state`, warm `acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
|
|
118
130
|
|
|
119
|
-
**Migration (
|
|
131
|
+
**Migration (T112–T114 hard-cut):** CLI verb is `state` only (no `snapshot` alias). Bare defaults are `config/views/` (views) and `state/` (observations). Prior `config/snapshot/` and root `snapshot/` / `snapshots/` are not fallbacks — move views to `config/views/` or pass explicit path args.
|
|
120
132
|
|
|
121
133
|
### Seed `endpoint:` symbols
|
|
122
134
|
|
|
@@ -290,7 +302,7 @@ The packaged full `config init` seed (under `config/`) is the only scaffold.
|
|
|
290
302
|
```sh
|
|
291
303
|
gmake e2e # whole tier, about 20 minutes
|
|
292
304
|
gmake e2e FILE=test_provision_lifecycle # apply/diff focus
|
|
293
|
-
gmake e2e FILE=test_scenario_lifecycle # scenario +
|
|
305
|
+
gmake e2e FILE=test_scenario_lifecycle # scenario + state focus
|
|
294
306
|
```
|
|
295
307
|
|
|
296
308
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acumatica-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.15.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"
|
|
@@ -12,7 +12,7 @@ import click
|
|
|
12
12
|
import httpx
|
|
13
13
|
from click.shell_completion import get_completion_class
|
|
14
14
|
|
|
15
|
-
from . import bootstrap, extract, firstlogin, output, run, seed,
|
|
15
|
+
from . import bootstrap, extract, firstlogin, output, run, seed, state
|
|
16
16
|
from .client import AcumaticaClient
|
|
17
17
|
from .config import (
|
|
18
18
|
Instance,
|
|
@@ -419,7 +419,7 @@ def config_init(host: str | None, directory: Path | None) -> None:
|
|
|
419
419
|
Templates ship with the package; every value is a placeholder or a
|
|
420
420
|
verified example - no secrets. Single full seed under ``config/``
|
|
421
421
|
(bootstrap/baseline/setup/master) + lifecycle ``scenario/`` +
|
|
422
|
-
``config/
|
|
422
|
+
``config/views/`` + README; full company ``project.xml``
|
|
423
423
|
(Bootstrap/1.0.0). No ``--flavor`` (V28/T108). Existing files are
|
|
424
424
|
never overwritten (reported as skipped). DIRECTORY defaults to the
|
|
425
425
|
current directory and is created if absent. No git init, no gpg.
|
|
@@ -437,6 +437,7 @@ def config_init(host: str | None, directory: Path | None) -> None:
|
|
|
437
437
|
output.data(" 4. acu apply config/")
|
|
438
438
|
output.data(" 5. acu run scenario/")
|
|
439
439
|
output.data(" 6. acu diff config/")
|
|
440
|
+
output.data(" 7. acu state")
|
|
440
441
|
|
|
441
442
|
|
|
442
443
|
@config_group.command("show")
|
|
@@ -863,9 +864,9 @@ def run_cmd(inst: Instance, files: tuple[Path, ...], dry_run: bool) -> None:
|
|
|
863
864
|
def _complete_only(
|
|
864
865
|
_ctx: click.Context, _param: click.Parameter, incomplete: str
|
|
865
866
|
) -> list[str]:
|
|
866
|
-
"""--only value completion: entity names off the packaged
|
|
867
|
+
"""--only value completion: entity names off the packaged seed catalog.
|
|
867
868
|
|
|
868
|
-
Fires per keystroke, so it stays local-only (V23): the
|
|
869
|
+
Fires per keystroke, so it stays local-only (V23): the catalog is
|
|
869
870
|
package data - never REST, never SSH, never a live instance.
|
|
870
871
|
"""
|
|
871
872
|
return [
|
|
@@ -887,7 +888,7 @@ def _complete_only(
|
|
|
887
888
|
"--only",
|
|
888
889
|
multiple=True,
|
|
889
890
|
shell_complete=_complete_only,
|
|
890
|
-
help="Limit to matching
|
|
891
|
+
help="Limit to matching catalog rows (entity name or file stem); repeatable",
|
|
891
892
|
)
|
|
892
893
|
@click.option("--force", is_flag=True, help="Overwrite existing files")
|
|
893
894
|
@click.option(
|
|
@@ -901,16 +902,17 @@ def extract_cmd(
|
|
|
901
902
|
force: bool,
|
|
902
903
|
dry_run: bool,
|
|
903
904
|
) -> None:
|
|
904
|
-
"""Extract live tenant state into seed YAML
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
905
|
+
"""Extract live tenant state into seed YAML under config/ (inverse of apply).
|
|
906
|
+
|
|
907
|
+
Catalog-driven (packaged ``seed_catalog.yaml`` is the verified entity
|
|
908
|
+
registry): each row is read from the live tenant and written under
|
|
909
|
+
``config/{bootstrap,baseline,setup,master}/`` (hard-cut; never root
|
|
910
|
+
SEED_DIRS; no ``--layout``). Apply and diff consume those files
|
|
911
|
+
unchanged. Features synthesize to ``config/bootstrap/features.yaml``.
|
|
912
|
+
Existing files are skipped unless --force; an entity with no live
|
|
913
|
+
records produces no file. A failing row is reported and the run
|
|
914
|
+
continues (a virgin tenant extracts whole). Exit 0 when every row
|
|
915
|
+
wrote or skipped clean, 1 when any row failed - drift stays with diff.
|
|
914
916
|
"""
|
|
915
917
|
assert_target_compatible(inst)
|
|
916
918
|
with AcumaticaClient(inst) as client:
|
|
@@ -935,7 +937,7 @@ def _exit_on_drift(inst: Instance, drifts: list[str], files: int) -> None:
|
|
|
935
937
|
output.success(f"no drift on {inst.tenant} ({inst.base_url}, {files} file(s))")
|
|
936
938
|
|
|
937
939
|
|
|
938
|
-
@cli.command("
|
|
940
|
+
@cli.command("state")
|
|
939
941
|
@click.argument(
|
|
940
942
|
"files", nargs=-1, required=False, type=click.Path(exists=True, path_type=Path)
|
|
941
943
|
)
|
|
@@ -963,7 +965,7 @@ def _exit_on_drift(inst: Instance, drifts: list[str], files: int) -> None:
|
|
|
963
965
|
help="Resolve views and validate sources without HTTP",
|
|
964
966
|
)
|
|
965
967
|
@pass_instance
|
|
966
|
-
def
|
|
968
|
+
def state_cmd(
|
|
967
969
|
inst: Instance,
|
|
968
970
|
files: tuple[Path, ...],
|
|
969
971
|
out_dir: Path | None,
|
|
@@ -973,39 +975,36 @@ def snapshot_cmd(
|
|
|
973
975
|
) -> None:
|
|
974
976
|
"""Capture live derived state into committed observation files.
|
|
975
977
|
|
|
976
|
-
FILES are
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
moved). Never writes seed trees or endpoint: symbols (V32). Exit 0
|
|
978
|
+
FILES are view YAML files or directories. Omitted, they default to the
|
|
979
|
+
data repo's config/views/ directory (hard-cut; no config/snapshot/
|
|
980
|
+
fallback). Default write target is state/ (--out). Bare capture writes
|
|
981
|
+
observations (change is fine). --diff compares live to disk without
|
|
982
|
+
writing. --assert-unchanged is the warm-run idempotence gate (exit 2
|
|
983
|
+
when moved). Never writes seed trees or endpoint: symbols (V32). Exit 0
|
|
983
984
|
ok, 1 op fail, 2 only under --assert-unchanged when state moved.
|
|
984
985
|
"""
|
|
985
986
|
assert_target_compatible(inst)
|
|
986
987
|
if not files:
|
|
987
|
-
default = data_root() / "config" / "
|
|
988
|
+
default = data_root() / "config" / "views"
|
|
988
989
|
if not default.is_dir():
|
|
989
|
-
raise SystemExit(f"{default}:
|
|
990
|
+
raise SystemExit(f"{default}: views directory does not exist")
|
|
990
991
|
files = (Path(os.path.relpath(default)),)
|
|
991
|
-
paths =
|
|
992
|
-
views = [
|
|
992
|
+
paths = state.expand_view_files(files)
|
|
993
|
+
views = [state.load_view(path) for path in paths]
|
|
993
994
|
dest = out_dir if out_dir is not None else Path("state")
|
|
994
995
|
if dry_run:
|
|
995
|
-
code =
|
|
996
|
+
code = state.run_views(None, views, out_dir=dest, mode="dry")
|
|
996
997
|
else:
|
|
997
998
|
mode = "assert" if assert_unchanged else "diff" if do_diff else "write"
|
|
998
999
|
with AcumaticaClient(inst) as client:
|
|
999
|
-
code =
|
|
1000
|
+
code = state.run_views(client, views, out_dir=dest, mode=mode)
|
|
1000
1001
|
if code:
|
|
1001
1002
|
raise SystemExit(code)
|
|
1002
1003
|
if dry_run:
|
|
1003
1004
|
return
|
|
1004
1005
|
if assert_unchanged:
|
|
1005
|
-
output.success(f"{len(views)}
|
|
1006
|
+
output.success(f"{len(views)} view(s) unchanged on {inst.tenant}")
|
|
1006
1007
|
elif do_diff:
|
|
1007
|
-
output.success(f"{len(views)}
|
|
1008
|
+
output.success(f"{len(views)} view(s) compared on {inst.tenant}")
|
|
1008
1009
|
else:
|
|
1009
|
-
output.success(
|
|
1010
|
-
f"{len(views)} snapshot(s) written under {dest} on {inst.tenant}"
|
|
1011
|
-
)
|
|
1010
|
+
output.success(f"{len(views)} view(s) written under {dest} on {inst.tenant}")
|
|
@@ -539,7 +539,7 @@ class AcumaticaClient:
|
|
|
539
539
|
self._http.post("/CustomizationApi/publishEnd", json={})
|
|
540
540
|
).json()
|
|
541
541
|
|
|
542
|
-
# -- OData Generic Inquiry (
|
|
542
|
+
# -- OData Generic Inquiry (state gi: source; V33) --
|
|
543
543
|
|
|
544
544
|
def _odata_gi_root(self) -> str:
|
|
545
545
|
"""24R2+ OData GI service root ``/t/<tenant>/api/odata/gi`` (V33)."""
|
|
@@ -61,10 +61,6 @@ INIT_TEMPLATES = (
|
|
|
61
61
|
"config/baseline/60-ledger-company.yaml",
|
|
62
62
|
),
|
|
63
63
|
("config/baseline/90-uoms.yaml", "config/baseline/90-uoms.yaml"),
|
|
64
|
-
(
|
|
65
|
-
"config/baseline/91-company-packaging.yaml",
|
|
66
|
-
"config/baseline/91-company-packaging.yaml",
|
|
67
|
-
),
|
|
68
64
|
("config/setup/10-financial-year.yaml", "config/setup/10-financial-year.yaml"),
|
|
69
65
|
("config/setup/20-master-calendar.yaml", "config/setup/20-master-calendar.yaml"),
|
|
70
66
|
("config/setup/30-open-periods.yaml", "config/setup/30-open-periods.yaml"),
|
|
@@ -118,8 +114,8 @@ INIT_TEMPLATES = (
|
|
|
118
114
|
"config/master/85-kit-specifications.yaml",
|
|
119
115
|
),
|
|
120
116
|
(
|
|
121
|
-
"config/
|
|
122
|
-
"config/
|
|
117
|
+
"config/views/10-trial-balance.yaml",
|
|
118
|
+
"config/views/10-trial-balance.yaml",
|
|
123
119
|
),
|
|
124
120
|
("scenario/10-seed-capital.yaml", "scenario/10-seed-capital.yaml"),
|
|
125
121
|
("scenario/20-buy.yaml", "scenario/20-buy.yaml"),
|