acumatica-cli 0.18.0__tar.gz → 0.18.2__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.18.0 → acumatica_cli-0.18.2}/PKG-INFO +2 -2
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/README.md +1 -1
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/pyproject.toml +1 -1
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/pyproject.toml.orig +1 -1
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/cli.py +17 -1
- acumatica_cli-0.18.2/src/acumatica_cli/templates/gitignore +5 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/tenant.py +48 -2
- acumatica_cli-0.18.0/src/acumatica_cli/templates/gitignore +0 -2
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/__init__.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/bootstrap.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/bootstrap_project.xml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/client.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/config.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/extract.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/firstlogin.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/inventory.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/models.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/output.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/reconcile.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/run.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/seed.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/seed_catalog.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/snapshot_map.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/state.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/target.py +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/README.md +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/env +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
- {acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/target +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: acumatica-cli
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.2
|
|
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>
|
|
@@ -107,7 +107,7 @@ A path like `config/` expands nested seed dirs in that fixed order.
|
|
|
107
107
|
`extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
|
|
108
108
|
`inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
|
|
109
109
|
`reconcile` is offline: compare `inventory/` to optional `config/` and write `findings/` only (never writes seed).
|
|
110
|
-
Optional `snapshot_map.yaml` (data-repo root or package defaults) maps DAC tables to catalog entities and normalizes join (pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label
|
|
110
|
+
Optional `snapshot_map.yaml` (data-repo root or package defaults) maps DAC tables to catalog entities and normalizes join (pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code).
|
|
111
111
|
See [docs/demo-seed.md](docs/demo-seed.md).
|
|
112
112
|
`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
|
|
113
113
|
Run `acu <command> --help` for details on any command.
|
|
@@ -89,7 +89,7 @@ A path like `config/` expands nested seed dirs in that fixed order.
|
|
|
89
89
|
`extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
|
|
90
90
|
`inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
|
|
91
91
|
`reconcile` is offline: compare `inventory/` to optional `config/` and write `findings/` only (never writes seed).
|
|
92
|
-
Optional `snapshot_map.yaml` (data-repo root or package defaults) maps DAC tables to catalog entities and normalizes join (pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label
|
|
92
|
+
Optional `snapshot_map.yaml` (data-repo root or package defaults) maps DAC tables to catalog entities and normalizes join (pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code).
|
|
93
93
|
See [docs/demo-seed.md](docs/demo-seed.md).
|
|
94
94
|
`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
|
|
95
95
|
Run `acu <command> --help` for details on any command.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acumatica-cli"
|
|
3
|
-
version = "0.18.
|
|
3
|
+
version = "0.18.2"
|
|
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"
|
|
@@ -7,6 +7,7 @@ from collections.abc import Callable
|
|
|
7
7
|
from importlib.metadata import distribution
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Concatenate
|
|
10
|
+
from urllib.parse import urlparse
|
|
10
11
|
|
|
11
12
|
import click
|
|
12
13
|
import httpx
|
|
@@ -239,8 +240,10 @@ def tenant_group() -> None:
|
|
|
239
240
|
def tenant_list(inst: Instance) -> None:
|
|
240
241
|
"""List tenants: CompanyID, sign-in name, internal CD, type."""
|
|
241
242
|
tenants = TenantManager(inst).list()
|
|
243
|
+
# V9/B27: control-plane identity is the host, never scheme+path
|
|
244
|
+
host = urlparse(inst.base_url).hostname or inst.base_url
|
|
242
245
|
output.table(
|
|
243
|
-
f"Tenants on {
|
|
246
|
+
f"Tenants on {host}",
|
|
244
247
|
("ID", "Login", "CD", "Type"),
|
|
245
248
|
(
|
|
246
249
|
(str(t.company_id), t.login_name, t.company_cd, t.company_type)
|
|
@@ -306,6 +309,11 @@ def tenant_create(
|
|
|
306
309
|
create is skipped and the init + digest-gated publish chain still runs,
|
|
307
310
|
so re-running create is the republish route for existing tenants. --id
|
|
308
311
|
must match the existing CompanyID, else hard error naming both.
|
|
312
|
+
|
|
313
|
+
After create (or on exists-skip), CompanyCD is set equal to --login.
|
|
314
|
+
ac.exe only writes LoginName into CompanyKey and auto-generates CD
|
|
315
|
+
(Company2, Company3, …); the align step matches what operators do on
|
|
316
|
+
the Companies screen so ``acu tenant list`` shows Login = CD.
|
|
309
317
|
"""
|
|
310
318
|
mgr = TenantManager(inst)
|
|
311
319
|
existing = next((t for t in mgr.list() if t.login_name == login_name), None)
|
|
@@ -326,6 +334,14 @@ def tenant_create(
|
|
|
326
334
|
company_id, login_name, parent_id, not hidden, company_type or ""
|
|
327
335
|
)
|
|
328
336
|
output.data(raw.splitlines()[-1] if raw.strip() else "created")
|
|
337
|
+
# ac.exe only sets CompanyKey from LoginName; CompanyCD stays auto-generated
|
|
338
|
+
# (Company2/Company3/…). Align CD → login so list shows matching columns.
|
|
339
|
+
# Idempotent on re-run / exists-skip (docs/ac-exe.md).
|
|
340
|
+
with output.step(f"aligning CompanyCD to login ({login_name})"):
|
|
341
|
+
if mgr.set_company_cd(company_id, login_name):
|
|
342
|
+
output.data(f"CompanyCD set to {login_name}")
|
|
343
|
+
else:
|
|
344
|
+
output.data(f"CompanyCD already {login_name}")
|
|
329
345
|
if no_init:
|
|
330
346
|
output.warn("skipping init: tenant is invisible until an app-pool recycle")
|
|
331
347
|
return
|
|
@@ -14,8 +14,10 @@ class Tenant(Model):
|
|
|
14
14
|
"""One row of the instance's Company table.
|
|
15
15
|
|
|
16
16
|
login_name is dbo.Company.CompanyKey — the name on the sign-in page and
|
|
17
|
-
the REST login's tenant value. company_cd is
|
|
18
|
-
|
|
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
|
+
(docs/ac-exe.md). ``acu tenant create`` aligns CompanyCD to the login
|
|
20
|
+
after CompanyConfig so list Login and CD match.
|
|
19
21
|
"""
|
|
20
22
|
|
|
21
23
|
company_id: int
|
|
@@ -131,6 +133,9 @@ class TenantManager:
|
|
|
131
133
|
flags preset the tenant's admin to the instance credentials with no
|
|
132
134
|
must-change flag, so the tenant is REST-loginable right after the
|
|
133
135
|
app-pool recycle — no first-login dance (verified, docs/ac-exe.md).
|
|
136
|
+
|
|
137
|
+
Does not set CompanyCD (ac.exe auto-generates it). Call
|
|
138
|
+
:meth:`set_company_cd` after create to align CD with the login.
|
|
134
139
|
"""
|
|
135
140
|
company = (
|
|
136
141
|
f"CompanyID={company_id};ParentID={parent_id};"
|
|
@@ -142,6 +147,47 @@ class TenantManager:
|
|
|
142
147
|
)
|
|
143
148
|
return self._company_config(company, extra=admin)
|
|
144
149
|
|
|
150
|
+
def set_company_cd(self, company_id: int, company_cd: str) -> bool:
|
|
151
|
+
"""Set ``dbo.Company.CompanyCD`` for a tenant (sqlcmd over SSH).
|
|
152
|
+
|
|
153
|
+
ac.exe CompanyConfig only writes LoginName into CompanyKey; CompanyCD
|
|
154
|
+
stays auto-generated (``Company2``, ``Company3``, …). Aligning CD to
|
|
155
|
+
the login makes ``acu tenant list`` show matching Login/CD columns and
|
|
156
|
+
matches the Companies-screen rename operators do by hand.
|
|
157
|
+
|
|
158
|
+
Returns True when an UPDATE ran, False when CD already matches.
|
|
159
|
+
Raises if the target id is missing or ``company_cd`` is already used
|
|
160
|
+
by a different CompanyID.
|
|
161
|
+
"""
|
|
162
|
+
tenants = self.list()
|
|
163
|
+
target = next((t for t in tenants if t.company_id == company_id), None)
|
|
164
|
+
if target is None:
|
|
165
|
+
raise RuntimeError(f"no tenant with CompanyID={company_id}")
|
|
166
|
+
if target.company_cd == company_cd:
|
|
167
|
+
return False
|
|
168
|
+
conflict = next(
|
|
169
|
+
(
|
|
170
|
+
t
|
|
171
|
+
for t in tenants
|
|
172
|
+
if t.company_cd == company_cd and t.company_id != company_id
|
|
173
|
+
),
|
|
174
|
+
None,
|
|
175
|
+
)
|
|
176
|
+
if conflict is not None:
|
|
177
|
+
raise RuntimeError(
|
|
178
|
+
f"CompanyCD {company_cd!r} already used by "
|
|
179
|
+
f"CompanyID={conflict.company_id}"
|
|
180
|
+
)
|
|
181
|
+
# company_id is int; company_cd is quoted with doubled single-quotes
|
|
182
|
+
escaped = company_cd.replace("'", "''")
|
|
183
|
+
query = (
|
|
184
|
+
f"UPDATE {DB_NAME}.dbo.Company "
|
|
185
|
+
f"SET CompanyCD = N'{escaped}' "
|
|
186
|
+
f"WHERE CompanyID = {company_id}"
|
|
187
|
+
)
|
|
188
|
+
self._ssh(f'sqlcmd -S "(local)" -E -C -Q "{query}"')
|
|
189
|
+
return True
|
|
190
|
+
|
|
145
191
|
def delete(self, company_id: int) -> str:
|
|
146
192
|
"""Delete the tenant and all its data.
|
|
147
193
|
|
|
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.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/scenario/20-buy.yaml
RENAMED
|
File without changes
|
{acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/scenario/30-build.yaml
RENAMED
|
File without changes
|
{acumatica_cli-0.18.0 → acumatica_cli-0.18.2}/src/acumatica_cli/templates/scenario/40-sell.yaml
RENAMED
|
File without changes
|
|
File without changes
|