Compare commits
5 Commits
f4216d790c
...
d4a835f178
| Author | SHA1 | Date | |
|---|---|---|---|
| d4a835f178 | |||
| 0fe8417602 | |||
| 1f59e16b26 | |||
| 59e4b19dee | |||
| 021d01efe6 |
34
README.md
34
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# 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
|
||||
|
||||
|
|
@ -28,40 +28,10 @@ Dieses Odoo v13.0 Modul erweitert das POS- und Kontakt-Modul um Funktionen für
|
|||
## Installation
|
||||
|
||||
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.
|
||||
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)
|
||||
2. Im Odoo Backend unter Apps installieren
|
||||
|
||||
## 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
|
||||
- Bearbeitung der Maschinenfreigaben im Backend
|
||||
- Automatische Erstellung von `mail.message` bei manueller Freigabe
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
from . import models
|
||||
from . import controllers
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,39 @@
|
|||
{
|
||||
'name': 'POS Open Workshop',
|
||||
'license': 'AGPL-3',
|
||||
'version': '16.0.1.0.0',
|
||||
'version': '18.0.1.0.0',
|
||||
'summary': 'Erstellt Maschinenfreigaben basierend auf POS-Einweisungsprodukten',
|
||||
'depends': ['base','product','sale','contacts','point_of_sale'],
|
||||
'author': 'matthias.lotz',
|
||||
'category': 'Point of Sale',
|
||||
'data': [
|
||||
'data/data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
|
||||
'views/machine_product_training_views.xml',
|
||||
'views/menu_views.xml',
|
||||
'views/machine_area_views.xml',
|
||||
'views/machine_views.xml',
|
||||
'views/res_partner_view.xml',
|
||||
'data/data.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'open_workshop/static/src/css/category_color.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',
|
||||
],
|
||||
},
|
||||
'description': """
|
||||
Diese App erstellt Maschinenfreigaben basierend auf POS-Einweisungsprodukten.
|
||||
Die App ist für den Einsatz in der Odoo-Version 16.0 konzipiert.
|
||||
Die App ist für den Einsatz in der Odoo-Version 18.0 konzipiert.
|
||||
""",
|
||||
}
|
||||
|
|
|
|||
3
controllers/__init__.py
Normal file
3
controllers/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Datei: open_workshop/controllers/__init__.py
|
||||
|
||||
from . import pos_access
|
||||
14
controllers/pos_access.py
Normal file
14
controllers/pos_access.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
class OpenWorkshopPOSController(http.Controller):
|
||||
|
||||
@http.route('/open_workshop/partner_access', type='json', auth='user')
|
||||
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()
|
||||
return Machine.get_access_list_grouped(partner_id)
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
from . import ows_models
|
||||
|
||||
from . import pos_order
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,33 @@ import logging
|
|||
_logger = logging.getLogger(__name__)
|
||||
_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)
|
||||
_logger.info(f"[OWS] Administrator-Benutzer gefunden: {admin_user.name} (ID: {admin_user.id})")
|
||||
admin_employee = self.search([('user_id', '=', admin_user.id)], limit=1)
|
||||
|
||||
if admin_employee:
|
||||
admin_employee.write({
|
||||
'name': 'TESTSYSTEM',
|
||||
'job_title': 'Testumgebung',
|
||||
'work_email': False,
|
||||
'work_phone': False,
|
||||
})
|
||||
_logger.info("[OWS] Admin-Angestellter wurde umbenannt.")
|
||||
else:
|
||||
_logger.warning("[OWS] Kein Angestellter für user_admin gefunden.")
|
||||
|
||||
# Alle anderen Angestellten archivieren
|
||||
other_employees = self.search([('id', '!=', admin_employee.id)])
|
||||
other_employees.write({'active': False})
|
||||
_logger.info("[OWS] %d Angestellte archiviert.", len(other_employees))
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
_logger.info("✅ ows ResPartner geladen")
|
||||
|
|
@ -141,32 +168,50 @@ class ResPartner(models.Model):
|
|||
def _compute_machine_access_html(self):
|
||||
areas = self.env['ows.machine.area'].search([], order="name")
|
||||
for partner in self:
|
||||
html = "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem;'>"
|
||||
html = ""
|
||||
for area in areas:
|
||||
html += f"<div class='o_group' style='margin-bottom: 1em;'>"
|
||||
html += f"<table class='o_group o_inner_group'>"
|
||||
html += f"<thead><tr><th>{area.name}</th><th></th><th>Datum</th><th>Gültig bis</th></tr></thead><tbody>"
|
||||
html += f"""
|
||||
<div class="o_form_sheet">
|
||||
<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")
|
||||
|
||||
for machine in machines:
|
||||
access = self.env['ows.machine.access'].search([
|
||||
('partner_id', '=', partner.id),
|
||||
('machine_id', '=', machine.id),
|
||||
], limit=1)
|
||||
icon = "<span class='fa fa-check text-success'></span>" if access else "<span class='fa fa-times text-danger'></span>"
|
||||
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 ""
|
||||
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_expiry = access.date_expiry.strftime('%Y-%m-%d') if access and access.date_expiry else "-"
|
||||
|
||||
html += f"""
|
||||
<tr>
|
||||
<td class='o_td_label'><label>{machine.name}</label></td>
|
||||
<td class='o_td_field'>{icon}</td>
|
||||
<td class='o_td_field'>{date_granted}</td>
|
||||
<td class='o_td_field'>{date_expiry}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{machine.name}</td>
|
||||
<td>{icon}</td>
|
||||
<td>{date_granted}</td>
|
||||
<td>{date_expiry}</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
html += "</tbody></table></div>"
|
||||
html += "</div>"
|
||||
|
||||
partner.machine_access_html = html
|
||||
|
||||
|
||||
|
||||
|
||||
@api.model
|
||||
def migrate_existing_partners(self):
|
||||
"""
|
||||
|
|
@ -283,6 +328,31 @@ class ResPartner(models.Model):
|
|||
|
||||
_logger.info(f"[OWS Migration] ✅ Maschinenfreigaben erstellt: {count_created}")
|
||||
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):
|
||||
_name = 'ows.user'
|
||||
|
|
@ -307,15 +377,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):
|
||||
_name = 'ows.machine.area'
|
||||
_table = "ows_machine_area"
|
||||
_table = 'ows_machine_area'
|
||||
_description = 'OWS: Maschinenbereich'
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
#color = fields.Integer(string="Farbe")
|
||||
color_hex = fields.Char(string="Farbe (Hex)", help="Hex-Farbcode wie #FF0000 für Rot")
|
||||
name = fields.Char(string="Name", required=True, translate=True)
|
||||
|
||||
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):
|
||||
|
|
@ -325,13 +437,37 @@ class OwsMachine(models.Model):
|
|||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
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()
|
||||
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_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_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')
|
||||
def _compute_product_using_names(self):
|
||||
|
|
@ -354,12 +490,34 @@ class OwsMachine(models.Model):
|
|||
|
||||
@api.model
|
||||
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")
|
||||
_logger.info("🔍 Maschinenbereiche: %s", areas.mapped('name'))
|
||||
_logger.info("🔍 Partner_id: %s", partner_id)
|
||||
res = []
|
||||
_logger.info("Access RPC called with partner_id=%s", partner_id)
|
||||
access_by_area = []
|
||||
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 = []
|
||||
for machine in machines:
|
||||
has_access = bool(self.env['ows.machine.access'].search([
|
||||
|
|
@ -370,12 +528,22 @@ class OwsMachine(models.Model):
|
|||
'name': machine.name,
|
||||
'has_access': has_access,
|
||||
})
|
||||
res.append({
|
||||
'area': area.name,
|
||||
'color_hex': area.color_hex or '#000000',
|
||||
'machines': machine_list
|
||||
})
|
||||
return res
|
||||
if machine_list:
|
||||
access_by_area.append({
|
||||
'area': area.name,
|
||||
'color_hex': area.color_hex or '#000000',
|
||||
'machines': machine_list
|
||||
})
|
||||
|
||||
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):
|
||||
|
|
@ -398,7 +566,7 @@ class OwsMachineAccess(models.Model):
|
|||
class OwsMachineProduct(models.Model):
|
||||
_name = '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')
|
||||
machine_id = fields.Many2one('ows.machine', required=True, ondelete='cascade')
|
||||
|
|
@ -406,7 +574,7 @@ class OwsMachineProduct(models.Model):
|
|||
class OwsMachineTraining(models.Model):
|
||||
_name = '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')
|
||||
machine_id = fields.Many2one('ows.machine', required=True, ondelete='cascade')
|
||||
|
|
|
|||
49
models/pos_order.py
Normal file
49
models/pos_order.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from odoo import models, fields, api
|
||||
from collections import defaultdict
|
||||
|
||||
#import debugpy
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
_logger.info("✅ pos_order.py geladen")
|
||||
|
||||
# debugpy.listen(("0.0.0.0", 5678))
|
||||
print("✅ debugpy wartet auf Verbindung (Port 5678) ...")
|
||||
# Optional: Starte erst, wenn VS Code verbunden ist
|
||||
#debugpy.wait_for_client()
|
||||
|
||||
class PosOrder(models.Model):
|
||||
_inherit = 'pos.order'
|
||||
|
||||
def _process_order(self, order, draft, existing_order):
|
||||
pos_order_id = super(PosOrder, self)._process_order(order, draft, existing_order)
|
||||
pos_order = self.browse(pos_order_id)
|
||||
|
||||
training_products = self.env['ows.machine.training'].search([])
|
||||
product_map = defaultdict(list)
|
||||
for tp in training_products:
|
||||
product_map[tp.training_id.product_tmpl_id.id].append(tp.machine_id.id)
|
||||
|
||||
partner = pos_order.partner_id
|
||||
if not partner:
|
||||
_logger.info("🟡 POS-Bestellung ohne Partner – keine Freigabe möglich")
|
||||
return pos_order_id
|
||||
|
||||
for line in pos_order.lines:
|
||||
product_tmpl_id = line.product_id.product_tmpl_id.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)
|
||||
for machine_id in machine_ids:
|
||||
already_exists = self.env['ows.machine.access'].search([
|
||||
('partner_id', '=', partner.id),
|
||||
('machine_id', '=', machine_id)
|
||||
], limit=1)
|
||||
if not already_exists:
|
||||
self.env['ows.machine.access'].create({
|
||||
'partner_id': partner.id,
|
||||
'machine_id': machine_id,
|
||||
'granted_by_pos': True
|
||||
})
|
||||
_logger.info("✅ Maschinenfreigabe erstellt: %s für %s", machine_id, partner.name)
|
||||
|
||||
return pos_order_id
|
||||
|
|
@ -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}")
|
||||
'''
|
||||
|
|
@ -2,7 +2,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|||
access_ows_machine_access_user,ows.machine.access,model_ows_machine_access,base.group_user,1,1,1,1
|
||||
access_ows_machine_user,ows.machine,model_ows_machine,base.group_user,1,1,1,1
|
||||
access_ows_machine_product_user,ows.machine.product,model_ows_machine_product,base.group_user,1,1,1,1
|
||||
access_ows_machine_training_user,access_ows_machine_training_user,model_ows_machine_training,base.group_user,1,1,1,1
|
||||
access_ows_machine_area,ows.machine.area,model_ows_machine_area,base.group_user,1,1,1,1
|
||||
access_ows_user,ows.user,model_ows_user,base.group_user,1,1,1,1
|
||||
access_ows_machine_training_user,ows.machine.training,model_ows_machine_training,base.group_user,1,1,1,1
|
||||
|
||||
access_ows_machine_training,ows.machine.training,model_ows_machine_training,base.group_user,1,1,1,1
|
||||
|
|
|
|||
|
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;
|
||||
}
|
||||
57
static/src/css/pos.css
Normal file
57
static/src/css/pos.css
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
.custompane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ows-sidebar {
|
||||
flex: 1 1 auto;
|
||||
width: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.order-entry:hover { cursor: pointer; }
|
||||
.order-entry.selected { background-color: #007bff; color: white; }
|
||||
|
||||
.ows-customer-list {
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
min-height: 0; /* notwendig für Scrollbar */
|
||||
}
|
||||
|
||||
.client-details-grid {
|
||||
flex-shrink: 0;
|
||||
max-height: 60%;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.pos *::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height:8px;
|
||||
}
|
||||
|
||||
.sidebar-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.5em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
||||
.sidebar-date {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-name {
|
||||
flex-shrink: 1;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
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);
|
||||
|
||||
79
static/src/js/ows_pos_customer_sidebar.js
Normal file
79
static/src/js/ows_pos_customer_sidebar.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// @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() {
|
||||
const order = this.pos.get_order();
|
||||
if (!order) return;
|
||||
|
||||
// 🛑 Sicherheitsabfrage: Order enthält bereits Positionen?
|
||||
if (order.get_orderlines().length > 0) {
|
||||
const confirmed = await ask(this.dialog, {
|
||||
title: _t("Order enthält Positionen"),
|
||||
body: _t("Möchtest du diese Order wirklich löschen?"),
|
||||
confirmText: _t("Löschen"),
|
||||
cancelText: _t("Abbrechen"),
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
// 📋 Alle verbleibenden Orders (nach der aktuellen)
|
||||
const remainingOrders = this.pos.get_order_list().filter(o => o !== order);
|
||||
|
||||
// 🗑 Order entfernen
|
||||
this.pos.removeOrder(order);
|
||||
|
||||
// ✅ Wenn noch andere Orders existieren, eine davon aktivieren
|
||||
if (remainingOrders.length > 0) {
|
||||
this.pos.set_order(remainingOrders[remainingOrders.length - 1]);
|
||||
}
|
||||
|
||||
this.env.bus.trigger('partner-changed');
|
||||
}
|
||||
|
||||
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);
|
||||
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>
|
||||
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>
|
||||
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,2 +0,0 @@
|
|||
from . import test_res_partner
|
||||
from . import test_access_rights
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
'''
|
||||
/odoo_env/src/odoo/odoo-bin \
|
||||
-d hh16-test \
|
||||
--data-dir=/env/filestore/ \
|
||||
--addons-path=/odoo_env/src/odoo/addons,/odoo_env/src/odoo/odoo/addons,/odoo_env/src/openupgrade,/odoo_env/src/OCA/web,/odoo_env/src/OCA/server-tools,/odoo_env/src/vvow \
|
||||
--test-enable \
|
||||
--stop-after-init \
|
||||
--test-tags open_workshop \
|
||||
--log-level=debug \
|
||||
--http-port=9070 \
|
||||
--db_host=db \
|
||||
--db_user=odoo \
|
||||
--db_password=odoo
|
||||
'''
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests import tagged
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@tagged('post_install', 'open_workshop', 'access_rights')
|
||||
class TestOpenWorkshopAccessRights(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group_user = self.env.ref('base.group_user')
|
||||
self.test_user = self.env['res.users'].create({
|
||||
'name': 'Access Test User',
|
||||
'login': 'access_test_user',
|
||||
'groups_id': [(6, 0, [self.group_user.id])],
|
||||
})
|
||||
self.models_to_test = [
|
||||
'ows.machine',
|
||||
'ows.machine.access',
|
||||
'ows.machine.product',
|
||||
'ows.machine.training',
|
||||
'ows.machine.area',
|
||||
'ows.user',
|
||||
]
|
||||
|
||||
def test_model_access_rights(self):
|
||||
for model_name in self.models_to_test:
|
||||
with self.subTest(model=model_name):
|
||||
model = self.env[model_name].with_user(self.test_user)
|
||||
records = model.search([], limit=1)
|
||||
|
||||
# Test Lesezugriff
|
||||
self.assertTrue(records.exists(), f"Kein Zugriff auf {model_name} oder leer")
|
||||
|
||||
# Test Schreib-, Erstell- und Löschrechte nur wenn Eintrag existiert
|
||||
if records:
|
||||
# Schreibtest
|
||||
write_fields = [f for f in records._fields if records._fields[f].type in ('char', 'text') and f != 'id']
|
||||
if write_fields:
|
||||
field = write_fields[0]
|
||||
test_value = 'Test Write'
|
||||
try:
|
||||
records.write({field: test_value})
|
||||
except Exception as e:
|
||||
self.fail(f"❌ Schreibrechte auf {model_name}.{field} verweigert: {e}")
|
||||
|
||||
# Erstellung
|
||||
try:
|
||||
new = records.copy()
|
||||
self.assertTrue(new.exists(), f"❌ Kein Erstellrecht auf {model_name}")
|
||||
except Exception as e:
|
||||
self.fail(f"❌ Erstellung in {model_name} verweigert: {e}")
|
||||
|
||||
# Löschung
|
||||
try:
|
||||
new.unlink()
|
||||
except Exception as e:
|
||||
self.fail(f"❌ Löschrechte auf {model_name} verweigert: {e}")
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
## Testausführung
|
||||
# /odoo_env/src/odoo/odoo-bin -d hh16 --test-enable --stop-after-init --test-tags open_workshop --log-level=debug --update open_workshop --stop-after-init --db_host=db --db_user=odoo --db_password=odoo
|
||||
|
||||
|
||||
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)
|
||||
135
todo.md
135
todo.md
|
|
@ -1,72 +1,101 @@
|
|||
# TODO: Reaktivierung des Moduls "Open Workshop" in Odoo 16.0
|
||||
# 🎯 Ziel des Moduls „Open Workshop“
|
||||
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.
|
||||
|
||||
## ✨ Ziel
|
||||
Die schrittweise Wiederherstellung der Funktionalität des Moduls `open_workshop` in einer nach Odoo 16.0 migrierten Instanz, basierend auf einer zuvor deaktivierten Datenbank.
|
||||
#🛠️ Funktionaler Rahmen & Umgebung
|
||||
## Betriebsumgebung
|
||||
Odoo in mehreren Versionen (aktuell Fokus auf Odoo 18.0)
|
||||
|
||||
---
|
||||
Docker-basierte Installation mit Entwicklungs-, Test- und Produktivinstanzen
|
||||
|
||||
## 🔄 1. Grundlagen sicherstellen
|
||||
- [x] Sicherstellen, dass `ows_models.py` korrekt geladen wird
|
||||
- [x] Alle Modelle müssen sich fehlerfrei installieren lassen
|
||||
- [x] Tabellen wie `ows_user`, `ows_machine`, etc. sind vorhanden und konsistent
|
||||
- [x] Relation `res.partner.ows_user_id` vorhanden
|
||||
PostgreSQL-Datenbank
|
||||
|
||||
---
|
||||
Gitea zur Versionierung und CI (z. B. act_runner)
|
||||
|
||||
## 🧹 2. Datenbasierte Komponenten reaktivieren
|
||||
### 2.1 `data/demo_data.xml`
|
||||
- [ ] Beispieldaten für `ows.machine.area` und `ows.machine` erstellen
|
||||
- [ ] IDs müssen konfliktfrei mit bestehender Datenbank sein
|
||||
- [ ] Module danach neu starten: `-u open_workshop`
|
||||
POS läuft in einem speziellen Frontend mit festen Ansichten und Touch-Bedienung
|
||||
|
||||
### 2.2 `security/ir.model.access.csv`
|
||||
- [x] Zugriff für alle verwendeten Modelle definieren
|
||||
- `ows.user`, `ows.machine`, `ows.machine.access`
|
||||
- Optional: `res.partner` (nur read)
|
||||
- [x] Installieren
|
||||
- [ ] testen ob Zugriff möglich ist -> tests/test_access_rights.py
|
||||
Nutzung durch Personal & Werkstattnutzer:innen auf verschiedenen Geräten
|
||||
|
||||
---
|
||||
Kiosksysteme für Anmeldung, Einweisung, ggf. auch Selbstbedienung
|
||||
|
||||
## 🎨 3. Backend Views stufenweise aktivieren
|
||||
### 3.1 `views/menu_views.xml`
|
||||
- [ ] Menüs einbinden, ohne Abhängigkeiten
|
||||
- [ ] Test: Odoo starten & Menüs sichtbar?
|
||||
Interne Systeme z. T. über *.lan.hobbyhimmel.de zugänglich
|
||||
|
||||
### 3.2 `views/machine_area_views.xml`
|
||||
### 3.3 `views/machine_views.xml`
|
||||
- [ ] Baumansicht (list) zuerst aktivieren
|
||||
- [ ] Danach Form-View (form) hinzufügen
|
||||
# 🔐 Kernfunktionen des Moduls „Open Workshop“
|
||||
## Maschinenverwaltung
|
||||
Maschinenmodell mit Zuordnung zu Bereichen (machine areas)
|
||||
|
||||
### 3.4 `views/res_partner_view.xml`
|
||||
- [ ] Tab "Maschinenfreigaben" aktivieren
|
||||
- [ ] Nur Felder mit klarer Modellbindung einbinden
|
||||
Darstellung aller Maschinen gruppiert nach Bereich im POS
|
||||
|
||||
---
|
||||
Maschinenbereiche enthalten u. a. Farbe (hex), Beschreibung etc.
|
||||
|
||||
## 💻 4. POS Assets & QWeb (optional)
|
||||
### 4.1 `views/assets.xml`
|
||||
- [ ] QWeb-Templates und POS JS nur aktivieren, wenn POS-Modul auch vorhanden ist
|
||||
- [ ] Kompatibilität zu JS (ES5 / `odoo.define`) prüfen
|
||||
Upload von Dokumenten und Bildern möglich
|
||||
|
||||
---
|
||||
## Zugriffsverwaltung / Einweisungen
|
||||
Modell ows.machine.access steuert, welche:r Partner:in auf welche Maschine zugreifen darf
|
||||
|
||||
## 🔬 5. Tools & Debugging
|
||||
- [ ] `odoo-bin shell -d hh16` für gezielte Tests nutzen
|
||||
- [ ] Überprüfen ob Einträge in `ir.model.data` korrekt vorhanden sind
|
||||
- [ ] Logdateien auf Foreign Key oder View-Probleme prüfen
|
||||
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)
|
||||
|
||||
## 🔧 Optional
|
||||
- [ ] `migrate_existing_partners()` über `res.partner` testen
|
||||
- [ ] Migration der alten `vvow_*` Felder validieren
|
||||
Darstellung im POS über grünes Häkchen / rotes Kreuz
|
||||
|
||||
---
|
||||
## Sicherheitsinformationen
|
||||
Modell ows.user verknüpft 1:1 mit res.partner, enthält:
|
||||
|
||||
## ⚙ Nächste Schritte
|
||||
- [ ] Schritt 2 (Demo- und Security-Dateien) zuerst
|
||||
- [ ] Schrittweise View-Dateien aktivieren
|
||||
- [ ] Modul vollständig über Backend installierbar machen
|
||||
- [ ] POS-Integration zuletzt wiederherstellen
|
||||
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
|
||||
|
||||
|
|
|
|||
8
views/assets.xml
Normal file
8
views/assets.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<odoo>
|
||||
<template id="assets_open_workshop" inherit_id="point_of_sale._assets_pos">
|
||||
<xpath expr="." position="inside">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
40
views/machine_area_views.xml
Normal file
40
views/machine_area_views.xml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<!-- machine_area_views.xml -->
|
||||
<odoo>
|
||||
<!-- Action zum Anzeigen der Bereiche -->
|
||||
<record id="action_machine_area_list" model="ir.actions.act_window">
|
||||
<field name="name">Maschinenbereiche</field>
|
||||
<field name="res_model">ows.machine.area</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Menüpunkt unter Maschinen > Konfiguration -->
|
||||
<menuitem id="menu_machine_area" name="Bereiche" parent="menu_machine_config" action="open_workshop.action_machine_area_list" sequence="30"/>
|
||||
|
||||
<!-- Listenansicht -->
|
||||
<record id="view_machine_area_tree" model="ir.ui.view">
|
||||
<field name="name">ows.machine.area.tree</field>
|
||||
<field name="model">ows.machine.area</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="color_hex_value" string="Farbe (Hex)"/>
|
||||
<field name="color_name" string="Farbname"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Formularansicht -->
|
||||
<record id="view_machine_area_form" model="ir.ui.view">
|
||||
<field name="name">ows.machine.area.form</field>
|
||||
<field name="model">ows.machine.area</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Maschinenbereich">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="color_hex"/>
|
||||
<field name="color_name" readonly="1"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
49
views/machine_product_training_views.xml
Normal file
49
views/machine_product_training_views.xml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<odoo>
|
||||
<!-- Tree View: Nutzungsprodukte -->
|
||||
<record id="view_machine_product_tree" model="ir.ui.view">
|
||||
<field name="name">ows.machine.product.tree</field>
|
||||
<field name="model">ows.machine.product</field>
|
||||
<field name="arch" type="xml">
|
||||
<list editable="bottom">
|
||||
<field name="machine_id"/>
|
||||
<field name="product_id" domain="[('categ_id.name', '=', 'Maschinennutzung')]"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Tree View: Einweisungsprodukte -->
|
||||
<record id="view_machine_training_tree" model="ir.ui.view">
|
||||
<field name="name">ows.machine.training.tree</field>
|
||||
<field name="model">ows.machine.training</field>
|
||||
<field name="arch" type="xml">
|
||||
<list editable="bottom">
|
||||
<field name="machine_id"/>
|
||||
<field name="training_id" domain="[('categ_id.name', 'in', ['Einweisungen', 'Kurse'])]"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action: Nutzungsprodukte -->
|
||||
<record id="action_machine_product" model="ir.actions.act_window">
|
||||
<field name="name">Maschinen-Nutzungsprodukte</field>
|
||||
<field name="res_model">ows.machine.product</field>
|
||||
<field name="view_mode">list</field>
|
||||
<field name="view_id" ref="view_machine_product_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p>Verwalte die Zuordnung von Maschinen zu Nutzungsprodukten.</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action: Einweisungsprodukte -->
|
||||
<record id="action_machine_training" model="ir.actions.act_window">
|
||||
<field name="name">Maschinen-Einweisungsprodukte</field>
|
||||
<field name="res_model">ows.machine.training</field>
|
||||
<field name="view_mode">list</field>
|
||||
<field name="view_id" ref="view_machine_training_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p>Verwalte die Zuordnung von Maschinen zu Einweisungsprodukten.</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
71
views/machine_views.xml
Normal file
71
views/machine_views.xml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<!-- machine_views.xml -->
|
||||
<odoo>
|
||||
<!-- Maschinen Listenansicht -->
|
||||
<record id="view_machine_tree" model="ir.ui.view">
|
||||
<field name="name">ows.machine.tree</field>
|
||||
<field name="model">ows.machine</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="category_icon" string="⚙" readonly="1"/>
|
||||
<field name="name"/>
|
||||
<field name="category"/>
|
||||
<field name="code"/>
|
||||
<field name="area_id" widget="many2one_color"/>
|
||||
<field name="product_names"/>
|
||||
<field name="training_names"/>
|
||||
<field name="storage_location"/>
|
||||
<field name="purchase_price"/>
|
||||
<field name="purchase_date"/>
|
||||
<field name="active"/>
|
||||
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Maschinen Formularansicht -->
|
||||
<record id="view_machine_form" model="ir.ui.view">
|
||||
<field name="name">ows.machine.form</field>
|
||||
<field name="model">ows.machine</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Maschine">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="category_icon" string="⚙" readonly="1"/>
|
||||
<field name="name"/>
|
||||
<field name="category"/>
|
||||
<field name="category_icon" string="⚙" readonly="1"/>
|
||||
<field name="code"/>
|
||||
<field name="area_id"/>
|
||||
|
||||
</group>
|
||||
<group>
|
||||
|
||||
<field name="description"/>
|
||||
<field name="storage_location"/>
|
||||
<field name="purchase_price"/>
|
||||
<field name="purchase_date"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
|
||||
<!-- Neue
|
||||
<notebook>
|
||||
<page string="Nutzungsprodukte">
|
||||
<field name="product_ids" context="{'default_machine_id': active_id}">
|
||||
<list editable="bottom">
|
||||
<field name="product_id" domain="[('categ_id.name', '=', 'Maschinennutzung')]" />
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Einweisungsprodukte">
|
||||
<field name="training_ids" context="{'default_machine_id': context.get('active_id', False)}"/>
|
||||
<list editable="bottom">
|
||||
<field name="training_id" domain="[('categ_id.name', 'in', ['Einweisungen', 'Kurse'])]" />
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>-->
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
81
views/menu_views.xml
Normal file
81
views/menu_views.xml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<!-- menu_views.xml -->
|
||||
<odoo>
|
||||
<!-- Maschinenliste -->
|
||||
<record id="action_machine_list" model="ir.actions.act_window">
|
||||
<field name="name">Maschinen</field>
|
||||
<field name="res_model">ows.machine</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Trainingsprodukt-Liste -->
|
||||
<record id="action_training_product_list" model="ir.actions.act_window">
|
||||
<field name="name">Einweisungs-Produkte</field>
|
||||
<field name="res_model">ows.machine.product</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Menüstruktur -->
|
||||
<!-- Oberstes Menü -->
|
||||
<menuitem id="menu_machine_root"
|
||||
name="Maschinen"
|
||||
sequence="10"/>
|
||||
|
||||
<!-- Konfigurationsebene -->
|
||||
<menuitem id="menu_machine_config"
|
||||
name="Konfiguration"
|
||||
parent="menu_machine_root"
|
||||
sequence="10"/>
|
||||
|
||||
<!-- Menüpunkt: Maschinenliste (klickbar) -->
|
||||
<menuitem id="menu_machine_list_action"
|
||||
name="Alle Maschinen"
|
||||
parent="menu_machine_config"
|
||||
action="open_workshop.action_machine_list"
|
||||
sequence="10"/>
|
||||
|
||||
<!-- Menücontainer: Zuordnungen -->
|
||||
<menuitem id="menu_machine_list"
|
||||
name="Zuordnungen"
|
||||
parent="menu_machine_config"
|
||||
sequence="20"/>
|
||||
|
||||
<!-- Untermenü: Nutzungsprodukte -->
|
||||
<menuitem id="menu_machine_product"
|
||||
name="Nutzungsprodukte"
|
||||
parent="menu_machine_list"
|
||||
action="action_machine_product"
|
||||
sequence="10"/>
|
||||
|
||||
<!-- Untermenü: Einweisungsprodukte -->
|
||||
<menuitem id="menu_machine_training"
|
||||
name="Einweisungsprodukte"
|
||||
parent="menu_machine_list"
|
||||
action="action_machine_training"
|
||||
sequence="20"/>
|
||||
|
||||
|
||||
<!-- List & Form Views für training.product -->
|
||||
<record id="view_training_product_tree" model="ir.ui.view">
|
||||
<field name="name">ows.machine.product.tree</field>
|
||||
<field name="model">ows.machine.product</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="product_id"/>
|
||||
<field name="machine_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_training_product_form" model="ir.ui.view">
|
||||
<field name="name">ows.machine.product.form</field>
|
||||
<field name="model">ows.machine.product</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Einweisungs-Produkt">
|
||||
<group>
|
||||
<field name="product_id"/>
|
||||
<field name="machine_id"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
104
views/res_partner_view.xml
Normal file
104
views/res_partner_view.xml
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<odoo>
|
||||
<!-- 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="10"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='sales_purchases']" position="before">
|
||||
<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="date_granted"/>
|
||||
<field name="date_expiry"/>
|
||||
<field name="granted_by_pos"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
|
||||
<!-- ÜBERSICHT: Volle Breite -->
|
||||
<group string="Maschinenfreigaben Übersicht" >
|
||||
<field name="machine_access_html" colspan="2" readonly="1" widget="html" nolabel="1"/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</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>
|
||||
Loading…
Reference in New Issue
Block a user