Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dff2de1755 | |||
| d56ae65b56 | |||
| bc0459ab9b | |||
| 7230bcb6f8 | |||
| f07f9dd8b3 | |||
| 5c7fd4330d | |||
| ab696db035 | |||
| 1069630e86 | |||
| 558dff276a | |||
| e0a9205fea | |||
| 24baff2a86 | |||
| 62dbf92b36 | |||
| bbb5181a74 | |||
| 05f9ef0990 | |||
| 33db478c72 | |||
| 7e8840f2a5 | |||
| d4a835f178 | |||
| 0fe8417602 | |||
| 1f59e16b26 | |||
| 59e4b19dee | |||
| 021d01efe6 | |||
| f4216d790c | |||
| eb17894a13 |
15
.devcontainer/devcontainer.json
Normal file
15
.devcontainer/devcontainer.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "Open Workshop (Odoo Dev)",
|
||||||
|
"dockerComposeFile": ["${localWorkspaceFolder}/../../odoo/docker-compose.dev.yaml"],
|
||||||
|
"service": "odoo-dev",
|
||||||
|
"workspaceFolder": "/mnt/extra-addons/open_workshop",
|
||||||
|
"runServices": ["odoo-dev", "db"],
|
||||||
|
"shutdownAction": "stopCompose",
|
||||||
|
"remoteUser": "root",
|
||||||
|
"extensions": [
|
||||||
|
"ms-python.python",
|
||||||
|
"ms-python.vscode-pylance"
|
||||||
|
],
|
||||||
|
"forwardPorts": [4338],
|
||||||
|
"postStartCommand": "echo 'Devcontainer started'"
|
||||||
|
}
|
||||||
289
.vscode/.jslintrc
vendored
Normal file
289
.vscode/.jslintrc
vendored
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
{
|
||||||
|
"globals": {
|
||||||
|
"$": false,
|
||||||
|
"_": false,
|
||||||
|
"fuzzy": false,
|
||||||
|
"jQuery": false,
|
||||||
|
"moment": false,
|
||||||
|
"odoo": false,
|
||||||
|
"openerp": false,
|
||||||
|
"self": false
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-alert": "error",
|
||||||
|
"no-array-constructor": "error",
|
||||||
|
"no-bitwise": "off",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-case-declarations": "error",
|
||||||
|
"no-catch-shadow": "error",
|
||||||
|
"no-class-assign": "error",
|
||||||
|
"no-cond-assign": "error",
|
||||||
|
"no-confusing-arrow": "error",
|
||||||
|
"no-console": "error",
|
||||||
|
"no-const-assign": "error",
|
||||||
|
"no-constant-condition": "error",
|
||||||
|
"no-continue": "off",
|
||||||
|
"no-control-regex": "error",
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-delete-var": "error",
|
||||||
|
"no-div-regex": "error",
|
||||||
|
"no-dupe-args": "error",
|
||||||
|
"no-dupe-class-members": "error",
|
||||||
|
"no-dupe-keys": "error",
|
||||||
|
"no-duplicate-case": "error",
|
||||||
|
"no-duplicate-imports": "error",
|
||||||
|
"no-else-return": "error",
|
||||||
|
"no-empty": "error",
|
||||||
|
"no-empty-character-class": "error",
|
||||||
|
"no-empty-function": "error",
|
||||||
|
"no-empty-pattern": "error",
|
||||||
|
"no-eq-null": "error",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-ex-assign": "error",
|
||||||
|
"no-extend-native": "error",
|
||||||
|
"no-extra-bind": "error",
|
||||||
|
"no-extra-boolean-cast": "error",
|
||||||
|
"no-extra-label": "error",
|
||||||
|
"no-extra-parens": "error",
|
||||||
|
"no-extra-semi": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-floating-decimal": "error",
|
||||||
|
"no-func-assign": "error",
|
||||||
|
"no-implicit-coercion": ["error", {
|
||||||
|
"allow": ["~"]
|
||||||
|
}],
|
||||||
|
"no-implicit-globals": "error",
|
||||||
|
"no-implied-eval": "error",
|
||||||
|
"no-inline-comments": "error",
|
||||||
|
"no-inner-declarations": "error",
|
||||||
|
"no-invalid-regexp": "error",
|
||||||
|
"no-invalid-this": "off",
|
||||||
|
"no-irregular-whitespace": "error",
|
||||||
|
"no-iterator": "error",
|
||||||
|
"no-label-var": "error",
|
||||||
|
"no-labels": "error",
|
||||||
|
"no-lone-blocks": "error",
|
||||||
|
"no-lonely-if": "error",
|
||||||
|
"no-loop-func": "off",
|
||||||
|
"no-magic-numbers": "off",
|
||||||
|
"no-mixed-operators": "error",
|
||||||
|
"no-mixed-requires": "error",
|
||||||
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
|
"no-multi-spaces": "error",
|
||||||
|
"no-multi-str": "error",
|
||||||
|
"no-multiple-empty-lines": "error",
|
||||||
|
"no-native-reassign": "error",
|
||||||
|
"no-negated-condition": "error",
|
||||||
|
"no-negated-in-lhs": "error",
|
||||||
|
"no-nested-ternary": "off",
|
||||||
|
"no-new": "error",
|
||||||
|
"no-new-func": "error",
|
||||||
|
"no-new-object": "error",
|
||||||
|
"no-new-require": "error",
|
||||||
|
"no-new-symbol": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-obj-calls": "error",
|
||||||
|
"no-octal": "error",
|
||||||
|
"no-octal-escape": "error",
|
||||||
|
"no-param-reassign": "error",
|
||||||
|
"no-path-concat": "error",
|
||||||
|
"no-plusplus": "off",
|
||||||
|
"no-process-env": "error",
|
||||||
|
"no-process-exit": "error",
|
||||||
|
"no-proto": "error",
|
||||||
|
"no-prototype-builtins": "error",
|
||||||
|
"no-redeclare": "error",
|
||||||
|
"no-regex-spaces": "error",
|
||||||
|
"no-restricted-globals": "error",
|
||||||
|
"no-restricted-imports": "error",
|
||||||
|
"no-restricted-modules": "error",
|
||||||
|
"no-restricted-syntax": "error",
|
||||||
|
"no-return-assign": "error",
|
||||||
|
"no-script-url": "error",
|
||||||
|
"no-self-assign": "error",
|
||||||
|
"no-self-compare": "error",
|
||||||
|
"no-sequences": "error",
|
||||||
|
"no-shadow": "error",
|
||||||
|
"no-shadow-restricted-names": "error",
|
||||||
|
"no-whitespace-before-property": "error",
|
||||||
|
"no-spaced-func": "error",
|
||||||
|
"no-sparse-arrays": "error",
|
||||||
|
"no-sync": "error",
|
||||||
|
"no-tabs": "error",
|
||||||
|
"no-ternary": "off",
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
"no-this-before-super": "error",
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-undef": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-undefined": "off",
|
||||||
|
"no-unexpected-multiline": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-unmodified-loop-condition": "error",
|
||||||
|
"no-unneeded-ternary": "error",
|
||||||
|
"no-unreachable": "error",
|
||||||
|
"no-unsafe-finally": "error",
|
||||||
|
"no-unused-expressions": "error",
|
||||||
|
"no-unused-labels": "error",
|
||||||
|
"no-unused-vars": "error",
|
||||||
|
"no-use-before-define": "error",
|
||||||
|
"no-useless-call": "error",
|
||||||
|
"no-useless-computed-key": "error",
|
||||||
|
"no-useless-concat": "error",
|
||||||
|
"no-useless-constructor": "error",
|
||||||
|
"no-useless-escape": "error",
|
||||||
|
"no-useless-rename": "error",
|
||||||
|
"no-void": "error",
|
||||||
|
"no-var": "off",
|
||||||
|
"no-warning-comments": "off",
|
||||||
|
"no-with": "error",
|
||||||
|
"array-bracket-spacing": "off",
|
||||||
|
"array-callback-return": "error",
|
||||||
|
"arrow-body-style": "error",
|
||||||
|
"arrow-parens": "error",
|
||||||
|
"arrow-spacing": "off",
|
||||||
|
"accessor-pairs": "error",
|
||||||
|
"block-scoped-var": "off",
|
||||||
|
"block-spacing": ["error", "always"],
|
||||||
|
"brace-style": "error",
|
||||||
|
"callback-return": "error",
|
||||||
|
"camelcase": "off",
|
||||||
|
"capitalized-comments": ["error", "always", {
|
||||||
|
"ignoreConsecutiveComments": true,
|
||||||
|
"ignoreInlineComments": true
|
||||||
|
}],
|
||||||
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
|
"comma-spacing": ["error", {
|
||||||
|
"before": false,
|
||||||
|
"after": true
|
||||||
|
}],
|
||||||
|
"comma-style": "error",
|
||||||
|
"complexity": [
|
||||||
|
"error",
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"computed-property-spacing": "off",
|
||||||
|
"consistent-return": "off",
|
||||||
|
"consistent-this": "off",
|
||||||
|
"constructor-super": "error",
|
||||||
|
"curly": "error",
|
||||||
|
"default-case": "off",
|
||||||
|
"dot-location": ["error", "property"],
|
||||||
|
"dot-notation": "error",
|
||||||
|
"eol-last": "error",
|
||||||
|
"eqeqeq": "error",
|
||||||
|
"func-names": "off",
|
||||||
|
"func-style": "off",
|
||||||
|
"generator-star-spacing": "off",
|
||||||
|
"global-require": "error",
|
||||||
|
"guard-for-in": "off",
|
||||||
|
"handle-callback-err": "error",
|
||||||
|
"id-blacklist": "error",
|
||||||
|
"id-length": "off",
|
||||||
|
"id-match": "error",
|
||||||
|
"indent": "error",
|
||||||
|
"init-declarations": "error",
|
||||||
|
"jsx-quotes": "error",
|
||||||
|
"key-spacing": "off",
|
||||||
|
"keyword-spacing": "error",
|
||||||
|
"linebreak-style": [
|
||||||
|
"error",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"lines-around-comment": "error",
|
||||||
|
"max-depth": "error",
|
||||||
|
"max-len": ["error", {
|
||||||
|
"code": 80,
|
||||||
|
"ignorePattern": "odoo\\.define\\(",
|
||||||
|
"tabWidth": 4
|
||||||
|
}],
|
||||||
|
"max-lines": "off",
|
||||||
|
"max-nested-callbacks": "error",
|
||||||
|
"max-params": "off",
|
||||||
|
"max-statements": "off",
|
||||||
|
"max-statements-per-line": "error",
|
||||||
|
"multiline-ternary": "off",
|
||||||
|
"new-cap": "off",
|
||||||
|
"new-parens": "error",
|
||||||
|
"newline-after-var": "off",
|
||||||
|
"newline-before-return": "off",
|
||||||
|
"newline-per-chained-call": "off",
|
||||||
|
"object-curly-newline": ["error", { "consistent": true }],
|
||||||
|
"object-curly-spacing": ["error", "never"],
|
||||||
|
"object-property-newline": ["error", {
|
||||||
|
"allowAllPropertiesOnSameLine": true
|
||||||
|
}],
|
||||||
|
"object-shorthand": "off",
|
||||||
|
"one-var": "off",
|
||||||
|
"one-var-declaration-per-line": "off",
|
||||||
|
"operator-assignment": "error",
|
||||||
|
"operator-linebreak": "error",
|
||||||
|
"padded-blocks": "off",
|
||||||
|
"prefer-arrow-callback": "off",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"prefer-reflect": "off",
|
||||||
|
"prefer-rest-params": "off",
|
||||||
|
"prefer-spread": "off",
|
||||||
|
"prefer-template": "off",
|
||||||
|
"quote-props": "off",
|
||||||
|
"quotes": "off",
|
||||||
|
"radix": "error",
|
||||||
|
"require-yield": "error",
|
||||||
|
"rest-spread-spacing": "off",
|
||||||
|
"semi": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"semi-spacing": "error",
|
||||||
|
"sort-imports": "error",
|
||||||
|
"sort-vars": "off",
|
||||||
|
"space-before-blocks": "error",
|
||||||
|
"space-before-function-paren": "error",
|
||||||
|
"space-in-parens": "off",
|
||||||
|
"space-infix-ops": "off",
|
||||||
|
"space-unary-ops": "off",
|
||||||
|
"spaced-comment": ["error", "always"],
|
||||||
|
"strict": ["error", "function"],
|
||||||
|
"template-curly-spacing": "off",
|
||||||
|
"unicode-bom": "error",
|
||||||
|
"use-isnan": "error",
|
||||||
|
"valid-jsdoc": ["error", {
|
||||||
|
"prefer": {
|
||||||
|
"arg": "param",
|
||||||
|
"argument": "param",
|
||||||
|
"augments": "extends",
|
||||||
|
"constructor": "class",
|
||||||
|
"exception": "throws",
|
||||||
|
"func": "function",
|
||||||
|
"method": "function",
|
||||||
|
"prop": "property",
|
||||||
|
"return": "returns",
|
||||||
|
"virtual": "abstract",
|
||||||
|
"yield": "yields"
|
||||||
|
},
|
||||||
|
"preferType": {
|
||||||
|
"array": "Array",
|
||||||
|
"bool": "Boolean",
|
||||||
|
"boolean": "Boolean",
|
||||||
|
"number": "Number",
|
||||||
|
"object": "Object",
|
||||||
|
"str": "String",
|
||||||
|
"string": "String"
|
||||||
|
},
|
||||||
|
"requireParamDescription": false,
|
||||||
|
"requireReturn": false,
|
||||||
|
"requireReturnDescription": false,
|
||||||
|
"requireReturnType": false
|
||||||
|
}],
|
||||||
|
"valid-typeof": "error",
|
||||||
|
"vars-on-top": "off",
|
||||||
|
"wrap-iife": "error",
|
||||||
|
"wrap-regex": "error",
|
||||||
|
"yield-star-spacing": "off",
|
||||||
|
"yoda": "error"
|
||||||
|
},
|
||||||
|
"parserOptions": {}
|
||||||
|
}
|
||||||
64
.vscode/README.md
vendored
Normal file
64
.vscode/README.md
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Quickstart: Debugging für open_workshop
|
||||||
|
|
||||||
|
Diese Datei hilft dir, das `open_workshop` Addon schnell in VS Code zu öffnen und sowohl das Addon als auch (optional) den Odoo‑Core zu debuggen.
|
||||||
|
|
||||||
|
Kurzfassung
|
||||||
|
- Öffne in VS Code den Ordner `extra-addons/open_workshop`.
|
||||||
|
- Starte die Development-Container/Composer Umgebung mit `./dev.sh` (Option 1 oder 2).
|
||||||
|
- Verwende die DevContainer‑Funktion oder die Debug‑Konfigurationen unten.
|
||||||
|
|
||||||
|
1) Container starten
|
||||||
|
|
||||||
|
Empfohlen: im Projektstamm (`odoo`) ausführen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dev.sh
|
||||||
|
# Option 1: Normal starten (ODOO_DEV=1)
|
||||||
|
# Option 2: Debug-Modus (ODOO_DEBUG=1) — der Container wartet auf eine Debug-Verbindung
|
||||||
|
```
|
||||||
|
|
||||||
|
2) VS Code als DevContainer verbinden (empfohlen)
|
||||||
|
|
||||||
|
- Öffne VS Code im lokalen Ordner `extra-addons/open_workshop`.
|
||||||
|
- Command Palette → `Dev Containers: Attach to Running Container...` → wähle `odoo-dev`.
|
||||||
|
- VS Code öffnet den Container als Arbeitsumgebung (remoteUser ist `odoo`).
|
||||||
|
- Öffne dann das Workspace‑Verzeichnis `/mnt/extra-addons/open_workshop`.
|
||||||
|
|
||||||
|
Vorteil: Du arbeitest direkt im Container (kein lokales Kopieren der Core‑Sourcen nötig) und Breakpoints funktionieren zuverlässig.
|
||||||
|
|
||||||
|
3) Debugging (Attach)
|
||||||
|
|
||||||
|
- Wenn du als DevContainer verbunden bist, verwende die Debug‑Konfiguration "Odoo Attach (container)" (port 5678).
|
||||||
|
- Wenn du lokal arbeitest und den Host‑Port benutzt, verwende "Odoo Attach (host)" (port 4338).
|
||||||
|
|
||||||
|
4) Pfad‑Mapping
|
||||||
|
|
||||||
|
- Die `launch.json` enthält Pfadabbildungen:
|
||||||
|
- `${workspaceFolder}` ↔ `/mnt/extra-addons/open_workshop` (dein Addon)
|
||||||
|
- `${workspaceFolder}/../../odoo-source/odoo` ↔ `/usr/lib/python3/dist-packages/odoo` (falls du lokal eine Kopie des Odoo‑Cores hast)
|
||||||
|
|
||||||
|
Hinweis: Lokale `odoo-source` ist nicht erforderlich, wenn du per DevContainer arbeitest, weil VS Code die Dateien direkt im Container liest.
|
||||||
|
|
||||||
|
5) Troubleshooting
|
||||||
|
|
||||||
|
- Debug-Verbindung schlägt fehl: prüfe, ob der Container im Debug‑Modus läuft und der Port gemappt ist:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.dev.yaml ps
|
||||||
|
docker compose -f docker-compose.dev.yaml logs -f odoo-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- VS Code meldet, dass Breakpoints nicht aufgelöst werden: vergewissere dich, dass die `pathMappings` korrekt sind und die lokalen Dateien existieren (oder nutze DevContainer).
|
||||||
|
|
||||||
|
6) Image/Compose aktualisieren
|
||||||
|
|
||||||
|
- Wenn du `Dockerfile.Dev` geändert hast: neu bauen (Option 3 in `./dev.sh`), dann Container neu starten (Option 5 oder down/up).
|
||||||
|
|
||||||
|
7) Kurze Checklist für Mitentwickler
|
||||||
|
|
||||||
|
- `./dev.sh` → Option 3 (einmalig) wenn du das Dev‑Image bauen musst.
|
||||||
|
- `./dev.sh` → Option 1 oder 2 zum Starten.
|
||||||
|
- In VS Code: Öffne `extra-addons/open_workshop`, dann `Dev Containers: Attach to Running Container...`.
|
||||||
|
- Starte Debug mit "Odoo Attach (container)" oder "Odoo Attach (host)".
|
||||||
|
|
||||||
|
|
||||||
45
.vscode/launch.json
vendored
Normal file
45
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Odoo Attach (host)",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "attach",
|
||||||
|
"connect": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 4338
|
||||||
|
},
|
||||||
|
"pathMappings": [
|
||||||
|
{
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "/mnt/extra-addons/open_workshop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"localRoot": "${workspaceFolder}/../../odoo-source/odoo",
|
||||||
|
"remoteRoot": "/usr/lib/python3/dist-packages/odoo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"justMyCode": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Odoo Attach (container)",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "attach",
|
||||||
|
"connect": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 5678
|
||||||
|
},
|
||||||
|
"pathMappings": [
|
||||||
|
{
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "/mnt/extra-addons/open_workshop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"localRoot": "${workspaceFolder}/../../odoo-source/odoo",
|
||||||
|
"remoteRoot": "/usr/lib/python3/dist-packages/odoo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"justMyCode": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
30
.vscode/settings.json
vendored
Normal file
30
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"python.pythonPath": "/usr/bin/python3",
|
||||||
|
"python.analysis.extraPaths": [
|
||||||
|
"${workspaceFolder}",
|
||||||
|
"${workspaceFolder}/../../odoo-source/odoo"
|
||||||
|
],
|
||||||
|
"python.analysis.typeCheckingMode": "off"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"editor.rulers": [80, 100, 120],
|
||||||
|
"files.eol": "\n",
|
||||||
|
"[python]": {
|
||||||
|
"editor.defaultFormatter": "ms-python.python",
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.tabSize": 4
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.tabSize": 2
|
||||||
|
},
|
||||||
|
"python.analysis.extraPaths": [
|
||||||
|
"../../odoo-source/odoo",
|
||||||
|
],
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"--odoo-http",
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"python.testing.pytestEnabled": false,
|
||||||
|
"python.testing.unittestEnabled": false
|
||||||
|
}
|
||||||
68
.vscode/tasks.json
vendored
Normal file
68
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "start-odoo-dev",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker",
|
||||||
|
"args": ["compose", "-f", "${workspaceFolder}/../../odoo/docker-compose.dev.yaml", "up", "-d"],
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"ODOO_DEV": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": { "kind": "build", "isDefault": true },
|
||||||
|
"presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" },
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "start-odoo-debug",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker",
|
||||||
|
"args": ["compose", "-f", "${workspaceFolder}/../../odoo/docker-compose.dev.yaml", "up", "-d"],
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"ODOO_DEBUG": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": "build",
|
||||||
|
"presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" },
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "rebuild-odoo-dev",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker",
|
||||||
|
"args": ["compose", "-f", "${workspaceFolder}/../../odoo/docker-compose.dev.yaml", "up", "--build", "-d"],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" },
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "stop-odoo-dev",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker",
|
||||||
|
"args": ["compose", "-f", "${workspaceFolder}/../..odoo/docker-compose.dev.yaml", "down"],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" },
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "odoo-logs",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker",
|
||||||
|
"args": ["compose", "-f", "${workspaceFolder}/../../odoo/docker-compose.dev.yaml", "logs", "-f", "odoo-dev"],
|
||||||
|
"group": "test",
|
||||||
|
"presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" },
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "shell-odoo",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker",
|
||||||
|
"args": ["compose", "-f", "${workspaceFolder}/../../odoo/docker-compose.dev.yaml", "exec", "odoo-dev", "bash"],
|
||||||
|
"presentation": { "echo": true, "reveal": "always", "focus": true, "panel": "shared" },
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
34
README.md
34
README.md
|
|
@ -1,6 +1,6 @@
|
||||||
# Open Workshop (open_workshop ows)
|
# Open Workshop (open_workshop ows)
|
||||||
|
|
||||||
Dieses Odoo v13.0 Modul erweitert das POS- und Kontakt-Modul um Funktionen für offene Werkstätten (FabLabs, Makerspaces etc.) und dient der Verwaltung von Maschinen, Naschinen Einweisungen Produkten, Maschinen Nutzungsprodukten und Zugangsberechtigungen zu den Maschinen.
|
Dieses Odoo v18.0 Modul erweitert das POS- und Kontakt-Modul um Funktionen für offene Werkstätten (FabLabs, Makerspaces etc.) und dient der Verwaltung von Maschinen, Naschinen Einweisungen Produkten, Maschinen Nutzungsprodukten und Zugangsberechtigungen zu den Maschinen.
|
||||||
|
|
||||||
## Funktionen
|
## Funktionen
|
||||||
|
|
||||||
|
|
@ -28,40 +28,10 @@ Dieses Odoo v13.0 Modul erweitert das POS- und Kontakt-Modul um Funktionen für
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Dieses Modul in den Custom-Addons-Ordner kopieren
|
1. Dieses Modul in den Custom-Addons-Ordner kopieren
|
||||||
2. Vor der Installation von open_worshop muss vvow_pos deinstalliert werden. Die Funktionalität von vvow_pos wird durch open_workshop ersetzt und erweitert.
|
2. Im Odoo Backend unter Apps installieren
|
||||||
3. ggf. muss die alte Datenbank manuell migiriert werden, es gibt ca 9 gelöscht res.partner auf die Verweise aus POS bestehen. Diese res.parnter müssen wieder hergestellt werden. Dazu gibt es ein Skript unter
|
|
||||||
```folder
|
|
||||||
scripts/fix_missing_pos_partner.py
|
|
||||||
```
|
|
||||||
```bash
|
|
||||||
opt/odoo/odoo/odoo-bin shell -d hobbyhimmel < scrpts/fix_missing_pos_partner.py
|
|
||||||
```
|
|
||||||
4. Odoo starten mit:
|
|
||||||
```bash
|
|
||||||
odoo-bin -d deine_datenbank -u open_workshop
|
|
||||||
```
|
|
||||||
5. Alternativ im Backend unter Apps installieren
|
|
||||||
|
|
||||||
## Automatische Migrationen
|
|
||||||
|
|
||||||
Beim ersten Laden des Moduls werden folgende Migrationen durchgeführt:
|
|
||||||
- Bestehende `res.partner` erhalten automatisch `ows.user`-Eintrag (inkl. Übernahme alter Felder wie vvow_birthday, vvow_security, vvow_security_id, vvow_rfid.
|
|
||||||
- Alte Felder mit Maschinenfreigaben (`vvow_holz_*`, `vvow_metall_*`, `vvow_fablab_*`) werden in `ows.machine.access` übertragen
|
|
||||||
- inkl. Übernahme des Änderungsdatum aus `mail.message` wann der Nutzer die Einweisung erhalten hat (ist noch fehlerhaft)
|
|
||||||
|
|
||||||
## Entwicklerhinweise
|
## Entwicklerhinweise
|
||||||
|
|
||||||
### post_init_hook
|
|
||||||
Die Datei `post_init_hook.py` ruft automatisch nach der Installation folgende Methoden auf:
|
|
||||||
```python
|
|
||||||
res.partner.migrate_existing_partners()
|
|
||||||
res.partner.migrate_machine_access_from_old_fields()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Datenimport
|
|
||||||
- Maschinenbereiche, Maschinen werden über `.xml`-Dateien in `data/` geladen
|
|
||||||
- Die Zuordnung von Maschine zu Einweisungsprodukten und Nutzungsprodukten muss derzeit noch manuell erstellt werden. Ein skript dafür folgt.
|
|
||||||
|
|
||||||
## ToDos
|
## ToDos
|
||||||
- Bearbeitung der Maschinenfreigaben im Backend
|
- Bearbeitung der Maschinenfreigaben im Backend
|
||||||
- Automatische Erstellung von `mail.message` bei manueller Freigabe
|
- Automatische Erstellung von `mail.message` bei manueller Freigabe
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from . import models
|
from . import models
|
||||||
from . import controllers
|
from . import controllers
|
||||||
from . import post_init_hook
|
|
||||||
# damit run_migration sichtbar ist:
|
|
||||||
run_migration = post_init_hook.run_migration
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
'name': 'POS Open Workshop',
|
'name': 'POS Open Workshop',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'version': '13.0.1.0.0',
|
'version': '18.0.1.0.1',
|
||||||
'summary': 'Erstellt Maschinenfreigaben basierend auf POS-Einweisungsprodukten',
|
'summary': 'Erstellt Maschinenfreigaben basierend auf POS-Einweisungsprodukten',
|
||||||
'depends': ['base','product','sale','contacts','point_of_sale'],
|
'depends': ['base', 'account', 'hr','product','sale','contacts','point_of_sale'],
|
||||||
'author': 'matthias.lotz',
|
'author': 'matthias.lotz',
|
||||||
'category': 'Point of Sale',
|
'category': 'Point of Sale',
|
||||||
'data': [
|
'data': [
|
||||||
|
|
@ -13,26 +13,27 @@
|
||||||
'views/machine_area_views.xml',
|
'views/machine_area_views.xml',
|
||||||
'views/machine_views.xml',
|
'views/machine_views.xml',
|
||||||
'views/res_partner_view.xml',
|
'views/res_partner_view.xml',
|
||||||
'views/assets.xml',
|
|
||||||
'data/data.xml',
|
'data/data.xml',
|
||||||
],
|
],
|
||||||
'qweb': [
|
|
||||||
'static/src/xml/ows_briefing_details.xml',
|
|
||||||
'static/src/xml/ows_briefing_details_edit.xml',
|
|
||||||
'static/src/xml/ows_pos_order_selector.xml',
|
|
||||||
'static/src/xml/ows_machine_sidebar.xml',
|
|
||||||
'static/src/xml/ows_pos_machine_access_view.xml',
|
|
||||||
],
|
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'assets': {
|
'assets': {
|
||||||
'point_of_sale.assets': [
|
'web.assets_backend': [
|
||||||
'static/src/js/machine_access_sidebar.js',
|
'open_workshop/static/src/css/category_color.css',
|
||||||
'static/src/css/pos.css',
|
],
|
||||||
|
'point_of_sale._assets_pos': [
|
||||||
|
'open_workshop/static/src/css/pos.css',
|
||||||
|
'open_workshop/static/src/js/ows_machine_access_list.js',
|
||||||
|
'open_workshop/static/src/js/ows_pos_customer_sidebar.js',
|
||||||
|
'open_workshop/static/src/js/ows_pos_sidebar.js',
|
||||||
|
'open_workshop/static/src/js/ows_product_screen_template_patch.js',
|
||||||
|
'open_workshop/static/src/xml/ows_machine_access_list.xml',
|
||||||
|
'open_workshop/static/src/xml/ows_pos_customer_sidebar.xml',
|
||||||
|
'open_workshop/static/src/xml/ows_pos_sidebar.xml',
|
||||||
|
'open_workshop/static/src/xml/ows_product_screen_template_patch.xml',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'post_init_hook': 'run_migration',
|
|
||||||
'description': """
|
'description': """
|
||||||
Diese App erstellt Maschinenfreigaben basierend auf POS-Einweisungsprodukten.
|
Diese App erstellt Maschinenfreigaben basierend auf POS-Einweisungsprodukten.
|
||||||
Die App ist für den Einsatz in der Odoo-Version 13.0 konzipiert.
|
Die App ist für den Einsatz in der Odoo-Version 18.0 konzipiert.
|
||||||
""",
|
""",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
# Datei: controllers/pos_access.py
|
|
||||||
|
|
||||||
from odoo import http
|
from odoo import http
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
|
|
||||||
class OpenWorkshopPOSController(http.Controller):
|
class OpenWorkshopPOSController(http.Controller):
|
||||||
|
|
||||||
@http.route('/open_workshop/partner_access', type='json', auth='user')
|
@http.route('/open_workshop/partner_access', type='json', auth='user')
|
||||||
def get_partner_machine_access(self, partner_id):
|
def get_partner_machine_access(self, **kwargs):
|
||||||
|
partner_id = kwargs.get('params', {}).get('partner_id')
|
||||||
|
if not partner_id:
|
||||||
|
return {"error": "Missing partner_id"}
|
||||||
|
|
||||||
Machine = request.env['ows.machine'].sudo()
|
Machine = request.env['ows.machine'].sudo()
|
||||||
return Machine.get_access_list_grouped(partner_id)
|
return Machine.get_access_list_grouped(partner_id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
id,name,street,zip,city,phone,email,company_type,customer_rank,supplier_rank
|
|
||||||
res_partner_demo_1, AAAA Max Mustermann,Musterstraße 1,,,,,person,15,0
|
|
||||||
res_partner_demo_2, Benjamin Winter,,,,,,person,1,0
|
|
||||||
res_partner_demo_3, Martin Berthelon,Fabrikstr. 3,73728,Esslingen,,martin.berthelon@hotmail.fr,person,15,0
|
|
||||||
res_partner_demo_4,Aaron Christ,Hohewartstraße 46,70469,Stuttgart,,christ.aaron@web.de,person,14,0
|
|
||||||
res_partner_demo_5,Aaron Dörr,Riegeläckerstr. 60,71229,Leonberg,,aaron_doerr@web.de,person,33,0
|
|
||||||
res_partner_demo_6,Aaron Gale,Chopinstr. 20,70195,Stuttgart,015172165290,aarongale1@live.com,person,4,0
|
|
||||||
res_partner_demo_7,Aaron Zimmermann,Heinrichstr. 15,38106 ,Braunschweig,016091647469,,person,1,0
|
|
||||||
res_partner_demo_8,Abalrahman Alsadi,Bachstr. 29,70563,Stuttgart,,abdulrahman.m.saadi@gmail.com,person,1,0
|
|
||||||
res_partner_demo_9,Abdullah Zengin,Engelbertstr. 124,70499,Stuttgart,,,person,3,0
|
|
||||||
res_partner_demo_10,Abdussamed Korkmaz,Bertha-von-Suttner-Straße 1,74366,Kirchheim Am Neckar,,korkmaz.abdussamed@gmail.com,person,1,0
|
|
||||||
res_partner_demo_11,Achim Brendle,Oberwiesenstraße 45,70619,Stuttgart,7114797505,achim.brendle@web.de,person,2,0
|
|
||||||
res_partner_demo_12,Achim Jatkowski,Hummelstr. 38,70569,Stuttgart,017621512316,achim.jatkowski@gmail.com,person,1,0
|
|
||||||
res_partner_demo_13,Achim Jung,Kurt Tucholsky Str. 6,71254,Ditzingen,07156174013,acjung@web.de,person,1,0
|
|
||||||
res_partner_demo_14,Achim Kelbel,Vivaldiweg 6,70195,Stuttgart,,a.kelbel@t-online.de,person,2,0
|
|
||||||
res_partner_demo_15,Achim Kramer,Reinsburger 172,70197,Stuttgart,,achim@zibra.de,person,1,0
|
|
||||||
res_partner_demo_16,Adalbert Zeisl,Bachstr. 20,71364,Winnenden,07195-2092884,betz1000@gmx.de,person,2,0
|
|
||||||
res_partner_demo_17,Adalina Schäfer,Sancenbacherstr. 26,74538,Rosengarten,015778855550,lina_max_schaefer@gmx.de,person,1,0
|
|
||||||
res_partner_demo_18,Adam Riegel,Marabustr. 35 / 84,70378,Stuttgart,0711 532082,,person,1,0
|
|
||||||
res_partner_demo_19,Adam Swais,Obertürkheimerstr. 54,73733,Esslingen,,adamswais@web.de,person,1,0
|
|
||||||
res_partner_demo_20,Adela Spulber,Obere Bismarck Str. 97,70197,Stuttgart,,,person,1,0
|
|
||||||
res_partner_demo_21,Adem Uzun,Liesel-Bach-Str. 54,71034,Böblingen,015251690873,adem.uzun2@gmail.com,person,1,0
|
|
||||||
res_partner_demo_22,Adnan Djekic,Vesoulerstr. 33,70839,Gerlingen,01724227468,adnandjekic@alice-dsl.net,person,1,0
|
|
||||||
res_partner_demo_23,Adrian Berres,Bärgstadter Str. 90,63928,Gehenbühl,,a.berres@gmx.de,person,1,0
|
|
||||||
res_partner_demo_24,Adrian Lanksweirt,Heidestraße 6,70469,Stuttgart,,adrian.lanksweirt@gmail.com,person,1,0
|
|
||||||
res_partner_demo_25,Adrian Popov,Hallerstr. 42,90419,Nürnberg,+4915114305751,adrinuernberg@gmail.com,person,2,0
|
|
||||||
res_partner_demo_26,Agnes Krettek,Seyfferstr. 62,70187,Stuttgart,,agneskrettek@gmail.com,person,1,0
|
|
||||||
res_partner_demo_27,Ahmad Taijan,Rümelinstr 69,70191,Stuttgart,,,person,2,0
|
|
||||||
res_partner_demo_28,Aileen Becker,Eichendorffstr. 4,73630,Remshalden,015780645637,aileen.becker@gmx.de,person,87,0
|
|
||||||
res_partner_demo_29,Ailey Simpson,Eierstraße 44 A,70199,Stuttgart,,aileywsimpson@gmail.com,person,1,0
|
|
||||||
res_partner_demo_30,Akira Mitsu,Fritz-Ulrich-Weg 5,70567,Stuttgart,,mitsuakira0914@gmail.com,person,5,0
|
|
||||||
res_partner_demo_31,Aksel Özdemir,Rotebühlstraße 53,70178,Stuttgart,,aksel.oezdemir@gmx.de,person,2,0
|
|
||||||
res_partner_demo_32,Albert Ebenbichler,Am Backhaus 9,73666,Boltmannsweiler,01726101655,info@albert-ebenbichler.com,person,1,0
|
|
||||||
res_partner_demo_33,Albert Kaupp,Waldäckerstr. 10,70435,Stuttgart,0711 8263232,albert.kaupp@online.de,person,2,0
|
|
||||||
res_partner_demo_34,Albrecht Barth,Klopstockstr. 39,70193,Stuttgart,,albrecht.barth@web.de,person,3,0
|
|
||||||
res_partner_demo_35,Albrecht Schlayer,Im Netzbrunnen 17,70825,K-Münchingen,,aws1308@gmail.com,person,1,0
|
|
||||||
res_partner_demo_36,Alec Dobler,Kräherwald 251,70193,Stuttgart,,,person,1,0
|
|
||||||
res_partner_demo_37,Alejandro Cano Perez,Burgstallstraße 66,70199,Stuttgart,,cano.perez@gmx.de,person,2,0
|
|
||||||
res_partner_demo_38,Alejandro Rodriguez,Im Hirschwinkel 1,76297,Stutensee,015771409317,ralexei95@yahoo.de,person,1,0
|
|
||||||
res_partner_demo_39,Alejandro Zarza Aguado,Reinsburgstr. 152,70197,Stuttgart,017628401435,11alex96@gmail.com,person,1,0
|
|
||||||
res_partner_demo_40,Aleksandar Vasić,Lothringer Str. 5,70435,Stuttgart,,aleksvasic@web.de,person,3,0
|
|
||||||
res_partner_demo_41,Alen Minasyan,Kastanienallee 41/1,71638,Ludwigsburg,,bidilik@gmx.de,person,1,0
|
|
||||||
res_partner_demo_42,Alex Olenberg,Theodor-Rottschildstr. 25,73760,Stuttgart,,,person,26,0
|
|
||||||
res_partner_demo_43,Alex Schaut,Braunenbergweg 9,70806,Kornwestheim,07154 16530,aschaut@gmx.de,person,3,0
|
|
||||||
res_partner_demo_44,Alexander Adloff,Charlottenstraße 2,74074,Heilbronn,,alexadloff@gmx.de,person,3,0
|
|
||||||
res_partner_demo_45,Alexander Bauer,Im Himmel 20,70569,Stuttgart,071172237601,ab.312@icloud.com,person,1,0
|
|
||||||
res_partner_demo_46,Alexander Blendl,Neckarstr. 8,70736,Fellbach,,blendl.alex@gmail.com,person,4,0
|
|
||||||
res_partner_demo_47,Alexander Borshov,Schellingstraße 24,71277,Rutesheim,,aborshov@gmail.com,person,1,0
|
|
||||||
res_partner_demo_48,Alexander Bosch,Osterwiesenstr. 37,70794,Filderstadt,,bosch-alexander@web.de,person,1,0
|
|
||||||
res_partner_demo_49,Alexander Braig,Holzgrund Str. 25,70806,Kornwestheim,,a.braig84@gmx.de,person,17,0
|
|
||||||
res_partner_demo_50,Alexander Carolus,Kornbergstr. 23,70176,Stuttgart,,alexander.carolus,person,1,0
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
/opt/odoo/odoo/odoo-bin shell -d hobbyhimmel < /home/odoo/custom_addons/open_workshop/demo/export_partner.py
|
|
||||||
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import csv
|
|
||||||
import random
|
|
||||||
|
|
||||||
# Beispielsweise 50 Kunden mit Namen und E-Mail
|
|
||||||
partners = env['res.partner'].search(
|
|
||||||
[('customer_rank', '>', 0), ('is_company', '=', False)],
|
|
||||||
limit=50
|
|
||||||
)
|
|
||||||
|
|
||||||
with open('/home/odoo/custom_addons/open_workshop/demo/demo_partners.csv', 'w', newline='') as f:
|
|
||||||
writer = csv.writer(f)
|
|
||||||
writer.writerow([
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'street',
|
|
||||||
'zip',
|
|
||||||
'city',
|
|
||||||
'phone',
|
|
||||||
'email',
|
|
||||||
'company_type',
|
|
||||||
'customer_rank',
|
|
||||||
'supplier_rank'
|
|
||||||
])
|
|
||||||
|
|
||||||
for idx, partner in enumerate(partners, start=1):
|
|
||||||
partner_id = f'res_partner_demo_{idx}'
|
|
||||||
writer.writerow([
|
|
||||||
partner_id,
|
|
||||||
partner.name or '',
|
|
||||||
partner.street or '',
|
|
||||||
partner.zip or '',
|
|
||||||
partner.city or '',
|
|
||||||
partner.phone or '',
|
|
||||||
partner.email or '',
|
|
||||||
partner.company_type or 'person',
|
|
||||||
partner.customer_rank,
|
|
||||||
partner.supplier_rank,
|
|
||||||
])
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from odoo import SUPERUSER_ID
|
|
||||||
from odoo.api import Environment
|
|
||||||
import logging
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
MISSING_PARTNERS = [
|
|
||||||
6534, 1594, 4700, 6557, 5392, 4960, 5226, 6535, 4666
|
|
||||||
]
|
|
||||||
|
|
||||||
def insert_missing_partners(cr, registry):
|
|
||||||
env = Environment(cr, SUPERUSER_ID, {})
|
|
||||||
|
|
||||||
for partner_id in MISSING_PARTNERS:
|
|
||||||
cr.execute("""
|
|
||||||
INSERT INTO res_partner (
|
|
||||||
id, name, customer_rank, create_uid, create_date, write_uid, write_date
|
|
||||||
)
|
|
||||||
VALUES (%s, %s, 1, %s, now(), %s, now())
|
|
||||||
ON CONFLICT (id) DO NOTHING;
|
|
||||||
""", (partner_id, f"Fehlender Partner {partner_id}", SUPERUSER_ID, SUPERUSER_ID))
|
|
||||||
|
|
||||||
cr.execute("SELECT setval('res_partner_id_seq', (SELECT MAX(id) FROM res_partner));")
|
|
||||||
|
|
||||||
_logger.info(f"[OWS Repair] {len(MISSING_PARTNERS)} fehlende Partner hinzugefügt.")
|
|
||||||
cr.commit()
|
|
||||||
|
|
||||||
# Automatischer Start in odoo-bin shell
|
|
||||||
if 'env' in globals():
|
|
||||||
insert_missing_partners(env.cr, env.registry)
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
|
||||||
<odoo>
|
|
||||||
<record id="cat_einweisungen" model="product.category">
|
|
||||||
<field name="name">Einweisungen</field>
|
|
||||||
</record>
|
|
||||||
<record id="cat_maschinennutzung" model="product.category">
|
|
||||||
<field name="name">Maschinennutzung</field>
|
|
||||||
</record>
|
|
||||||
<record id="prod_3d_druck_30_minuten" model="product.product">
|
|
||||||
<field name="name">3D Druck (30 Minuten)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.25</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_bandschleifer_1_minute" model="product.product">
|
|
||||||
<field name="name">Bandschleifer (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.1</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_bandsäge_1_minute" model="product.product">
|
|
||||||
<field name="name">Bandsäge (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.1</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_cnc_fräse_1_minute" model="product.product">
|
|
||||||
<field name="name">CNC Fräse (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.1</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_cnc_sicherheitseinweisung" model="product.product">
|
|
||||||
<field name="name">CNC Sicherheitseinweisung</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">25.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_drehbank_1_minute" model="product.product">
|
|
||||||
<field name="name">Drehbank (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.1</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_3d_drucker_delta" model="product.product">
|
|
||||||
<field name="name">Einweisung 3D Drucker Delta</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">15.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_3d_drucker_prusa" model="product.product">
|
|
||||||
<field name="name">Einweisung 3D Drucker Prusa</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">20.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_bandsäge" model="product.product">
|
|
||||||
<field name="name">Einweisung Bandsäge</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">15.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_drehbank" model="product.product">
|
|
||||||
<field name="name">Einweisung Drehbank</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">20.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_fks" model="product.product">
|
|
||||||
<field name="name">Einweisung FKS</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">20.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_hobel" model="product.product">
|
|
||||||
<field name="name">Einweisung Hobel</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">15.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_laser" model="product.product">
|
|
||||||
<field name="name">Einweisung Laser</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">15.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_metallfräse" model="product.product">
|
|
||||||
<field name="name">Einweisung Metallfräse</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">20.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_schweißgerät" model="product.product">
|
|
||||||
<field name="name">Einweisung Schweißgerät</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">10.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_einweisung_in_maschinelle_holzverbindungen" model="product.product">
|
|
||||||
<field name="name">Einweisung in maschinelle Holzverbindungen</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">15.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_einweisungen" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_formatkreissäge_1_minute" model="product.product">
|
|
||||||
<field name="name">Formatkreissäge (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.1</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_fräse___deckel_1_minute" model="product.product">
|
|
||||||
<field name="name">Fräse - Deckel (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.1</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_hobel_1_minute" model="product.product">
|
|
||||||
<field name="name">Hobel (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.1</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_laser_aktivminute" model="product.product">
|
|
||||||
<field name="name">Laser (Aktivminute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.7000000000000001</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_sandstrahlbox_1_minute" model="product.product">
|
|
||||||
<field name="name">Sandstrahlbox (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.2</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_schweißgerät_1_minute" model="product.product">
|
|
||||||
<field name="name">Schweißgerät (1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.2</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_schweißkabine_eigenes_schweißgerät___1_minute" model="product.product">
|
|
||||||
<field name="name">Schweißkabine (eigenes Schweißgerät - 1 Minute)</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">0.1</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
<record id="prod_sonstige_dienstleistungen_nutzung" model="product.product">
|
|
||||||
<field name="name">Sonstige Dienstleistungen/Nutzung</field>
|
|
||||||
<field name="default_code" />
|
|
||||||
<field name="list_price">1.0</field>
|
|
||||||
<field name="available_in_pos">True</field>
|
|
||||||
<field ref="cat_maschinennutzung" name="categ_id" />
|
|
||||||
</record>
|
|
||||||
</odoo>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
/opt/odoo/odoo/odoo-bin shell -d hobbyhimmel < /home/odoo/custom_addons/open_workshop/data/export_products_and_categories.py
|
|
||||||
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
# /opt/odoo/odoo/odoo-bin shell -d <alte datebase> < export_categories.py
|
|
||||||
import csv
|
|
||||||
from odoo import api, SUPERUSER_ID
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
categories = env['product.category'].search([('name', 'in', ['Einweisungen', 'Maschinennutzung'])])
|
|
||||||
file_path = os.path.join(os.getcwd(), 'product_category.csv')
|
|
||||||
|
|
||||||
with open(file_path, 'w', newline='') as csvfile:
|
|
||||||
writer = csv.writer(csvfile)
|
|
||||||
writer.writerow(['id', 'name', 'parent_id/id'])
|
|
||||||
for cat in categories:
|
|
||||||
xml_id = f"open_workshop.cat_{cat.name.lower().replace(' ', '_')}"
|
|
||||||
parent_id = cat.parent_id and f"base.{cat.parent_id.xml_id}" or ''
|
|
||||||
writer.writerow([xml_id, cat.name, parent_id])
|
|
||||||
|
|
||||||
# Aufruf in odoo shell z. B.:
|
|
||||||
# env = odoo.api.Environment(cr, SUPERUSER_ID, {})
|
|
||||||
# export_categories(env)
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
# /opt/odoo/odoo/odoo-bin shell -d <alte datebase> < export_products.py
|
|
||||||
import csv
|
|
||||||
from odoo import api, SUPERUSER_ID
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Kategorien suchen
|
|
||||||
category_names = ['Einweisungen', 'Maschinennutzung']
|
|
||||||
categories = env['product.category'].search([('name', 'in', category_names)])
|
|
||||||
products = env['product.product'].search([('categ_id', 'in', categories.ids)])
|
|
||||||
|
|
||||||
file_path = os.path.join(os.getcwd(), 'product_product.csv')
|
|
||||||
|
|
||||||
with open(file_path, 'w', newline='') as csvfile:
|
|
||||||
writer = csv.writer(csvfile)
|
|
||||||
writer.writerow(['id', 'name', 'default_code', 'list_price', 'categ_id/id', 'available_in_pos'])
|
|
||||||
for prod in products:
|
|
||||||
cat_xml_id = f"open_workshop.cat_{prod.categ_id.name.lower().replace(' ', '_')}"
|
|
||||||
xml_id = f"open_workshop.prod_{prod.default_code or prod.name.lower().replace(' ', '_')}"
|
|
||||||
writer.writerow([
|
|
||||||
xml_id,
|
|
||||||
prod.name,
|
|
||||||
prod.default_code or '',
|
|
||||||
prod.list_price,
|
|
||||||
cat_xml_id,
|
|
||||||
'1' if prod.available_in_pos else '0'
|
|
||||||
])
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# /opt/odoo/odoo/odoo-bin shell -d <alte datebase> < export_products_and_categories.py
|
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
def xml_safe_id(name):
|
|
||||||
return name.lower().replace(' ', '_').replace('/', '_').replace('(', '').replace(')', '').replace('-', '_')
|
|
||||||
|
|
||||||
def export_categories_and_products(env):
|
|
||||||
root = ET.Element("odoo")
|
|
||||||
|
|
||||||
# Export Kategorien
|
|
||||||
categories = env['product.category'].search([
|
|
||||||
('name', 'in', ['Einweisungen', 'Maschinennutzung'])
|
|
||||||
])
|
|
||||||
for cat in categories:
|
|
||||||
record = ET.SubElement(root, "record", {
|
|
||||||
"id": f"cat_{xml_safe_id(cat.name)}",
|
|
||||||
"model": "product.category"
|
|
||||||
})
|
|
||||||
ET.SubElement(record, "field", name="name").text = cat.name
|
|
||||||
if cat.parent_id:
|
|
||||||
ET.SubElement(record, "field", name="parent_id", attrib={"ref": f"cat_{xml_safe_id(cat.parent_id.name)}"})
|
|
||||||
|
|
||||||
# Export Produkte
|
|
||||||
products = env['product.product'].search([
|
|
||||||
('categ_id.name', 'in', ['Einweisungen', 'Maschinennutzung'])
|
|
||||||
])
|
|
||||||
for product in products:
|
|
||||||
record = ET.SubElement(root, "record", {
|
|
||||||
"id": f"prod_{xml_safe_id(product.name)}",
|
|
||||||
"model": "product.product"
|
|
||||||
})
|
|
||||||
ET.SubElement(record, "field", name="name").text = product.name or ''
|
|
||||||
ET.SubElement(record, "field", name="default_code").text = product.default_code or ''
|
|
||||||
ET.SubElement(record, "field", name="list_price").text = str(product.list_price or 0.0)
|
|
||||||
ET.SubElement(record, "field", name="available_in_pos").text = "True"
|
|
||||||
if product.categ_id:
|
|
||||||
ET.SubElement(record, "field", name="categ_id", attrib={"ref": f"cat_{xml_safe_id(product.categ_id.name)}"})
|
|
||||||
|
|
||||||
tree = ET.ElementTree(root)
|
|
||||||
tree.write("data_product_and_categories.xml", encoding="utf-8", xml_declaration=True)
|
|
||||||
print("✅ XML export saved to data_product_and_categories.xml")
|
|
||||||
|
|
||||||
# Automatischer Start in odoo-bin shell
|
|
||||||
if 'env' in globals():
|
|
||||||
export_categories_and_products(env)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/opt/odoo/odoo/odoo-bin -d hobbyhimmel13_dev -i open_workshop --stop-after-init
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
<odoo>
|
|
||||||
<record id="machine_prusa_training_prod_einweisung_3d_drucker_prusa" model="ows.machine.training">
|
|
||||||
<field name="machine_id" ref="machine_prusa"/>
|
|
||||||
<field name="training_id" ref="prod_einweisung_3d_drucker_prusa"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_formatkreissaege_usage_prod_formatkreissäge_1_minute" model="ows.machine.product">
|
|
||||||
<field name="machine_id" ref="machine_formatkreissaege"/>
|
|
||||||
<field name="product_id" ref="prod_formatkreissäge_1_minute"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_bandsaege_holz_usage_prod_bandsäge_1_minute" model="ows.machine.product">
|
|
||||||
<field name="machine_id" ref="machine_bandsaege_holz"/>
|
|
||||||
<field name="product_id" ref="prod_bandsäge_1_minute"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_bandsaege_holz_training_prod_einweisung_bandsäge" model="ows.machine.training">
|
|
||||||
<field name="machine_id" ref="machine_bandsaege_holz"/>
|
|
||||||
<field name="training_id" ref="prod_einweisung_bandsäge"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_kreissaege_metall_usage_prod_formatkreissäge_1_minute" model="ows.machine.product">
|
|
||||||
<field name="machine_id" ref="machine_kreissaege_metall"/>
|
|
||||||
<field name="product_id" ref="prod_formatkreissäge_1_minute"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_bandsaege_metall_usage_prod_bandsäge_1_minute" model="ows.machine.product">
|
|
||||||
<field name="machine_id" ref="machine_bandsaege_metall"/>
|
|
||||||
<field name="product_id" ref="prod_bandsäge_1_minute"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_bandsaege_metall_training_prod_einweisung_bandsäge" model="ows.machine.training">
|
|
||||||
<field name="machine_id" ref="machine_bandsaege_metall"/>
|
|
||||||
<field name="training_id" ref="prod_einweisung_bandsäge"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_drehbank_usage_prod_drehbank_1_minute" model="ows.machine.product">
|
|
||||||
<field name="machine_id" ref="machine_drehbank"/>
|
|
||||||
<field name="product_id" ref="prod_drehbank_1_minute"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_drehbank_training_prod_einweisung_drehbank" model="ows.machine.training">
|
|
||||||
<field name="machine_id" ref="machine_drehbank"/>
|
|
||||||
<field name="training_id" ref="prod_einweisung_drehbank"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_fraese_usage_prod_cnc_fräse_1_minute" model="ows.machine.product">
|
|
||||||
<field name="machine_id" ref="machine_fraese"/>
|
|
||||||
<field name="product_id" ref="prod_cnc_fräse_1_minute"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_fraese_usage_prod_fräse___deckel_1_minute" model="ows.machine.product">
|
|
||||||
<field name="machine_id" ref="machine_fraese"/>
|
|
||||||
<field name="product_id" ref="prod_fräse___deckel_1_minute"/>
|
|
||||||
</record>
|
|
||||||
<record id="machine_fraese_training_prod_einweisung_metallfräse" model="ows.machine.training">
|
|
||||||
<field name="machine_id" ref="machine_fraese"/>
|
|
||||||
<field name="training_id" ref="prod_einweisung_metallfräse"/>
|
|
||||||
</record>
|
|
||||||
</odoo>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/opt/odoo/odoo/odoo-bin -d hobbyhimmel --update=open_workshop --dev=all --stop-after-init
|
|
||||||
2
log/.gitignore
vendored
Normal file
2
log/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
|
@ -8,6 +8,42 @@ import logging
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
_logger.info("✅ ows_models.py geladen")
|
_logger.info("✅ ows_models.py geladen")
|
||||||
|
|
||||||
|
class HREmployee(models.Model):
|
||||||
|
_inherit = 'hr.employee'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def anonymize_for_testsystem(self):
|
||||||
|
"""Benennt Admin-Angestellten um und archiviert alle anderen für das Testsystem."""
|
||||||
|
admin_user = self.env['res.users'].search([('name', '=', 'Administrator')], limit=1)
|
||||||
|
if not admin_user:
|
||||||
|
_logger.error("[OWS] Administrator-Benutzer nicht gefunden!")
|
||||||
|
return
|
||||||
|
|
||||||
|
_logger.info(f"[OWS] Administrator-Benutzer gefunden: {admin_user.name} (ID: {admin_user.id})")
|
||||||
|
|
||||||
|
# Suche auch archivierte Employees
|
||||||
|
admin_employee = self.with_context(active_test=False).search([('user_id', '=', admin_user.id)], limit=1)
|
||||||
|
|
||||||
|
if admin_employee:
|
||||||
|
# Administrator-Employee reaktivieren und umbenennen
|
||||||
|
admin_employee.write({
|
||||||
|
'name': 'TESTSYSTEM',
|
||||||
|
'job_title': 'Testumgebung',
|
||||||
|
'work_email': 'office@hobbyhimmel.de',
|
||||||
|
'work_phone': False,
|
||||||
|
'active': True, # Reaktivieren falls archiviert
|
||||||
|
})
|
||||||
|
_logger.info(f"[OWS] Admin-Angestellter reaktiviert und umbenannt: {admin_employee.name} (ID: {admin_employee.id})")
|
||||||
|
else:
|
||||||
|
_logger.warning("[OWS] Kein Angestellter für Administrator gefunden.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Alle anderen Angestellten archivieren (auch bereits archivierte berücksichtigen)
|
||||||
|
other_employees = self.with_context(active_test=False).search([('id', '!=', admin_employee.id)])
|
||||||
|
other_employees.write({'active': False})
|
||||||
|
_logger.info("[OWS] %d Angestellte archiviert.", len(other_employees))
|
||||||
|
|
||||||
|
|
||||||
class ResPartner(models.Model):
|
class ResPartner(models.Model):
|
||||||
_inherit = 'res.partner'
|
_inherit = 'res.partner'
|
||||||
_logger.info("✅ ows ResPartner geladen")
|
_logger.info("✅ ows ResPartner geladen")
|
||||||
|
|
@ -141,32 +177,50 @@ class ResPartner(models.Model):
|
||||||
def _compute_machine_access_html(self):
|
def _compute_machine_access_html(self):
|
||||||
areas = self.env['ows.machine.area'].search([], order="name")
|
areas = self.env['ows.machine.area'].search([], order="name")
|
||||||
for partner in self:
|
for partner in self:
|
||||||
html = "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem;'>"
|
html = ""
|
||||||
for area in areas:
|
for area in areas:
|
||||||
html += f"<div class='o_group' style='margin-bottom: 1em;'>"
|
html += f"""
|
||||||
html += f"<table class='o_group o_inner_group'>"
|
<div class="o_form_sheet">
|
||||||
html += f"<thead><tr><th>{area.name}</th><th></th><th>Datum</th><th>Gültig bis</th></tr></thead><tbody>"
|
<h3 class="o_form_label">{area.name}</h3>
|
||||||
|
<table class="table table-sm table-bordered o_form_table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Maschine</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Datum</th>
|
||||||
|
<th>Gültig bis</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
"""
|
||||||
|
|
||||||
machines = self.env['ows.machine'].search([('area_id', '=', area.id)], order="name")
|
machines = self.env['ows.machine'].search([('area_id', '=', area.id)], order="name")
|
||||||
|
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
access = self.env['ows.machine.access'].search([
|
access = self.env['ows.machine.access'].search([
|
||||||
('partner_id', '=', partner.id),
|
('partner_id', '=', partner.id),
|
||||||
('machine_id', '=', machine.id),
|
('machine_id', '=', machine.id),
|
||||||
], limit=1)
|
], limit=1)
|
||||||
icon = "<span class='fa fa-check text-success'></span>" if access else "<span class='fa fa-times text-danger'></span>"
|
icon = '<span class="text-success fa fa-check"/>' if access else '<span class="text-danger fa fa-times"/>'
|
||||||
date_granted = access.date_granted.strftime('%Y-%m-%d') if access and access.date_granted else ""
|
date_granted = access.date_granted.strftime('%Y-%m-%d') if access and access.date_granted else "-"
|
||||||
date_expiry = access.date_expiry.strftime('%Y-%m-%d') if access and access.date_expiry else ""
|
date_expiry = access.date_expiry.strftime('%Y-%m-%d') if access and access.date_expiry else "-"
|
||||||
|
|
||||||
html += f"""
|
html += f"""
|
||||||
<tr>
|
<tr>
|
||||||
<td class='o_td_label'><label>{machine.name}</label></td>
|
<td>{machine.name}</td>
|
||||||
<td class='o_td_field'>{icon}</td>
|
<td>{icon}</td>
|
||||||
<td class='o_td_field'>{date_granted}</td>
|
<td>{date_granted}</td>
|
||||||
<td class='o_td_field'>{date_expiry}</td>
|
<td>{date_expiry}</td>
|
||||||
</tr>
|
</tr>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
html += "</tbody></table></div>"
|
html += "</tbody></table></div>"
|
||||||
html += "</div>"
|
|
||||||
partner.machine_access_html = html
|
partner.machine_access_html = html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def migrate_existing_partners(self):
|
def migrate_existing_partners(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -283,6 +337,31 @@ class ResPartner(models.Model):
|
||||||
|
|
||||||
_logger.info(f"[OWS Migration] ✅ Maschinenfreigaben erstellt: {count_created}")
|
_logger.info(f"[OWS Migration] ✅ Maschinenfreigaben erstellt: {count_created}")
|
||||||
self.env.cr.commit()
|
self.env.cr.commit()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def archive_partners_without_users(self):
|
||||||
|
"""
|
||||||
|
Archiviert alle Partner (res.partner), die keine Benutzer (res.users) sind.
|
||||||
|
"""
|
||||||
|
Partner = self.env['res.partner']
|
||||||
|
User = self.env['res.users']
|
||||||
|
|
||||||
|
# IDs aller Partner, die ein Benutzerkonto haben
|
||||||
|
user_partner_ids = User.search([]).mapped('partner_id').ids
|
||||||
|
|
||||||
|
# Alle Partner ohne Benutzerkonto
|
||||||
|
partners_to_archive = Partner.search([
|
||||||
|
('id', 'not in', user_partner_ids),
|
||||||
|
('active', '=', True),
|
||||||
|
])
|
||||||
|
|
||||||
|
count = len(partners_to_archive)
|
||||||
|
partners_to_archive.write({'active': False})
|
||||||
|
for p in partners_to_archive:
|
||||||
|
_logger.debug(f"[OWS] Archiviert Partner: {p.name} (ID {p.id})")
|
||||||
|
_logger.info(f"[OWS] Archiviert {count} Partner ohne Benutzerkonto.")
|
||||||
|
self.env.cr.commit()
|
||||||
|
|
||||||
|
|
||||||
class OwsUser(models.Model):
|
class OwsUser(models.Model):
|
||||||
_name = 'ows.user'
|
_name = 'ows.user'
|
||||||
|
|
@ -307,15 +386,57 @@ class OwsUser(models.Model):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
AVAILABLE_COLORS = [
|
||||||
|
('#000000', 'schwarz'),
|
||||||
|
('#ff0000', 'Rot'),
|
||||||
|
('#E91E63', 'Pink'),
|
||||||
|
('#9C27B0', 'Lila'),
|
||||||
|
('#3F51B5', 'Indigo'),
|
||||||
|
('#0000ff', 'Blau'),
|
||||||
|
('#008000', 'Grün'),
|
||||||
|
('#ffff00', 'Gelb'),
|
||||||
|
('#FF9800', 'Orange'),
|
||||||
|
('#795548', 'Braun'),
|
||||||
|
('#ffffff', 'Weiss'),
|
||||||
|
]
|
||||||
|
|
||||||
class OwsMachineArea(models.Model):
|
class OwsMachineArea(models.Model):
|
||||||
_name = 'ows.machine.area'
|
_name = 'ows.machine.area'
|
||||||
_table = "ows_machine_area"
|
_table = 'ows_machine_area'
|
||||||
_description = 'OWS: Maschinenbereich'
|
_description = 'OWS: Maschinenbereich'
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
|
|
||||||
name = fields.Char(required=True, translate=True)
|
name = fields.Char(string="Name", required=True, translate=True)
|
||||||
#color = fields.Integer(string="Farbe")
|
|
||||||
color_hex = fields.Char(string="Farbe (Hex)", help="Hex-Farbcode wie #FF0000 für Rot")
|
color_hex = fields.Selection(
|
||||||
|
selection=AVAILABLE_COLORS,
|
||||||
|
string="Farbe (Hex)",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
color_hex_value = fields.Char(
|
||||||
|
string="Farbcode",
|
||||||
|
compute='_compute_color_hex_value',
|
||||||
|
store=False
|
||||||
|
)
|
||||||
|
|
||||||
|
color_name = fields.Char(
|
||||||
|
string="Farbname",
|
||||||
|
compute='_compute_color_name',
|
||||||
|
store=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('color_hex')
|
||||||
|
def _compute_color_hex_value(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.color_hex_value = rec.color_hex or ''
|
||||||
|
|
||||||
|
@api.depends('color_hex')
|
||||||
|
def _compute_color_name(self):
|
||||||
|
label_dict = dict(AVAILABLE_COLORS)
|
||||||
|
for rec in self:
|
||||||
|
rec.color_name = label_dict.get(rec.color_hex, 'Unbekannt')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class OwsMachine(models.Model):
|
class OwsMachine(models.Model):
|
||||||
|
|
@ -325,13 +446,37 @@ class OwsMachine(models.Model):
|
||||||
|
|
||||||
name = fields.Char(required=True, translate=True)
|
name = fields.Char(required=True, translate=True)
|
||||||
code = fields.Char(required=True, help="Eindeutiger Kurzcode, z.B. 'lasercutter'")
|
code = fields.Char(required=True, help="Eindeutiger Kurzcode, z.B. 'lasercutter'")
|
||||||
|
category = fields.Selection([
|
||||||
|
('green', 'Kategorie 1: grün'),
|
||||||
|
('yellow', 'Kategorie 2: gelb'),
|
||||||
|
('red', 'Kategorie 3: rot'),
|
||||||
|
], string="Sicherheitskategorie", required=True, default='red', help="Sicherheitsrelevante Maschinenkategorie:\n"
|
||||||
|
"- grün: keine Einweisungspflicht\n"
|
||||||
|
"- gelb: empfohlene Einweisung\n"
|
||||||
|
"- rot: Einweisung zwingend erforderlich")
|
||||||
|
|
||||||
|
category_icon = fields.Char(string="Kategorie-Symbol", compute="_compute_category_icon", store=False)
|
||||||
|
|
||||||
|
@api.depends('category')
|
||||||
|
def _compute_category_icon(self):
|
||||||
|
for rec in self:
|
||||||
|
icon_map = {
|
||||||
|
'green': '🟢',
|
||||||
|
'yellow': '🟡',
|
||||||
|
'red': '🔴',
|
||||||
|
}
|
||||||
|
rec.category_icon = icon_map.get(rec.category, '⚪')
|
||||||
|
|
||||||
description = fields.Text()
|
description = fields.Text()
|
||||||
active = fields.Boolean(default=True)
|
active = fields.Boolean(default=True)
|
||||||
area_id = fields.Many2one('ows.machine.area', string='Bereich')
|
area_id = fields.Many2one('ows.machine.area', string='Bereich', help="Bereich, in dem die Maschine oder das Gerät steht.")
|
||||||
product_ids = fields.One2many('ows.machine.product', 'machine_id', string="Nutzungsprodukte")
|
product_ids = fields.One2many('ows.machine.product', 'machine_id', string="Nutzungsprodukte")
|
||||||
product_names = fields.Char(string="Nutzungsprodukte Liste", compute="_compute_product_using_names", store=False,)
|
product_names = fields.Char(string="Liste der Nutzungsprodukte", compute="_compute_product_using_names", store=False,)
|
||||||
training_ids = fields.One2many('ows.machine.training', 'machine_id', string="Einweisungsprodukte")
|
training_ids = fields.One2many('ows.machine.training', 'machine_id', string="Einweisungsprodukte")
|
||||||
training_names = fields.Char(string="Einweisungsprodukte Liste", compute="_compute_product_training_names", store=False,)
|
training_names = fields.Char(string="Liste der Einweisungsprodukte", compute="_compute_product_training_names", store=False,)
|
||||||
|
storage_location = fields.Char(string="Lagerort", help="Lagerort der Maschine oder des Geräts.")
|
||||||
|
purchase_price = fields.Float(string="Kaufpreis", help="Kaufpreis der Maschine oder des Geräts.")
|
||||||
|
purchase_date = fields.Date(string="Kaufdatum", help="Kaufdatum der Maschine oder des Geräts.")
|
||||||
|
|
||||||
@api.depends('product_ids.product_id.name')
|
@api.depends('product_ids.product_id.name')
|
||||||
def _compute_product_using_names(self):
|
def _compute_product_using_names(self):
|
||||||
|
|
@ -354,12 +499,34 @@ class OwsMachine(models.Model):
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_access_list_grouped(self, partner_id):
|
def get_access_list_grouped(self, partner_id):
|
||||||
|
"""
|
||||||
|
Gibt eine gruppierte Liste von Maschinenzugängen für einen bestimmten Partner zurück. Diese Funktion wird in
|
||||||
|
Odoo POS Frontend verwendet um die Ansicht zu erzeugen auf Welche Maschinen der Partner Zugriff hat.
|
||||||
|
|
||||||
|
Für einen gegebenen Partner (über die partner_id) werden alle Maschinenbereiche (areas) abgefragt.
|
||||||
|
Für jeden Bereich wird geprüft, auf welche Maschinen der Partner Zugriff hat. Das Ergebnis wird
|
||||||
|
als Liste von Bereichen mit jeweils zugehörigen Maschinen und Zugriffsstatus zurückgegeben.
|
||||||
|
|
||||||
|
Zusätzlich werden sicherheitsrelevante Informationen des Partners (wie Sicherheitsunterweisung,
|
||||||
|
Sicherheits-ID, RFID-Karte und Geburtstag) aus dem zugehörigen ows_user ermittelt und mitgeliefert.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
partner_id (int): Die ID des Partners, für den die Zugriffsübersicht erstellt werden soll.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Ein Dictionary mit folgenden Schlüsseln:
|
||||||
|
- 'access_by_area': Liste von Bereichen mit Maschinen und Zugriffsstatus.
|
||||||
|
- 'security_briefing': Sicherheitsunterweisung des Nutzers (bool oder False).
|
||||||
|
- 'security_id': Sicherheits-ID des Nutzers (str oder '').
|
||||||
|
- 'rfid_card': RFID-Kartennummer des Nutzers (str oder '').
|
||||||
|
- 'birthday': Geburtstag des Nutzers (str oder '').
|
||||||
|
"""
|
||||||
|
partner = self.env['res.partner'].browse(partner_id)
|
||||||
areas = self.env['ows.machine.area'].search([], order="name")
|
areas = self.env['ows.machine.area'].search([], order="name")
|
||||||
_logger.info("🔍 Maschinenbereiche: %s", areas.mapped('name'))
|
_logger.info("Access RPC called with partner_id=%s", partner_id)
|
||||||
_logger.info("🔍 Partner_id: %s", partner_id)
|
access_by_area = []
|
||||||
res = []
|
|
||||||
for area in areas:
|
for area in areas:
|
||||||
machines = self.search([('area_id', '=', area.id)], order="name")
|
machines = self.search([('area_id', '=', area.id), ('category', '=', 'red')], order="name")
|
||||||
machine_list = []
|
machine_list = []
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
has_access = bool(self.env['ows.machine.access'].search([
|
has_access = bool(self.env['ows.machine.access'].search([
|
||||||
|
|
@ -370,12 +537,22 @@ class OwsMachine(models.Model):
|
||||||
'name': machine.name,
|
'name': machine.name,
|
||||||
'has_access': has_access,
|
'has_access': has_access,
|
||||||
})
|
})
|
||||||
res.append({
|
if machine_list:
|
||||||
'area': area.name,
|
access_by_area.append({
|
||||||
'color_hex': area.color_hex or '#000000',
|
'area': area.name,
|
||||||
'machines': machine_list
|
'color_hex': area.color_hex or '#000000',
|
||||||
})
|
'machines': machine_list
|
||||||
return res
|
})
|
||||||
|
|
||||||
|
user = partner.ows_user_id[:1]
|
||||||
|
return {
|
||||||
|
'access_by_area': access_by_area,
|
||||||
|
'security_briefing': user.security_briefing if user else False,
|
||||||
|
'security_id': user.security_id if user else '',
|
||||||
|
'rfid_card': user.rfid_card if user else '',
|
||||||
|
'birthday': user.birthday if user else '',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class OwsMachineAccess(models.Model):
|
class OwsMachineAccess(models.Model):
|
||||||
|
|
@ -398,7 +575,7 @@ class OwsMachineAccess(models.Model):
|
||||||
class OwsMachineProduct(models.Model):
|
class OwsMachineProduct(models.Model):
|
||||||
_name = 'ows.machine.product'
|
_name = 'ows.machine.product'
|
||||||
_table = 'ows_machine_product'
|
_table = 'ows_machine_product'
|
||||||
_description = 'OWS: Zurordnung Produkt der Nutzung zur die Maschine'
|
_description = 'OWS: Zuordnung Produkt der Nutzung zu der Maschine'
|
||||||
|
|
||||||
product_id = fields.Many2one('product.product', required=True, domain=[('available_in_pos', '=', True)], ondelete='cascade')
|
product_id = fields.Many2one('product.product', required=True, domain=[('available_in_pos', '=', True)], ondelete='cascade')
|
||||||
machine_id = fields.Many2one('ows.machine', required=True, ondelete='cascade')
|
machine_id = fields.Many2one('ows.machine', required=True, ondelete='cascade')
|
||||||
|
|
@ -406,7 +583,7 @@ class OwsMachineProduct(models.Model):
|
||||||
class OwsMachineTraining(models.Model):
|
class OwsMachineTraining(models.Model):
|
||||||
_name = 'ows.machine.training'
|
_name = 'ows.machine.training'
|
||||||
_table = 'ows_machine_training'
|
_table = 'ows_machine_training'
|
||||||
_description = 'OWS: Zurordnung Produkt der Einweisung zur die Maschine'
|
_description = 'OWS: Zuordnung Produkt der Einweisung zu der Maschine'
|
||||||
|
|
||||||
training_id = fields.Many2one('product.product', required=True, domain=[('available_in_pos', '=', True)], ondelete='cascade')
|
training_id = fields.Many2one('product.product', required=True, domain=[('available_in_pos', '=', True)], ondelete='cascade')
|
||||||
machine_id = fields.Many2one('ows.machine', required=True, ondelete='cascade')
|
machine_id = fields.Many2one('ows.machine', required=True, ondelete='cascade')
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
#import debugpy
|
#import debugpy
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
_logger.info("✅ pos_order.py geladen")
|
_logger.info("✅ pos_order.py geladen Test 2")
|
||||||
|
|
||||||
# debugpy.listen(("0.0.0.0", 5678))
|
#debugpy.listen(("0.0.0.0", 5678))
|
||||||
print("✅ debugpy wartet auf Verbindung (Port 5678) ...")
|
print("✅ debugpy wartet auf Verbindung (Port 5678) ...")
|
||||||
# Optional: Starte erst, wenn VS Code verbunden ist
|
# Optional: Starte erst, wenn VS Code verbunden ist
|
||||||
#debugpy.wait_for_client()
|
#debugpy.wait_for_client()
|
||||||
|
|
@ -14,15 +15,15 @@ print("✅ debugpy wartet auf Verbindung (Port 5678) ...")
|
||||||
class PosOrder(models.Model):
|
class PosOrder(models.Model):
|
||||||
_inherit = 'pos.order'
|
_inherit = 'pos.order'
|
||||||
|
|
||||||
def _process_order(self, order, draft, existing_order):
|
def _process_order(self, order, existing_order):
|
||||||
pos_order_id = super(PosOrder, self)._process_order(order, draft, existing_order)
|
_logger.info("🚨 DEBUG: _process_order wurde aufgerufen mit order: %s", order.get('name', 'unbekannt'))
|
||||||
|
pos_order_id = super(PosOrder, self)._process_order(order, existing_order)
|
||||||
pos_order = self.browse(pos_order_id)
|
pos_order = self.browse(pos_order_id)
|
||||||
|
|
||||||
training_products = self.env['ows.machine.training'].search([])
|
training_products = self.env['ows.machine.training'].search([])
|
||||||
product_map = {
|
product_map = defaultdict(list)
|
||||||
tp.training_id.product_tmpl_id.id: tp.machine_id.id
|
for tp in training_products:
|
||||||
for tp in training_products
|
product_map[tp.training_id.product_tmpl_id.id].append(tp.machine_id.id)
|
||||||
}
|
|
||||||
|
|
||||||
partner = pos_order.partner_id
|
partner = pos_order.partner_id
|
||||||
if not partner:
|
if not partner:
|
||||||
|
|
@ -31,15 +32,13 @@ class PosOrder(models.Model):
|
||||||
|
|
||||||
for line in pos_order.lines:
|
for line in pos_order.lines:
|
||||||
product_tmpl_id = line.product_id.product_tmpl_id.id
|
product_tmpl_id = line.product_id.product_tmpl_id.id
|
||||||
machine_id = product_map.get(product_tmpl_id)
|
machine_ids = product_map.get(product_tmpl_id, [])
|
||||||
|
_logger.info("🔍 Prüfe Produkt %s → Maschinen IDs: %s", line.product_id.display_name, machine_ids)
|
||||||
_logger.info("🔍 Prüfe Produkt %s → Maschine ID: %s", line.product_id.display_name, machine_id)
|
for machine_id in machine_ids:
|
||||||
|
|
||||||
if machine_id:
|
|
||||||
already_exists = self.env['ows.machine.access'].search([
|
already_exists = self.env['ows.machine.access'].search([
|
||||||
('partner_id', '=', partner.id),
|
('partner_id', '=', partner.id),
|
||||||
('machine_id', '=', machine_id)
|
('machine_id', '=', machine_id)
|
||||||
])
|
], limit=1)
|
||||||
if not already_exists:
|
if not already_exists:
|
||||||
self.env['ows.machine.access'].create({
|
self.env['ows.machine.access'].create({
|
||||||
'partner_id': partner.id,
|
'partner_id': partner.id,
|
||||||
|
|
|
||||||
11
open_workshop.code-workspace
Normal file
11
open_workshop.code-workspace
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../../usr/lib/python3/dist-packages/odoo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from odoo import SUPERUSER_ID
|
|
||||||
from odoo.api import Environment
|
|
||||||
#from . import import_machine_products
|
|
||||||
import logging
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def run_migration(cr, registry):
|
|
||||||
"""
|
|
||||||
Wird nach der Modulinstallation automatisch ausgeführt.
|
|
||||||
Migriert vorhandene res.partner-Einträge zu ows.user.
|
|
||||||
"""
|
|
||||||
env = Environment(cr, SUPERUSER_ID, {})
|
|
||||||
|
|
||||||
_logger.info("[OWS] Starte automatische Partner-Migration bei Modulinstallation...")
|
|
||||||
try:
|
|
||||||
env['res.partner'].migrate_existing_partners()
|
|
||||||
_logger.info("[OWS] Automatische Partner-Migration abgeschlossen.")
|
|
||||||
except Exception as e:
|
|
||||||
_logger.error(f"[OWS] Fehler bei automatischer Partner-Migration: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
env['res.partner'].migrate_machine_access_from_old_fields()
|
|
||||||
_logger.info("[OWS] Automatische Felder-Migration für Maschinenfreigaben in res.partner.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
_logger.error(f"[OWS] Fehler bei automatischer Felder-Migration: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#import_machine_products.run_import(cr, registry)
|
|
||||||
|
|
||||||
|
|
||||||
''' Funktioniert nicht:
|
|
||||||
try:
|
|
||||||
module = env['ir.module.module'].search([('name', '=', 'vvow_einweisungen')], limit=1)
|
|
||||||
if module and module.state != 'uninstalled':
|
|
||||||
_logger.info("[OWS] Deinstalliere altes Modul vvow_einweisungen...")
|
|
||||||
module.button_immediate_uninstall()
|
|
||||||
except Exception as e:
|
|
||||||
_logger.error(f"[OWS] Fehler bei deinstallieren von vvow_einweisungen: {e}")
|
|
||||||
'''
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
from odoo import SUPERUSER_ID
|
|
||||||
|
|
||||||
def fix_missing_pos_order_partners(cr, registry):
|
|
||||||
"""
|
|
||||||
Findet POS-Bestellungen mit fehlendem Partner und fügt Dummy-Partner
|
|
||||||
direkt per SQL mit der passenden ID ein.
|
|
||||||
"""
|
|
||||||
cr.execute("""
|
|
||||||
SELECT DISTINCT partner_id FROM pos_order
|
|
||||||
WHERE partner_id IS NOT NULL
|
|
||||||
AND partner_id NOT IN (SELECT id FROM res_partner)
|
|
||||||
""")
|
|
||||||
missing_ids = [row[0] for row in cr.fetchall()]
|
|
||||||
print(f"Superuser: {SUPERUSER_ID}")
|
|
||||||
if not missing_ids:
|
|
||||||
print("✅ Keine fehlenden Partner gefunden.")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"🚧 Fehlende Partner-IDs: {missing_ids}")
|
|
||||||
|
|
||||||
# Direkter SQL-Insert für res_partner
|
|
||||||
for pid in missing_ids:
|
|
||||||
print(f"➕ Erzeuge Dummy-Partner mit ID {pid}")
|
|
||||||
cr.execute("""
|
|
||||||
INSERT INTO res_partner (id, name, customer_rank, create_uid, create_date, write_uid, write_date)
|
|
||||||
VALUES (%s, %s, %s, %s, now(), %s, now())
|
|
||||||
ON CONFLICT (id) DO NOTHING
|
|
||||||
""", (pid, f"Fehlender Partner {pid}", 1, SUPERUSER_ID, SUPERUSER_ID))
|
|
||||||
|
|
||||||
# Sequenz zurücksetzen, um ID-Kollision zu verhindern
|
|
||||||
cr.execute("""
|
|
||||||
SELECT setval('res_partner_id_seq', (SELECT MAX(id) FROM res_partner))
|
|
||||||
""")
|
|
||||||
print("✅ Alle fehlenden Partner ergänzt.")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Automatischer Start in odoo-bin shell
|
|
||||||
if 'env' in globals():
|
|
||||||
fix_missing_pos_order_partners(env.cr, env.registry)
|
|
||||||
env.cr.commit()
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
# import_machine_products.py
|
|
||||||
# odoo-bin shell -d deine_datenbank < import_machine_products.py
|
|
||||||
import logging
|
|
||||||
from odoo import api, SUPERUSER_ID
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Mapping von product_id > machine_id
|
|
||||||
TRAINING_MAPPING = {
|
|
||||||
'16':'1',
|
|
||||||
'11':'2',
|
|
||||||
'10':'4',
|
|
||||||
'117':'5',
|
|
||||||
'14':'6',
|
|
||||||
'12':'7',
|
|
||||||
'15':'8',
|
|
||||||
'118':'10',
|
|
||||||
'118':'11',
|
|
||||||
'118':'12',
|
|
||||||
'18':'15',
|
|
||||||
'18':'16',
|
|
||||||
'13':'18',
|
|
||||||
'17':'19',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Mapping von product_id > machine_id
|
|
||||||
USAGE_MAPPING = {
|
|
||||||
'50':'1',
|
|
||||||
'49':'2',
|
|
||||||
'49':'3',
|
|
||||||
'49':'4',
|
|
||||||
'53':'5',
|
|
||||||
'52':'7',
|
|
||||||
'55':'8',
|
|
||||||
'59':'15',
|
|
||||||
'60':'15',
|
|
||||||
'59':'16',
|
|
||||||
'60':'16',
|
|
||||||
'57':'18',
|
|
||||||
'58':'19',
|
|
||||||
}
|
|
||||||
|
|
||||||
def run_import(cr, registry):
|
|
||||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
|
||||||
|
|
||||||
machine_model = env['ows.machine']
|
|
||||||
product_model = env['product.product']
|
|
||||||
training_model = env['ows.machine.training']
|
|
||||||
usage_model = env['ows.machine.product']
|
|
||||||
|
|
||||||
created_training = 0
|
|
||||||
created_usage = 0
|
|
||||||
|
|
||||||
for product_id, machine_id in TRAINING_MAPPING.items():
|
|
||||||
machine = machine_model.search([('id', '=', machine_id)], limit=1)
|
|
||||||
product = product_model.search([('id', '=', product_id)], limit=1)
|
|
||||||
if machine and product:
|
|
||||||
training_model.create({
|
|
||||||
'machine_id': machine.id,
|
|
||||||
'training_id': product.id,
|
|
||||||
})
|
|
||||||
created_training += 1
|
|
||||||
|
|
||||||
for product_id, machine_id in USAGE_MAPPING.items():
|
|
||||||
machine = machine_model.search([('id', '=', machine_id)], limit=1)
|
|
||||||
product = product_model.search([('id', '=', product_id)], limit=1)
|
|
||||||
if machine and product:
|
|
||||||
usage_model.create({
|
|
||||||
'machine_id': machine.id,
|
|
||||||
'product_id': product.id,
|
|
||||||
})
|
|
||||||
created_usage += 1
|
|
||||||
|
|
||||||
_logger.info(f"[OWS Import] ✅ Trainings-Zuordnungen erstellt: {created_training}, Nutzungs-Zuordnungen erstellt: {created_usage}")
|
|
||||||
env.cr.commit()
|
|
||||||
|
|
||||||
# Automatischer Start in odoo-bin shell
|
|
||||||
if 'env' in globals():
|
|
||||||
run_import(env.cr, env.registry)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/opt/odoo/odoo/odoo-bin -d hobbyhimmel13_dev -i base,sale,pos_time_based_products,wk_coupons,pos_coupons,open_workshop --stop-after-init --load-language=de_DE
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
BACKUP_DIR=/root # Verzeichnis, in dem das Backup temporär gespeichert wird.
|
|
||||||
#MONITOR_URL='https://cronitor.link/p/c6debfe4e00f46fe9eb430ab9d2b2588/7PPIpd' # Cronitor-URL zur Überwachung des Skriptstatus.
|
|
||||||
ODOO_DATABASE=hobbyhimmel # Der Name der Odoo-Datenbank, die wiederhergestellt werden soll.
|
|
||||||
DEFAULT_URL="http://hobbybackend2.fritz.box:9013" # Standard-URL für die Odoo-Datenbank-Backup-API.
|
|
||||||
URL="${1:-$DEFAULT_URL}" # Falls ein Argument übergeben wird, wird es als URL genutzt, sonst bleibt die Standard-URL.
|
|
||||||
BACKUP_NAME=${ODOO_DATABASE}.latest.zip # Der Name der Backup-Datei, wobei das aktuelle Datum an den Namen angehängt wird.
|
|
||||||
remote_directory=/www/htdocs/${sftp_user}/odoo # Das Verzeichnis auf dem Remote-SFTP-Server, in dem die Backups gespeichert werden.
|
|
||||||
|
|
||||||
# Function to report failure to Cronitor with step
|
|
||||||
# Diese Funktion meldet einen Fehler an Cronitor und beendet das Skript.
|
|
||||||
# Parameter: $1 - Der Schrittname, der beim Fehlschlag gemeldet wird.
|
|
||||||
report_failure() {
|
|
||||||
local step=$1 # Schrittname wird als erster Parameter übergeben.
|
|
||||||
echo "Error during $step" # Meldet den Fehler.
|
|
||||||
#curl "$MONITOR_URL?state=fail&message=$step" # Meldet den Fehlerstatus an Cronitor.
|
|
||||||
exit 1 # Beendet das Skript mit Exit-Code 1 (Fehler).
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Restoring Odoo database from backup..." # Meldet den Beginn der Wiederherstellung der Odoo-Datenbank.
|
|
||||||
echo "Verbindungsdaten: ${sftp_password}, ssh-${sftp_user}, ${sftp_host}"
|
|
||||||
|
|
||||||
cd /root # Wechselt in das Home-Verzeichnis des Benutzers.
|
|
||||||
# Holt die verschlüsselte Backup-Datei vom Remote-SFTP-Server in das lokale Verzeichnis.
|
|
||||||
sshpass -p "${sftp_password}" sftp -i ~/.ssh/id_rsa ssh-${sftp_user}@${sftp_host}:${remote_directory} <<EOF
|
|
||||||
get ${BACKUP_NAME}.gpg
|
|
||||||
get ${BACKUP_NAME}
|
|
||||||
EOF
|
|
||||||
# Prüft, ob der vorherige Befehl erfolgreich war, sonst bricht das Skript ab.
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
report_failure "SFTP transfer"
|
|
||||||
fi
|
|
||||||
cd /root
|
|
||||||
|
|
||||||
# Backup entschlüsseln -> Funktioniert nicht, weil im Passwort ein ´ enthalten ist
|
|
||||||
#rm ${BACKUP_NAME}
|
|
||||||
#gpg --batch --yes --passphrase "{$gpg_password}" --output "${BACKUP_DIR}/${BACKUP_NAME}" --decrypt "${BACKUP_DIR}/${BACKUP_NAME}.gpg"
|
|
||||||
|
|
||||||
echo "Admin password: ${ADMIN_PASSWORD}" # Meldet das Admin-Passwort.
|
|
||||||
echo "Database name: ${ODOO_DATABASE}" # Meldet den Namen der Odoo-Datenbank.
|
|
||||||
echo "Backup name: ${BACKUP_NAME}" # Meldet den Namen der Backup-Datei.
|
|
||||||
echo "URL: ${URL}" # Meldet die URL der Odoo-Datenbank-Backup-API.
|
|
||||||
echo "Backup directory: ${BACKUP_DIR}" # Meldet das Verzeichnis, in dem das Backup gespeichert wird.
|
|
||||||
echo "Delete database: ${ODOO_DATABASE}" # Meldet, dass die Datenbank gelöscht wird.
|
|
||||||
curl -X POST -s \
|
|
||||||
-F "master_pwd=${ADMIN_PASSWORD}" \
|
|
||||||
-F "name=${ODOO_DATABASE}" \
|
|
||||||
${URL}/web/database/drop || report_failure "Database deletion"
|
|
||||||
|
|
||||||
echo "Restoring database..." # Meldet den Beginn der Wiederherstellung der Datenbank.
|
|
||||||
curl \
|
|
||||||
-F "master_pwd=${ADMIN_PASSWORD}" \
|
|
||||||
-F "name=${ODOO_DATABASE}" \
|
|
||||||
-F "backup_file=@${BACKUP_DIR}/${BACKUP_NAME}" \
|
|
||||||
-F "backup_format=zip" \
|
|
||||||
-F "copy=true" \
|
|
||||||
${URL}/web/database/restore || report_failure "Database restoration"
|
|
||||||
|
|
||||||
# If everything was successful, report completion
|
|
||||||
# Meldet den erfolgreichen Abschluss des Skripts an Cronitor.
|
|
||||||
#curl "$MONITOR_URL?state=complete"
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import xmlrpc.client
|
|
||||||
|
|
||||||
# ----------------------
|
|
||||||
# KONFIGURATION AUS UMGEBUNG
|
|
||||||
# ----------------------
|
|
||||||
url = os.getenv("ODOO_URL", "http://localhost:8069")
|
|
||||||
db = os.getenv("ODOO_DB", "hobbyhimmel")
|
|
||||||
username = os.getenv("ODOO_USERNAME")
|
|
||||||
password = os.getenv("ODOO_PASSWORD")
|
|
||||||
|
|
||||||
# ----------------------
|
|
||||||
# PRÜFUNG DER KONFIGURATION
|
|
||||||
# ----------------------
|
|
||||||
if not all([url, db, username, password]):
|
|
||||||
print("❌ Fehler: ODOO_URL, ODOO_DB, ODOO_USERNAME und ODOO_PASSWORD müssen als Umgebungsvariablen gesetzt sein.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# ----------------------
|
|
||||||
# PARAMETER PRÜFEN
|
|
||||||
# ----------------------
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print("❌ Fehler: Modulname muss als Parameter übergeben werden.")
|
|
||||||
print("👉 Beispiel: python3 uninstall_rpc.py vvow_einweisungen")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
module_name = sys.argv[1]
|
|
||||||
|
|
||||||
# ----------------------
|
|
||||||
# AUTHENTIFIZIERUNG
|
|
||||||
# ----------------------
|
|
||||||
common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common")
|
|
||||||
uid = common.authenticate(db, username, password, {})
|
|
||||||
|
|
||||||
if not uid:
|
|
||||||
print("❌ Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# ----------------------
|
|
||||||
# MODUL SUCHEN & DEINSTALLIEREN
|
|
||||||
# ----------------------
|
|
||||||
models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object")
|
|
||||||
|
|
||||||
ids = models.execute_kw(db, uid, password,
|
|
||||||
'ir.module.module', 'search',
|
|
||||||
[[['name', '=', module_name], ['state', '=', 'installed']]],
|
|
||||||
{'limit': 1}
|
|
||||||
)
|
|
||||||
|
|
||||||
if ids:
|
|
||||||
print(f"📦 Deinstalliere Modul: {module_name}")
|
|
||||||
models.execute_kw(db, uid, password,
|
|
||||||
'ir.module.module', 'button_immediate_uninstall',
|
|
||||||
[ids]
|
|
||||||
)
|
|
||||||
print("✅ Deinstallation abgeschlossen.")
|
|
||||||
else:
|
|
||||||
print(f"ℹ️ Modul '{module_name}' ist nicht installiert oder nicht vorhanden.")
|
|
||||||
9
static/src/css/category_color.css
Normal file
9
static/src/css/category_color.css
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
.category-color-circle {
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-left: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,132 +0,0 @@
|
||||||
/* This file is based on the original code from the OrderWidget in Odoo Point of Sale module (screen.js).
|
|
||||||
* It has been modified to create a sidebar that displays machine access information for the selected customer.*/
|
|
||||||
|
|
||||||
odoo.define('open_workshop.machine_access_sidebar', function (require) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const DUMMY_PARTNER = {
|
|
||||||
id: 1,
|
|
||||||
name: "AAAA Max Mustermann",
|
|
||||||
security_briefing: false,
|
|
||||||
security_id: null,
|
|
||||||
create_date: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rpc = require('web.rpc');
|
|
||||||
var screens = require('point_of_sale.screens');
|
|
||||||
var chrome = require('point_of_sale.chrome');
|
|
||||||
var core = require('web.core');
|
|
||||||
var QWeb = core.qweb;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var MachineAccessSidebar = screens.ScreenWidget.extend({
|
|
||||||
template: 'MachineAccessSidebar',
|
|
||||||
|
|
||||||
init: function(parent, options) {
|
|
||||||
this._super(parent, options);
|
|
||||||
this.partner = null;
|
|
||||||
this.pos.bind('change:selectedOrder', this.bind_order_events, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
show: function() {
|
|
||||||
this._super();
|
|
||||||
this.render_access();
|
|
||||||
},
|
|
||||||
|
|
||||||
update_machine_access: function(partner) {
|
|
||||||
console.log("🔁 Sidebar aktualisiert Maschinenfreigaben für Partner:", partner);
|
|
||||||
this.partner = partner;
|
|
||||||
this.render_access();
|
|
||||||
},
|
|
||||||
|
|
||||||
render_access: function() {
|
|
||||||
var self = this;
|
|
||||||
var partner = this.partner || DUMMY_PARTNER;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
rpc.query({
|
|
||||||
model: 'ows.machine',
|
|
||||||
method: 'get_access_list_grouped',
|
|
||||||
args: [partner.id],
|
|
||||||
}).then(function (result) {
|
|
||||||
partner.create_date = partner.create_date && partner.create_date.substring(0, 10);
|
|
||||||
var html = QWeb.render('PartnerMachineAccessList', {
|
|
||||||
areas: result || [],
|
|
||||||
partner: partner,
|
|
||||||
});
|
|
||||||
self.$('.access-content').html(html);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
bind_order_events: function () {
|
|
||||||
var order = this.pos.get_order();
|
|
||||||
if (!order) return;
|
|
||||||
|
|
||||||
/*order.unbind('change:client', this);
|
|
||||||
order.bind('change:client', this, function () {
|
|
||||||
this.update_machine_access(order.get_client());
|
|
||||||
});*/
|
|
||||||
|
|
||||||
this.update_machine_access(order.get_client());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sidebar aktualisieren bei Klick im ClientListScreenWidget (Vorschau)
|
|
||||||
screens.ClientListScreenWidget.include({
|
|
||||||
show: function () {
|
|
||||||
this._super();
|
|
||||||
$('.order-selector').hide(); // ← wird hier versteckt
|
|
||||||
},
|
|
||||||
hide: function () {
|
|
||||||
this._super();
|
|
||||||
$('.order-selector').show(); // ← beim Verlassen wieder zeigen
|
|
||||||
},
|
|
||||||
display_client_details: function (visibility, partner, clickpos) {
|
|
||||||
this._super(visibility, partner, clickpos);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (partner && typeof partner === 'object' && 'id' in partner) {
|
|
||||||
var sidebar = this.pos.chrome.sidebar_widget;
|
|
||||||
if (sidebar) {
|
|
||||||
console.log("👤 ClientListScreen: Vorschau für", partner.name);
|
|
||||||
sidebar.update_machine_access(partner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("⚠️ Fehler beim Update der Sidebar nach Kundenauswahl:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
chrome.Chrome.include({
|
|
||||||
build_widgets: function () {
|
|
||||||
this._super();
|
|
||||||
|
|
||||||
var sidebar = new MachineAccessSidebar(this, {});
|
|
||||||
this.sidebar_widget = sidebar;
|
|
||||||
sidebar.appendTo(this.$el);
|
|
||||||
|
|
||||||
//this.pos.bind('change:selectedOrder', sidebar.bind_order_events, sidebar);
|
|
||||||
|
|
||||||
if (this.pos.get_order()) {
|
|
||||||
sidebar.bind_order_events();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
odoo.define('open_workshop.models', function (require) {
|
|
||||||
"use strict";
|
|
||||||
var models = require('point_of_sale.models');
|
|
||||||
var field_utils = require('web.field_utils');
|
|
||||||
models.load_fields('res.partner', 'create_date');
|
|
||||||
models.load_fields('res.partner', 'birthday');
|
|
||||||
models.load_fields('res.partner', 'security_briefing');
|
|
||||||
models.load_fields('res.partner', 'security_id');
|
|
||||||
models.load_fields('res.partner', 'rfid_card');
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
72
static/src/js/ows_machine_access_list.js
Normal file
72
static/src/js/ows_machine_access_list.js
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// @odoo-module ows_machine_access_list.js
|
||||||
|
|
||||||
|
import { Component, useState } from "@odoo/owl";
|
||||||
|
import { useBus } from "@web/core/utils/hooks";
|
||||||
|
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||||
|
import { rpc } from "@web/core/network/rpc";
|
||||||
|
import { registry } from "@web/core/registry";
|
||||||
|
|
||||||
|
export class OwsMachineAccessList extends Component {
|
||||||
|
static template = 'open_workshop.OwsMachineAccessList';
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.pos = usePos();
|
||||||
|
|
||||||
|
this.state = useState({
|
||||||
|
client: null,
|
||||||
|
grouped_accesses: [],
|
||||||
|
security_briefing: false,
|
||||||
|
security_id: '',
|
||||||
|
rfid_card: '',
|
||||||
|
birthday: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 🔁 Reagiere auf Partnerwechsel über den Odoo-Bus
|
||||||
|
useBus(this.env.bus, 'partner-changed', () => {
|
||||||
|
this.updateAccessList();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 🔃 Beim Mounten initiale Daten laden
|
||||||
|
this.updateAccessList();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAccessList() {
|
||||||
|
const order = this.pos.get_order();
|
||||||
|
const partner = order?.get_partner?.();
|
||||||
|
this.state.client = partner || null;
|
||||||
|
if (!partner) {
|
||||||
|
this.state.grouped_accesses = [];
|
||||||
|
this.state.security_briefing = false;
|
||||||
|
this.state.security_id = '';
|
||||||
|
this.state.rfid_card = '';
|
||||||
|
this.state.birthday = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await rpc("/open_workshop/partner_access", {
|
||||||
|
params: { partner_id: partner.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.state.grouped_accesses = data.access_by_area || [];
|
||||||
|
this.state.security_briefing = data.security_briefing;
|
||||||
|
this.state.security_id = data.security_id;
|
||||||
|
this.state.rfid_card = data.rfid_card;
|
||||||
|
this.state.birthday = data.birthday;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Fehler beim Laden der Einweisungen:", error);
|
||||||
|
this.state.grouped_accesses = [];
|
||||||
|
this.state.security_briefing = false;
|
||||||
|
this.state.security_id = '';
|
||||||
|
this.state.rfid_card = '';
|
||||||
|
this.state.birthday = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
registry.category("templates").add("open_workshop.OwsMachineAccessList", OwsMachineAccessList);
|
||||||
|
|
||||||
54
static/src/js/ows_pos_customer_sidebar.js
Normal file
54
static/src/js/ows_pos_customer_sidebar.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
// @odoo-module ows_pos_customer_sidebar.js
|
||||||
|
|
||||||
|
import { Component } from "@odoo/owl";
|
||||||
|
import { useService } from "@web/core/utils/hooks";
|
||||||
|
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { ask } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||||
|
|
||||||
|
export class OwsPosCustomerSidebar extends Component {
|
||||||
|
static template = "open_workshop.OwsPosCustomerSidebar";
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.pos = usePos(); // ✅ Holt dir Zugriff auf den zentralen POS-Store
|
||||||
|
this.dialog = useService("dialog");
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrder() {
|
||||||
|
this.pos.add_new_order(); // ✅ Neue Order wird aktive Order
|
||||||
|
this.pos.showScreen("ProductScreen");
|
||||||
|
this.pos.selectPartner();
|
||||||
|
this.env.bus.trigger('partner-changed'); // ✅ Event manuell auslösen
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeCurrentOrder() {
|
||||||
|
this.pos.onDeleteOrder(this.pos.get_order())
|
||||||
|
}
|
||||||
|
|
||||||
|
openTicketScreen() {
|
||||||
|
this.pos.showScreen("TicketScreen");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔧 FIXED: Zugriff auf Order-Liste korrigiert
|
||||||
|
getFilteredOrderList() {
|
||||||
|
return this.pos.get_open_orders();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDate(order) {
|
||||||
|
const date = new Date(order.date_order);
|
||||||
|
const dd = String(date.getDate()).padStart(2, '0');
|
||||||
|
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const hh = String(date.getHours()).padStart(2, '0');
|
||||||
|
const mi = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
return `${dd}.${mm}. ${hh}:${mi}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPartner(order) {
|
||||||
|
return order.get_partner()?.name || "Kein Kunde";
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOrder(order) {
|
||||||
|
this.pos.set_order(order);
|
||||||
|
this.env.bus.trigger('partner-changed');
|
||||||
|
}
|
||||||
|
}
|
||||||
11
static/src/js/ows_pos_sidebar.js
Normal file
11
static/src/js/ows_pos_sidebar.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
// ows_pos_sidebar.js
|
||||||
|
// @odoo-module
|
||||||
|
|
||||||
|
import { Component } from "@odoo/owl";
|
||||||
|
import { OwsPosCustomerSidebar } from "./ows_pos_customer_sidebar";
|
||||||
|
import { OwsMachineAccessList } from "./ows_machine_access_list";
|
||||||
|
|
||||||
|
export class OwsPosSidebar extends Component {
|
||||||
|
static template = "open_workshop.OwsPosSidebar";
|
||||||
|
static components = { OwsPosCustomerSidebar, OwsMachineAccessList };
|
||||||
|
}
|
||||||
15
static/src/js/ows_product_screen_template_patch.js
Normal file
15
static/src/js/ows_product_screen_template_patch.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// product_screen_template_patch.js
|
||||||
|
// @odoo-module
|
||||||
|
|
||||||
|
import { registry } from "@web/core/registry";
|
||||||
|
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
|
||||||
|
import { OwsPosSidebar } from "./ows_pos_sidebar";
|
||||||
|
|
||||||
|
class OwsProductScreen extends ProductScreen {
|
||||||
|
static components = Object.assign({}, ProductScreen.components, {
|
||||||
|
OwsPosSidebar,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.category("pos_screens").remove("ProductScreen");
|
||||||
|
registry.category("pos_screens").add("ProductScreen", OwsProductScreen);
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<templates id="template" xml:space="preserve">
|
|
||||||
<!-- overwrite client details (view) -->
|
|
||||||
<t t-extend="ClientDetails">
|
|
||||||
<t t-jquery=".client-details-left" t-operation="replace">
|
|
||||||
<div class='client-details-left'>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Address</span>
|
|
||||||
<t t-if='partner.address'>
|
|
||||||
<span class='detail client-address'>
|
|
||||||
<t t-esc='partner.address' />
|
|
||||||
</span>
|
|
||||||
</t>
|
|
||||||
<t t-if='!partner.address'>
|
|
||||||
<span class='detail client-address empty'>N/A</span>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Email</span>
|
|
||||||
<t t-if='partner.email'>
|
|
||||||
<span class='detail client-email'>
|
|
||||||
<t t-esc='partner.email' />
|
|
||||||
</span>
|
|
||||||
</t>
|
|
||||||
<t t-if='!partner.email'>
|
|
||||||
<span class='detail client-email empty'>N/A</span>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Geburtstag</span>
|
|
||||||
<t t-if='partner.birthday'>
|
|
||||||
<span class='detail client-vvow_birthday'>
|
|
||||||
<t t-esc='partner.birthday' />
|
|
||||||
</span>
|
|
||||||
</t>
|
|
||||||
<t t-if='!partner.birthday'>
|
|
||||||
<span class='detail client-vvow_birthday empty'>N/A</span>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Phone</span>
|
|
||||||
<t t-if='partner.phone'>
|
|
||||||
<span class='detail client-phone'>
|
|
||||||
<t t-esc='partner.phone' />
|
|
||||||
</span>
|
|
||||||
</t>
|
|
||||||
<t t-if='!partner.phone'>
|
|
||||||
<span class='detail client-phone empty'>N/A</span>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
<div t-attf-class='client-detail #{widget.pos.pricelists.length <= 1 ? "oe_hidden" : ""}'>
|
|
||||||
<span class='label'>Pricelist</span>
|
|
||||||
<t t-if='partner.property_product_pricelist'>
|
|
||||||
<span class='detail property_product_pricelist'>
|
|
||||||
<t t-esc='partner.property_product_pricelist[1]'/>
|
|
||||||
</span>
|
|
||||||
</t>
|
|
||||||
<t t-if='!partner.property_product_pricelist'>
|
|
||||||
<span class='detail property_product_pricelist empty'>N/A</span>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<t t-if='!partner.security_briefing'><span class='detail client-details-vvow_sec_briefing_error'>Haftungsausschluss prüfen!</span></t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</t>
|
|
||||||
<t t-jquery=".client-details-right" t-operation="replace"/>
|
|
||||||
|
|
||||||
</t>
|
|
||||||
</templates>
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<templates id="template" xml:space="preserve">
|
|
||||||
<!-- overwrite client details (view) -->
|
|
||||||
<t t-extend="ClientDetailsEdit">
|
|
||||||
<t t-jquery=".client-details-left" t-operation="replace">
|
|
||||||
<div class='client-details-left'>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Street</span>
|
|
||||||
<input class='detail client-address-street' name='street' t-att-value='partner.street || ""' placeholder='Street'></input>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Postcode</span>
|
|
||||||
<input class='detail client-address-zip' name='zip' t-att-value='partner.zip || ""' placeholder='ZIP'></input>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>City</span>
|
|
||||||
<input class='detail client-address-city' name='city' t-att-value='partner.city || ""' placeholder='City'></input>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Country</span>
|
|
||||||
<select class='detail client-address-country needsclick' name='country_id'>
|
|
||||||
<option value=''>None</option>
|
|
||||||
<t t-foreach='widget.pos.countries' t-as='country'>
|
|
||||||
<option t-att-value='country.id' t-att-selected="partner.country_id ? ((country.id === partner.country_id[0]) ? true : undefined) : undefined">
|
|
||||||
<t t-esc='country.name'/>
|
|
||||||
</option>
|
|
||||||
</t>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Email</span>
|
|
||||||
<input class='detail client-email' name='email' type='email' t-att-value='partner.email || ""'></input>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Phone</span>
|
|
||||||
<input class='detail client-phone' name='phone' type='tel' t-att-value='partner.phone || ""'></input>
|
|
||||||
</div>
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Geburtstag</span>
|
|
||||||
<input class='detail client-birthday' name='birthday' type='date' t-att-value='partner.birthday || ""'></input>
|
|
||||||
</div>
|
|
||||||
<div t-attf-class='client-detail #{widget.pos.pricelists.length <= 1 ? "oe_hidden" : ""}'>
|
|
||||||
<span class='label'>Pricelist</span>
|
|
||||||
<select class='detail needsclick' name='property_product_pricelist'>
|
|
||||||
<t t-foreach='widget.pos.pricelists' t-as='pricelist'>
|
|
||||||
<option t-att-value='pricelist.id' t-att-selected="partner.property_product_pricelist ? (pricelist.id === partner.property_product_pricelist[0] ? true : undefined) : undefined">
|
|
||||||
<t t-esc='pricelist.display_name'/>
|
|
||||||
</option>
|
|
||||||
</t>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='client-detail'>
|
|
||||||
<span class='label'>Haftungsauschschluß</span>
|
|
||||||
<select class='detail client-vvow_security_briefing-states needsclick' name='security_briefing'>
|
|
||||||
<option value='true' t-att-selected="partner.security_briefing ? true : undefined">Ja</option>
|
|
||||||
<option value='' t-att-selected="!partner.security_briefing ? true: undefined">Nein</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<t t-jquery=".client-details-right" t-operation="replace"/>
|
|
||||||
</t>
|
|
||||||
</templates>
|
|
||||||
76
static/src/xml/ows_machine_access_list.xml
Normal file
76
static/src/xml/ows_machine_access_list.xml
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<t t-name="open_workshop.OwsMachineAccessList">
|
||||||
|
<div class="client-details-grid p-2 small">
|
||||||
|
|
||||||
|
<!-- ✅ Sicherheitsbereich -->
|
||||||
|
<t t-if="state.client">
|
||||||
|
<div class="client-details-header">
|
||||||
|
<ul>
|
||||||
|
<li><span class="client-details-label">Einweisungen</span></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="client-details-area border" t-att-style="'border: solid 3px #ffffff; margin: 5px;'">
|
||||||
|
<ul>
|
||||||
|
<li class="client-detail">
|
||||||
|
<span class="detail client-details-vvow_briefing">✅</span>
|
||||||
|
<span class="briefinglabel">Werkstatt</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="client-detail">
|
||||||
|
<t t-if="!state.security_briefing">
|
||||||
|
<span class="detail client-details-vvow_briefing_error">❌</span>
|
||||||
|
</t>
|
||||||
|
<t t-if="state.security_briefing">
|
||||||
|
<span class="detail client-details-vvow_briefing">✅</span>
|
||||||
|
</t>
|
||||||
|
<span class="briefinglabel">Haftungsausschluss</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<t t-if="!state.security_briefing">
|
||||||
|
<li class="client-detail">
|
||||||
|
<ul class="subpoints">
|
||||||
|
<span class="detail client-details-vvow_sec_briefing_error">‼️Bitte Prüfen‼️</span>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<t t-if="state.security_briefing">
|
||||||
|
<ul class="subpoints">
|
||||||
|
<li class="client-detail">
|
||||||
|
<span class="label">Id:</span>
|
||||||
|
<span class="detail client-details-vvow_security_id">
|
||||||
|
<t t-esc="state.security_id || 'N/A'" />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li class="client-detail">
|
||||||
|
<span class="label">Geburtstag:</span>
|
||||||
|
<span class="detail client-details-vvow_security_id">
|
||||||
|
<t t-esc="state.birthday || 'N/A'" />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</t>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<!-- ✅ Maschinenliste: immer sichtbar, gefiltert -->
|
||||||
|
<t t-foreach="state.grouped_accesses" t-as="area" t-key="area.area">
|
||||||
|
<t t-if="area.machines.length > 0">
|
||||||
|
<div class="client-details-area" t-att-style="'border: solid 3px ' + area.color_hex + '; margin: 5px;'">
|
||||||
|
<ul>
|
||||||
|
<t t-foreach="area.machines" t-as="machine" t-key="machine.name">
|
||||||
|
<li class="client-detail">
|
||||||
|
<span t-attf-class="detail {{ machine.has_access ? 'client-details-vvow_briefing' : 'client-details-vvow_briefing_error' }}">
|
||||||
|
<t t-esc="machine.has_access ? '✅' : '❌'" />
|
||||||
|
</span>
|
||||||
|
<span class="briefinglabel"><t t-esc="machine.name"/></span>
|
||||||
|
</li>
|
||||||
|
</t>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<templates id="template" xml:space="preserve">
|
|
||||||
<t t-name="MachineAccessSidebar" owl="1">
|
|
||||||
<div class="machine-access-sidebar">
|
|
||||||
<div class="access-content">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<t t-name="PartnerMachineAccessList">
|
|
||||||
<div class="client-details-grid">
|
|
||||||
<div class="client-details-header">
|
|
||||||
<ul>
|
|
||||||
<li><span class="client-details-label">Einweisungen</span></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="client-details-area" t-att-style="'border: solid 3px #ffffff; margin: 5px;'">
|
|
||||||
<ul>
|
|
||||||
<li class="client-detail">
|
|
||||||
<span class="detail client-details-vvow_briefing">✅</span>
|
|
||||||
<span class="briefinglabel">Werkstatt</span>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="client-detail">
|
|
||||||
<t t-if="!partner.security_briefing">
|
|
||||||
<span class="detail client-details-vvow_briefing_error">❌</span>
|
|
||||||
</t>
|
|
||||||
<t t-if="partner.security_briefing">
|
|
||||||
<span class="detail client-details-vvow_briefing">✅</span>
|
|
||||||
</t>
|
|
||||||
<span class="briefinglabel">Haftungsausschluss</span>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
|
||||||
<t t-if="!partner.security_briefing">
|
|
||||||
<li class="client-detail">
|
|
||||||
<ul class="subpoints"><span class="detail client-details-vvow_sec_briefing_error">‼️Bitte Prüfen‼️</span></ul>
|
|
||||||
</li>
|
|
||||||
</t>
|
|
||||||
|
|
||||||
|
|
||||||
<t t-if="partner.security_briefing">
|
|
||||||
<ul class="subpoints">
|
|
||||||
<li class="client-detail">
|
|
||||||
<span class="label">Id:</span>
|
|
||||||
<t t-if="partner.security_id">
|
|
||||||
<span class="detail client-details-vvow_security_id"><t t-esc="partner.security_id"/></span>
|
|
||||||
</t>
|
|
||||||
<t t-if="!partner.security_id">
|
|
||||||
<span class="detail client-details-vvow_security_id">N/A</span>
|
|
||||||
</t>
|
|
||||||
</li>
|
|
||||||
<li class="client-detail">
|
|
||||||
<span class="label">Erstellt:</span>
|
|
||||||
<t t-if="partner.create_date">
|
|
||||||
<span class="detail client-details-vvow_security_id"><t t-esc="partner.create_date"/></span>
|
|
||||||
</t>
|
|
||||||
<t t-if="!partner.create_date">
|
|
||||||
<span class="detail client-vvow_birthday">N/A</span>
|
|
||||||
</t>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</t>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<t t-foreach="areas" t-as="area">
|
|
||||||
<div class="client-details-area" t-att-style="'border: solid 3px ' + area.color_hex + '; margin: 5px;'">
|
|
||||||
<ul>
|
|
||||||
<t t-foreach="area.machines" t-as="machine">
|
|
||||||
<li class="client-detail">
|
|
||||||
<t t-if="!machine.has_access">
|
|
||||||
<span class="detail client-details-vvow_briefing_error">❌</span>
|
|
||||||
</t>
|
|
||||||
<t t-if="machine.has_access">
|
|
||||||
<span class="detail client-details-vvow_briefing">✅</span>
|
|
||||||
</t>
|
|
||||||
<span class="briefinglabel"><t t-esc="machine.name"/></span>
|
|
||||||
</li>
|
|
||||||
</t>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
27
static/src/xml/ows_pos_customer_sidebar.xml
Normal file
27
static/src/xml/ows_pos_customer_sidebar.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve">
|
||||||
|
<t t-name="open_workshop.OwsPosCustomerSidebar" owl="1">
|
||||||
|
<div class="ows-sidebar p-2 bg-light border-end h-100">
|
||||||
|
<div class="ows-sidebar-header mb-2 d-flex justify-content-between align-items-center">
|
||||||
|
<button class="btn btn-secondary" t-on-click="openTicketScreen">Orders</button>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-sm btn-success me-1" t-on-click="addOrder">+</button>
|
||||||
|
<button class="btn btn-sm btn-danger" t-on-click="removeCurrentOrder">–</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ows-customer-list overflow-auto">
|
||||||
|
<t t-foreach="getFilteredOrderList()" t-as="order" t-key="order.uid">
|
||||||
|
<!--div class="order-entry p-1 rounded mb-1 border"
|
||||||
|
t-att-class="order === pos.get_order() ? 'bg-primary text-white' : 'bg-white'"
|
||||||
|
t-on-click="() => selectOrder(order)"-->
|
||||||
|
<div t-att-class="'order-entry' + (order === pos.get_order() ? ' selected' : '')" t-on-click="() => this.selectOrder(order)">
|
||||||
|
<div class="sidebar-line">
|
||||||
|
<span class="sidebar-date"><t t-esc="getDate(order)"/></span>
|
||||||
|
<span class="sidebar-name" t-att-title="getPartner(order)"><t t-esc="getPartner(order)"/></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<templates id="template" xml:space="preserve">
|
|
||||||
<!-- Test replace logo -->
|
|
||||||
<t t-extend="Chrome">
|
|
||||||
<t t-jquery=".pos-branding" t-operation="replace">
|
|
||||||
<img style="max-height:48px; max-width: 100%; width:auto" src="/web/binary/company_logo" alt="HOBBYHIMMEL"/>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
|
|
||||||
<!-- Remove old order-selector container -->
|
|
||||||
<t t-extend="Chrome">
|
|
||||||
<t t-jquery=".placeholder-OrderSelectorWidget" t-operation="replace">
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
<!-- insert order-selector container in new position -->
|
|
||||||
<t t-extend="Chrome">
|
|
||||||
<t t-jquery=".pos" t-operation="prepend">
|
|
||||||
<div class="placeholder-OrderSelectorWidget"></div>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
|
|
||||||
<!-- completly overwrite OrderSelectorWidget -->
|
|
||||||
<t t-name="OrderSelectorWidget">
|
|
||||||
<div class="order-selector" >
|
|
||||||
<div class="new-order-button">
|
|
||||||
<span class="order-button square neworder-button">
|
|
||||||
<i class='fa fa-plus' role="img" aria-label="New order" title="New order"/>
|
|
||||||
</span>
|
|
||||||
<span class="order-button square deleteorder-button">
|
|
||||||
<i class='fa fa-minus' role="img" aria-label="Delete order" title="Delete order"/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span class="orders touch-scrollable">
|
|
||||||
<t t-foreach="widget.pos.get_order_list()" t-as="order">
|
|
||||||
<t t-if="order === widget.pos.get_order()">
|
|
||||||
<span class="order-button select-order selected" t-att-title="order.sequence_number" t-att-data-uid="order.uid">
|
|
||||||
<span class="order-time">
|
|
||||||
<t t-esc="moment(order.creation_date).format('HH:mm')"/>
|
|
||||||
</span>
|
|
||||||
<span class="order-customer">
|
|
||||||
<t t-if="order.get_client()">
|
|
||||||
<t t-esc="order.get_client().name" />
|
|
||||||
</t>
|
|
||||||
<t t-if="!order.get_client()">
|
|
||||||
?
|
|
||||||
</t>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</t>
|
|
||||||
<t t-if="order !== widget.pos.get_order()">
|
|
||||||
<span class="order-button select-order" t-att-title="order.sequence_number" t-att-data-uid="order.uid">
|
|
||||||
<span class="order-time">
|
|
||||||
<t t-esc="moment(order.creation_date).format('HH:mm')"/>
|
|
||||||
</span>
|
|
||||||
<span class="order-customer">
|
|
||||||
<t t-if="order.get_client()">
|
|
||||||
<t t-esc="order.get_client().name" />
|
|
||||||
</t>
|
|
||||||
<t t-if="!order.get_client()">
|
|
||||||
?
|
|
||||||
</t>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
9
static/src/xml/ows_pos_sidebar.xml
Normal file
9
static/src/xml/ows_pos_sidebar.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve">
|
||||||
|
<t t-name="open_workshop.OwsPosSidebar" owl="1">
|
||||||
|
<div class="custompane d-flex flex-column border-end bg-200">
|
||||||
|
<OwsPosCustomerSidebar />
|
||||||
|
<OwsMachineAccessList />
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
8
static/src/xml/ows_product_screen_template_patch.xml
Normal file
8
static/src/xml/ows_product_screen_template_patch.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve">
|
||||||
|
<t t-name="point_of_sale.ProductScreen" t-inherit="point_of_sale.ProductScreen" t-inherit-mode="extension">
|
||||||
|
<xpath expr="//div[contains(@class, 'leftpane')]" position="before">
|
||||||
|
<OwsPosSidebar />
|
||||||
|
</xpath>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from . import test_res_partner
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
## Testausführung
|
|
||||||
# odoo-bin -d deine_testdatenbank --test-enable --init open_workshop
|
|
||||||
## oder:
|
|
||||||
# /opt/odoo/odoo/odoo-bin -d hobbyhimmel --test-enable --test-tags /test_res_partner.py --stop-after-init --http-port=8070 --log-level=debug
|
|
||||||
# /opt/odoo/odoo/odoo-bin -d hobbyhimmel --test-enable --stop-after-init --test-tags open_workshop --log-level=debug --update open_workshop --http-port=8070
|
|
||||||
|
|
||||||
from odoo.tests.common import TransactionCase
|
|
||||||
from odoo.tests import tagged
|
|
||||||
import logging
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
@tagged('post_install', 'open_workshop', 'vvow')
|
|
||||||
class TestResPartnerVvowFields(TransactionCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.partner_model = self.env['res.partner']
|
|
||||||
self.user_model = self.env['ows.user']
|
|
||||||
|
|
||||||
def test_create_with_vvow_fields_creates_user(self):
|
|
||||||
partner = self.partner_model.create({
|
|
||||||
'name': 'Max Test',
|
|
||||||
'vvow_birthday': '1990-01-01',
|
|
||||||
'vvow_rfid_card': 'ABC12345',
|
|
||||||
'vvow_security_briefing': True,
|
|
||||||
'vvow_security_id': 'HB-001',
|
|
||||||
})
|
|
||||||
|
|
||||||
user = partner.ows_user_id
|
|
||||||
self.assertEqual(len(user), 1)
|
|
||||||
self.assertEqual(user.birthday.isoformat(), '1990-01-01')
|
|
||||||
self.assertEqual(user.rfid_card, 'ABC12345')
|
|
||||||
self.assertTrue(user.security_briefing)
|
|
||||||
self.assertEqual(user.security_id, 'HB-001')
|
|
||||||
|
|
||||||
# Check that Fassade korrekt ausgerechnet ist
|
|
||||||
self.assertEqual(partner.birthday.isoformat(), '1990-01-01')
|
|
||||||
self.assertEqual(partner.rfid_card, 'ABC12345')
|
|
||||||
self.assertTrue(partner.security_briefing)
|
|
||||||
self.assertEqual(partner.security_id, 'HB-001')
|
|
||||||
|
|
||||||
def test_write_vvow_fields_updates_user_and_fassade(self):
|
|
||||||
partner = self.partner_model.create({'name': 'Update Tester'})
|
|
||||||
user = partner.ows_user_id
|
|
||||||
|
|
||||||
partner.write({
|
|
||||||
'vvow_birthday': '1985-12-31',
|
|
||||||
'vvow_rfid_card': 'NEW123',
|
|
||||||
'vvow_security_briefing': False,
|
|
||||||
'vvow_security_id': 'NEU-SEC',
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEqual(user.birthday.isoformat(), '1985-12-31')
|
|
||||||
self.assertEqual(user.rfid_card, 'NEW123')
|
|
||||||
self.assertFalse(user.security_briefing)
|
|
||||||
self.assertEqual(user.security_id, 'NEU-SEC')
|
|
||||||
|
|
||||||
# Sicherstellen, dass die berechneten Felder auch aktualisiert wurden
|
|
||||||
self.assertEqual(partner.birthday.isoformat(), '1985-12-31')
|
|
||||||
self.assertEqual(partner.rfid_card, 'NEW123')
|
|
||||||
self.assertFalse(partner.security_briefing)
|
|
||||||
self.assertEqual(partner.security_id, 'NEU-SEC')
|
|
||||||
|
|
||||||
def test_change_fassade_field_updates_user(self):
|
|
||||||
partner = self.partner_model.create({
|
|
||||||
'name': 'Change Fassade',
|
|
||||||
'vvow_security_briefing': True,
|
|
||||||
})
|
|
||||||
user = partner.ows_user_id
|
|
||||||
|
|
||||||
# Ändere security_briefing (die Fassade)
|
|
||||||
partner.write({'security_briefing': False})
|
|
||||||
|
|
||||||
# Muss sich im ows.user widerspiegeln
|
|
||||||
self.assertFalse(user.security_briefing)
|
|
||||||
|
|
||||||
# vvow-Sync: vvow bleibt wie gehabt (wird nicht überschrieben)
|
|
||||||
self.assertTrue(partner.vvow_security_briefing)
|
|
||||||
|
|
||||||
def test_change_and_read_consistency(self):
|
|
||||||
partner = self.partner_model.create({
|
|
||||||
'name': 'Read-Change-Test',
|
|
||||||
'vvow_security_briefing': True,
|
|
||||||
})
|
|
||||||
user = partner.ows_user_id
|
|
||||||
|
|
||||||
# Ändere security_briefing über Fassade
|
|
||||||
partner.write({'security_briefing': False})
|
|
||||||
self.assertFalse(user.security_briefing)
|
|
||||||
self.assertFalse(partner.security_briefing)
|
|
||||||
|
|
||||||
# Lies vvow_* separat aus – sollte weiterhin den ursprünglichen vvow-Wert zeigen
|
|
||||||
self.assertTrue(partner.vvow_security_briefing)
|
|
||||||
|
|
||||||
# Ändere vvow_* erneut – jetzt sollte alles wieder gesetzt werden
|
|
||||||
partner.write({'vvow_security_briefing': True})
|
|
||||||
self.assertTrue(partner.security_briefing)
|
|
||||||
self.assertTrue(user.security_briefing)
|
|
||||||
|
|
||||||
|
|
||||||
def test_inverse_does_not_clear_unrelated_fields(self):
|
|
||||||
_logger.info("🔍 Starte Test für Feldsynchronisation")
|
|
||||||
# Schritt 1: Partner mit vvow_* Feldern erzeugen → soll automatisch ows.user erzeugen
|
|
||||||
partner = self.partner_model.create({
|
|
||||||
'name': 'Unabhängigkeits-Test',
|
|
||||||
'vvow_birthday': '1995-05-05',
|
|
||||||
'vvow_rfid_card': 'ABC999',
|
|
||||||
'vvow_security_briefing': True,
|
|
||||||
'vvow_security_id': 'SEC-XYZ',
|
|
||||||
})
|
|
||||||
_logger.debug("✅ Partner angelegt: %s", partner.name)
|
|
||||||
|
|
||||||
# Validierung: es wurde ein ows.user erzeugt
|
|
||||||
self.assertEqual(len(partner.ows_user_id), 1, "Kein ows.user erzeugt")
|
|
||||||
user = partner.ows_user_id
|
|
||||||
_logger.debug("👤 ows.user: %s", user.read(['birthday', 'security_id']))
|
|
||||||
|
|
||||||
# Sicherstellen, dass alle Felder korrekt gesetzt wurden
|
|
||||||
self.assertEqual(user.birthday.isoformat(), '1995-05-05')
|
|
||||||
self.assertEqual(user.rfid_card, 'ABC999')
|
|
||||||
self.assertTrue(user.security_briefing)
|
|
||||||
self.assertEqual(user.security_id, 'SEC-XYZ')
|
|
||||||
|
|
||||||
# Schritt 2: Nur ein Feld über die Fassade ändern (inverse wird getriggert)
|
|
||||||
partner.write({'security_briefing': False})
|
|
||||||
_logger.info("✏️ security_briefing auf False gesetzt")
|
|
||||||
|
|
||||||
# Schritt 3: DB-Cache leeren
|
|
||||||
user.flush()
|
|
||||||
user.invalidate_cache()
|
|
||||||
|
|
||||||
_logger.debug("📦 user nach write: %s", user.read(['birthday', 'security_id']))
|
|
||||||
|
|
||||||
# Schritt 4: Sicherstellen, dass die anderen Felder NICHT gelöscht wurden
|
|
||||||
self.assertEqual(user.birthday.isoformat(), '1995-05-05')
|
|
||||||
self.assertEqual(user.rfid_card, 'ABC999')
|
|
||||||
self.assertEqual(user.security_id, 'SEC-XYZ')
|
|
||||||
self.assertFalse(user.security_briefing)
|
|
||||||
104
todo.md
104
todo.md
|
|
@ -1,3 +1,101 @@
|
||||||
[ ] Help System
|
# 🎯 Ziel des Moduls „Open Workshop“
|
||||||
[ ] Möglichkeit, Einweisungen manuell zu setzen?
|
Das Modul Open Workshop ist für den Einsatz in einer offenen Werkstatt / einem FabLab gedacht, in dem Kund:innen selbstständig Maschinen nutzen können, aber nur, wenn bestimmte Voraussetzungen erfüllt sind (z. B. Einweisung). Das Ziel ist es, Sicherheit, Zugriffssteuerung, Selbstverwaltung und Abrechnung in einer Odoo-gestützten Umgebung zu ermöglichen – insbesondere im POS (Point-of-Sale)-Modul.
|
||||||
[ ]
|
|
||||||
|
#🛠️ Funktionaler Rahmen & Umgebung
|
||||||
|
## Betriebsumgebung
|
||||||
|
Odoo in mehreren Versionen (aktuell Fokus auf Odoo 18.0)
|
||||||
|
|
||||||
|
Docker-basierte Installation mit Entwicklungs-, Test- und Produktivinstanzen
|
||||||
|
|
||||||
|
PostgreSQL-Datenbank
|
||||||
|
|
||||||
|
Gitea zur Versionierung und CI (z. B. act_runner)
|
||||||
|
|
||||||
|
POS läuft in einem speziellen Frontend mit festen Ansichten und Touch-Bedienung
|
||||||
|
|
||||||
|
Nutzung durch Personal & Werkstattnutzer:innen auf verschiedenen Geräten
|
||||||
|
|
||||||
|
Kiosksysteme für Anmeldung, Einweisung, ggf. auch Selbstbedienung
|
||||||
|
|
||||||
|
Interne Systeme z. T. über *.lan.hobbyhimmel.de zugänglich
|
||||||
|
|
||||||
|
# 🔐 Kernfunktionen des Moduls „Open Workshop“
|
||||||
|
## Maschinenverwaltung
|
||||||
|
Maschinenmodell mit Zuordnung zu Bereichen (machine areas)
|
||||||
|
|
||||||
|
Darstellung aller Maschinen gruppiert nach Bereich im POS
|
||||||
|
|
||||||
|
Maschinenbereiche enthalten u. a. Farbe (hex), Beschreibung etc.
|
||||||
|
|
||||||
|
Upload von Dokumenten und Bildern möglich
|
||||||
|
|
||||||
|
## Zugriffsverwaltung / Einweisungen
|
||||||
|
Modell ows.machine.access steuert, welche:r Partner:in auf welche Maschine zugreifen darf
|
||||||
|
|
||||||
|
Einweisungen sind Produkte in Odoo, deren Kauf automatisch eine machine.access-Eintragung erzeugt
|
||||||
|
|
||||||
|
Kunden können mehrfach eingewiesen werden (z. B. bei mehreren Maschinen in einem Kurs)
|
||||||
|
|
||||||
|
Darstellung im POS über grünes Häkchen / rotes Kreuz
|
||||||
|
|
||||||
|
## Sicherheitsinformationen
|
||||||
|
Modell ows.user verknüpft 1:1 mit res.partner, enthält:
|
||||||
|
|
||||||
|
Geburtstag
|
||||||
|
|
||||||
|
RFID-Card-ID
|
||||||
|
|
||||||
|
Sicherheitsunterweisung (bool)
|
||||||
|
|
||||||
|
Sicherheits-ID
|
||||||
|
|
||||||
|
Synchronisation zwischen res.partner-Fassadenfeldern und ows.user
|
||||||
|
|
||||||
|
## POS-Integration
|
||||||
|
Erweiterung der POS-Ansicht:
|
||||||
|
|
||||||
|
Rechte Spalte zeigt dauerhaft Maschinenfreigaben an
|
||||||
|
|
||||||
|
Dynamische Darstellung pro Partner
|
||||||
|
|
||||||
|
Gruppiert nach Maschinenbereichen
|
||||||
|
|
||||||
|
Farben und Styles aus der DB
|
||||||
|
|
||||||
|
Buttons zur Partnerauswahl und Order-Wechsel
|
||||||
|
|
||||||
|
Maschinennutzungsprodukte nur sichtbar, wenn Kunde eingewiesen ist
|
||||||
|
|
||||||
|
Einweisungsvideos können verlinkt sein
|
||||||
|
|
||||||
|
## Datenpflege und Tests
|
||||||
|
Produkte aus Kategorien „Einweisungen“ und „Maschinennutzung“ werden im Modul als fest installierte Daten gepflegt
|
||||||
|
|
||||||
|
Strukturierte Tests für res.partner und ows.user (Erstellen, Lesen, Schreiben, Sync)
|
||||||
|
|
||||||
|
# 🧩 Mögliche Erweiterungsbereiche (aus deinen bisherigen Ideen)
|
||||||
|
Dokumentation & Tutorials per DokuWiki-Integration
|
||||||
|
|
||||||
|
Selbstregistrierung von Nutzer:innen (Kioskmodus)
|
||||||
|
|
||||||
|
Zeiterfassung an Maschinen per RFID/IoT (z. B. IoT-Box oder AMC3301)
|
||||||
|
|
||||||
|
Verwaltung von Maschinen-Verbrauchsmaterialien
|
||||||
|
|
||||||
|
Kalenderbuchung & Eventintegration (für Einweisungstermine)
|
||||||
|
|
||||||
|
Automatisiertes Backup & Restore von POS-Umgebungen via CI
|
||||||
|
|
||||||
|
Nutzung von OWL-Komponenten (ab v17) zur modernen POS-Darstellung
|
||||||
|
|
||||||
|
# 📦 Modulstruktur und Philosophie
|
||||||
|
Das Modul ist modular und gut in Odoo integriert
|
||||||
|
|
||||||
|
Wo möglich, nutzt du bestehende Odoo-Modelle (res.partner, product.template, pos.order)
|
||||||
|
|
||||||
|
Zusätzliche Modelle (ows.machine, ows.machine.access, ows.user, ows.machine.area) kapseln Werkstatt-spezifische Logik
|
||||||
|
|
||||||
|
Fokus auf Datensicherheit, Nachvollziehbarkeit und einfache Erweiterbarkeit
|
||||||
|
|
||||||
|
Frontend-Anpassungen (POS) sind tief integriert, ohne das Standardverhalten zu sehr zu stören
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
<odoo>
|
<odoo>
|
||||||
<data>
|
<template id="assets_open_workshop" inherit_id="point_of_sale._assets_pos">
|
||||||
<template id="assets" inherit_id="point_of_sale.assets">
|
<xpath expr="." position="inside">
|
||||||
<xpath expr="." position="inside">
|
<script type="text/javascript" src="/open_workshop/static/src/js/machine_access_sidebar.js"/>
|
||||||
<script type="text/javascript" src="/open_workshop/static/src/js/machine_access_sidebar.js"/>
|
<link rel="stylesheet" type="text/css" href="/open_workshop/static/src/css/pos.css"/>
|
||||||
<template id="machine_access_template" name="Maschinenfreigaben Template" src="/open_workshop/static/src/xml/ows_machine_sidebar.xml"/>
|
</xpath>
|
||||||
</xpath>
|
</template>
|
||||||
<xpath expr="//link[@href='/point_of_sale/static/src/css/pos.css']" position="replace">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/open_workshop/static/src/css/pos.css"/>
|
|
||||||
</xpath>
|
|
||||||
</template>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<record id="action_machine_area_list" model="ir.actions.act_window">
|
<record id="action_machine_area_list" model="ir.actions.act_window">
|
||||||
<field name="name">Maschinenbereiche</field>
|
<field name="name">Maschinenbereiche</field>
|
||||||
<field name="res_model">ows.machine.area</field>
|
<field name="res_model">ows.machine.area</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Menüpunkt unter Maschinen > Konfiguration -->
|
<!-- Menüpunkt unter Maschinen > Konfiguration -->
|
||||||
|
|
@ -15,10 +15,11 @@
|
||||||
<field name="name">ows.machine.area.tree</field>
|
<field name="name">ows.machine.area.tree</field>
|
||||||
<field name="model">ows.machine.area</field>
|
<field name="model">ows.machine.area</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree>
|
<list>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="color_hex" widget="color_picker"/>
|
<field name="color_hex_value" string="Farbe (Hex)"/>
|
||||||
</tree>
|
<field name="color_name" string="Farbname"/>
|
||||||
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
@ -30,7 +31,8 @@
|
||||||
<form string="Maschinenbereich">
|
<form string="Maschinenbereich">
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="color_hex" widget="color_picker"/>
|
<field name="color_hex"/>
|
||||||
|
<field name="color_name" readonly="1"/>
|
||||||
</group>
|
</group>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@
|
||||||
<field name="name">ows.machine.product.tree</field>
|
<field name="name">ows.machine.product.tree</field>
|
||||||
<field name="model">ows.machine.product</field>
|
<field name="model">ows.machine.product</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree editable="bottom">
|
<list editable="bottom">
|
||||||
<field name="machine_id"/>
|
<field name="machine_id"/>
|
||||||
<field name="product_id" domain="[('categ_id.name', '=', 'Maschinennutzung')]"/>
|
<field name="product_id" domain="[('categ_id.name', '=', 'Maschinennutzung')]"/>
|
||||||
</tree>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
@ -16,10 +16,10 @@
|
||||||
<field name="name">ows.machine.training.tree</field>
|
<field name="name">ows.machine.training.tree</field>
|
||||||
<field name="model">ows.machine.training</field>
|
<field name="model">ows.machine.training</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree editable="bottom">
|
<list editable="bottom">
|
||||||
<field name="machine_id"/>
|
<field name="machine_id"/>
|
||||||
<field name="training_id" domain="[('categ_id.name', '=', 'Einweisungen')]"/>
|
<field name="training_id" domain="[('categ_id.name', 'in', ['Einweisungen', 'Kurse'])]"/>
|
||||||
</tree>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
<record id="action_machine_product" model="ir.actions.act_window">
|
<record id="action_machine_product" model="ir.actions.act_window">
|
||||||
<field name="name">Maschinen-Nutzungsprodukte</field>
|
<field name="name">Maschinen-Nutzungsprodukte</field>
|
||||||
<field name="res_model">ows.machine.product</field>
|
<field name="res_model">ows.machine.product</field>
|
||||||
<field name="view_mode">tree</field>
|
<field name="view_mode">list</field>
|
||||||
<field name="view_id" ref="view_machine_product_tree"/>
|
<field name="view_id" ref="view_machine_product_tree"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p>Verwalte die Zuordnung von Maschinen zu Nutzungsprodukten.</p>
|
<p>Verwalte die Zuordnung von Maschinen zu Nutzungsprodukten.</p>
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
<record id="action_machine_training" model="ir.actions.act_window">
|
<record id="action_machine_training" model="ir.actions.act_window">
|
||||||
<field name="name">Maschinen-Einweisungsprodukte</field>
|
<field name="name">Maschinen-Einweisungsprodukte</field>
|
||||||
<field name="res_model">ows.machine.training</field>
|
<field name="res_model">ows.machine.training</field>
|
||||||
<field name="view_mode">tree</field>
|
<field name="view_mode">list</field>
|
||||||
<field name="view_id" ref="view_machine_training_tree"/>
|
<field name="view_id" ref="view_machine_training_tree"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p>Verwalte die Zuordnung von Maschinen zu Einweisungsprodukten.</p>
|
<p>Verwalte die Zuordnung von Maschinen zu Einweisungsprodukten.</p>
|
||||||
|
|
|
||||||
|
|
@ -5,49 +5,62 @@
|
||||||
<field name="name">ows.machine.tree</field>
|
<field name="name">ows.machine.tree</field>
|
||||||
<field name="model">ows.machine</field>
|
<field name="model">ows.machine</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree>
|
<list>
|
||||||
|
<field name="category_icon" string="⚙" readonly="1"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="category"/>
|
||||||
<field name="code"/>
|
<field name="code"/>
|
||||||
<field name="area_id" widget="many2one_color"/>
|
<field name="area_id" widget="many2one_color"/>
|
||||||
<field name="product_names"/>
|
<field name="product_names"/>
|
||||||
<field name="training_names"/>
|
<field name="training_names"/>
|
||||||
|
<field name="storage_location"/>
|
||||||
|
<field name="purchase_price"/>
|
||||||
|
<field name="purchase_date"/>
|
||||||
<field name="active"/>
|
<field name="active"/>
|
||||||
</tree>
|
|
||||||
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Maschinen Formularansicht -->
|
<!-- Maschinen Formularansicht -->
|
||||||
<record id="view_machine_form" model="ir.ui.view">
|
<record id="view_machine_form" model="ir.ui.view">
|
||||||
<field name="name">ows.machine.form</field>
|
<field name="name">ows.machine.form</field>
|
||||||
<field name="model">ows.machine</field>
|
<field name="model">ows.machine</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Maschine">
|
<form string="Maschine">
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="category_icon" string="⚙" readonly="1"/>
|
||||||
<field name="code"/>
|
<field name="name"/>
|
||||||
<field name="area_id"/>
|
<field name="category"/>
|
||||||
<field name="description"/>
|
<field name="code"/>
|
||||||
<field name="active"/>
|
<field name="area_id"/>
|
||||||
</group>
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="description"/>
|
||||||
|
<field name="storage_location"/>
|
||||||
|
<field name="purchase_price"/>
|
||||||
|
<field name="purchase_date"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</group>
|
||||||
|
|
||||||
<!-- Neue -->
|
<!-- Notebook für Produkte und Einweisungen -->
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Nutzungsprodukte">
|
<page string="Nutzungsprodukte">
|
||||||
<field name="product_ids" context="{'default_machine_id': active_id}">
|
<field name="product_ids" context="{'default_machine_id': id}">
|
||||||
<tree editable="bottom">
|
<list editable="bottom">
|
||||||
<field name="product_id" domain="[('categ_id.name', '=', 'Maschinennutzung')]" />
|
<field name="product_id" domain="[('categ_id.name', '=', 'Maschinennutzung')]" />
|
||||||
</tree>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
<page string="Einweisungsprodukte">
|
<page string="Einweisungsprodukte">
|
||||||
<field name="training_ids" context="{'default_machine_id': active_id}">
|
<field name="training_ids" context="{'default_machine_id': id}">
|
||||||
<tree editable="bottom">
|
<list editable="bottom">
|
||||||
<field name="training_id" domain="[('categ_id.name', '=', 'Einweisungen')]" />
|
<field name="training_id" domain="[('categ_id.name', 'in', ['Einweisungen', 'Kurse'])]" />
|
||||||
</tree>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
||||||
<record id="action_machine_list" model="ir.actions.act_window">
|
<record id="action_machine_list" model="ir.actions.act_window">
|
||||||
<field name="name">Maschinen</field>
|
<field name="name">Maschinen</field>
|
||||||
<field name="res_model">ows.machine</field>
|
<field name="res_model">ows.machine</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Trainingsprodukt-Liste -->
|
<!-- Trainingsprodukt-Liste -->
|
||||||
<record id="action_training_product_list" model="ir.actions.act_window">
|
<record id="action_training_product_list" model="ir.actions.act_window">
|
||||||
<field name="name">Einweisungs-Produkte</field>
|
<field name="name">Einweisungs-Produkte</field>
|
||||||
<field name="res_model">ows.machine.product</field>
|
<field name="res_model">ows.machine.product</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Menüstruktur -->
|
<!-- Menüstruktur -->
|
||||||
|
|
@ -59,10 +59,10 @@
|
||||||
<field name="name">ows.machine.product.tree</field>
|
<field name="name">ows.machine.product.tree</field>
|
||||||
<field name="model">ows.machine.product</field>
|
<field name="model">ows.machine.product</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree>
|
<list>
|
||||||
<field name="product_id"/>
|
<field name="product_id"/>
|
||||||
<field name="machine_id"/>
|
<field name="machine_id"/>
|
||||||
</tree>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,131 +1,127 @@
|
||||||
<!-- res_partner_view.xml -->
|
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- View-Erweiterung für res.partner: Tab mit HTML-Tabelle
|
<!-- Entfernt die Partner-Warnung (Duplicate Bank Accounts) in res.partner
|
||||||
Der Inhalt wird in der Methode _compute_machine_access_html() generiert.
|
<record id="patch_res_partner_duplicate_warning" model="ir.ui.view">
|
||||||
Diese Methode wird in der Klasse res.partner definiert in der Datei models/ows_models.py.
|
<field name="name">res.partner.remove.duplicate.bank.warning</field>
|
||||||
Die Methode wird aufgerufen, wenn das Partnerformular geöffnet wird.
|
|
||||||
Die HTML-Tabelle wird in der Variable machine_access_html gespeichert.
|
|
||||||
Die Variable wird in der View angezeigt.
|
|
||||||
-->
|
|
||||||
<record id="view_partner_form_inherit_open_workshop_html" model="ir.ui.view">
|
|
||||||
<field name="name">res.partner.form.ows.machine.access.html</field>
|
|
||||||
<field name="model">res.partner</field>
|
<field name="model">res.partner</field>
|
||||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||||
|
<field name="priority" eval="99"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<notebook position="inside">
|
<xpath expr="//div[@name='warning_tax' and @class='alert alert-warning oe_edit_only']" position="replace"/>
|
||||||
<page string="HOBBYHIMMEL Einweisungen">
|
|
||||||
<field name="machine_access_html" readonly="1" widget="html"/>
|
|
||||||
</page>
|
|
||||||
</notebook>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>-->
|
||||||
|
|
||||||
<!-- Teil 1: Maschinenfreigaben-Tabelle -->
|
<!-- Entfernt die Bankkonto-Warnung in res.partner.bank
|
||||||
<record id="view_partner_form_inherit_open_workshop" model="ir.ui.view">
|
<record id="patch_res_partner_bank_duplicate_warning" model="ir.ui.view">
|
||||||
<field name="name">res.partner.form.ows.machine.access</field>
|
<field name="name">res.partner.bank.remove.duplicate.warning</field>
|
||||||
<field name="model">res.partner</field>
|
<field name="model">res.partner.bank</field>
|
||||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
<field name="inherit_id" ref="account.view_partner_bank_form_inherit_account"/>
|
||||||
|
<field name="priority" eval="99"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<notebook position="inside">
|
<xpath expr="//div[@class='alert alert-warning']" position="replace"/>
|
||||||
<page string="Einweisungen (Liste)">
|
</field>
|
||||||
<field name="machine_access_ids">
|
</record> -->
|
||||||
<tree>
|
|
||||||
|
<!-- Zentrale View für alle drei Tabs in garantierter Reihenfolge -->
|
||||||
|
|
||||||
|
<record id="view_partner_form_inherit_open_workshop_tabs" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.form.ows.tabs</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="priority" eval="20"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//notebook" position="inside">
|
||||||
|
<page name="ows_machine_access" string="Offene Werkstatt (Hobbyhimmel)">
|
||||||
|
<!-- EINWEISUNG: Zwei Felder nebeneinander -->
|
||||||
|
<group name="container_row_2" string="Sicherheitseinweisung" col="2">
|
||||||
|
<field name="security_briefing"/>
|
||||||
|
<field name="security_id"/>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<!-- MASCHINENFREIGABEN: Volle Breite -->
|
||||||
|
<group string="Maschinenfreigaben" col="2">
|
||||||
|
<field name="machine_access_ids" colspan="2" context="{'default_partner_id': id}" nolabel="1">
|
||||||
|
<list>
|
||||||
<field name="machine_id"/>
|
<field name="machine_id"/>
|
||||||
<field name="date_granted"/>
|
<field name="date_granted"/>
|
||||||
<field name="date_expiry"/>
|
<field name="date_expiry"/>
|
||||||
<field name="granted_by_pos"/>
|
<field name="granted_by_pos"/>
|
||||||
</tree>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</group>
|
||||||
</notebook>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Teil 2: HOBBYHIMMEL Basis (ows_user_id Felder) -->
|
<!-- ÜBERSICHT: Volle Breite -->
|
||||||
<record id="view_partner_form_inherit_ows_user" model="ir.ui.view">
|
<group string="Maschinenfreigaben Übersicht" >
|
||||||
<field name="name">res.partner.form.ows.user</field>
|
<field name="machine_access_html" colspan="2" readonly="1" widget="html" nolabel="1"/>
|
||||||
<field name="model">res.partner</field>
|
|
||||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
|
|
||||||
<!-- Geburtstag direkt unter USt-ID -->
|
|
||||||
<xpath expr="//field[@name='vat']" position="after">
|
|
||||||
<field name="birthday"/>
|
|
||||||
</xpath>
|
|
||||||
|
|
||||||
<!-- Eigene Seite "Basis" nach der Verkaufsseite -->
|
|
||||||
<xpath expr="//page[@name='sales_purchases']" position="after">
|
|
||||||
<page name="ows_basic" string="HOBBYHIMMEL Basis">
|
|
||||||
<group name="container_row_2">
|
|
||||||
<group string="Sicherheit">
|
|
||||||
<field name="security_briefing"/>
|
|
||||||
<field name="security_id"/>
|
|
||||||
</group>
|
|
||||||
<group string="Zugang">
|
|
||||||
<field name="rfid_card"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
<record id="contacts.action_contacts" model="ir.actions.act_window">
|
|
||||||
<field name="view_mode">tree,kanban,form,activity</field>
|
|
||||||
</record>
|
|
||||||
<record id="contacts.action_contacts_view_kanban" model="ir.actions.act_window.view">
|
|
||||||
<field name="sequence" eval="1"/>
|
|
||||||
</record>
|
|
||||||
<record id="contacts.action_contacts_view_tree" model="ir.actions.act_window.view">
|
|
||||||
<field name="sequence" eval="0"/>
|
|
||||||
<!--tree default_order="create_date desc"/-->
|
|
||||||
</record>
|
|
||||||
<!-- Action to set default view to list view for Contacts
|
|
||||||
<record id="contacts.action_contacts" model="ir.actions.act_window">
|
|
||||||
<field name="name">Contacts</field>
|
|
||||||
<field name="res_model">res.partner</field>
|
|
||||||
<field name="view_mode">tree,kanban,form</field>
|
|
||||||
<field name="view_id" ref="base.view_partner_tree"/>
|
|
||||||
</record>
|
|
||||||
-->
|
|
||||||
<record id="ows_userList_inherit" model="ir.ui.view">
|
|
||||||
<field name="name">res.partner.ows.tree</field>
|
|
||||||
<field name="model">res.partner</field>
|
|
||||||
<field name="inherit_id" ref="base.view_partner_tree"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='vat']" position="after">
|
|
||||||
<field name="create_date" optional="show"/>
|
|
||||||
<field name="security_briefing" optional="show"/>
|
|
||||||
<field name="security_id" optional="show"/>
|
|
||||||
<field name="rfid_card" optional="show"/>
|
|
||||||
<field name="category_id" widget="many2many_tags"/>
|
|
||||||
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='vat']" position="replace">
|
|
||||||
<field name="vat" invisible="1"/>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='email']" position="replace">
|
|
||||||
<field name="email" invisible="1"/>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='phone']" position="replace">
|
|
||||||
<field name="phone" invisible="1"/>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='state_id']" position="replace">
|
|
||||||
<field name="state_id" invisible="1"/>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='country_id']" position="replace">
|
|
||||||
<field name="country_id" invisible="1"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
<record id="view_partner_form_inherit" model="ir.ui.view">
|
|
||||||
<field name="name">res.partner.form.inherit.default_person</field>
|
|
||||||
<field name="model">res.partner</field>
|
|
||||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<field name="company_type" position="attributes">
|
|
||||||
<attribute name="default">person</attribute>
|
|
||||||
</field>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- Geburtstag direkt nach der USt-ID -->
|
||||||
|
<record id="view_partner_form_inherit_ows_birthday" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.form.ows.birthday</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="priority" eval="15"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='vat']" position="after">
|
||||||
|
<field name="birthday"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- List View Anpassung -->
|
||||||
|
<record id="ows_userList_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.ows.tree</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='vat']" position="after">
|
||||||
|
<field name="create_date" optional="show"/>
|
||||||
|
<field name="security_briefing" optional="show"/>
|
||||||
|
<field name="security_id" optional="show"/>
|
||||||
|
<field name="rfid_card" optional="show"/>
|
||||||
|
<field name="category_id" widget="many2many_tags"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='vat']" position="replace">
|
||||||
|
<field name="vat" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='email']" position="replace">
|
||||||
|
<field name="email" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='phone']" position="replace">
|
||||||
|
<field name="phone" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='state_id']" position="replace">
|
||||||
|
<field name="state_id" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='country_id']" position="replace">
|
||||||
|
<field name="country_id" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Standardwerte setzen (company_type = person) -->
|
||||||
|
<record id="view_partner_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.form.inherit.default_person</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="company_type" position="attributes">
|
||||||
|
<attribute name="default">person</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Optional: Kontakte-Action, falls gebraucht -->
|
||||||
|
<record id="contacts.action_contacts" model="ir.actions.act_window">
|
||||||
|
<field name="view_mode">list,kanban,form,activity</field>
|
||||||
|
</record>
|
||||||
|
<record id="contacts.action_contacts_view_kanban" model="ir.actions.act_window.view">
|
||||||
|
<field name="sequence" eval="1"/>
|
||||||
|
</record>
|
||||||
|
<record id="contacts.action_contacts_view_tree" model="ir.actions.act_window.view">
|
||||||
|
<field name="sequence" eval="0"/>
|
||||||
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user