acumatica-cli 0.32.0__tar.gz → 0.34.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.32.0 → acumatica_cli-0.34.0}/PKG-INFO +30 -16
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/README.md +29 -15
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/pyproject.toml +1 -1
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/pyproject.toml.orig +1 -1
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/bootstrap.py +4 -4
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/bootstrap_plugin.cs +1 -1
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/bootstrap_project.xml +22 -9
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/cli.py +3 -3
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/client.py +5 -5
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/extract.py +5 -2
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/firstlogin.py +2 -2
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/inventory.py +1 -1
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/seed.py +16 -8
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/seed_catalog.yaml +10 -8
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/05-numbering-sequences.yaml +13 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/tenant.py +9 -9
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/__init__.py +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/config.py +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/models.py +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/output.py +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/reconcile.py +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/run.py +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/snapshot_map.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/state.py +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/README.md +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/91-company-packaging.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/bootstrap/segmented-key.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/90-roles.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/91-users.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/92-role-users.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/env +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/gitignore +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/overlays/README.md +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/overlays/default-24.200.001/README.md +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/overlays/default-24.200.001/scenario/30-build.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
- {acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: acumatica-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.34.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>
|
|
@@ -79,7 +79,6 @@ acu --tenant DEV state # capture state/ trial-balance
|
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
|
|
82
|
-
See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, apply-order notes, NumberingSequence vs prefs `*NumberingID`, curated *Preferences field depth (V41), and Role/User + password seed rules.
|
|
83
82
|
|
|
84
83
|
**Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env`.
|
|
85
84
|
The scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes.
|
|
@@ -140,7 +139,7 @@ When you omit FILES:
|
|
|
140
139
|
- `state` defaults to `config/views/`; writes go to `state/` (`--out`).
|
|
141
140
|
|
|
142
141
|
Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
|
|
143
|
-
`config/views` and `state` keep Period pinned
|
|
142
|
+
`config/views` and `state` keep Period pinned.
|
|
144
143
|
|
|
145
144
|
`survey extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
|
|
146
145
|
|
|
@@ -153,8 +152,6 @@ It never writes seed.
|
|
|
153
152
|
Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
|
|
154
153
|
It normalizes the join: pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code.
|
|
155
154
|
|
|
156
|
-
See [docs/demo-seed.md](docs/demo-seed.md).
|
|
157
|
-
|
|
158
155
|
`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
|
|
159
156
|
|
|
160
157
|
Run `acu --help` for the full mental model (workflow, planes, exit codes, command map).
|
|
@@ -178,8 +175,6 @@ Do not confuse them with each other or with `state`:
|
|
|
178
175
|
`inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
|
|
179
176
|
Binary `.adb` snapshots are rejected (XML only).
|
|
180
177
|
|
|
181
|
-
See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-seed.md](docs/demo-seed.md) for the extract/state/inventory map.
|
|
182
|
-
|
|
183
178
|
## The data repo
|
|
184
179
|
|
|
185
180
|
Your configuration lives in its own git repo.
|
|
@@ -212,7 +207,6 @@ Seed YAML is state: `apply` upserts it, `diff` proves it.
|
|
|
212
207
|
`acu survey extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
|
|
213
208
|
|
|
214
209
|
Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
|
|
215
|
-
The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
|
|
216
210
|
|
|
217
211
|
Features synthesize to `config/bootstrap/features.yaml`.
|
|
218
212
|
Existing files skip unless `--force`; empty live sets skip.
|
|
@@ -257,10 +251,10 @@ Or clone and install editable for development:
|
|
|
257
251
|
```sh
|
|
258
252
|
git clone https://github.com/kborovik/acumatica-cli.git
|
|
259
253
|
cd acumatica-cli
|
|
260
|
-
gmake install #
|
|
254
|
+
gmake install # uv sync; does not replace the PyPI tool
|
|
261
255
|
```
|
|
262
256
|
|
|
263
|
-
Verify with `acu --version
|
|
257
|
+
Verify with `uv run acu --version` (`+dev`).
|
|
264
258
|
|
|
265
259
|
## Configuration
|
|
266
260
|
|
|
@@ -268,7 +262,7 @@ Secrets, REST where, and the Default contract pin live in one `.env` file (`ACU_
|
|
|
268
262
|
Leftover `matrix.yaml` is ignored and never loaded.
|
|
269
263
|
|
|
270
264
|
```sh
|
|
271
|
-
ACU_BASE_URL=http://
|
|
265
|
+
ACU_BASE_URL=http://erp.example.com/AcumaticaERP # REST where (required)
|
|
272
266
|
ACU_API_VERSION=25.200.001 # Default contract half
|
|
273
267
|
ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
|
|
274
268
|
# ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
|
|
@@ -351,7 +345,7 @@ Elsewhere plain `make` is fine when it is GNU Make.
|
|
|
351
345
|
```sh
|
|
352
346
|
git clone https://github.com/kborovik/acumatica-cli.git
|
|
353
347
|
cd acumatica-cli
|
|
354
|
-
gmake install #
|
|
348
|
+
gmake install # uv sync; does not replace the PyPI tool
|
|
355
349
|
gmake check # offline gate: ruff, basedpyright strict, pytest
|
|
356
350
|
```
|
|
357
351
|
|
|
@@ -391,16 +385,36 @@ GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via
|
|
|
391
385
|
Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
|
|
392
386
|
`gmake e2e` refuses to start without it.
|
|
393
387
|
|
|
394
|
-
The tier is
|
|
388
|
+
The tier is three pipeline files: provision (apply/diff), scenario (run/state), and extract round-trip.
|
|
389
|
+
Per-bug contract probes fold onto those tenants.
|
|
390
|
+
|
|
395
391
|
Each run scaffolds a synthetic single-org company from the packaged `acu config init` templates into a temporary directory, copies the real `.env` into it, and runs the installed `acu` binary from there — no data repo, no pre-existing fixtures on the instance.
|
|
396
392
|
|
|
397
393
|
Scratch tenants (`E2E`, `E2EA`, `E2EB`, `E2ESCEN`) are created on the way in and always deleted on the way out, so nothing persists.
|
|
398
394
|
The packaged full `config init` seed (under `config/`) is the only scaffold.
|
|
399
395
|
|
|
400
396
|
```sh
|
|
401
|
-
gmake e2e # whole tier,
|
|
402
|
-
gmake e2e FILE=test_provision_lifecycle # apply/diff
|
|
403
|
-
gmake e2e FILE=test_scenario_lifecycle # scenario + state
|
|
397
|
+
gmake e2e # whole tier, three files
|
|
398
|
+
gmake e2e FILE=test_provision_lifecycle # apply/diff + folded probes
|
|
399
|
+
gmake e2e FILE=test_scenario_lifecycle # scenario + state + kit alloc
|
|
400
|
+
gmake e2e FILE=test_extract_roundtrip # extract inverse
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### CLI test-seed soak
|
|
404
|
+
|
|
405
|
+
This checkout carries a CLI test seed at repo-root `config/` and `scenario/` (no `config/qms/`).
|
|
406
|
+
Soak the live CLI tenant from here with `--tenant ACUCLI`.
|
|
407
|
+
|
|
408
|
+
Never use tenant `CNBN`. Never `cd` the sibling GitOps repo for CLI soak.
|
|
409
|
+
|
|
410
|
+
`gmake e2e` still scaffolds from packaged `config init` templates into a tmp dir.
|
|
411
|
+
That path is not the soak.
|
|
412
|
+
|
|
413
|
+
```sh
|
|
414
|
+
uv run acu --tenant ACUCLI apply
|
|
415
|
+
uv run acu --tenant ACUCLI run
|
|
416
|
+
uv run acu --tenant ACUCLI diff
|
|
417
|
+
uv run acu --tenant ACUCLI state
|
|
404
418
|
```
|
|
405
419
|
|
|
406
420
|
## License
|
|
@@ -61,7 +61,6 @@ acu --tenant DEV state # capture state/ trial-balance
|
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
|
|
64
|
-
See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, apply-order notes, NumberingSequence vs prefs `*NumberingID`, curated *Preferences field depth (V41), and Role/User + password seed rules.
|
|
65
64
|
|
|
66
65
|
**Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env`.
|
|
67
66
|
The scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes.
|
|
@@ -122,7 +121,7 @@ When you omit FILES:
|
|
|
122
121
|
- `state` defaults to `config/views/`; writes go to `state/` (`--out`).
|
|
123
122
|
|
|
124
123
|
Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
|
|
125
|
-
`config/views` and `state` keep Period pinned
|
|
124
|
+
`config/views` and `state` keep Period pinned.
|
|
126
125
|
|
|
127
126
|
`survey extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
|
|
128
127
|
|
|
@@ -135,8 +134,6 @@ It never writes seed.
|
|
|
135
134
|
Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
|
|
136
135
|
It normalizes the join: pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code.
|
|
137
136
|
|
|
138
|
-
See [docs/demo-seed.md](docs/demo-seed.md).
|
|
139
|
-
|
|
140
137
|
`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
|
|
141
138
|
|
|
142
139
|
Run `acu --help` for the full mental model (workflow, planes, exit codes, command map).
|
|
@@ -160,8 +157,6 @@ Do not confuse them with each other or with `state`:
|
|
|
160
157
|
`inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
|
|
161
158
|
Binary `.adb` snapshots are rejected (XML only).
|
|
162
159
|
|
|
163
|
-
See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-seed.md](docs/demo-seed.md) for the extract/state/inventory map.
|
|
164
|
-
|
|
165
160
|
## The data repo
|
|
166
161
|
|
|
167
162
|
Your configuration lives in its own git repo.
|
|
@@ -194,7 +189,6 @@ Seed YAML is state: `apply` upserts it, `diff` proves it.
|
|
|
194
189
|
`acu survey extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
|
|
195
190
|
|
|
196
191
|
Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
|
|
197
|
-
The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
|
|
198
192
|
|
|
199
193
|
Features synthesize to `config/bootstrap/features.yaml`.
|
|
200
194
|
Existing files skip unless `--force`; empty live sets skip.
|
|
@@ -239,10 +233,10 @@ Or clone and install editable for development:
|
|
|
239
233
|
```sh
|
|
240
234
|
git clone https://github.com/kborovik/acumatica-cli.git
|
|
241
235
|
cd acumatica-cli
|
|
242
|
-
gmake install #
|
|
236
|
+
gmake install # uv sync; does not replace the PyPI tool
|
|
243
237
|
```
|
|
244
238
|
|
|
245
|
-
Verify with `acu --version
|
|
239
|
+
Verify with `uv run acu --version` (`+dev`).
|
|
246
240
|
|
|
247
241
|
## Configuration
|
|
248
242
|
|
|
@@ -250,7 +244,7 @@ Secrets, REST where, and the Default contract pin live in one `.env` file (`ACU_
|
|
|
250
244
|
Leftover `matrix.yaml` is ignored and never loaded.
|
|
251
245
|
|
|
252
246
|
```sh
|
|
253
|
-
ACU_BASE_URL=http://
|
|
247
|
+
ACU_BASE_URL=http://erp.example.com/AcumaticaERP # REST where (required)
|
|
254
248
|
ACU_API_VERSION=25.200.001 # Default contract half
|
|
255
249
|
ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
|
|
256
250
|
# ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
|
|
@@ -333,7 +327,7 @@ Elsewhere plain `make` is fine when it is GNU Make.
|
|
|
333
327
|
```sh
|
|
334
328
|
git clone https://github.com/kborovik/acumatica-cli.git
|
|
335
329
|
cd acumatica-cli
|
|
336
|
-
gmake install #
|
|
330
|
+
gmake install # uv sync; does not replace the PyPI tool
|
|
337
331
|
gmake check # offline gate: ruff, basedpyright strict, pytest
|
|
338
332
|
```
|
|
339
333
|
|
|
@@ -373,16 +367,36 @@ GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via
|
|
|
373
367
|
Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
|
|
374
368
|
`gmake e2e` refuses to start without it.
|
|
375
369
|
|
|
376
|
-
The tier is
|
|
370
|
+
The tier is three pipeline files: provision (apply/diff), scenario (run/state), and extract round-trip.
|
|
371
|
+
Per-bug contract probes fold onto those tenants.
|
|
372
|
+
|
|
377
373
|
Each run scaffolds a synthetic single-org company from the packaged `acu config init` templates into a temporary directory, copies the real `.env` into it, and runs the installed `acu` binary from there — no data repo, no pre-existing fixtures on the instance.
|
|
378
374
|
|
|
379
375
|
Scratch tenants (`E2E`, `E2EA`, `E2EB`, `E2ESCEN`) are created on the way in and always deleted on the way out, so nothing persists.
|
|
380
376
|
The packaged full `config init` seed (under `config/`) is the only scaffold.
|
|
381
377
|
|
|
382
378
|
```sh
|
|
383
|
-
gmake e2e # whole tier,
|
|
384
|
-
gmake e2e FILE=test_provision_lifecycle # apply/diff
|
|
385
|
-
gmake e2e FILE=test_scenario_lifecycle # scenario + state
|
|
379
|
+
gmake e2e # whole tier, three files
|
|
380
|
+
gmake e2e FILE=test_provision_lifecycle # apply/diff + folded probes
|
|
381
|
+
gmake e2e FILE=test_scenario_lifecycle # scenario + state + kit alloc
|
|
382
|
+
gmake e2e FILE=test_extract_roundtrip # extract inverse
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### CLI test-seed soak
|
|
386
|
+
|
|
387
|
+
This checkout carries a CLI test seed at repo-root `config/` and `scenario/` (no `config/qms/`).
|
|
388
|
+
Soak the live CLI tenant from here with `--tenant ACUCLI`.
|
|
389
|
+
|
|
390
|
+
Never use tenant `CNBN`. Never `cd` the sibling GitOps repo for CLI soak.
|
|
391
|
+
|
|
392
|
+
`gmake e2e` still scaffolds from packaged `config init` templates into a tmp dir.
|
|
393
|
+
That path is not the soak.
|
|
394
|
+
|
|
395
|
+
```sh
|
|
396
|
+
uv run acu --tenant ACUCLI apply
|
|
397
|
+
uv run acu --tenant ACUCLI run
|
|
398
|
+
uv run acu --tenant ACUCLI diff
|
|
399
|
+
uv run acu --tenant ACUCLI state
|
|
386
400
|
```
|
|
387
401
|
|
|
388
402
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acumatica-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.34.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"
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"""Bootstrap customization package: build the zip, publish via /CustomizationApi.
|
|
2
2
|
|
|
3
3
|
An unconfigured tenant cannot be configured through the Default endpoint
|
|
4
|
-
(features are off, no company/branch exists, credit terms have no entity
|
|
5
|
-
|
|
4
|
+
(features are off, no company/branch exists, credit terms have no entity).
|
|
5
|
+
The CustomizationApi is the one door that works on a
|
|
6
6
|
virgin tenant, so bootstrap = publish a package whose CustomizationPlugin
|
|
7
7
|
(`bootstrap_plugin.cs`) enables features on publish — the contract API
|
|
8
8
|
cannot write CS100000 at all (T3 verdict) — and whose Bootstrap contract
|
|
9
9
|
endpoint exposes the seeding surface (serialization verified T12).
|
|
10
10
|
|
|
11
11
|
Bootstrap endpoint contract is package SoT (V2/V21/T178): always the
|
|
12
|
-
packaged full company ``bootstrap_project.xml`` (``Bootstrap/1.
|
|
12
|
+
packaged full company ``bootstrap_project.xml`` (``Bootstrap/1.11.0``).
|
|
13
13
|
Data-repo ``config/bootstrap/project.xml`` or ``bootstrap/project.xml`` is
|
|
14
14
|
not a seed — present → hard error naming package SoT (no dual contract line).
|
|
15
15
|
|
|
@@ -68,7 +68,7 @@ def _bootstrap_file(root: Path, name: str) -> Path | None:
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def packaged_contract_xml() -> bytes:
|
|
71
|
-
"""The CLI-shipped full company Bootstrap contract (``Bootstrap/1.
|
|
71
|
+
"""The CLI-shipped full company Bootstrap contract (``Bootstrap/1.11.0``)."""
|
|
72
72
|
return (resources.files("acumatica_cli") / "bootstrap_project.xml").read_bytes()
|
|
73
73
|
|
|
74
74
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Runs in-process on customization publish - the one write path to
|
|
4
4
|
// FeaturesSet that works: the contract API cannot persist features no
|
|
5
|
-
// matter what endpoint fronts CS100000 (T3 verdict
|
|
5
|
+
// matter what endpoint fronts CS100000 (T3 verdict).
|
|
6
6
|
//
|
|
7
7
|
// Deliberately writes through PXDatabase, not FeaturesMaint: the graph
|
|
8
8
|
// save collides with the publish pipeline's concurrent plugin invocations
|
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
and drops Selected (AllowedRoles was EPLoginTypeAllowsRole / EP202500);
|
|
14
14
|
T228 bumps 1.8.0 -> 1.9.0 for Role.Users -> UsersByRole (SM201005
|
|
15
15
|
Users tab; mapped-detail PUT is a no-op — B33) and 1.9.0 -> 1.10.0
|
|
16
|
-
for Role AssignUser action (PXDatabase UsersInRoles persist)
|
|
16
|
+
for Role AssignUser action (PXDatabase UsersInRoles persist);
|
|
17
|
+
T251 bumps 1.10.0 -> 1.11.0 for NumberingSequence Header NewSymbol
|
|
18
|
+
(CS201010 insert requires New Number Symbol or Manual Numbering;
|
|
19
|
+
gh #44 / V58).
|
|
17
20
|
|
|
18
|
-
package_zip()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
package_zip() always loads this packaged contract. Data-repo
|
|
22
|
+
config/bootstrap/project.xml or bootstrap/project.xml is forbidden (V21);
|
|
23
|
+
present -> hard error naming package SoT.
|
|
21
24
|
|
|
22
25
|
package_zip() injects a
|
|
23
26
|
<Graph ClassName="AcuBootstrapPlugin" FileType="NewFile" Source="..."/>
|
|
@@ -64,16 +67,22 @@
|
|
|
64
67
|
T150 NumberingSequence (gh #25) - Default endpoint has no numbering
|
|
65
68
|
surface; CS201010 Numbering Sequences (NumberingMaint — NOT the
|
|
66
69
|
misnamed NumberingSequenceMaint):
|
|
67
|
-
- PrimaryView Header (DAC Numbering): key NumberingID + Descr
|
|
70
|
+
- PrimaryView Header (DAC Numbering): key NumberingID + Descr +
|
|
71
|
+
NewSymbol (T251/V58; CS201010 "New Number Symbol"). UserNumbering
|
|
72
|
+
is the OR alternative (manual numbering), not a required pair.
|
|
73
|
+
Mapped GET returns NewSymbol (seed; GET-omit is mapping-miss drift).
|
|
68
74
|
- Detail view Sequence (DAC NumberingSequence): bounds StartNbr,
|
|
69
75
|
EndNbr, WarnNbr, NbrStep, StartDate. LastNbr is runtime counter —
|
|
70
76
|
omitted from contract (V40/T152 strip/ignore if ever present).
|
|
71
|
-
- Flat entity (Currency multi-view idiom): NumberingID/Descr
|
|
72
|
-
bounds on Sequence. Single sequence row per NumberingID
|
|
73
|
-
demo/LAB5 shape; multi-segment / branch-scoped rows are out
|
|
77
|
+
- Flat entity (Currency multi-view idiom): NumberingID/Descr/NewSymbol
|
|
78
|
+
on Header, bounds on Sequence. Single sequence row per NumberingID
|
|
79
|
+
is the demo/LAB5 shape; multi-segment / branch-scoped rows are out
|
|
80
|
+
of v1.
|
|
74
81
|
T159: view names were wrong in 1.2.0/1.3.0 (Sequence/SequenceDetail);
|
|
75
82
|
live aspx + NumberingMaint reflection = Header/Sequence. Bump 1.3.0
|
|
76
83
|
-> 1.4.0 (V21). Wrong maps made every PUT 422 on NewSymbol mask.
|
|
84
|
+
T251: map NewSymbol to Header (not Sequence). Insert of a new
|
|
85
|
+
NumberingID without it 422s (B38).
|
|
77
86
|
|
|
78
87
|
T204 SegmentedKey (gh #30 / V50) - Default endpoint has no CS202000
|
|
79
88
|
surface; DimensionMaint Segmented Keys:
|
|
@@ -121,7 +130,7 @@
|
|
|
121
130
|
-->
|
|
122
131
|
<Customization level="" description="acu bootstrap: CustomizationPlugin enables features on publish; Bootstrap endpoint exposes full company surface (company + credit terms + financial currency + GL chain + module preferences + vendor class + statement cycle + warehouse + order type + cash account + role + user + membership + numbering sequence + segmented key)" product-version="26.101">
|
|
123
132
|
<EntityEndpoint>
|
|
124
|
-
<Endpoint xmlns="http://www.acumatica.com/entity/maintenance/5.31" name="Bootstrap" version="1.
|
|
133
|
+
<Endpoint xmlns="http://www.acumatica.com/entity/maintenance/5.31" name="Bootstrap" version="1.11.0" systemContractVersion="4">
|
|
125
134
|
<TopLevelEntity name="Company" screen="CS101500">
|
|
126
135
|
<Fields>
|
|
127
136
|
<Field name="AcctCD" type="StringValue" />
|
|
@@ -1120,6 +1129,7 @@
|
|
|
1120
1129
|
<Fields>
|
|
1121
1130
|
<Field name="NumberingID" type="StringValue" />
|
|
1122
1131
|
<Field name="Descr" type="StringValue" />
|
|
1132
|
+
<Field name="NewSymbol" type="StringValue" />
|
|
1123
1133
|
<Field name="StartNbr" type="StringValue" />
|
|
1124
1134
|
<Field name="EndNbr" type="StringValue" />
|
|
1125
1135
|
<Field name="WarnNbr" type="StringValue" />
|
|
@@ -1133,6 +1143,9 @@
|
|
|
1133
1143
|
<Mapping field="Descr">
|
|
1134
1144
|
<To object="Header" field="Descr" />
|
|
1135
1145
|
</Mapping>
|
|
1146
|
+
<Mapping field="NewSymbol">
|
|
1147
|
+
<To object="Header" field="NewSymbol" />
|
|
1148
|
+
</Mapping>
|
|
1136
1149
|
<Mapping field="StartNbr">
|
|
1137
1150
|
<To object="Sequence" field="StartNbr" />
|
|
1138
1151
|
</Mapping>
|
|
@@ -76,7 +76,7 @@ DEFAULT PATHS (when FILES omitted)
|
|
|
76
76
|
survey extract -> always writes config/{bootstrap,baseline,setup,master}/
|
|
77
77
|
|
|
78
78
|
Run `acu <command> --help` for flags, examples, and prerequisites.
|
|
79
|
-
See package README
|
|
79
|
+
See package README for seed YAML shape.
|
|
80
80
|
"""
|
|
81
81
|
|
|
82
82
|
import functools
|
|
@@ -382,7 +382,7 @@ def tenant_list(inst: Instance) -> None:
|
|
|
382
382
|
@click.option(
|
|
383
383
|
"--type",
|
|
384
384
|
"company_type",
|
|
385
|
-
# the V12-verified dataset folders on the box
|
|
385
|
+
# the V12-verified dataset folders on the box; System
|
|
386
386
|
# is the system-tenant dataset, deliberately not offered
|
|
387
387
|
type=click.Choice(["SalesDemo", "T100", "U100"]),
|
|
388
388
|
default=None,
|
|
@@ -466,7 +466,7 @@ def tenant_create(
|
|
|
466
466
|
output.data(raw.splitlines()[-1] if raw.strip() else "created")
|
|
467
467
|
# ac.exe only sets CompanyKey from LoginName; CompanyCD stays auto-generated
|
|
468
468
|
# (Company2/Company3/…). Align CD → login so list shows matching columns.
|
|
469
|
-
# Idempotent on re-run / exists-skip
|
|
469
|
+
# Idempotent on re-run / exists-skip.
|
|
470
470
|
with output.step(f"aligning CompanyCD to login ({login_name})"):
|
|
471
471
|
if mgr.set_company_cd(company_id, login_name):
|
|
472
472
|
output.data(f"CompanyCD set to {login_name}")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Contract-based REST API session
|
|
1
|
+
"""Contract-based REST API session."""
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
4
|
import html
|
|
@@ -162,7 +162,7 @@ OPTIMIZATION_500 = "Optimization cannot be performed"
|
|
|
162
162
|
# tenant answers PXSetupNotEnteredException on most entities until bootstrap
|
|
163
163
|
# lands, and the exceptionMessage names the missing form — "The required
|
|
164
164
|
# configuration data is not entered on the <Form> form." (verified vs
|
|
165
|
-
# 26.101.0225
|
|
165
|
+
# 26.101.0225).
|
|
166
166
|
SETUP_NOT_ENTERED_500 = "configuration data is not entered"
|
|
167
167
|
|
|
168
168
|
|
|
@@ -188,7 +188,7 @@ class AcumaticaClient:
|
|
|
188
188
|
self._refreshed_after_company = False
|
|
189
189
|
|
|
190
190
|
def __enter__(self) -> AcumaticaClient:
|
|
191
|
-
# tenant guard (V5
|
|
191
|
+
# tenant guard (V5): an omitted or empty tenant is
|
|
192
192
|
# the one login the server still routes silently — to the default
|
|
193
193
|
# tenant. Defense-in-depth vs wrong-tenant writes: every data-plane
|
|
194
194
|
# session must name its tenant, so refuse before any HTTP happens.
|
|
@@ -219,7 +219,7 @@ class AcumaticaClient:
|
|
|
219
219
|
|
|
220
220
|
Login accepting the name proves nothing - a stale tenant map
|
|
221
221
|
reroutes named logins to the default tenant, and a single-tenant
|
|
222
|
-
instance accepts ANY name (both verified live
|
|
222
|
+
instance accepts ANY name (both verified live).
|
|
223
223
|
"""
|
|
224
224
|
creds: dict[str, str] = {
|
|
225
225
|
"name": self.instance.user,
|
|
@@ -261,7 +261,7 @@ class AcumaticaClient:
|
|
|
261
261
|
def _landed_tenant(self) -> str:
|
|
262
262
|
"""The tenant this session actually landed on (login name).
|
|
263
263
|
|
|
264
|
-
Verified vs 26.101.0225
|
|
264
|
+
Verified vs 26.101.0225: the contract API exposes
|
|
265
265
|
nothing tenant-identifying, but an authenticated GET of the sign-in
|
|
266
266
|
page renders a hidden ``txtSingleCompany`` input whose value is the
|
|
267
267
|
session tenant's login name in every observed state - multi-tenant,
|
|
@@ -465,8 +465,11 @@ def _synth_open_periods(client: AcumaticaClient) -> dict[str, Any] | None:
|
|
|
465
465
|
years = _years(live)
|
|
466
466
|
# OrganizationID = the extracted Company's AcctCD: the reference
|
|
467
467
|
# resolves inside the emitted set (V22 - config/bootstrap/company.yaml
|
|
468
|
-
# creates the organization the action names)
|
|
469
|
-
|
|
468
|
+
# creates the organization the action names).
|
|
469
|
+
# Company list GET 500s once DecPlQty/WeightUOM/VolumeUOM map
|
|
470
|
+
# commonsetup (V52; BQL-delegate view, B9). $select keeps AcctCD
|
|
471
|
+
# only so the synth does not pull those fields.
|
|
472
|
+
companies = client.get_list("Company", params={"$select": "AcctCD"}, endpoint=ep)
|
|
470
473
|
if not companies:
|
|
471
474
|
raise RuntimeError("open-periods: no Company on tenant")
|
|
472
475
|
org = sorted(str(unwrap(c)["AcctCD"]) for c in companies)[0]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""First-login admin password initialization via the Login.aspx screen flow.
|
|
2
2
|
|
|
3
3
|
A freshly created tenant seeds ``admin``/``setup`` with a
|
|
4
|
-
must-change-on-first-login flag the contract REST API cannot clear (verified
|
|
5
|
-
|
|
4
|
+
must-change-on-first-login flag the contract REST API cannot clear (verified
|
|
5
|
+
live). The sign-in screen's WebForms flow can, with plain
|
|
6
6
|
HTTP: GET the page for the hidden fields, POST the seed credentials, then
|
|
7
7
|
POST again with the new password into the change view the server renders.
|
|
8
8
|
"""
|
|
@@ -13,7 +13,7 @@ Accepted artifacts (V37):
|
|
|
13
13
|
Rejected: binary ``.adb`` (and ZIPs that only carry ``.adb``) — named
|
|
14
14
|
error, fail-closed.
|
|
15
15
|
|
|
16
|
-
Table XML shape (verified ac-exe export / dataset format
|
|
16
|
+
Table XML shape (verified ac-exe export / dataset format):
|
|
17
17
|
|
|
18
18
|
<data>
|
|
19
19
|
<table name="Account">
|
|
@@ -83,24 +83,31 @@ _BRANCH_EMPTY = "'Branch' cannot be empty"
|
|
|
83
83
|
# YAML; extract always strips; diff never compares (GET returns hashes
|
|
84
84
|
# or nothing — never a re-seedable value).
|
|
85
85
|
PASSWORD_FIELDS = frozenset({"Password", "b64__Password"})
|
|
86
|
-
# NumberingSequence runtime counters (V40/gh #25):
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
86
|
+
# NumberingSequence runtime counters (V40/gh #25): LastNbr (+ advanced
|
|
87
|
+
# counter if ever exposed) = issued progress, not desired config. Extract
|
|
88
|
+
# hard-strips; diff never compares; apply never PUTs them (re-apply must
|
|
89
|
+
# not reset live counters).
|
|
90
90
|
NUMBERING_RUNTIME_FIELDS = frozenset({"LastNbr"})
|
|
91
91
|
# Fields omitted from source↔live compare (V39 write-only + V40 runtime).
|
|
92
|
-
# Field-name deny at every nesting level.
|
|
93
|
-
# that would hide real drift on unrelated fields (V56).
|
|
92
|
+
# Field-name deny at every nesting level.
|
|
94
93
|
_DIFF_IGNORE_FIELDS = PASSWORD_FIELDS | NUMBERING_RUNTIME_FIELDS
|
|
95
94
|
# Path-qualified write-only GET-omit (V56): (entity, detail, field).
|
|
96
95
|
# Diff ignores; extract strips; apply still PUTs when present in YAML.
|
|
96
|
+
# Do not add bare ``Value`` to _DIFF_IGNORE_FIELDS — that would hide
|
|
97
|
+
# real drift on unrelated fields.
|
|
97
98
|
_DIFF_IGNORE_PATHS = frozenset({("LotSerialClass", "Segments", "Value")})
|
|
99
|
+
# Whole-detail skip (V57/B37): mapped GET Role.Users / User.Roles may be
|
|
100
|
+
# empty while UsersInRoles has session-company rows. Extra live members
|
|
101
|
+
# are not drift (AssignUser is additive-only).
|
|
102
|
+
_DIFF_SKIP_DETAILS = frozenset({("Role", "Users"), ("User", "Roles")})
|
|
98
103
|
|
|
99
104
|
|
|
100
105
|
def _diff_ignored(entity: str, *path: str) -> bool:
|
|
101
|
-
"""True when source↔live compare skips this field (V39/V40/V56)."""
|
|
106
|
+
"""True when source↔live compare skips this field (V39/V40/V56/V57)."""
|
|
102
107
|
if path and path[-1] in _DIFF_IGNORE_FIELDS:
|
|
103
108
|
return True
|
|
109
|
+
if len(path) == 1 and (entity, path[0]) in _DIFF_SKIP_DETAILS:
|
|
110
|
+
return True
|
|
104
111
|
return (entity, *path) in _DIFF_IGNORE_PATHS
|
|
105
112
|
|
|
106
113
|
|
|
@@ -679,7 +686,8 @@ def diff(client: AcumaticaClient, baseline: BaselineFile | ActionFile) -> list[s
|
|
|
679
686
|
drifts.append(f"{label}: missing on tenant")
|
|
680
687
|
continue
|
|
681
688
|
actual = unwrap(live)
|
|
682
|
-
# V39/V40/V56: never compare write-only, runtime,
|
|
689
|
+
# V39/V40/V56/V57: never compare write-only, runtime, GET-omit,
|
|
690
|
+
# or membership details whose mapped GET may stay empty.
|
|
683
691
|
fields = {
|
|
684
692
|
k: v for k, v in record.items() if not _diff_ignored(baseline.entity, k)
|
|
685
693
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# entity contract entity name (the GET source and the emitted `entity:`)
|
|
7
7
|
# endpoint omitted = the instance Default endpoint; the symbolic name
|
|
8
8
|
# `bootstrap` stays symbolic in emitted files and resolves to
|
|
9
|
-
# the packaged full company Bootstrap/1.
|
|
9
|
+
# the packaged full company Bootstrap/1.11.0 at HTTP/load time
|
|
10
10
|
# (package SoT - V20/V21/T178; data-repo project.xml forbidden)
|
|
11
11
|
# keys key fields: the emitted `key:`, the record sort order, and
|
|
12
12
|
# the B9 fallback's $select projection; the tuple must
|
|
@@ -181,10 +181,10 @@ entities:
|
|
|
181
181
|
# include field-partition (same live warehouse row, three apply phases).
|
|
182
182
|
# Multi-file StockItem uses OData filter-split on ItemClass.
|
|
183
183
|
#
|
|
184
|
-
# NumberingSequence (T151/gh #25): V22 prefix 05- sorts before
|
|
185
|
-
# file that may reference *NumberingID (IN/SO/PO/AR/AP/CA/GL
|
|
186
|
-
# only — Default has no numbering surface.
|
|
187
|
-
# runtime counter is V40
|
|
184
|
+
# NumberingSequence (T151/gh #25, T252/gh #44): V22 prefix 05- sorts before
|
|
185
|
+
# every prefs file that may reference *NumberingID (IN/SO/PO/AR/AP/CA/GL
|
|
186
|
+
# setup). Bootstrap only — Default has no numbering surface. Include bounds
|
|
187
|
+
# + NewSymbol (insert-required, V58); no LastNbr (runtime counter is V40).
|
|
188
188
|
|
|
189
189
|
- entity: NumberingSequence
|
|
190
190
|
endpoint: bootstrap
|
|
@@ -192,6 +192,7 @@ entities:
|
|
|
192
192
|
file: config/master/05-numbering-sequences.yaml
|
|
193
193
|
include:
|
|
194
194
|
- Descr
|
|
195
|
+
- NewSymbol
|
|
195
196
|
- StartNbr
|
|
196
197
|
- EndNbr
|
|
197
198
|
- WarnNbr
|
|
@@ -535,9 +536,10 @@ entities:
|
|
|
535
536
|
# config/master/ Role then User then Role.Users (V22 prefixes 90/91/92;
|
|
536
537
|
# T145/T146/T148/T228). Bootstrap only — Default has no Role/User surface.
|
|
537
538
|
# Persist = Role action AssignUser (PXDatabase UsersInRoles; T228/B33).
|
|
538
|
-
# User detail Roles
|
|
539
|
-
# B31/B32/B33).
|
|
540
|
-
#
|
|
539
|
+
# User detail Roles = GET/extract; diff skips Users/Roles (V57).
|
|
540
|
+
# PUT never writes UsersInRoles (B31/B32/B33).
|
|
541
|
+
# Package 90-roles = full pre-build CompanyID=1 Roles dump;
|
|
542
|
+
# 91-users demo soadmin identity; 92-role-users membership.
|
|
541
543
|
# Password is write-only on apply (V39/T147): include omits Password so
|
|
542
544
|
# extract never emits hashes (b64__Password also out of include).
|
|
543
545
|
|
|
@@ -3,78 +3,91 @@ key: NumberingID
|
|
|
3
3
|
endpoint: bootstrap
|
|
4
4
|
records:
|
|
5
5
|
- NumberingID: APBILL
|
|
6
|
+
NewSymbol: '<NEW>'
|
|
6
7
|
StartNbr: '000000'
|
|
7
8
|
EndNbr: '999999'
|
|
8
9
|
WarnNbr: '999990'
|
|
9
10
|
NbrStep: 1
|
|
10
11
|
StartDate: '1900-01-01'
|
|
11
12
|
- NumberingID: APPAYMENT
|
|
13
|
+
NewSymbol: '<NEW>'
|
|
12
14
|
StartNbr: '000000'
|
|
13
15
|
EndNbr: '999999'
|
|
14
16
|
WarnNbr: '999990'
|
|
15
17
|
NbrStep: 1
|
|
16
18
|
StartDate: '1900-01-01'
|
|
17
19
|
- NumberingID: ARINVOICE
|
|
20
|
+
NewSymbol: '<NEW>'
|
|
18
21
|
StartNbr: '000000'
|
|
19
22
|
EndNbr: '999999'
|
|
20
23
|
WarnNbr: '999990'
|
|
21
24
|
NbrStep: 1
|
|
22
25
|
StartDate: '1900-01-01'
|
|
23
26
|
- NumberingID: ARPAYMENT
|
|
27
|
+
NewSymbol: '<NEW>'
|
|
24
28
|
StartNbr: '000000'
|
|
25
29
|
EndNbr: '999999'
|
|
26
30
|
WarnNbr: '999990'
|
|
27
31
|
NbrStep: 1
|
|
28
32
|
StartDate: '1900-01-01'
|
|
29
33
|
- NumberingID: BATCH
|
|
34
|
+
NewSymbol: '<NEW>'
|
|
30
35
|
StartNbr: '000000'
|
|
31
36
|
EndNbr: '999999'
|
|
32
37
|
WarnNbr: '999990'
|
|
33
38
|
NbrStep: 1
|
|
34
39
|
StartDate: '1900-01-01'
|
|
35
40
|
- NumberingID: INADJUST
|
|
41
|
+
NewSymbol: '<NEW>'
|
|
36
42
|
StartNbr: '000000'
|
|
37
43
|
EndNbr: '999999'
|
|
38
44
|
WarnNbr: '999990'
|
|
39
45
|
NbrStep: 1
|
|
40
46
|
StartDate: '1900-01-01'
|
|
41
47
|
- NumberingID: INISSUE
|
|
48
|
+
NewSymbol: '<NEW>'
|
|
42
49
|
StartNbr: '000000'
|
|
43
50
|
EndNbr: '999999'
|
|
44
51
|
WarnNbr: '999990'
|
|
45
52
|
NbrStep: 1
|
|
46
53
|
StartDate: '1900-01-01'
|
|
47
54
|
- NumberingID: INKITASSY
|
|
55
|
+
NewSymbol: '<NEW>'
|
|
48
56
|
StartNbr: '000000'
|
|
49
57
|
EndNbr: '999999'
|
|
50
58
|
WarnNbr: '999990'
|
|
51
59
|
NbrStep: 1
|
|
52
60
|
StartDate: '1900-01-01'
|
|
53
61
|
- NumberingID: INRECEIPT
|
|
62
|
+
NewSymbol: '<NEW>'
|
|
54
63
|
StartNbr: '000000'
|
|
55
64
|
EndNbr: '999999'
|
|
56
65
|
WarnNbr: '999990'
|
|
57
66
|
NbrStep: 1
|
|
58
67
|
StartDate: '1900-01-01'
|
|
59
68
|
- NumberingID: POORDER
|
|
69
|
+
NewSymbol: '<NEW>'
|
|
60
70
|
StartNbr: '000000'
|
|
61
71
|
EndNbr: '999999'
|
|
62
72
|
WarnNbr: '999990'
|
|
63
73
|
NbrStep: 1
|
|
64
74
|
StartDate: '1900-01-01'
|
|
65
75
|
- NumberingID: PORECEIPT
|
|
76
|
+
NewSymbol: '<NEW>'
|
|
66
77
|
StartNbr: '000000'
|
|
67
78
|
EndNbr: '999999'
|
|
68
79
|
WarnNbr: '999990'
|
|
69
80
|
NbrStep: 1
|
|
70
81
|
StartDate: '1900-01-01'
|
|
71
82
|
- NumberingID: SOORDER
|
|
83
|
+
NewSymbol: '<NEW>'
|
|
72
84
|
StartNbr: '000000'
|
|
73
85
|
EndNbr: '999999'
|
|
74
86
|
WarnNbr: '999990'
|
|
75
87
|
NbrStep: 1
|
|
76
88
|
StartDate: '1900-01-01'
|
|
77
89
|
- NumberingID: SOSHIPMENT
|
|
90
|
+
NewSymbol: '<NEW>'
|
|
78
91
|
StartNbr: '000000'
|
|
79
92
|
EndNbr: '999999'
|
|
80
93
|
WarnNbr: '999990'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Tenant CRUD on the instance.
|
|
2
2
|
|
|
3
|
-
Thin SSH wrapper around ac.exe CompanyConfig (the only supported CLI route
|
|
4
|
-
|
|
3
|
+
Thin SSH wrapper around ac.exe CompanyConfig (the only supported CLI route)
|
|
4
|
+
plus a sqlcmd read side.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import subprocess
|
|
@@ -15,8 +15,8 @@ class Tenant(Model):
|
|
|
15
15
|
|
|
16
16
|
login_name is dbo.Company.CompanyKey — the name on the sign-in page and
|
|
17
17
|
the REST login's tenant value. company_cd is dbo.Company.CompanyCD; ac.exe
|
|
18
|
-
auto-generates it (Company2, Company3, …) and ignores LoginName for it
|
|
19
|
-
|
|
18
|
+
auto-generates it (Company2, Company3, …) and ignores LoginName for it.
|
|
19
|
+
``acu tenant create`` aligns CompanyCD to the login
|
|
20
20
|
after CompanyConfig so list Login and CD match.
|
|
21
21
|
"""
|
|
22
22
|
|
|
@@ -97,8 +97,8 @@ class TenantManager:
|
|
|
97
97
|
|
|
98
98
|
The tenant map loads at app start; without this, tenants created or
|
|
99
99
|
deleted by CompanyConfig stay invisible to the sign-in page and REST
|
|
100
|
-
routing silently falls back to the default tenant
|
|
101
|
-
The pool is named after the instance
|
|
100
|
+
routing silently falls back to the default tenant.
|
|
101
|
+
The pool is named after the instance.
|
|
102
102
|
"""
|
|
103
103
|
self._ssh(
|
|
104
104
|
"Import-Module WebAdministration; "
|
|
@@ -108,7 +108,7 @@ class TenantManager:
|
|
|
108
108
|
def _company_config(self, company: str, extra: str = "") -> str:
|
|
109
109
|
# -iname AND -h are both required: without -iname CompanyConfig can't
|
|
110
110
|
# find the site; without -h its web.config step dies on a null path
|
|
111
|
-
# (verified 2026-07-08
|
|
111
|
+
# (verified 2026-07-08).
|
|
112
112
|
return self._ssh(
|
|
113
113
|
f"& '{AC_EXE}' "
|
|
114
114
|
f'-configmode:"CompanyConfig" -output:"Forced" '
|
|
@@ -132,7 +132,7 @@ class TenantManager:
|
|
|
132
132
|
company_type: '' = clean tenant, 'SalesDemo' = demo data. The admin
|
|
133
133
|
flags preset the tenant's admin to the instance credentials with no
|
|
134
134
|
must-change flag, so the tenant is REST-loginable right after the
|
|
135
|
-
app-pool recycle — no first-login dance (verified
|
|
135
|
+
app-pool recycle — no first-login dance (verified live).
|
|
136
136
|
|
|
137
137
|
Does not set CompanyCD (ac.exe auto-generates it). Call
|
|
138
138
|
:meth:`set_company_cd` after create to align CD with the login.
|
|
@@ -199,7 +199,7 @@ class TenantManager:
|
|
|
199
199
|
Identify by ``company_id`` or ``login_name`` (exactly one). Login is
|
|
200
200
|
the sign-in name / REST tenant value (``CompanyKey``).
|
|
201
201
|
|
|
202
|
-
Two verified gotchas
|
|
202
|
+
Two verified gotchas:
|
|
203
203
|
|
|
204
204
|
- The sub-key is ``Deleted``, not the officially documented
|
|
205
205
|
``Delete`` — an unknown key is silently ignored and the run degrades
|
|
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.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/overlays/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/20-buy.yaml
RENAMED
|
File without changes
|
{acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/30-build.yaml
RENAMED
|
File without changes
|
{acumatica_cli-0.32.0 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/40-sell.yaml
RENAMED
|
File without changes
|