neues skript sql_dump_diff.py zum Vergleich von 2 sql-dumps.
- leere, frisch initialisierte Datenbanken der Versionen verglichen - datasetprogramme erkennen jetzt auch suche nach standardtabellen wie uom
This commit is contained in:
+33
-4
@@ -124,7 +124,7 @@ def load_spec(path):
|
||||
return json.loads(text)
|
||||
|
||||
|
||||
def resolve_value(value, created):
|
||||
def resolve_value(value, created, search_cache):
|
||||
"""
|
||||
Löst Referenzen der Form {"$ref": "irgendein_key"} auf bereits in
|
||||
diesem Lauf erzeugte Datensätze auf (z. B. um eine Adresse mit der
|
||||
@@ -135,6 +135,21 @@ def resolve_value(value, created):
|
||||
Werte, die kein einfacher JSON-Typ sind:
|
||||
{"$decimal": "12.34"} -> decimal.Decimal("12.34")
|
||||
{"$date": "2024-01-15"} -> datetime.date(2024, 1, 15)
|
||||
{"$search": {"model": "product.uom",
|
||||
"domain": [["name", "=", "Unit"]]}}
|
||||
-> sucht den Record live auf dem
|
||||
Zielserver (Model.find(domain)) -
|
||||
für Stammdaten, die nicht Teil des
|
||||
Testdatensatzes sind, sondern beim
|
||||
Modul-Aktivieren per XML-Fixture
|
||||
angelegt werden (Länder, Währungen,
|
||||
Einheiten, Standard-Lagerorte, ...).
|
||||
Wirft einen Fehler, wenn nichts
|
||||
gefunden wird, statt einen neuen
|
||||
Record anzulegen - ein fehlender
|
||||
Treffer bedeutet meist, dass sich
|
||||
die Fixture-Daten zwischen den
|
||||
Versionen geändert haben.
|
||||
{"$manual": "<ausdruck>"} -> Fehler mit Klartext, da diese
|
||||
Stelle noch von Hand ausgefüllt
|
||||
werden muss.
|
||||
@@ -147,6 +162,19 @@ def resolve_value(value, created):
|
||||
f"fehlgeschlagen."
|
||||
)
|
||||
return created[value["$ref"]]
|
||||
if isinstance(value, dict) and "$search" in value:
|
||||
spec = value["$search"]
|
||||
cache_key = (spec["model"], json.dumps(spec["domain"], sort_keys=True))
|
||||
if cache_key not in search_cache:
|
||||
matches = Model.get(spec["model"]).find(spec["domain"])
|
||||
if not matches:
|
||||
raise ValueError(
|
||||
f"$search fand keinen Treffer: model={spec['model']!r} "
|
||||
f"domain={spec['domain']!r} - Stammdaten auf diesem "
|
||||
f"Server geändert/entfernt?"
|
||||
)
|
||||
search_cache[cache_key] = matches[0].id
|
||||
return search_cache[cache_key]
|
||||
if isinstance(value, dict) and "$decimal" in value:
|
||||
return decimal.Decimal(value["$decimal"])
|
||||
if isinstance(value, dict) and "$date" in value:
|
||||
@@ -158,9 +186,9 @@ def resolve_value(value, created):
|
||||
f"dieser Datensatz angelegt wird."
|
||||
)
|
||||
if isinstance(value, list):
|
||||
return [resolve_value(v, created) for v in value]
|
||||
return [resolve_value(v, created, search_cache) for v in value]
|
||||
if isinstance(value, dict):
|
||||
return {k: resolve_value(v, created) for k, v in value.items()}
|
||||
return {k: resolve_value(v, created, search_cache) for k, v in value.items()}
|
||||
return value
|
||||
|
||||
|
||||
@@ -187,6 +215,7 @@ def serialize(value):
|
||||
|
||||
def create_records(spec, created, results):
|
||||
fields_cache = {} # model_name -> Set der auf DIESEM Server bekannten Felder
|
||||
search_cache = {} # (model, domain-json) -> id, damit dieselbe Stammdaten-Suche nicht mehrfach läuft
|
||||
|
||||
for entry in spec["records"]:
|
||||
key = entry["key"]
|
||||
@@ -212,7 +241,7 @@ def create_records(spec, created, results):
|
||||
for field_name, raw_value in fields.items():
|
||||
actual_name = resolve_field_name(RecordModel, model_name, field_name, fields_cache)
|
||||
actual_names[field_name] = actual_name
|
||||
setattr(record, actual_name, resolve_value(raw_value, created))
|
||||
setattr(record, actual_name, resolve_value(raw_value, created, search_cache))
|
||||
|
||||
record.save()
|
||||
created[key] = record.id
|
||||
|
||||
+35005
File diff suppressed because it is too large
Load Diff
+38412
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,27 @@ Pro Modell wird zusätzlich `persisted` erkannt (erbt die Klasse `ModelSQL`
|
||||
o. ä., oder nur `ModelView` - dann ist es ein reines Wizard-/
|
||||
Transient-Formular ohne eigene Tabelle).
|
||||
|
||||
Verfolgt zusätzlich ganz normale **Python-Vererbung innerhalb derselben
|
||||
Datei**: erbt eine Klasse mit eigenem `__name__` von einer anderen lokalen
|
||||
Klasse - z. B. `class BOMOutput(BOMInput): __name__ = 'production.bom.output'`
|
||||
in `production/bom.py`, die selbst keine einzige `fields.X(...)`-Zeile
|
||||
deklariert, sondern alle Felder von `BOMInput` übernimmt - werden die
|
||||
geerbten Felder mit aufgenommen (eigene Felder überschreiben geerbte bei
|
||||
Namensgleichheit). Ohne das sähe ein rein über Vererbung wiederverwendetes
|
||||
Modell wie eines OHNE Felder aus, und Änderungen daran (z. B. eine
|
||||
Feldumbenennung im Elternteil) blieben komplett unsichtbar - genau das hat
|
||||
`sql_dump_diff.py` (Schritt 4) einmal live aufgedeckt.
|
||||
|
||||
Die Basisklassen-Auflösung ist bewusst auf "gleiche Datei" beschränkt
|
||||
(nicht "gleiches Modul"): derselbe lokale Klassenname wie `Cron` taucht
|
||||
innerhalb eines Moduls oft mehrfach für völlig unterschiedliche Klassen
|
||||
auf (Tryton-Konvention: eine PoolMeta-Erweiterung heißt oft genauso wie
|
||||
das Original, z. B. `currency/currency.py` definiert `class Cron(ModelSQL,
|
||||
ModelView): __name__ = 'currency.cron'`, während `currency/ir.py`
|
||||
unabhängig davon eine andere Klasse `class Cron(metaclass=PoolMeta):` zur
|
||||
Erweiterung von `ir.cron` definiert) - ein modulweites Registry würde sich
|
||||
da gegenseitig überschreiben.
|
||||
|
||||
```bash
|
||||
python3 extract_model_tree.py --repo /mnt/tryton \
|
||||
--modules company country currency party product ... \
|
||||
@@ -81,11 +102,50 @@ python3 model_coverage.py --model-tree reports/model_tree_5.0.json \
|
||||
--report reports/coverage_5.0.md
|
||||
```
|
||||
|
||||
### 4. `sql_dump_diff.py` – gegen den echten DB-Zustand validieren
|
||||
|
||||
Alles bisherige ist reine Code-Analyse. Zum Cross-Check: zwei mit
|
||||
`pg_dump` (Klartext-Format) erzeugte Dumps frisch initialisierter (leerer)
|
||||
Datenbanken beider Versionen strukturell vergleichen - Tabellen
|
||||
entfernt/neu, Spalten hinzugefügt/entfernt/Typ geändert, Zeilenzahl
|
||||
(z. B. mehr XML-Fixture-Daten in der neueren Version). Zeilen werden
|
||||
bereits vollständig geparst (für eine spätere inhaltliche
|
||||
Vergleichs-Erweiterung, sobald es Snapshots mit echten Testdaten gibt),
|
||||
aktuell nutzt der Report davon aber nur die Zeilenzahl.
|
||||
|
||||
Mit `--model-diff reports/model_diff.json` wird pro betroffener Tabelle
|
||||
zusätzlich geprüft, ob die rein statische Code-Analyse (Schritt 2)
|
||||
dieselbe Änderung schon vorhergesagt hatte (Modellname -> Tabellenname:
|
||||
Punkte durch Unterstriche ersetzen, wie `extract_updates.py`/
|
||||
`compare_snapshots.py` es auch tun) - Übereinstimmung bzw. Abweichung wird
|
||||
markiert.
|
||||
|
||||
```bash
|
||||
python3 sql_dump_diff.py --dump-a data/leer5.sql --label-a 5.0 \
|
||||
--dump-b data/leer8.sql --label-b 8.0 \
|
||||
--model-diff reports/model_diff.json \
|
||||
--output reports/dump_diff_5.0_8.0.json --report reports/dump_diff_5.0_8.0.md
|
||||
```
|
||||
|
||||
**Stand:** 78 von 92 vergleichbaren Fällen stimmen überein (~85%). Die
|
||||
verbleibenden Abweichungen gehen meist in eine erwartbare Richtung: die
|
||||
Code-Analyse findet MEHR Veränderung, als im leeren DB-Schema sichtbar
|
||||
ist (z. B. ein entferntes Function-Feld ohne eigene Spalte, oder
|
||||
Selection-Wertänderungen ohne Typänderung) - kein Fehler, sondern zwei
|
||||
Werkzeuge, die unterschiedliche Ebenen messen (Python-Feld-Definition vs.
|
||||
physisches SQL-Schema). Der `production.bom.output`-Fund oben (Schritt 1)
|
||||
kam genau aus einer vorherigen Abweichung dieser Art zustande.
|
||||
|
||||
## Bekannte Grenzen
|
||||
|
||||
- Rein statische Analyse: Felder, die nicht als einfaches
|
||||
`name = fields.X(...)` auf Klassenebene stehen (z. B. per Schleife/Helper
|
||||
dynamisch erzeugt), werden nicht erkannt.
|
||||
- Python-Vererbung wird nur INNERHALB derselben Datei aufgelöst (s. o.).
|
||||
Erbt eine Klasse von einer Basisklasse aus einer anderen Datei desselben
|
||||
oder eines anderen Moduls (Import statt lokaler Definition), werden
|
||||
deren Felder nicht gefunden - unauffällig, aber ein bekannter blinder
|
||||
Fleck.
|
||||
- `persisted`-Erkennung ist eine Textsuche über die Basisklassen-Ausdrücke
|
||||
(`ModelSQL`/`Workflow`/`DeactivableMixin`) - deckt den Normalfall ab, ist
|
||||
aber kein echtes Type-Checking.
|
||||
|
||||
+31
-4
@@ -42,10 +42,27 @@ erkennt:
|
||||
|
||||
→ wird zu einem YAML-Record mit `key`, `model`, `fields`. Referenzen auf
|
||||
zuvor im selben File angelegte Records werden zu `{"$ref": "..."}`,
|
||||
`Decimal(...)`/`date(...)` zu `{"$decimal": ...}`/`{"$date": ...}`. Alles
|
||||
nicht automatisch Auflösbare (Wizards, `.click(...)`, Helper-Funktionsaufrufe)
|
||||
wird als `{"$manual": "<Ausdruck>"}` markiert oder ganz übersprungen -
|
||||
bewusst sichtbar unvollständig statt still falsch.
|
||||
`Decimal(...)`/`date(...)` zu `{"$decimal": ...}`/`{"$date": ...}`.
|
||||
|
||||
Ein weiteres, sehr häufiges Muster wird ebenfalls erkannt: Zugriff auf
|
||||
bereits vorhandene Stammdaten (Länder, Währungen, Einheiten,
|
||||
Standard-Lagerorte, ... - i. d. R. per XML-Fixture beim Modul-Aktivieren
|
||||
angelegt, nicht Teil des eigentlichen Testdatensatzes):
|
||||
|
||||
```
|
||||
>>> ProductUom = Model.get('product.uom')
|
||||
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
|
||||
```
|
||||
|
||||
→ wird zu `{"$search": {"model": "product.uom", "domain": [["name", "=", "Unit"]]}}`
|
||||
- aufgelöst wird das erst live beim Anlegen durch `create_dataset.py`
|
||||
(Schritt 3), nicht schon hier, da diese Records ja nicht selbst Teil der
|
||||
YAML sind. Nur literale Domains werden erkannt (keine Variablen darin);
|
||||
sonst bleibt es wie bisher `$manual`.
|
||||
|
||||
Alles sonst nicht automatisch Auflösbare (Wizards, `.click(...)`,
|
||||
Helper-Funktionsaufrufe) wird als `{"$manual": "<Ausdruck>"}` markiert oder
|
||||
ganz übersprungen - bewusst sichtbar unvollständig statt still falsch.
|
||||
|
||||
Record-Keys werden mit dem Dateinamen präfixiert, damit beim
|
||||
Zusammenführen mehrerer Module keine Kollisionen entstehen.
|
||||
@@ -83,6 +100,16 @@ bleibt trotzdem ein Fehler - das ist ja gerade das gesuchte Signal für eine
|
||||
echte Schema-Änderung. Weitere Umbenennungen bei Bedarf in `FIELD_RENAMES`
|
||||
ergänzen, sobald ein Lauf sie aufdeckt.
|
||||
|
||||
**`$search`-Auflösung:** Werte der Form `{"$search": {"model": ...,
|
||||
"domain": ...}}` (von `rst_to_dataset.py` erzeugt, siehe Schritt 1) werden
|
||||
live per `Model.get(model).find(domain)` gegen den Zielserver aufgelöst -
|
||||
für Stammdaten, die nicht selbst angelegt werden, sondern schon per
|
||||
XML-Fixture da sind. Ergebnis wird pro Lauf gecacht (dieselbe Suche, z. B.
|
||||
nach der Einheit "Unit", kommt in vielen Records vor). Kein Treffer ->
|
||||
Fehler mit Klartext statt eines neu angelegten Records - das ist selbst
|
||||
ein Signal, dass sich die Stammdaten zwischen den Versionen geändert
|
||||
haben.
|
||||
|
||||
### 4. `compare_snapshots.py` – zwei Versionen gegenüberstellen
|
||||
|
||||
Vergleicht zwei Snapshots (gleiche YAML, zwei Versionen/Datenbanken) und
|
||||
|
||||
+132
-31
@@ -13,6 +13,18 @@ z. B. stock_split eine Klasse mit __name__ = 'stock.move' hinzu, landen
|
||||
deren Felder unter demselben Modell-Eintrag, mit Vermerk, aus welchem
|
||||
Modul sie stammen.
|
||||
|
||||
Zusätzlich wird ganz normale Python-Vererbung innerhalb eines Moduls
|
||||
verfolgt: erbt eine Klasse mit eigenem __name__ von einer anderen lokalen
|
||||
Klasse (z. B. `class BOMOutput(BOMInput): __name__ = 'production.bom.output'`
|
||||
in production/bom.py, die selbst keine einzige fields.X(...)-Zeile
|
||||
deklariert, sondern alle Felder von BOMInput übernimmt), werden die
|
||||
geerbten Felder mit aufgenommen. Ohne das sähe ein rein über Vererbung
|
||||
wiederverwendetes Modell wie ein Modell OHNE Felder aus - und Änderungen
|
||||
daran (z. B. eine Feldumbenennung im Elternteil) blieben unsichtbar.
|
||||
Löst nur Basisklassen auf, die im selben Modul per einfachem Namen (kein
|
||||
Attribut-Zugriff auf ein anderes Modul) gefunden werden - externe Basen
|
||||
wie ModelSQL/ModelView/Workflow tragen ohnehin keine fields.X(...) bei.
|
||||
|
||||
Zweck hier: BEVOR versucht wird, dieselbe dataset.yaml gegen 5.0 und 8.0
|
||||
laufen zu lassen, erstmal Schwarz auf Weiß sehen, welche Modelle/Felder in
|
||||
der 5.0-Codebasis (bei den tatsächlich aktiven Modulen) überhaupt
|
||||
@@ -132,44 +144,117 @@ def is_persisted_class(node):
|
||||
return False
|
||||
|
||||
|
||||
def scan_file(path, module_name, module_dir, models, persisted):
|
||||
def own_fields_of(node, module_name, module_dir, path):
|
||||
"""Nur die in DIESEM Klassenkörper direkt deklarierten fields.X(...)."""
|
||||
fields = {}
|
||||
for stmt in node.body:
|
||||
if not (
|
||||
isinstance(stmt, ast.Assign)
|
||||
and len(stmt.targets) == 1
|
||||
and isinstance(stmt.targets[0], ast.Name)
|
||||
):
|
||||
continue
|
||||
info = fields_call_info(stmt.value)
|
||||
if info is None:
|
||||
continue
|
||||
field_type, relation, required, is_function, has_setter = info
|
||||
fields[stmt.targets[0].id] = {
|
||||
"type": field_type,
|
||||
"relation": relation,
|
||||
"required": required,
|
||||
"settable": (not is_function) or has_setter,
|
||||
"module": module_name,
|
||||
"file": f"{module_name}/{path.relative_to(module_dir)}",
|
||||
}
|
||||
return fields
|
||||
|
||||
|
||||
def base_class_names(node):
|
||||
"""Best-effort Namen der Basisklassen als String, nur einfache
|
||||
`class X(Y):`-Fälle (kein Attribut-Zugriff wie `other.Y`, das wäre
|
||||
ohnehin eine externe/andere Modul-Klasse, die wir hier nicht kennen)."""
|
||||
return [base.id for base in node.bases if isinstance(base, ast.Name)]
|
||||
|
||||
|
||||
def collect_classes(path, module_name, module_dir, registry):
|
||||
"""Erste Durchsicht: registriert jede Klasse (lokaler Python-Name,
|
||||
Basisklassen, eigene Felder, ggf. Tryton-__name__, ggf. direkt
|
||||
persistent) - unabhängig davon, ob/wie sie später gebraucht wird.
|
||||
Die eigentliche Feld-/Persisted-Auflösung inkl. Vererbung passiert
|
||||
danach in resolve_class_fields()/resolve_class_persisted().
|
||||
|
||||
Registry-Key ist (module_name, Dateipfad), NICHT nur module_name:
|
||||
derselbe lokale Klassenname (z. B. "Cron") kommt innerhalb eines
|
||||
Moduls in mehreren Dateien für kompott unterschiedliche Klassen vor
|
||||
(Tryton-Konvention: eine PoolMeta-Erweiterung heißt oft genauso wie
|
||||
das Original) - ein modulweites Registry würde sich da gegenseitig
|
||||
überschreiben. Basisklassen-Auflösung bleibt dadurch bewusst auf
|
||||
"gleiche Datei" beschränkt, was dem tatsächlichen Python-Scoping in
|
||||
der weit überwiegenden Mehrheit der Fälle entspricht."""
|
||||
try:
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||
except (SyntaxError, UnicodeDecodeError) as e:
|
||||
print(f" WARNUNG: {path} übersprungen ({e})", file=sys.stderr)
|
||||
return
|
||||
|
||||
file_key = (module_name, str(path))
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.ClassDef):
|
||||
continue
|
||||
model_name = find_name_assignment(node.body)
|
||||
if not model_name:
|
||||
continue
|
||||
registry.setdefault(file_key, {})[node.name] = {
|
||||
"bases": base_class_names(node),
|
||||
"own_fields": own_fields_of(node, module_name, module_dir, path),
|
||||
"model_name": find_name_assignment(node.body),
|
||||
"persisted_here": is_persisted_class(node),
|
||||
}
|
||||
|
||||
if is_persisted_class(node):
|
||||
persisted.add(model_name)
|
||||
|
||||
model = models.setdefault(model_name, {})
|
||||
for stmt in node.body:
|
||||
if not (
|
||||
isinstance(stmt, ast.Assign)
|
||||
and len(stmt.targets) == 1
|
||||
and isinstance(stmt.targets[0], ast.Name)
|
||||
):
|
||||
continue
|
||||
field_name = stmt.targets[0].id
|
||||
info = fields_call_info(stmt.value)
|
||||
if info is None:
|
||||
continue
|
||||
field_type, relation, required, is_function, has_setter = info
|
||||
model[field_name] = {
|
||||
"type": field_type,
|
||||
"relation": relation,
|
||||
"required": required,
|
||||
"settable": (not is_function) or has_setter,
|
||||
"module": module_name,
|
||||
"file": f"{module_name}/{path.relative_to(module_dir)}",
|
||||
}
|
||||
def resolve_class_fields(scope_key, class_name, registry, cache, visiting):
|
||||
"""Felder einer lokalen Klasse inkl. per Basisklasse(n) in derselben
|
||||
Datei (scope_key = (module_name, Dateipfad)) geerbter Felder (eigene
|
||||
Felder überschreiben geerbte bei Namensgleichheit, wie bei echter
|
||||
Python-Attributauflösung)."""
|
||||
key = (scope_key, class_name)
|
||||
if key in cache:
|
||||
return cache[key]
|
||||
if key in visiting:
|
||||
return {} # zyklische Vererbung - sollte nicht vorkommen, Notbremse
|
||||
class_info = registry.get(scope_key, {}).get(class_name)
|
||||
if class_info is None:
|
||||
return {} # externe/unbekannte Basisklasse (ModelSQL, Workflow, ...)
|
||||
|
||||
visiting.add(key)
|
||||
fields = {}
|
||||
for base_name in class_info["bases"]:
|
||||
fields.update(resolve_class_fields(scope_key, base_name, registry, cache, visiting))
|
||||
fields.update(class_info["own_fields"])
|
||||
visiting.discard(key)
|
||||
|
||||
cache[key] = fields
|
||||
return fields
|
||||
|
||||
|
||||
def resolve_class_persisted(scope_key, class_name, registry, cache, visiting):
|
||||
"""True, wenn diese Klasse selbst oder eine ihrer lokalen Basisklassen
|
||||
ModelSQL/Workflow/DeactivableMixin als Basis hat."""
|
||||
key = (scope_key, class_name)
|
||||
if key in cache:
|
||||
return cache[key]
|
||||
if key in visiting:
|
||||
return False
|
||||
class_info = registry.get(scope_key, {}).get(class_name)
|
||||
if class_info is None:
|
||||
return False
|
||||
|
||||
visiting.add(key)
|
||||
result = class_info["persisted_here"] or any(
|
||||
resolve_class_persisted(scope_key, base_name, registry, cache, visiting)
|
||||
for base_name in class_info["bases"]
|
||||
)
|
||||
visiting.discard(key)
|
||||
|
||||
cache[key] = result
|
||||
return result
|
||||
|
||||
|
||||
def module_directory(repo, module_name):
|
||||
@@ -181,8 +266,8 @@ def module_directory(repo, module_name):
|
||||
|
||||
|
||||
def scan_modules(repo, modules):
|
||||
models = {}
|
||||
persisted = set()
|
||||
# Phase 1: alle Klassen roh einsammeln (Basen, eigene Felder, __name__).
|
||||
registry = {}
|
||||
for module_name in modules:
|
||||
module_dir = module_directory(repo, module_name)
|
||||
if not module_dir.is_dir():
|
||||
@@ -191,10 +276,26 @@ def scan_modules(repo, modules):
|
||||
for path in sorted(module_dir.rglob("*.py")):
|
||||
if "tests" in path.relative_to(module_dir).parts:
|
||||
continue
|
||||
scan_file(path, module_name, module_dir, models, persisted)
|
||||
collect_classes(path, module_name, module_dir, registry)
|
||||
|
||||
# Phase 2: pro Klasse mit __name__ die Felder (inkl. lokaler Python-
|
||||
# Vererbung) auflösen und unter dem Tryton-Modellnamen zusammenführen -
|
||||
# mehrere Klassen mit demselben __name__ (PoolMeta-Erweiterungen aus
|
||||
# anderen Modulen) tragen weiterhin gemeinsam zum selben Eintrag bei.
|
||||
fields_cache, persisted_cache = {}, {}
|
||||
models = {}
|
||||
persisted_models = set()
|
||||
for scope_key, classes in registry.items():
|
||||
for class_name, info in classes.items():
|
||||
if not info["model_name"]:
|
||||
continue
|
||||
resolved = resolve_class_fields(scope_key, class_name, registry, fields_cache, set())
|
||||
models.setdefault(info["model_name"], {}).update(resolved)
|
||||
if resolve_class_persisted(scope_key, class_name, registry, persisted_cache, set()):
|
||||
persisted_models.add(info["model_name"])
|
||||
|
||||
return {
|
||||
model_name: {"persisted": model_name in persisted, "fields": fields}
|
||||
model_name: {"persisted": model_name in persisted_models, "fields": fields}
|
||||
for model_name, fields in models.items()
|
||||
}
|
||||
|
||||
|
||||
+270
-27
@@ -77,7 +77,16 @@
|
||||
"fields": {
|
||||
"name": "product",
|
||||
"default_uom": {
|
||||
"$manual": "unit"
|
||||
"$search": {
|
||||
"model": "product.uom",
|
||||
"domain": [
|
||||
[
|
||||
"name",
|
||||
"=",
|
||||
"Unit"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "goods",
|
||||
"producible": true,
|
||||
@@ -92,7 +101,16 @@
|
||||
"fields": {
|
||||
"name": "component 1",
|
||||
"default_uom": {
|
||||
"$manual": "unit"
|
||||
"$search": {
|
||||
"model": "product.uom",
|
||||
"domain": [
|
||||
[
|
||||
"name",
|
||||
"=",
|
||||
"Unit"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "goods",
|
||||
"list_price": {
|
||||
@@ -106,7 +124,16 @@
|
||||
"fields": {
|
||||
"name": "component 2",
|
||||
"default_uom": {
|
||||
"$manual": "meter"
|
||||
"$search": {
|
||||
"model": "product.uom",
|
||||
"domain": [
|
||||
[
|
||||
"name",
|
||||
"=",
|
||||
"Meter"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "goods",
|
||||
"list_price": {
|
||||
@@ -120,7 +147,16 @@
|
||||
"fields": {
|
||||
"name": "Product",
|
||||
"default_uom": {
|
||||
"$manual": "unit"
|
||||
"$search": {
|
||||
"model": "product.uom",
|
||||
"domain": [
|
||||
[
|
||||
"name",
|
||||
"=",
|
||||
"Unit"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "goods",
|
||||
"list_price": {
|
||||
@@ -135,7 +171,16 @@
|
||||
"fields": {
|
||||
"name": "Product",
|
||||
"default_uom": {
|
||||
"$manual": "kg"
|
||||
"$search": {
|
||||
"model": "product.uom",
|
||||
"domain": [
|
||||
[
|
||||
"name",
|
||||
"=",
|
||||
"Kilogram"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "goods",
|
||||
"list_price": {
|
||||
@@ -150,7 +195,16 @@
|
||||
"fields": {
|
||||
"name": "Product",
|
||||
"default_uom": {
|
||||
"$manual": "unit"
|
||||
"$search": {
|
||||
"model": "product.uom",
|
||||
"domain": [
|
||||
[
|
||||
"name",
|
||||
"=",
|
||||
"Unit"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "goods",
|
||||
"list_price": {
|
||||
@@ -229,7 +283,16 @@
|
||||
"model": "stock.inventory",
|
||||
"fields": {
|
||||
"location": {
|
||||
"$manual": "storage_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"STO"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"empty_quantity": "keep"
|
||||
}
|
||||
@@ -239,7 +302,16 @@
|
||||
"model": "stock.inventory",
|
||||
"fields": {
|
||||
"location": {
|
||||
"$manual": "storage_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"STO"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"empty_quantity": "empty"
|
||||
}
|
||||
@@ -251,7 +323,16 @@
|
||||
"name": "Storage Sub",
|
||||
"type": "storage",
|
||||
"parent": {
|
||||
"$manual": "storage_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"STO"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -261,7 +342,16 @@
|
||||
"fields": {
|
||||
"name": "Child Location",
|
||||
"parent": {
|
||||
"$manual": "storage_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"STO"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"code": "CHI"
|
||||
}
|
||||
@@ -272,7 +362,16 @@
|
||||
"fields": {
|
||||
"name": "Storage 1",
|
||||
"parent": {
|
||||
"$manual": "storage_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"STO"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -307,14 +406,41 @@
|
||||
"$manual": "product"
|
||||
},
|
||||
"uom": {
|
||||
"$manual": "unit"
|
||||
"$search": {
|
||||
"model": "product.uom",
|
||||
"domain": [
|
||||
[
|
||||
"name",
|
||||
"=",
|
||||
"Unit"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"quantity": 1,
|
||||
"from_location": {
|
||||
"$manual": "supplier_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"SUP"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"to_location": {
|
||||
"$manual": "storage_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"STO"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"planned_date": {
|
||||
"$manual": "today"
|
||||
@@ -344,7 +470,16 @@
|
||||
"$ref": "scenario_stock_product_location__supplier"
|
||||
},
|
||||
"warehouse": {
|
||||
"$manual": "warehouse_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"WH"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"$manual": "company"
|
||||
@@ -362,7 +497,16 @@
|
||||
"$ref": "scenario_stock_reporting__supplier"
|
||||
},
|
||||
"warehouse": {
|
||||
"$manual": "warehouse_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"WH"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"$manual": "company"
|
||||
@@ -377,7 +521,16 @@
|
||||
"$ref": "scenario_stock_reporting__supplier"
|
||||
},
|
||||
"warehouse": {
|
||||
"$manual": "warehouse_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"WH"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"$manual": "company"
|
||||
@@ -395,10 +548,28 @@
|
||||
"$manual": "company"
|
||||
},
|
||||
"from_location": {
|
||||
"$manual": "storage_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"STO"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"to_location": {
|
||||
"$manual": "lost_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"type",
|
||||
"=",
|
||||
"lost_found"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -413,7 +584,16 @@
|
||||
"$ref": "scenario_stock_shipment_internal__internal_loc"
|
||||
},
|
||||
"to_location": {
|
||||
"$manual": "storage_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"STO"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -428,7 +608,16 @@
|
||||
"$ref": "scenario_stock_lot_shipment_out__customer"
|
||||
},
|
||||
"warehouse": {
|
||||
"$manual": "warehouse_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"WH"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"$manual": "company"
|
||||
@@ -446,7 +635,16 @@
|
||||
"$ref": "scenario_stock_lot_shipment_out__customer"
|
||||
},
|
||||
"warehouse": {
|
||||
"$manual": "warehouse_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"WH"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"$manual": "company"
|
||||
@@ -464,7 +662,16 @@
|
||||
"$ref": "scenario_stock_lot_shipment_out__customer"
|
||||
},
|
||||
"warehouse": {
|
||||
"$manual": "warehouse_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"WH"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"$manual": "company"
|
||||
@@ -479,7 +686,16 @@
|
||||
"$ref": "scenario_stock_lot_shipment_out__customer"
|
||||
},
|
||||
"warehouse": {
|
||||
"$manual": "warehouse_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"WH"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"$manual": "company"
|
||||
@@ -506,10 +722,28 @@
|
||||
"$ref": "scenario_stock_product_location__supplier"
|
||||
},
|
||||
"from_location": {
|
||||
"$manual": "output_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"OUT"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"to_location": {
|
||||
"$manual": "supplier_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"SUP"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -521,7 +755,16 @@
|
||||
"$ref": "scenario_stock_lot_shipment_out__customer"
|
||||
},
|
||||
"warehouse": {
|
||||
"$manual": "warehouse_loc"
|
||||
"$search": {
|
||||
"model": "stock.location",
|
||||
"domain": [
|
||||
[
|
||||
"code",
|
||||
"=",
|
||||
"WH"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"company": {
|
||||
"$manual": "company"
|
||||
|
||||
+711
-68
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
# Testdaten-Abdeckung gegen den 5.0-Modellbaum
|
||||
|
||||
|
||||
## Komplett unabgedeckte Modelle (35)
|
||||
## Komplett unabgedeckte Modelle (36)
|
||||
|
||||
Für diese Modelle gibt es noch gar keinen Record in der dataset.yaml:
|
||||
|
||||
@@ -16,10 +16,9 @@ Für diese Modelle gibt es noch gar keinen Record in der dataset.yaml:
|
||||
- `product.list_price` _[geändert]_
|
||||
- `product.product` _[geändert]_
|
||||
- `production.bom.input` _[geändert]_
|
||||
- `production.bom.output` _[geändert]_
|
||||
- `stock.inventory.line` _[geändert]_
|
||||
- `stock.lot` _[geändert]_
|
||||
- `stock.product.location` _[geändert]_
|
||||
- `stock.shipment.in.return` _[geändert]_
|
||||
- `country.zip` _[entfernt]_
|
||||
- `stock.lot.type` _[entfernt]_
|
||||
- `country.country` _[nur ergänzt]_
|
||||
@@ -29,6 +28,8 @@ Für diese Modelle gibt es noch gar keinen Record in der dataset.yaml:
|
||||
- `product.configuration` _[nur ergänzt]_
|
||||
- `production.configuration` _[nur ergänzt]_
|
||||
- `stock.configuration` _[nur ergänzt]_
|
||||
- `stock.lot` _[nur ergänzt]_
|
||||
- `stock.shipment.in.return` _[nur ergänzt]_
|
||||
- `party.category` _[unverändert]_
|
||||
- `party.party.lang` _[unverändert]_
|
||||
- `party.party.location` _[unverändert]_
|
||||
@@ -69,9 +70,6 @@ Modell hat mind. einen Record, aber nicht alle setzbaren Felder werden benutzt:
|
||||
### `stock.shipment.in` _[geändert]_
|
||||
- optional, ungenutzt: contact_address, effective_date, incoming_moves, inventory_moves, moves, number, reference, state
|
||||
|
||||
### `stock.shipment.internal` _[geändert]_
|
||||
- optional, ungenutzt: effective_date, effective_start_date, incoming_moves, moves, number, outgoing_moves, planned_start_date, reference, state
|
||||
|
||||
### `stock.shipment.out` _[geändert]_
|
||||
- **required, aber ungenutzt:** delivery_address
|
||||
- optional, ungenutzt: effective_date, inventory_moves, moves, number, outgoing_moves, reference, state
|
||||
@@ -95,6 +93,9 @@ Modell hat mind. einen Record, aber nicht alle setzbaren Felder werden benutzt:
|
||||
- **required, aber ungenutzt:** left, right
|
||||
- optional, ungenutzt: address, childs, flat_childs, input_location, output_location, picking_location, production_location, storage_location
|
||||
|
||||
### `stock.shipment.internal` _[nur ergänzt]_
|
||||
- optional, ungenutzt: effective_date, effective_start_date, incoming_moves, moves, number, outgoing_moves, planned_start_date, reference, state
|
||||
|
||||
### `ir.attachment` _[unverändert]_
|
||||
- **required, aber ungenutzt:** type
|
||||
- optional, ungenutzt: data, description, file_id, link
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,294 @@
|
||||
# SQL-Dump-Diff: 5.0 -> 8.0
|
||||
|
||||
|
||||
## Tabellen entfernt (4)
|
||||
|
||||
- `country_zip` _(`country.zip`=entfernt [übereinstimmung])_
|
||||
- `cron_company_rel` _(kein passendes Modell in model_diff gefunden)_
|
||||
- `party_category_rel` _(kein passendes Modell in model_diff gefunden)_
|
||||
- `stock_lot_type` _(`stock.lot.type`=entfernt [übereinstimmung])_
|
||||
|
||||
|
||||
## Tabellen neu (31)
|
||||
|
||||
- `company_company_logo_cache` _(`company.company.logo.cache`=neu [übereinstimmung])_
|
||||
- `company_company_tax_identifier` _(`company.company.tax_identifier`=neu [übereinstimmung])_
|
||||
- `country_organization` _(`country.organization`=neu [übereinstimmung])_
|
||||
- `country_organization_member` _(`country.organization.member`=neu [übereinstimmung])_
|
||||
- `country_postal_code` _(`country.postal_code`=neu [übereinstimmung])_
|
||||
- `country_region` _(`country.region`=neu [übereinstimmung])_
|
||||
- `currency_cron` _(`currency.cron`=neu [übereinstimmung])_
|
||||
- `ir_avatar` _(`ir.avatar`=neu [übereinstimmung])_
|
||||
- `ir_avatar_cache` _(`ir.avatar.cache`=neu [übereinstimmung])_
|
||||
- `ir_calendar_day` _(`ir.calendar.day`=neu [übereinstimmung])_
|
||||
- `ir_calendar_month` _(`ir.calendar.month`=neu [übereinstimmung])_
|
||||
- `ir_chat_channel` _(`ir.chat.channel`=neu [übereinstimmung])_
|
||||
- `ir_chat_follower` _(`ir.chat.follower`=neu [übereinstimmung])_
|
||||
- `ir_chat_message` _(`ir.chat.message`=neu [übereinstimmung])_
|
||||
- `ir_cron_log` _(`ir.cron.log`=neu [übereinstimmung])_
|
||||
- `ir_email` _(`ir.email`=neu [übereinstimmung])_
|
||||
- `ir_email_address` _(`ir.email.address`=neu [übereinstimmung])_
|
||||
- `ir_email_template` _(`ir.email.template`=neu [übereinstimmung])_
|
||||
- `ir_error` _(`ir.error`=neu [übereinstimmung])_
|
||||
- `ir_filestore_queue` _(`ir.filestore.queue`=neu [übereinstimmung])_
|
||||
- `ir_message` _(`ir.message`=neu [übereinstimmung])_
|
||||
- `ir_model_log` _(`ir.model.log`=neu [übereinstimmung])_
|
||||
- `ir_ui_view_tree_optional` _(`ir.ui.view_tree_optional`=neu [übereinstimmung])_
|
||||
- `party_address_subdivision_type` _(`party.address.subdivision_type`=neu [übereinstimmung])_
|
||||
- `party_contact_mechanism_language` _(`party.contact_mechanism.language`=neu [übereinstimmung])_
|
||||
- `product_configuration_default_lot_sequence` _(`product.configuration.default_lot_sequence`=neu [übereinstimmung])_
|
||||
- `product_cost_price_revision` _(`product.cost_price.revision`=neu [übereinstimmung])_
|
||||
- `product_identifier` _(`product.identifier`=neu [übereinstimmung])_
|
||||
- `res_notification` _(`res.notification`=neu [übereinstimmung])_
|
||||
- `res_user_device` _(`res.user.device`=neu [übereinstimmung])_
|
||||
- `stock_location_waste` _(`stock.location.waste`=neu [übereinstimmung])_
|
||||
|
||||
|
||||
## Tabellen mit Schema-Änderung (59)
|
||||
|
||||
Spalten wurden hinzugefügt/entfernt oder haben einen anderen Typ - vergleichbar mit einer model_diff.py-Kategorie.
|
||||
|
||||
|
||||
### `company_company` _(`company.company`=geändert [übereinstimmung])_
|
||||
- neue Spalten: logo
|
||||
- entfernte Spalten: parent
|
||||
|
||||
### `company_employee` _(`company.employee`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: supervisor
|
||||
|
||||
### `country_country` _(`country.country`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: active, region
|
||||
|
||||
### `country_subdivision` _(`country.subdivision`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: active
|
||||
- Zeilenzahl: 4835 -> 5046
|
||||
|
||||
### `ir_action` _(`ir.action`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: records
|
||||
- Zeilenzahl: 122 -> 167
|
||||
|
||||
### `ir_action_report` _(`ir.action.report`=geändert [übereinstimmung])_
|
||||
- neue Spalten: record_name
|
||||
- entfernte Spalten: email
|
||||
|
||||
### `ir_action_wizard` _(`ir.action.wizard`=geändert [übereinstimmung])_
|
||||
- entfernte Spalten: email
|
||||
- Zeilenzahl: 25 -> 32
|
||||
|
||||
### `ir_attachment` _(`ir.attachment`=unverändert [ABWEICHUNG])_
|
||||
- neue Spalten: copy_to_resources
|
||||
|
||||
### `ir_cache` _(`ir.cache`=unverändert [ABWEICHUNG])_
|
||||
- **create_date**: timestamp(6) without time zone -> timestamp without time zone
|
||||
- **write_date**: timestamp(6) without time zone -> timestamp without time zone
|
||||
- Zeilenzahl: 21 -> 43
|
||||
|
||||
### `ir_configuration` _(`ir.configuration`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: product_price_decimal, series
|
||||
|
||||
### `ir_cron` _(`ir.cron`=geändert [übereinstimmung])_
|
||||
- neue Spalten: day, hour, method, minute, weekday
|
||||
- entfernte Spalten: args, function, model, name, number_calls, repeat_missed, request_user, user
|
||||
- Zeilenzahl: 1 -> 10
|
||||
|
||||
### `ir_export` _(`ir.export`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: header, ignore_search_limit, records, user
|
||||
|
||||
### `ir_lang` _(`ir.lang`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: am, nplurals, pg_text_search, plural, pm
|
||||
- Zeilenzahl: 19 -> 25
|
||||
|
||||
### `ir_model` _(`ir.model`=geändert [übereinstimmung])_
|
||||
- neue Spalten: string
|
||||
- entfernte Spalten: model
|
||||
- Zeilenzahl: 159 -> 215
|
||||
|
||||
### `ir_model_access` _(`ir.model.access`=geändert [übereinstimmung])_
|
||||
- neue Spalten: active
|
||||
- **model**: integer -> character varying
|
||||
- Zeilenzahl: 138 -> 163
|
||||
|
||||
### `ir_model_button` _(`ir.model.button`=geändert [übereinstimmung])_
|
||||
- neue Spalten: active
|
||||
- **model**: integer -> character varying
|
||||
- Zeilenzahl: 67 -> 80
|
||||
|
||||
### `ir_model_data` _(`ir.model.data`=geändert [übereinstimmung])_
|
||||
- neue Spalten: field_names
|
||||
- entfernte Spalten: fs_values, values
|
||||
- Zeilenzahl: 6583 -> 2080
|
||||
|
||||
### `ir_model_field` _(`ir.model.field`=geändert [übereinstimmung])_
|
||||
- neue Spalten: access, string
|
||||
- entfernte Spalten: field_description
|
||||
- **model**: integer -> character varying
|
||||
- Zeilenzahl: 1709 -> 2858
|
||||
|
||||
### `ir_model_field_access` _(`ir.model.field.access`=geändert [übereinstimmung])_
|
||||
- neue Spalten: active, model
|
||||
- **field**: integer -> character varying
|
||||
|
||||
### `ir_note` _(`ir.note`=unverändert [ABWEICHUNG])_
|
||||
- neue Spalten: copy_to_resources
|
||||
|
||||
### `ir_queue` _(`ir.queue`=unverändert [ABWEICHUNG])_
|
||||
- **data**: text -> jsonb
|
||||
|
||||
### `ir_rule_group` _(`ir.rule.group`=geändert [übereinstimmung])_
|
||||
- **model**: integer -> character varying
|
||||
- Zeilenzahl: 26 -> 34
|
||||
|
||||
### `ir_sequence` _(`ir.sequence`=geändert [übereinstimmung])_
|
||||
- neue Spalten: sequence_type
|
||||
- entfernte Spalten: code
|
||||
|
||||
### `ir_sequence_strict` _(`ir.sequence.strict`=geändert [übereinstimmung])_
|
||||
- neue Spalten: sequence_type
|
||||
- entfernte Spalten: code
|
||||
|
||||
### `ir_sequence_type` _(`ir.sequence.type`=geändert [übereinstimmung])_
|
||||
- entfernte Spalten: code
|
||||
- Zeilenzahl: 8 -> 13
|
||||
|
||||
### `ir_session` _(`ir.session`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: ip_address
|
||||
|
||||
### `ir_translation` _(`ir.translation`=geändert [übereinstimmung])_
|
||||
- neue Spalten: src_plural, value_1, value_2, value_3
|
||||
- entfernte Spalten: src_md5
|
||||
- Zeilenzahl: 8173 -> 8940
|
||||
|
||||
### `ir_trigger` _(`ir.trigger`=geändert [übereinstimmung])_
|
||||
- neue Spalten: action, on_create_, on_delete_, on_time_, on_write_
|
||||
- entfernte Spalten: action_function, action_model, on_create, on_delete, on_time, on_write
|
||||
|
||||
### `ir_ui_menu_favorite` _(`ir.ui.menu.favorite`=unverändert [ABWEICHUNG])_
|
||||
- entfernte Spalten: sequence
|
||||
|
||||
### `ir_ui_view_tree_width` _(`ir.ui.view_tree_width`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: occurrence, screen_width
|
||||
|
||||
### `party_address` _(`party.address`=geändert [übereinstimmung])_
|
||||
- neue Spalten: attn, building_name, building_number, floor_number, post_box, post_office, postal_code, private_bag, room_number, street_name, street_unstructured, unit_number
|
||||
- entfernte Spalten: name, street, zip
|
||||
|
||||
### `party_address_format` _(`party.address.format`=geändert [übereinstimmung])_
|
||||
- neue Spalten: building_number_format, country_code, floor_number_format, language_code, post_box_format, post_office_format, private_bag_format, room_number_format, street_format, unit_number_format
|
||||
- entfernte Spalten: country, language
|
||||
- Zeilenzahl: 66 -> 222
|
||||
|
||||
### `party_configuration` _(`party.configuration`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: identifier_types
|
||||
|
||||
### `party_contact_mechanism` _(`party.contact_mechanism`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: address, delivery
|
||||
|
||||
### `party_identifier` _(`party.identifier`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: active, address, code_compact
|
||||
|
||||
### `party_party` _(`party.party`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: code_alnum, code_digit
|
||||
|
||||
### `product_category` _(`product.category`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: code
|
||||
|
||||
### `product_configuration` _(`product.configuration`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: category_sequence, product_sequence, template_sequence
|
||||
|
||||
### `product_list_price` _(`product.list_price`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: product
|
||||
|
||||
### `product_product` _(`product.product`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: position, replaced_by, suffix_code
|
||||
|
||||
### `product_template` _(`product.template`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: code, lot_required, lot_sequence
|
||||
|
||||
### `production` _(`production`=geändert [übereinstimmung])_
|
||||
- neue Spalten: assigned_by, done_by, origin, origin_planned_date, run_by, type, unit
|
||||
- entfernte Spalten: uom
|
||||
|
||||
### `production_bom` _(`production.bom`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: code, phantom, phantom_quantity, phantom_unit
|
||||
|
||||
### `production_bom_input` _(`production.bom.input`=geändert [übereinstimmung])_
|
||||
- neue Spalten: phantom_bom, unit
|
||||
- entfernte Spalten: uom
|
||||
|
||||
### `production_bom_output` _(`production.bom.output`=geändert [übereinstimmung])_
|
||||
- neue Spalten: phantom_bom, unit
|
||||
- entfernte Spalten: uom
|
||||
|
||||
### `production_configuration` _(`production.configuration`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: bom_sequence
|
||||
|
||||
### `res_group` _(`res.group`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: active, parent
|
||||
- Zeilenzahl: 10 -> 13
|
||||
|
||||
### `res_user` _(`res.user`=geändert [übereinstimmung])_
|
||||
- neue Spalten: company_filter, notifications, warehouse
|
||||
- entfernte Spalten: main_company
|
||||
- Zeilenzahl: 3 -> 2
|
||||
|
||||
### `res_user_login_attempt` _(`res.user.login.attempt`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: device_cookie
|
||||
|
||||
### `stock_inventory` _(`stock.inventory`=geändert [übereinstimmung])_
|
||||
- entfernte Spalten: lost_found
|
||||
|
||||
### `stock_location` _(`stock.location`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: allow_pickup, lost_found_location, production_output_location, production_picking_location
|
||||
|
||||
### `stock_lot` _(`stock.lot`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: active
|
||||
|
||||
### `stock_move` _(`stock.move`=geändert [übereinstimmung])_
|
||||
- neue Spalten: origin_planned_date, product_cost_price, production_cost_price_updated, unit, unit_price_updated
|
||||
- entfernte Spalten: internal_volume, internal_weight, uom
|
||||
|
||||
### `stock_product_location` _(`stock.product.location`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: template
|
||||
|
||||
### `stock_shipment_in` _(`stock.shipment.in`=geändert [ABWEICHUNG])_
|
||||
- neue Spalten: origin_planned_date, warehouse_input, warehouse_storage
|
||||
|
||||
### `stock_shipment_in_return` _(`stock.shipment.in.return`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: origin_planned_date
|
||||
|
||||
### `stock_shipment_internal` _(`stock.shipment.internal`=nur ergänzt [übereinstimmung])_
|
||||
- neue Spalten: internal_transit_location, origin_planned_date, packed_by
|
||||
|
||||
### `stock_shipment_out` _(`stock.shipment.out`=geändert [übereinstimmung])_
|
||||
- neue Spalten: origin_planned_date, picked_by, shipped_by, warehouse_output, warehouse_storage
|
||||
- entfernte Spalten: assigned_by
|
||||
|
||||
### `stock_shipment_out_return` _(`stock.shipment.out.return`=geändert [übereinstimmung])_
|
||||
- neue Spalten: contact_address, origin_planned_date, warehouse_input, warehouse_storage
|
||||
- entfernte Spalten: delivery_address
|
||||
|
||||
|
||||
## Nur Zeilenzahl unterschiedlich, Schema identisch (15)
|
||||
|
||||
Keine Code-Definitionsänderung zu erwarten (z. B. mehr XML-Fixture-Daten in der neueren Version) - daher kein model_diff-Abgleich.
|
||||
|
||||
- `currency_currency`: 170 -> 178
|
||||
- `currency_currency_rate`: 1 -> 0
|
||||
- `ir_action_act_window`: 87 -> 125
|
||||
- `ir_action_act_window_domain`: 39 -> 47
|
||||
- `ir_action_act_window_view`: 146 -> 225
|
||||
- `ir_action_keyword`: 117 -> 165
|
||||
- `ir_module`: 138 -> 219
|
||||
- `ir_module_config_wizard_item`: 3 -> 4
|
||||
- `ir_module_dependency`: 587 -> 951
|
||||
- `ir_rule`: 34 -> 38
|
||||
- `ir_ui_icon`: 16 -> 20
|
||||
- `ir_ui_menu`: 93 -> 108
|
||||
- `ir_ui_view`: 261 -> 367
|
||||
- `product_uom`: 32 -> 35
|
||||
- `product_uom_category`: 6 -> 7
|
||||
|
||||
|
||||
## Unverändert (30)
|
||||
|
||||
`ir_action_url`, `ir_export_line`, `ir_model_button_click`, `ir_model_button_rule`, `ir_note_read`, `ir_session_wizard`, `ir_trigger_log`, `ir_ui_view_search`, `ir_ui_view_tree_state`, `party_category`, `party_configuration_party_lang`, `party_configuration_party_sequence`, `party_party_lang`, `party_party_location`, `product_configuration_default_cost_price_method`, `product_cost_price`, `product_cost_price_method`, `production_configuration_production_sequence`, `production_lead_time`, `res_user_application`, `res_user_warning`, `stock_configuration`, `stock_configuration_location`, `stock_configuration_sequence`, `stock_inventory_line`, `stock_location_lead_time`, `stock_location_lot_shelf_life`, `stock_period`, `stock_period_cache`, `stock_period_cache_lot`
|
||||
+88
-63
@@ -167,9 +167,7 @@
|
||||
"type: Function[Binary] -> Function[Field]"
|
||||
]
|
||||
},
|
||||
"removed": [
|
||||
"action"
|
||||
]
|
||||
"removed": []
|
||||
},
|
||||
"ir.action.act_window.domain": {
|
||||
"added": [],
|
||||
@@ -214,25 +212,21 @@
|
||||
"category": "geändert",
|
||||
"changed": {},
|
||||
"removed": [
|
||||
"action",
|
||||
"email",
|
||||
"pyson_email"
|
||||
]
|
||||
},
|
||||
"ir.action.url": {
|
||||
"added": [],
|
||||
"category": "geändert",
|
||||
"category": "unverändert",
|
||||
"changed": {},
|
||||
"removed": [
|
||||
"action"
|
||||
]
|
||||
"removed": []
|
||||
},
|
||||
"ir.action.wizard": {
|
||||
"added": [],
|
||||
"category": "geändert",
|
||||
"changed": {},
|
||||
"removed": [
|
||||
"action",
|
||||
"email"
|
||||
]
|
||||
},
|
||||
@@ -736,10 +730,16 @@
|
||||
]
|
||||
},
|
||||
"ir.sequence.strict": {
|
||||
"added": [],
|
||||
"category": "unverändert",
|
||||
"added": [
|
||||
"preview",
|
||||
"sequence_type"
|
||||
],
|
||||
"category": "geändert",
|
||||
"changed": {},
|
||||
"removed": []
|
||||
"removed": [
|
||||
"code",
|
||||
"groups"
|
||||
]
|
||||
},
|
||||
"ir.sequence.type": {
|
||||
"added": [],
|
||||
@@ -1429,10 +1429,20 @@
|
||||
]
|
||||
},
|
||||
"production.bom.output": {
|
||||
"added": [],
|
||||
"category": "unverändert",
|
||||
"changed": {},
|
||||
"removed": []
|
||||
"added": [
|
||||
"phantom_bom",
|
||||
"unit"
|
||||
],
|
||||
"category": "geändert",
|
||||
"changed": {
|
||||
"product": [
|
||||
"required: True -> None"
|
||||
]
|
||||
},
|
||||
"removed": [
|
||||
"unit_digits",
|
||||
"uom"
|
||||
]
|
||||
},
|
||||
"production.bom.tree": {
|
||||
"added": [
|
||||
@@ -1724,18 +1734,12 @@
|
||||
},
|
||||
"stock.lot": {
|
||||
"added": [
|
||||
"default_uom"
|
||||
"default_uom",
|
||||
"has_sequence"
|
||||
],
|
||||
"category": "geändert",
|
||||
"category": "nur ergänzt",
|
||||
"changed": {},
|
||||
"removed": [
|
||||
"expiration_date",
|
||||
"expiration_state",
|
||||
"number",
|
||||
"product",
|
||||
"shelf_life_expiration_date",
|
||||
"shelf_life_expiration_state"
|
||||
]
|
||||
"removed": []
|
||||
},
|
||||
"stock.lot.trace": {
|
||||
"added": [],
|
||||
@@ -1820,7 +1824,11 @@
|
||||
},
|
||||
"stock.package": {
|
||||
"added": [
|
||||
"additional_weight_uom"
|
||||
"additional_weight_uom",
|
||||
"internal_volume",
|
||||
"internal_weight",
|
||||
"volume_uom",
|
||||
"weight_uom"
|
||||
],
|
||||
"category": "nur ergänzt",
|
||||
"changed": {},
|
||||
@@ -2015,7 +2023,14 @@
|
||||
"removed": []
|
||||
},
|
||||
"stock.shipment.in": {
|
||||
"added": [],
|
||||
"added": [
|
||||
"delay",
|
||||
"internal_volume",
|
||||
"internal_weight",
|
||||
"origin_planned_date",
|
||||
"volume_uom",
|
||||
"weight_uom"
|
||||
],
|
||||
"category": "geändert",
|
||||
"changed": {
|
||||
"warehouse_input": [
|
||||
@@ -2029,12 +2044,7 @@
|
||||
"required: None -> True"
|
||||
]
|
||||
},
|
||||
"removed": [
|
||||
"effective_date",
|
||||
"number",
|
||||
"planned_date",
|
||||
"reference"
|
||||
]
|
||||
"removed": []
|
||||
},
|
||||
"stock.shipment.in.restocking_list": {
|
||||
"added": [],
|
||||
@@ -2044,16 +2054,20 @@
|
||||
},
|
||||
"stock.shipment.in.return": {
|
||||
"added": [
|
||||
"warehouse"
|
||||
"delay",
|
||||
"internal_volume",
|
||||
"internal_weight",
|
||||
"origin_planned_date",
|
||||
"packages_volume",
|
||||
"packages_weight",
|
||||
"partially_assigned",
|
||||
"volume_uom",
|
||||
"warehouse",
|
||||
"weight_uom"
|
||||
],
|
||||
"category": "geändert",
|
||||
"category": "nur ergänzt",
|
||||
"changed": {},
|
||||
"removed": [
|
||||
"effective_date",
|
||||
"number",
|
||||
"planned_date",
|
||||
"reference"
|
||||
]
|
||||
"removed": []
|
||||
},
|
||||
"stock.shipment.in.return.assign": {
|
||||
"added": [],
|
||||
@@ -2069,18 +2083,22 @@
|
||||
},
|
||||
"stock.shipment.internal": {
|
||||
"added": [
|
||||
"delay",
|
||||
"internal_transit_location",
|
||||
"internal_volume",
|
||||
"internal_weight",
|
||||
"origin_planned_date",
|
||||
"partially_assigned",
|
||||
"to_warehouse",
|
||||
"warehouse"
|
||||
"volume",
|
||||
"volume_uom",
|
||||
"warehouse",
|
||||
"weight",
|
||||
"weight_uom"
|
||||
],
|
||||
"category": "geändert",
|
||||
"category": "nur ergänzt",
|
||||
"changed": {},
|
||||
"removed": [
|
||||
"effective_date",
|
||||
"number",
|
||||
"planned_date",
|
||||
"reference"
|
||||
]
|
||||
"removed": []
|
||||
},
|
||||
"stock.shipment.internal.assign": {
|
||||
"added": [],
|
||||
@@ -2101,7 +2119,17 @@
|
||||
"removed": []
|
||||
},
|
||||
"stock.shipment.out": {
|
||||
"added": [],
|
||||
"added": [
|
||||
"delay",
|
||||
"internal_volume",
|
||||
"internal_weight",
|
||||
"origin_planned_date",
|
||||
"packages_volume",
|
||||
"packages_weight",
|
||||
"partially_assigned",
|
||||
"volume_uom",
|
||||
"weight_uom"
|
||||
],
|
||||
"category": "geändert",
|
||||
"changed": {
|
||||
"warehouse_output": [
|
||||
@@ -2115,12 +2143,7 @@
|
||||
"required: None -> True"
|
||||
]
|
||||
},
|
||||
"removed": [
|
||||
"effective_date",
|
||||
"number",
|
||||
"planned_date",
|
||||
"reference"
|
||||
]
|
||||
"removed": []
|
||||
},
|
||||
"stock.shipment.out.assign": {
|
||||
"added": [],
|
||||
@@ -2148,7 +2171,13 @@
|
||||
},
|
||||
"stock.shipment.out.return": {
|
||||
"added": [
|
||||
"contact_address"
|
||||
"contact_address",
|
||||
"delay",
|
||||
"internal_volume",
|
||||
"internal_weight",
|
||||
"origin_planned_date",
|
||||
"volume_uom",
|
||||
"weight_uom"
|
||||
],
|
||||
"category": "geändert",
|
||||
"changed": {
|
||||
@@ -2164,11 +2193,7 @@
|
||||
]
|
||||
},
|
||||
"removed": [
|
||||
"delivery_address",
|
||||
"effective_date",
|
||||
"number",
|
||||
"planned_date",
|
||||
"reference"
|
||||
"delivery_address"
|
||||
]
|
||||
},
|
||||
"stock.shipment.out.return.restocking_list": {
|
||||
|
||||
+22
-27
@@ -16,7 +16,7 @@
|
||||
- `stock.shipment.out.assign`
|
||||
- `stock.shipment.out.assign.failed`
|
||||
|
||||
## geändert (57) - Feld(er) entfernt oder geändert - Kandidat für einen echten Vergleichslauf
|
||||
## geändert (55) - Feld(er) entfernt oder geändert - Kandidat für einen echten Vergleichslauf
|
||||
|
||||
|
||||
### `company.company`
|
||||
@@ -40,7 +40,6 @@
|
||||
- (zusätzlich neue Felder: action, records)
|
||||
|
||||
### `ir.action.act_window`
|
||||
- entfernte Felder: action
|
||||
- **domains**: type: Function[Binary] -> Function[Field]
|
||||
- **views**: type: Function[Binary] -> Function[Field]
|
||||
|
||||
@@ -57,14 +56,11 @@
|
||||
- **groups**: type: Function[One2Many] -> Function[Many2Many]
|
||||
|
||||
### `ir.action.report`
|
||||
- entfernte Felder: action, email, pyson_email
|
||||
- entfernte Felder: email, pyson_email
|
||||
- (zusätzlich neue Felder: record_name, report_content_html)
|
||||
|
||||
### `ir.action.url`
|
||||
- entfernte Felder: action
|
||||
|
||||
### `ir.action.wizard`
|
||||
- entfernte Felder: action, email
|
||||
- entfernte Felder: email
|
||||
|
||||
### `ir.cron`
|
||||
- entfernte Felder: args, function, model, name, number_calls, repeat_missed, request_user, user
|
||||
@@ -113,6 +109,10 @@
|
||||
- entfernte Felder: code, groups
|
||||
- (zusätzlich neue Felder: preview, sequence_type)
|
||||
|
||||
### `ir.sequence.strict`
|
||||
- entfernte Felder: code, groups
|
||||
- (zusätzlich neue Felder: preview, sequence_type)
|
||||
|
||||
### `ir.sequence.type`
|
||||
- entfernte Felder: code
|
||||
|
||||
@@ -177,6 +177,11 @@
|
||||
- **product**: required: True -> None
|
||||
- (zusätzlich neue Felder: phantom_bom, unit)
|
||||
|
||||
### `production.bom.output`
|
||||
- entfernte Felder: unit_digits, uom
|
||||
- **product**: required: True -> None
|
||||
- (zusätzlich neue Felder: phantom_bom, unit)
|
||||
|
||||
### `production.bom.tree`
|
||||
- entfernte Felder: unit_digits, uom
|
||||
- (zusätzlich neue Felder: unit)
|
||||
@@ -212,10 +217,6 @@
|
||||
- entfernte Felder: unit_digits, uom
|
||||
- (zusätzlich neue Felder: inventory_date, inventory_location, unit)
|
||||
|
||||
### `stock.lot`
|
||||
- entfernte Felder: expiration_date, expiration_state, number, product, shelf_life_expiration_date, shelf_life_expiration_state
|
||||
- (zusätzlich neue Felder: default_uom)
|
||||
|
||||
### `stock.move`
|
||||
- entfernte Felder: unit_digits, uom
|
||||
- (zusätzlich neue Felder: cost_price_required, delay, from_location_name, origin_planned_date, outcome_moves, product_cost_price, production, production_cost_price_updated, to_location_name, unit, unit_price_company, unit_price_updated)
|
||||
@@ -229,28 +230,20 @@
|
||||
- (zusätzlich neue Felder: template)
|
||||
|
||||
### `stock.shipment.in`
|
||||
- entfernte Felder: effective_date, number, planned_date, reference
|
||||
- **warehouse_input**: type: Function[Many2One] -> Many2One; relation: None -> stock.location; required: None -> True
|
||||
- **warehouse_storage**: type: Function[Many2One] -> Many2One; relation: None -> stock.location; required: None -> True
|
||||
|
||||
### `stock.shipment.in.return`
|
||||
- entfernte Felder: effective_date, number, planned_date, reference
|
||||
- (zusätzlich neue Felder: warehouse)
|
||||
|
||||
### `stock.shipment.internal`
|
||||
- entfernte Felder: effective_date, number, planned_date, reference
|
||||
- (zusätzlich neue Felder: internal_transit_location, to_warehouse, warehouse)
|
||||
- (zusätzlich neue Felder: delay, internal_volume, internal_weight, origin_planned_date, volume_uom, weight_uom)
|
||||
|
||||
### `stock.shipment.out`
|
||||
- entfernte Felder: effective_date, number, planned_date, reference
|
||||
- **warehouse_output**: type: Function[Many2One] -> Many2One; relation: None -> stock.location; required: None -> True
|
||||
- **warehouse_storage**: type: Function[Many2One] -> Many2One; relation: None -> stock.location; required: None -> True
|
||||
- (zusätzlich neue Felder: delay, internal_volume, internal_weight, origin_planned_date, packages_volume, packages_weight, partially_assigned, volume_uom, weight_uom)
|
||||
|
||||
### `stock.shipment.out.return`
|
||||
- entfernte Felder: delivery_address, effective_date, number, planned_date, reference
|
||||
- entfernte Felder: delivery_address
|
||||
- **warehouse_input**: type: Function[Many2One] -> Many2One; relation: None -> stock.location; required: None -> True
|
||||
- **warehouse_storage**: type: Function[Many2One] -> Many2One; relation: None -> stock.location; required: None -> True
|
||||
- (zusätzlich neue Felder: contact_address)
|
||||
- (zusätzlich neue Felder: contact_address, delay, internal_volume, internal_weight, origin_planned_date, volume_uom, weight_uom)
|
||||
|
||||
## neu (76) - Modell gibt es erst ab 8.0
|
||||
|
||||
@@ -331,7 +324,7 @@
|
||||
- `stock.shipment.assign.partial`
|
||||
- `stock.shipment.drop`
|
||||
|
||||
## nur ergänzt (24) - nur neue Felder dazugekommen, Rest unverändert
|
||||
## nur ergänzt (27) - nur neue Felder dazugekommen, Rest unverändert
|
||||
|
||||
- `company.employee`
|
||||
- `country.country`
|
||||
@@ -353,16 +346,20 @@
|
||||
- `res.user.login.attempt`
|
||||
- `stock.configuration`
|
||||
- `stock.location`
|
||||
- `stock.lot`
|
||||
- `stock.package`
|
||||
- `stock.product_quantities_warehouse`
|
||||
- `stock.product_quantities_warehouse.context`
|
||||
- `stock.products_by_locations.context`
|
||||
- `stock.shipment.in.return`
|
||||
- `stock.shipment.internal`
|
||||
|
||||
## unverändert (101) - komplett identisch geblieben
|
||||
## unverändert (100) - komplett identisch geblieben
|
||||
|
||||
- `company.company.config`
|
||||
- `company.company.config.start`
|
||||
- `ir.action-res.group`
|
||||
- `ir.action.url`
|
||||
- `ir.attachment`
|
||||
- `ir.cache`
|
||||
- `ir.cron-company.company`
|
||||
@@ -390,7 +387,6 @@
|
||||
- `ir.queue`
|
||||
- `ir.rule`
|
||||
- `ir.rule.group-res.group`
|
||||
- `ir.sequence.strict`
|
||||
- `ir.sequence.type-res.group`
|
||||
- `ir.session.wizard`
|
||||
- `ir.translation.clean`
|
||||
@@ -431,7 +427,6 @@
|
||||
- `product.template-product.category.all`
|
||||
- `product.uom`
|
||||
- `product.uom.category`
|
||||
- `production.bom.output`
|
||||
- `production.bom.tree.open`
|
||||
- `production.bom.tree.open.tree`
|
||||
- `production.configuration.production_sequence`
|
||||
|
||||
+246
-4
@@ -2400,8 +2400,121 @@
|
||||
"persisted": true
|
||||
},
|
||||
"ir.sequence.strict": {
|
||||
"fields": {},
|
||||
"persisted": false
|
||||
"fields": {
|
||||
"code": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Selection"
|
||||
},
|
||||
"company": {
|
||||
"file": "company/company.py",
|
||||
"module": "company",
|
||||
"relation": "company.company",
|
||||
"required": null,
|
||||
"settable": true,
|
||||
"type": "Many2One"
|
||||
},
|
||||
"groups": {
|
||||
"file": "res/ir.py",
|
||||
"module": "res",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Many2Many]"
|
||||
},
|
||||
"last_timestamp": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": true,
|
||||
"type": "Integer"
|
||||
},
|
||||
"name": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Char"
|
||||
},
|
||||
"number_increment": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": true,
|
||||
"type": "Integer"
|
||||
},
|
||||
"number_next": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function"
|
||||
},
|
||||
"number_next_internal": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": true,
|
||||
"type": "Integer"
|
||||
},
|
||||
"padding": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": true,
|
||||
"type": "Integer"
|
||||
},
|
||||
"prefix": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": true,
|
||||
"type": "Char"
|
||||
},
|
||||
"suffix": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": true,
|
||||
"type": "Char"
|
||||
},
|
||||
"timestamp_offset": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Float"
|
||||
},
|
||||
"timestamp_rounding": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Float"
|
||||
},
|
||||
"type": {
|
||||
"file": "ir/sequence.py",
|
||||
"module": "ir",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": true,
|
||||
"type": "Selection"
|
||||
}
|
||||
},
|
||||
"persisted": true
|
||||
},
|
||||
"ir.sequence.type": {
|
||||
"fields": {
|
||||
@@ -4721,8 +4834,57 @@
|
||||
"persisted": true
|
||||
},
|
||||
"production.bom.output": {
|
||||
"fields": {},
|
||||
"persisted": false
|
||||
"fields": {
|
||||
"bom": {
|
||||
"file": "production/bom.py",
|
||||
"module": "production",
|
||||
"relation": "production.bom",
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Many2One"
|
||||
},
|
||||
"product": {
|
||||
"file": "production/bom.py",
|
||||
"module": "production",
|
||||
"relation": "product.product",
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Many2One"
|
||||
},
|
||||
"quantity": {
|
||||
"file": "production/bom.py",
|
||||
"module": "production",
|
||||
"relation": null,
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Float"
|
||||
},
|
||||
"unit_digits": {
|
||||
"file": "production/bom.py",
|
||||
"module": "production",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Integer]"
|
||||
},
|
||||
"uom": {
|
||||
"file": "production/bom.py",
|
||||
"module": "production",
|
||||
"relation": "product.uom",
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Many2One"
|
||||
},
|
||||
"uom_category": {
|
||||
"file": "production/bom.py",
|
||||
"module": "production",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Many2One]"
|
||||
}
|
||||
},
|
||||
"persisted": true
|
||||
},
|
||||
"production.bom.tree": {
|
||||
"fields": {
|
||||
@@ -6269,6 +6431,22 @@
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
},
|
||||
"volume": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
},
|
||||
"weight": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
}
|
||||
},
|
||||
"persisted": false
|
||||
@@ -6582,6 +6760,14 @@
|
||||
"settable": false,
|
||||
"type": "Function[Many2One]"
|
||||
},
|
||||
"volume": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
},
|
||||
"warehouse": {
|
||||
"file": "stock/shipment.py",
|
||||
"module": "stock",
|
||||
@@ -6605,6 +6791,14 @@
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Many2One]"
|
||||
},
|
||||
"weight": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
}
|
||||
},
|
||||
"persisted": true
|
||||
@@ -6710,6 +6904,22 @@
|
||||
"required": true,
|
||||
"settable": true,
|
||||
"type": "Many2One"
|
||||
},
|
||||
"volume": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
},
|
||||
"weight": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
}
|
||||
},
|
||||
"persisted": true
|
||||
@@ -6975,6 +7185,14 @@
|
||||
"settable": true,
|
||||
"type": "Selection"
|
||||
},
|
||||
"volume": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
},
|
||||
"warehouse": {
|
||||
"file": "stock/shipment.py",
|
||||
"module": "stock",
|
||||
@@ -6998,6 +7216,14 @@
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Many2One]"
|
||||
},
|
||||
"weight": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
}
|
||||
},
|
||||
"persisted": true
|
||||
@@ -7133,6 +7359,14 @@
|
||||
"settable": true,
|
||||
"type": "Selection"
|
||||
},
|
||||
"volume": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
},
|
||||
"warehouse": {
|
||||
"file": "stock/shipment.py",
|
||||
"module": "stock",
|
||||
@@ -7156,6 +7390,14 @@
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Many2One]"
|
||||
},
|
||||
"weight": {
|
||||
"file": "stock_shipment_measurements/stock.py",
|
||||
"module": "stock_shipment_measurements",
|
||||
"relation": null,
|
||||
"required": null,
|
||||
"settable": false,
|
||||
"type": "Function[Float]"
|
||||
}
|
||||
},
|
||||
"persisted": true
|
||||
|
||||
@@ -409,6 +409,23 @@
|
||||
- **timestamp_rounding**: Float required
|
||||
- **type**: Selection
|
||||
|
||||
## `ir.sequence.strict` _(Felder aus: company, ir, res)_
|
||||
|
||||
- **code**: Selection required
|
||||
- **company**: Many2One -> `company.company` (aus Modul `company`)
|
||||
- **groups**: Function[Many2Many] NICHT setzbar (Function ohne setter) (aus Modul `res`)
|
||||
- **last_timestamp**: Integer
|
||||
- **name**: Char required
|
||||
- **number_increment**: Integer
|
||||
- **number_next**: Function NICHT setzbar (Function ohne setter)
|
||||
- **number_next_internal**: Integer
|
||||
- **padding**: Integer
|
||||
- **prefix**: Char
|
||||
- **suffix**: Char
|
||||
- **timestamp_offset**: Float required
|
||||
- **timestamp_rounding**: Float required
|
||||
- **type**: Selection
|
||||
|
||||
## `ir.sequence.type` _(Felder aus: ir, res)_
|
||||
|
||||
- **code**: Char required
|
||||
@@ -805,6 +822,15 @@
|
||||
- **uom**: Many2One -> `product.uom` required
|
||||
- **uom_category**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `production.bom.output` _(Felder aus: production)_
|
||||
|
||||
- **bom**: Many2One -> `production.bom` required
|
||||
- **product**: Many2One -> `product.product` required
|
||||
- **quantity**: Float required
|
||||
- **unit_digits**: Function[Integer] NICHT setzbar (Function ohne setter)
|
||||
- **uom**: Many2One -> `product.uom` required
|
||||
- **uom_category**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `production.bom.tree` _(Wizard/Transient - nicht persistent)_ _(Felder aus: production)_
|
||||
|
||||
- **childs**: One2Many -> `production.bom.tree`
|
||||
@@ -1062,6 +1088,8 @@
|
||||
- **additional_weight**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **total_volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **total_weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.period` _(Felder aus: stock, stock_lot)_
|
||||
|
||||
@@ -1105,7 +1133,7 @@
|
||||
- **forecast_date**: Date
|
||||
- **stock_date_end**: Function[Date] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `stock.shipment.in` _(Felder aus: stock)_
|
||||
## `stock.shipment.in` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **contact_address**: Many2One -> `party.address`
|
||||
@@ -1120,11 +1148,13 @@
|
||||
- **state**: Selection
|
||||
- **supplier**: Many2One -> `party.party` required
|
||||
- **supplier_location**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **warehouse**: Many2One -> `stock.location` required
|
||||
- **warehouse_input**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **warehouse_storage**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.in.return` _(Felder aus: stock)_
|
||||
## `stock.shipment.in.return` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **delivery_address**: Many2One -> `party.address`
|
||||
@@ -1138,6 +1168,8 @@
|
||||
- **state**: Selection
|
||||
- **supplier**: Many2One -> `party.party` required
|
||||
- **to_location**: Many2One -> `stock.location` required
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.in.return.assign.failed` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
|
||||
@@ -1164,7 +1196,7 @@
|
||||
|
||||
- **moves**: Many2Many -> `stock.move`
|
||||
|
||||
## `stock.shipment.out` _(Felder aus: stock)_
|
||||
## `stock.shipment.out` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **customer**: Many2One -> `party.party` required
|
||||
@@ -1179,15 +1211,17 @@
|
||||
- **planned_date**: Date
|
||||
- **reference**: Char
|
||||
- **state**: Selection
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **warehouse**: Many2One -> `stock.location` required
|
||||
- **warehouse_output**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **warehouse_storage**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.out.assign.failed` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
|
||||
- **inventory_moves**: Many2Many -> `stock.move`
|
||||
|
||||
## `stock.shipment.out.return` _(Felder aus: stock)_
|
||||
## `stock.shipment.out.return` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **customer**: Many2One -> `party.party` required
|
||||
@@ -1202,9 +1236,11 @@
|
||||
- **planned_date**: Date
|
||||
- **reference**: Char
|
||||
- **state**: Selection
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **warehouse**: Many2One -> `stock.location` required
|
||||
- **warehouse_input**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **warehouse_storage**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.split.start` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock_split)_
|
||||
|
||||
|
||||
+1647
-21
File diff suppressed because it is too large
Load Diff
+242
-10
@@ -141,6 +141,7 @@
|
||||
|
||||
- **act_window_domains**: One2Many -> `ir.action.act_window.domain`
|
||||
- **act_window_views**: One2Many -> `ir.action.act_window.view`
|
||||
- **action**: Many2One -> `ir.action` required
|
||||
- **context**: Char
|
||||
- **context_domain**: Char
|
||||
- **context_model**: Char
|
||||
@@ -178,6 +179,7 @@
|
||||
|
||||
## `ir.action.report` _(Felder aus: ir)_
|
||||
|
||||
- **action**: Many2One -> `ir.action` required
|
||||
- **direct_print**: Boolean
|
||||
- **extension**: Selection
|
||||
- **is_custom**: Function[Boolean] NICHT setzbar (Function ohne setter)
|
||||
@@ -196,10 +198,12 @@
|
||||
|
||||
## `ir.action.url` _(Felder aus: ir)_
|
||||
|
||||
- **action**: Many2One -> `ir.action` required
|
||||
- **url**: Char required
|
||||
|
||||
## `ir.action.wizard` _(Felder aus: ir)_
|
||||
|
||||
- **action**: Many2One -> `ir.action` required
|
||||
- **model**: Char
|
||||
- **window**: Boolean
|
||||
- **wiz_name**: Char required
|
||||
@@ -218,11 +222,15 @@
|
||||
## `ir.avatar` _(Felder aus: ir)_
|
||||
|
||||
- **cache**: One2Many -> `ir.avatar.cache`
|
||||
- **image**: Binary
|
||||
- **image_id**: Char
|
||||
- **uuid**: Char required
|
||||
|
||||
## `ir.avatar.cache` _(Felder aus: ir)_
|
||||
|
||||
- **avatar**: Many2One -> `ir.avatar` required
|
||||
- **image**: Binary
|
||||
- **image_id**: Char
|
||||
- **size**: Integer required
|
||||
|
||||
## `ir.cache` _(Felder aus: ir)_
|
||||
@@ -230,6 +238,18 @@
|
||||
- **name**: Char required
|
||||
- **timestamp**: Timestamp
|
||||
|
||||
## `ir.calendar.day` _(Felder aus: ir)_
|
||||
|
||||
- **abbreviation**: Char required
|
||||
- **index**: Integer required
|
||||
- **name**: Char required
|
||||
|
||||
## `ir.calendar.month` _(Felder aus: ir)_
|
||||
|
||||
- **abbreviation**: Char required
|
||||
- **index**: Integer required
|
||||
- **name**: Char required
|
||||
|
||||
## `ir.chat.channel` _(Felder aus: ir)_
|
||||
|
||||
- **followers**: One2Many -> `ir.chat.follower`
|
||||
@@ -238,15 +258,21 @@
|
||||
|
||||
## `ir.chat.follower` _(Felder aus: ir)_
|
||||
|
||||
- **author**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **channel**: Many2One -> `ir.chat.channel` required
|
||||
- **email**: Char
|
||||
- **user**: Many2One -> `res.user`
|
||||
|
||||
## `ir.chat.message` _(Felder aus: ir)_
|
||||
|
||||
- **audience**: Selection required
|
||||
- **author**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **channel**: Many2One -> `ir.chat.channel` required
|
||||
- **content**: Text required
|
||||
- **email**: Char
|
||||
- **reference**: Char
|
||||
- **summary**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **user**: Many2One -> `res.user`
|
||||
|
||||
## `ir.configuration` _(Felder aus: currency, ir, product)_
|
||||
|
||||
@@ -348,6 +374,11 @@
|
||||
- **export**: Many2One -> `ir.export` required
|
||||
- **group**: Many2One -> `res.group` required
|
||||
|
||||
## `ir.export-write-res.group` _(Felder aus: ir)_
|
||||
|
||||
- **export**: Many2One -> `ir.export` required
|
||||
- **group**: Many2One -> `res.group` required
|
||||
|
||||
## `ir.export.line` _(Felder aus: ir)_
|
||||
|
||||
- **export**: Many2One -> `ir.export` required
|
||||
@@ -588,6 +619,23 @@
|
||||
- **timestamp_rounding**: Float required
|
||||
- **type**: Selection
|
||||
|
||||
## `ir.sequence.strict` _(Felder aus: company, ir)_
|
||||
|
||||
- **company**: Many2One -> `company.company` (aus Modul `company`)
|
||||
- **last_timestamp**: Integer
|
||||
- **name**: Char required
|
||||
- **number_increment**: Integer
|
||||
- **number_next**: Function NICHT setzbar (Function ohne setter)
|
||||
- **number_next_internal**: Integer
|
||||
- **padding**: Integer
|
||||
- **prefix**: Char
|
||||
- **preview**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **sequence_type**: Many2One -> `ir.sequence.type` required
|
||||
- **suffix**: Char
|
||||
- **timestamp_offset**: Float required
|
||||
- **timestamp_rounding**: Float required
|
||||
- **type**: Selection
|
||||
|
||||
## `ir.sequence.type` _(Felder aus: ir)_
|
||||
|
||||
- **groups**: Many2Many -> `ir.sequence.type-res.group`
|
||||
@@ -923,6 +971,8 @@
|
||||
## `product.cost_price.revision` _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required (aus Modul `stock`)
|
||||
- **cost_price**: Char required (aus Modul `stock`)
|
||||
- **date**: Date required (aus Modul `stock`)
|
||||
- **product**: Many2One -> `product.product` (aus Modul `stock`)
|
||||
- **template**: Many2One -> `product.template` required (aus Modul `stock`)
|
||||
|
||||
@@ -1100,6 +1150,15 @@
|
||||
- **unit**: Many2One -> `product.uom` required
|
||||
- **uom_category**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `production.bom.output` _(Felder aus: production)_
|
||||
|
||||
- **bom**: Many2One -> `production.bom` required
|
||||
- **phantom_bom**: Many2One -> `production.bom`
|
||||
- **product**: Many2One -> `product.product`
|
||||
- **quantity**: Float required
|
||||
- **unit**: Many2One -> `product.uom` required
|
||||
- **uom_category**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `production.bom.tree` _(Wizard/Transient - nicht persistent)_ _(Felder aus: production)_
|
||||
|
||||
- **childs**: One2Many -> `production.bom.tree`
|
||||
@@ -1339,11 +1398,18 @@
|
||||
- **location**: Many2One -> `stock.location` required
|
||||
- **warehouse**: Many2One -> `stock.location` required
|
||||
|
||||
## `stock.lot` _(Felder aus: stock_lot)_
|
||||
## `stock.lot` _(Felder aus: stock_lot, stock_lot_sled)_
|
||||
|
||||
- **default_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot`)
|
||||
- **expiration_date**: Date (aus Modul `stock_lot_sled`)
|
||||
- **expiration_state**: Function[Selection] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot_sled`)
|
||||
- **forecast_quantity**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot`)
|
||||
- **has_sequence**: Function[Boolean] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot`)
|
||||
- **number**: Char required (aus Modul `stock_lot`)
|
||||
- **product**: Many2One -> `product.product` required (aus Modul `stock_lot`)
|
||||
- **quantity**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot`)
|
||||
- **shelf_life_expiration_date**: Date (aus Modul `stock_lot_sled`)
|
||||
- **shelf_life_expiration_state**: Function[Selection] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot_sled`)
|
||||
|
||||
## `stock.lot.trace` _(Felder aus: production, stock_lot)_
|
||||
|
||||
@@ -1420,11 +1486,18 @@
|
||||
- **quantity_remaining**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot`)
|
||||
- **unit**: Many2One -> `product.uom` (aus Modul `stock_lot`)
|
||||
|
||||
## `stock.move.add.lots.start.lot` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock_lot)_
|
||||
## `stock.move.add.lots.start.lot` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock_lot, stock_lot_sled)_
|
||||
|
||||
- **expiration_date**: Date (aus Modul `stock_lot_sled`)
|
||||
- **expiration_state**: Function[Selection] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot_sled`)
|
||||
- **has_sequence**: Function[Boolean] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot`)
|
||||
- **number**: Char required (aus Modul `stock_lot`)
|
||||
- **parent**: Many2One -> `stock.move.add.lots.start` (aus Modul `stock_lot`)
|
||||
- **product**: Many2One -> `product.product` required (aus Modul `stock_lot`)
|
||||
- **quantity**: Float required (aus Modul `stock_lot`)
|
||||
- **quantity_unit**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot`)
|
||||
- **shelf_life_expiration_date**: Date (aus Modul `stock_lot_sled`)
|
||||
- **shelf_life_expiration_state**: Function[Selection] NICHT setzbar (Function ohne setter) (aus Modul `stock_lot_sled`)
|
||||
|
||||
## `stock.move.split.start` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock_split)_
|
||||
|
||||
@@ -1437,8 +1510,14 @@
|
||||
|
||||
- **additional_weight**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **additional_weight_uom**: Many2One -> `product.uom` (aus Modul `stock_shipment_measurements`)
|
||||
- **internal_volume**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **internal_weight**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **total_volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **total_weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **volume_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.period` _(Felder aus: stock, stock_lot)_
|
||||
|
||||
@@ -1510,22 +1589,58 @@
|
||||
- **forecast_date**: Date
|
||||
- **stock_date_end**: Function[Date] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `stock.reporting.inventory` _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **input_quantity**: Float
|
||||
- **output_quantity**: Float
|
||||
- **product**: Many2One -> `product.product`
|
||||
- **product_reference**: Reference -> `Product Reference`
|
||||
- **product_template**: Many2One -> `product.template`
|
||||
- **quantity**: Float
|
||||
- **unit**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `stock.reporting.inventory.context` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **date**: Date required
|
||||
- **location**: Many2One -> `stock.location` required
|
||||
- **product_type**: Selection required
|
||||
|
||||
## `stock.reporting.inventory.daily` _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **from_date**: Date
|
||||
- **input_quantity**: Float
|
||||
- **output_quantity**: Float
|
||||
- **product**: Many2One -> `product.product`
|
||||
- **product_reference**: Reference -> `Product Reference`
|
||||
- **product_template**: Many2One -> `product.template`
|
||||
- **quantity**: Float
|
||||
- **to_date**: Date
|
||||
- **unit**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `stock.reporting.inventory.move` _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **date**: Date
|
||||
- **document**: Function[Reference] NICHT setzbar (Function ohne setter)
|
||||
- **input_quantity**: Float
|
||||
- **move**: Many2One -> `stock.move`
|
||||
- **origin**: Reference -> `Origin`
|
||||
- **output_quantity**: Float
|
||||
- **product**: Many2One -> `product.product`
|
||||
- **product_reference**: Reference -> `Product Reference`
|
||||
- **product_template**: Many2One -> `product.template`
|
||||
- **quantity**: Float
|
||||
- **unit**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `stock.reporting.inventory.range.context` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **from_date**: Date required
|
||||
- **location**: Many2One -> `stock.location` required
|
||||
- **to_date**: Date required
|
||||
|
||||
## `stock.reporting.inventory.turnover` _(Felder aus: stock)_
|
||||
|
||||
@@ -1540,12 +1655,30 @@
|
||||
|
||||
## `stock.reporting.inventory.turnover.context` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **from_date**: Date required
|
||||
- **location**: Many2One -> `stock.location` required
|
||||
- **product_type**: Selection required
|
||||
- **to_date**: Date required
|
||||
|
||||
## `stock.reporting.margin.category` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
## `stock.reporting.margin.category` _(Felder aus: stock)_
|
||||
|
||||
- **category**: Many2One -> `product.category`
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **currency**: Many2One -> `currency.currency`
|
||||
- **margin**: Numeric
|
||||
- **margin_trend**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **time_series**: One2Many -> `stock.reporting.margin.category.time_series`
|
||||
|
||||
## `stock.reporting.margin.category.time_series` _(Felder aus: stock)_
|
||||
|
||||
- **category**: Many2One -> `product.category`
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **currency**: Many2One -> `currency.currency`
|
||||
- **date**: Date
|
||||
- **margin**: Numeric
|
||||
- **margin_trend**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `stock.reporting.margin.category.tree` _(Felder aus: stock)_
|
||||
|
||||
- **children**: One2Many -> `stock.reporting.margin.category.tree`
|
||||
@@ -1565,90 +1698,189 @@
|
||||
- **period**: Selection required
|
||||
- **to_date**: Date
|
||||
|
||||
## `stock.reporting.margin.main` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
## `stock.reporting.margin.main` _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **currency**: Many2One -> `currency.currency`
|
||||
- **margin**: Numeric
|
||||
- **margin_trend**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **time_series**: Function[Many2Many] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `stock.reporting.margin.product` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
## `stock.reporting.margin.main.time_series` _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **currency**: Many2One -> `currency.currency`
|
||||
- **date**: Date
|
||||
- **margin**: Numeric
|
||||
- **margin_trend**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
|
||||
## `stock.reporting.margin.product` _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **currency**: Many2One -> `currency.currency`
|
||||
- **internal_quantity**: Float
|
||||
- **margin**: Numeric
|
||||
- **margin_trend**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **product**: Many2One -> `product.product`
|
||||
- **quantity**: Function[Float] NICHT setzbar (Function ohne setter)
|
||||
- **time_series**: One2Many -> `stock.reporting.margin.product.time_series`
|
||||
- **unit**: Many2One -> `product.uom`
|
||||
|
||||
## `stock.reporting.margin.product.time_series` _(Felder aus: stock)_
|
||||
|
||||
- **company**: Many2One -> `company.company`
|
||||
- **currency**: Many2One -> `currency.currency`
|
||||
- **date**: Date
|
||||
- **internal_quantity**: Float
|
||||
- **margin**: Numeric
|
||||
- **margin_trend**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **product**: Many2One -> `product.product`
|
||||
- **quantity**: Function[Float] NICHT setzbar (Function ohne setter)
|
||||
- **unit**: Many2One -> `product.uom`
|
||||
|
||||
## `stock.shipment.assign.partial` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock)_
|
||||
|
||||
- **moves**: Many2Many -> `stock.move`
|
||||
|
||||
## `stock.shipment.in` _(Felder aus: stock)_
|
||||
## `stock.shipment.in` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **contact_address**: Many2One -> `party.address`
|
||||
- **delay**: Function[TimeDelta] NICHT setzbar (Function ohne setter)
|
||||
- **effective_date**: Date
|
||||
- **incoming_moves**: Function[One2Many]
|
||||
- **internal_volume**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **internal_weight**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **inventory_moves**: Function[One2Many]
|
||||
- **moves**: One2Many -> `stock.move`
|
||||
- **number**: Char
|
||||
- **origin_planned_date**: Date
|
||||
- **origins**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **planned_date**: Date
|
||||
- **reference**: Char
|
||||
- **state**: Selection
|
||||
- **supplier**: Many2One -> `party.party` required
|
||||
- **supplier_location**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **volume_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **warehouse**: Many2One -> `stock.location` required
|
||||
- **warehouse_input**: Many2One -> `stock.location` required
|
||||
- **warehouse_storage**: Many2One -> `stock.location` required
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.in.return` _(Felder aus: stock)_
|
||||
## `stock.shipment.in.return` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **delay**: Function[TimeDelta] NICHT setzbar (Function ohne setter)
|
||||
- **delivery_address**: Many2One -> `party.address`
|
||||
- **effective_date**: Date
|
||||
- **from_location**: Many2One -> `stock.location` required
|
||||
- **internal_volume**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **internal_weight**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **moves**: One2Many -> `stock.move`
|
||||
- **number**: Char
|
||||
- **origin_planned_date**: Date
|
||||
- **origins**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **packages_volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **packages_weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **partially_assigned**: Function[Boolean] NICHT setzbar (Function ohne setter)
|
||||
- **planned_date**: Date
|
||||
- **reference**: Char
|
||||
- **state**: Selection
|
||||
- **supplier**: Many2One -> `party.party` required
|
||||
- **to_location**: Many2One -> `stock.location` required
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **volume_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **warehouse**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.internal` _(Felder aus: stock)_
|
||||
## `stock.shipment.internal` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **delay**: Function[TimeDelta] NICHT setzbar (Function ohne setter)
|
||||
- **effective_date**: Date
|
||||
- **effective_start_date**: Date
|
||||
- **from_location**: Many2One -> `stock.location` required
|
||||
- **incoming_moves**: Function[One2Many]
|
||||
- **internal_transit_location**: Many2One -> `stock.location`
|
||||
- **internal_volume**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **internal_weight**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **moves**: One2Many -> `stock.move`
|
||||
- **number**: Char
|
||||
- **origin_planned_date**: Date
|
||||
- **outgoing_moves**: Function[One2Many]
|
||||
- **partially_assigned**: Function[Boolean] NICHT setzbar (Function ohne setter)
|
||||
- **planned_date**: Date
|
||||
- **planned_start_date**: Date
|
||||
- **reference**: Char
|
||||
- **state**: Selection
|
||||
- **to_location**: Many2One -> `stock.location` required
|
||||
- **to_warehouse**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **transit_location**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **volume_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **warehouse**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.out` _(Felder aus: stock)_
|
||||
## `stock.shipment.out` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **customer**: Many2One -> `party.party` required
|
||||
- **customer_location**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **delay**: Function[TimeDelta] NICHT setzbar (Function ohne setter)
|
||||
- **delivery_address**: Many2One -> `party.address` required
|
||||
- **effective_date**: Date
|
||||
- **internal_volume**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **internal_weight**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **inventory_moves**: Function[One2Many]
|
||||
- **moves**: One2Many -> `stock.move`
|
||||
- **number**: Char
|
||||
- **origin_planned_date**: Date
|
||||
- **origins**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **outgoing_moves**: Function[One2Many]
|
||||
- **packages_volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **packages_weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **partially_assigned**: Function[Boolean] NICHT setzbar (Function ohne setter)
|
||||
- **planned_date**: Date
|
||||
- **reference**: Char
|
||||
- **state**: Selection
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **volume_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **warehouse**: Many2One -> `stock.location` required
|
||||
- **warehouse_output**: Many2One -> `stock.location` required
|
||||
- **warehouse_storage**: Many2One -> `stock.location` required
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.out.return` _(Felder aus: stock)_
|
||||
## `stock.shipment.out.return` _(Felder aus: stock, stock_shipment_measurements)_
|
||||
|
||||
- **company**: Many2One -> `company.company` required
|
||||
- **contact_address**: Many2One -> `party.address`
|
||||
- **customer**: Many2One -> `party.party` required
|
||||
- **customer_location**: Function[Many2One] NICHT setzbar (Function ohne setter)
|
||||
- **delay**: Function[TimeDelta] NICHT setzbar (Function ohne setter)
|
||||
- **effective_date**: Date
|
||||
- **incoming_moves**: Function[One2Many]
|
||||
- **internal_volume**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **internal_weight**: Float (aus Modul `stock_shipment_measurements`)
|
||||
- **inventory_moves**: Function[One2Many]
|
||||
- **moves**: One2Many -> `stock.move`
|
||||
- **number**: Char
|
||||
- **origin_planned_date**: Date
|
||||
- **origins**: Function[Char] NICHT setzbar (Function ohne setter)
|
||||
- **planned_date**: Date
|
||||
- **reference**: Char
|
||||
- **state**: Selection
|
||||
- **volume**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **volume_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **warehouse**: Many2One -> `stock.location` required
|
||||
- **warehouse_input**: Many2One -> `stock.location` required
|
||||
- **warehouse_storage**: Many2One -> `stock.location` required
|
||||
- **weight**: Function[Float] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
- **weight_uom**: Function[Many2One] NICHT setzbar (Function ohne setter) (aus Modul `stock_shipment_measurements`)
|
||||
|
||||
## `stock.shipment.split.start` _(Wizard/Transient - nicht persistent)_ _(Felder aus: stock_split)_
|
||||
|
||||
|
||||
@@ -96,10 +96,35 @@ def resolve_model_get(node):
|
||||
return node.args[0].value
|
||||
|
||||
|
||||
def resolve_find_call(node, model_vars):
|
||||
"""Erkennt `SomeModelVar.find([('feld', '=', 'wert'), ...])` bzw.
|
||||
`.search(...)` - das Standardmuster, mit dem Tryton-Tests auf bereits
|
||||
vorhandene Stammdaten zugreifen (Länder, Währungen, Einheiten,
|
||||
Lagerorte, ... - i. d. R. per XML-Fixture beim Modul-Aktivieren
|
||||
angelegt, nicht Teil des Testdatensatzes selbst). Liefert
|
||||
(model_name, domain) oder None, wenn das Domain-Argument nicht rein
|
||||
literal ist (z. B. eine Variable referenziert) oder das Muster nicht
|
||||
passt."""
|
||||
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Attribute):
|
||||
return None
|
||||
if node.func.attr not in ("find", "search"):
|
||||
return None
|
||||
if not isinstance(node.func.value, ast.Name) or node.func.value.id not in model_vars:
|
||||
return None
|
||||
if not node.args:
|
||||
return None
|
||||
try:
|
||||
domain = ast.literal_eval(node.args[0])
|
||||
except Exception:
|
||||
return None
|
||||
return model_vars[node.func.value.id], domain
|
||||
|
||||
|
||||
class ScenarioParser:
|
||||
def __init__(self):
|
||||
self.model_vars = {} # Variable -> Modellname (aus Model.get(...))
|
||||
self.record_vars = {} # Variable -> Record-Dict (key/model/fields)
|
||||
self.search_vars = {} # Variable -> {"model": ..., "domain": ...} (aus X.find(...)/.search(...))
|
||||
self.finalized = set() # Variablen, deren erster .save()-Aufruf schon verarbeitet wurde
|
||||
self.output = [] # fertige Record-Dicts in Reihenfolge
|
||||
self.manual_notes = [] # Zeilen, die nicht automatisch verstanden wurden
|
||||
@@ -111,6 +136,8 @@ class ScenarioParser:
|
||||
if isinstance(node, ast.Name):
|
||||
if node.id in self.record_vars:
|
||||
return {"$ref": self.record_vars[node.id]["key"]}
|
||||
if node.id in self.search_vars:
|
||||
return {"$search": self.search_vars[node.id]}
|
||||
return self._manual(node)
|
||||
|
||||
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
|
||||
@@ -163,6 +190,21 @@ class ScenarioParser:
|
||||
return
|
||||
target = node.targets[0]
|
||||
|
||||
# x, = SomeModelVar.find([('feld', '=', 'wert')]) (Zugriff auf
|
||||
# vorhandene Stammdaten statt Anlegen eines neuen Records)
|
||||
if (
|
||||
isinstance(target, ast.Tuple)
|
||||
and len(target.elts) == 1
|
||||
and isinstance(target.elts[0], ast.Name)
|
||||
):
|
||||
found = resolve_find_call(node.value, self.model_vars)
|
||||
if found:
|
||||
model_name, domain = found
|
||||
self.search_vars[target.elts[0].id] = {"model": model_name, "domain": domain}
|
||||
return
|
||||
self._manual(node)
|
||||
return
|
||||
|
||||
# X = Model.get('some.model')
|
||||
if isinstance(target, ast.Name):
|
||||
model_name = resolve_model_get(node.value)
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
sql_dump_diff.py
|
||||
|
||||
Vergleicht zwei mit `pg_dump` (Klartext-Format, --format=plain) erzeugte
|
||||
SQL-Dumps strukturell: welche Tabellen/Spalten gibt es in Version A, die
|
||||
es in B nicht (mehr) gibt (und umgekehrt), welche Spaltentypen haben sich
|
||||
geändert, und wie viele Zeilen stehen jeweils in einer gemeinsamen
|
||||
Tabelle. Gedacht für zwei frisch initialisierte (oder später: mit
|
||||
denselben Proteus-Testdaten befüllte) Datenbanken zweier Tryton-Versionen
|
||||
- als dritte, von der Code-Analyse (extract_updates.py/model_diff.py)
|
||||
unabhängige Quelle für "was hat sich am Schema wirklich geändert".
|
||||
|
||||
Mit --model-diff (Ausgabe von model_diff.py) wird zusätzlich pro
|
||||
betroffener Tabelle geprüft, ob die rein statische Code-Analyse dieselbe
|
||||
Änderung schon vorhergesagt hatte (Modellname -> Tabellenname: Punkte
|
||||
durch Unterstriche ersetzen, wie es extract_updates.py/
|
||||
compare_snapshots.py auch tun) - zeigt, wie gut beide Ansätze
|
||||
übereinstimmen, und macht Abweichungen sichtbar (z. B. Python-seitige
|
||||
Änderungen ohne SQL-Schemaauswirkung, oder Tabellenänderungen durch ein
|
||||
Modul, das nicht im --modules-Set von extract_model_tree.py steckte).
|
||||
|
||||
Zeilen werden bereits vollständig geparst (als dict pro Zeile, Spalte ->
|
||||
Wert) und im Rückgabewert von parse_dump() mitgeführt - für eine spätere
|
||||
inhaltliche Vergleichs-Erweiterung (z. B. per --key-column concrete Werte
|
||||
statt nur Zeilenzahlen gegenüberstellen). Der aktuelle Report nutzt davon
|
||||
bisher nur die Zeilenzahl.
|
||||
|
||||
Bekannte Grenzen: erkennt keine Indizes/Constraints/Sequences/
|
||||
Spaltenreihenfolge, nur Tabellen + Spalten(typen) + Zeilenzahl.
|
||||
|
||||
Nutzung:
|
||||
python3 sql_dump_diff.py --dump-a data/leer5.sql --label-a 5.0 \\
|
||||
--dump-b data/leer8.sql --label-b 8.0 \\
|
||||
--model-diff reports/model_diff.json \\
|
||||
--output reports/dump_diff_5.0_8.0.json \\
|
||||
--report reports/dump_diff_5.0_8.0.md
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
CREATE_TABLE_RE = re.compile(r'^CREATE TABLE public\.(\w+) \($')
|
||||
COPY_RE = re.compile(r'^COPY public\.(\w+) \(([^)]*)\) FROM stdin;$')
|
||||
|
||||
|
||||
def unescape_copy_value(raw):
|
||||
"""pg_dump-COPY-Escaping rückgängig machen (\\N -> NULL, \\t/\\n/\\\\ -> Zeichen)."""
|
||||
if raw == r'\N':
|
||||
return None
|
||||
out = []
|
||||
i = 0
|
||||
while i < len(raw):
|
||||
c = raw[i]
|
||||
if c == '\\' and i + 1 < len(raw):
|
||||
out.append({'t': '\t', 'n': '\n', 'r': '\r', '\\': '\\'}.get(raw[i + 1], raw[i + 1]))
|
||||
i += 2
|
||||
else:
|
||||
out.append(c)
|
||||
i += 1
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def parse_column_line(line):
|
||||
"""Eine Zeile aus dem CREATE-TABLE-Block -> (spaltenname, typ) oder (None, None)."""
|
||||
text = line.strip()
|
||||
if not text or text.startswith(('CONSTRAINT', 'PRIMARY KEY', 'UNIQUE')):
|
||||
return None, None
|
||||
text = text.rstrip(',')
|
||||
for stopper in (' NOT NULL', ' DEFAULT ', ' COLLATE '):
|
||||
idx = text.find(stopper)
|
||||
if idx != -1:
|
||||
text = text[:idx]
|
||||
parts = text.split(None, 1)
|
||||
if len(parts) != 2:
|
||||
return None, None
|
||||
name, coltype = parts
|
||||
return name.strip('"'), coltype.strip()
|
||||
|
||||
|
||||
def parse_dump(path):
|
||||
"""Liefert {tabelle: {"columns": {name: typ}, "row_count": int, "rows": [dict, ...]}}."""
|
||||
tables = {}
|
||||
with open(path, encoding='utf-8', errors='replace') as f:
|
||||
lines = iter(f)
|
||||
for line in lines:
|
||||
stripped = line.rstrip('\n')
|
||||
|
||||
m = CREATE_TABLE_RE.match(stripped)
|
||||
if m:
|
||||
table = m.group(1)
|
||||
columns = {}
|
||||
for col_line in lines:
|
||||
col_line = col_line.rstrip('\n')
|
||||
if col_line.startswith(');'):
|
||||
break
|
||||
name, coltype = parse_column_line(col_line)
|
||||
if name:
|
||||
columns[name] = coltype
|
||||
entry = tables.setdefault(table, {"columns": {}, "row_count": 0, "rows": []})
|
||||
entry["columns"] = columns
|
||||
continue
|
||||
|
||||
m = COPY_RE.match(stripped)
|
||||
if m:
|
||||
table, col_csv = m.group(1), m.group(2)
|
||||
col_names = [c.strip() for c in col_csv.split(',')]
|
||||
rows = []
|
||||
for data_line in lines:
|
||||
data_line = data_line.rstrip('\n')
|
||||
if data_line == '\\.':
|
||||
break
|
||||
values = [unescape_copy_value(v) for v in data_line.split('\t')]
|
||||
rows.append(dict(zip(col_names, values)))
|
||||
entry = tables.setdefault(table, {"columns": {}, "row_count": 0, "rows": []})
|
||||
entry["row_count"] = len(rows)
|
||||
entry["rows"] = rows
|
||||
|
||||
return tables
|
||||
|
||||
|
||||
def diff_dumps(tables_a, tables_b):
|
||||
tables_removed = sorted(set(tables_a) - set(tables_b))
|
||||
tables_added = sorted(set(tables_b) - set(tables_a))
|
||||
common = sorted(set(tables_a) & set(tables_b))
|
||||
|
||||
changed = {}
|
||||
unchanged = []
|
||||
for table in common:
|
||||
cols_a, cols_b = tables_a[table]["columns"], tables_b[table]["columns"]
|
||||
added_cols = sorted(set(cols_b) - set(cols_a))
|
||||
removed_cols = sorted(set(cols_a) - set(cols_b))
|
||||
type_changes = {
|
||||
c: [cols_a[c], cols_b[c]]
|
||||
for c in sorted(set(cols_a) & set(cols_b))
|
||||
if cols_a[c] != cols_b[c]
|
||||
}
|
||||
row_count_a, row_count_b = tables_a[table]["row_count"], tables_b[table]["row_count"]
|
||||
|
||||
schema_changed = bool(added_cols or removed_cols or type_changes)
|
||||
if schema_changed or row_count_a != row_count_b:
|
||||
if removed_cols or type_changes:
|
||||
expected_category = "geändert"
|
||||
elif added_cols:
|
||||
expected_category = "nur ergänzt"
|
||||
else:
|
||||
expected_category = None # nur Zeilenzahl anders - keine Code-Definitionsänderung
|
||||
changed[table] = {
|
||||
"added_columns": added_cols,
|
||||
"removed_columns": removed_cols,
|
||||
"type_changes": type_changes,
|
||||
"row_count_a": row_count_a,
|
||||
"row_count_b": row_count_b,
|
||||
"schema_changed": schema_changed,
|
||||
"expected_category": expected_category,
|
||||
}
|
||||
else:
|
||||
unchanged.append(table)
|
||||
|
||||
return {
|
||||
"tables_removed": tables_removed,
|
||||
"tables_added": tables_added,
|
||||
"tables_changed": changed,
|
||||
"tables_unchanged": unchanged,
|
||||
}
|
||||
|
||||
|
||||
def build_table_to_models(model_diff):
|
||||
mapping = {}
|
||||
for model_name in model_diff:
|
||||
mapping.setdefault(model_name.replace(".", "_"), []).append(model_name)
|
||||
return mapping
|
||||
|
||||
|
||||
def model_diff_note(table, model_diff, table_to_models, expected_category):
|
||||
"""Vergleicht einen Tabellen-Fund mit der Vorhersage aus model_diff.py.
|
||||
Liefert einen kurzen Text: Übereinstimmung, Abweichung, oder kein
|
||||
passendes Modell gefunden (z. B. reine Relations-/Interntabelle)."""
|
||||
if not model_diff:
|
||||
return ""
|
||||
models = table_to_models.get(table)
|
||||
if not models:
|
||||
return " _(kein passendes Modell in model_diff gefunden)_"
|
||||
parts = []
|
||||
for model in models:
|
||||
category = model_diff[model]["category"]
|
||||
flag = "übereinstimmung" if category == expected_category else "ABWEICHUNG"
|
||||
parts.append(f"`{model}`={category} [{flag}]")
|
||||
return " _(" + "; ".join(parts) + ")_"
|
||||
|
||||
|
||||
def write_report(diff, out_path, label_a, label_b, model_diff, table_to_models):
|
||||
lines = [f"# SQL-Dump-Diff: {label_a} -> {label_b}\n"]
|
||||
|
||||
lines.append(f"\n## Tabellen entfernt ({len(diff['tables_removed'])})\n")
|
||||
for t in diff["tables_removed"]:
|
||||
lines.append(f"- `{t}`{model_diff_note(t, model_diff, table_to_models, 'entfernt')}")
|
||||
|
||||
lines.append(f"\n\n## Tabellen neu ({len(diff['tables_added'])})\n")
|
||||
for t in diff["tables_added"]:
|
||||
lines.append(f"- `{t}`{model_diff_note(t, model_diff, table_to_models, 'neu')}")
|
||||
|
||||
schema_changed = {t: i for t, i in diff["tables_changed"].items() if i["schema_changed"]}
|
||||
rows_only = {t: i for t, i in diff["tables_changed"].items() if not i["schema_changed"]}
|
||||
|
||||
lines.append(f"\n\n## Tabellen mit Schema-Änderung ({len(schema_changed)})\n")
|
||||
lines.append(
|
||||
"Spalten wurden hinzugefügt/entfernt oder haben einen anderen Typ - "
|
||||
"vergleichbar mit einer model_diff.py-Kategorie.\n"
|
||||
)
|
||||
for t, info in sorted(schema_changed.items()):
|
||||
note = model_diff_note(t, model_diff, table_to_models, info["expected_category"])
|
||||
lines.append(f"\n### `{t}`{note}")
|
||||
if info["added_columns"]:
|
||||
lines.append(f"- neue Spalten: {', '.join(info['added_columns'])}")
|
||||
if info["removed_columns"]:
|
||||
lines.append(f"- entfernte Spalten: {', '.join(info['removed_columns'])}")
|
||||
for col, (ta, tb) in info["type_changes"].items():
|
||||
lines.append(f"- **{col}**: {ta} -> {tb}")
|
||||
if info["row_count_a"] != info["row_count_b"]:
|
||||
lines.append(f"- Zeilenzahl: {info['row_count_a']} -> {info['row_count_b']}")
|
||||
|
||||
lines.append(f"\n\n## Nur Zeilenzahl unterschiedlich, Schema identisch ({len(rows_only)})\n")
|
||||
lines.append(
|
||||
"Keine Code-Definitionsänderung zu erwarten (z. B. mehr XML-Fixture-Daten in "
|
||||
"der neueren Version) - daher kein model_diff-Abgleich.\n"
|
||||
)
|
||||
for t, info in sorted(rows_only.items()):
|
||||
lines.append(f"- `{t}`: {info['row_count_a']} -> {info['row_count_b']}")
|
||||
|
||||
lines.append(f"\n\n## Unverändert ({len(diff['tables_unchanged'])})\n")
|
||||
lines.append(", ".join(f"`{t}`" for t in diff["tables_unchanged"]))
|
||||
|
||||
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--dump-a", required=True)
|
||||
parser.add_argument("--dump-b", required=True)
|
||||
parser.add_argument("--label-a", default="A")
|
||||
parser.add_argument("--label-b", default="B")
|
||||
parser.add_argument("--model-diff", default=None, help="Optional: model_diff.json zum Abgleich")
|
||||
parser.add_argument("--output", default="reports/dump_diff.json")
|
||||
parser.add_argument("--report", default="reports/dump_diff.md")
|
||||
args = parser.parse_args()
|
||||
|
||||
tables_a = parse_dump(args.dump_a)
|
||||
tables_b = parse_dump(args.dump_b)
|
||||
diff = diff_dumps(tables_a, tables_b)
|
||||
|
||||
model_diff = json.loads(Path(args.model_diff).read_text(encoding="utf-8")) if args.model_diff else {}
|
||||
table_to_models = build_table_to_models(model_diff) if model_diff else {}
|
||||
|
||||
Path(args.output).write_text(json.dumps(diff, indent=2, ensure_ascii=False, sort_keys=True), encoding="utf-8")
|
||||
write_report(diff, Path(args.report), args.label_a, args.label_b, model_diff, table_to_models)
|
||||
|
||||
print(
|
||||
f"{len(diff['tables_removed'])} entfernt, {len(diff['tables_added'])} neu, "
|
||||
f"{len(diff['tables_changed'])} geändert, {len(diff['tables_unchanged'])} unverändert "
|
||||
f"-> {args.output}, {args.report}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user