Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7fa003337 | |||
| adffd9f2ba | |||
| 392d88c030 |
|
|
@ -1 +1,3 @@
|
||||||
from . import models
|
from . import models
|
||||||
|
from . import controllers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,39 @@
|
||||||
{
|
{
|
||||||
'name': 'POS Open Workshop',
|
'name': 'POS Open Workshop',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'version': '16.0.1.0.0',
|
'version': '17.0.1.0.0',
|
||||||
'summary': 'Erstellt Maschinenfreigaben basierend auf POS-Einweisungsprodukten',
|
'summary': 'Erstellt Maschinenfreigaben basierend auf POS-Einweisungsprodukten',
|
||||||
'depends': ['base','product','sale','contacts','point_of_sale'],
|
'depends': ['base','product','hr','sale','contacts','point_of_sale'],
|
||||||
'author': 'matthias.lotz',
|
'author': 'matthias.lotz',
|
||||||
'category': 'Point of Sale',
|
'category': 'Point of Sale',
|
||||||
'data': [
|
'data': [
|
||||||
'data/data.xml',
|
|
||||||
'security/ir.model.access.csv',
|
'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',
|
||||||
|
],
|
||||||
|
'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': {
|
||||||
|
'point_of_sale.assets': [
|
||||||
|
'open_workshop/static/src/js/product_screen_template_patch.js',
|
||||||
|
'open_workshop/static/src/css/pos.css',
|
||||||
|
'open_workshop/static/src/js/ows_pos_customer_sidebar.js',
|
||||||
|
'open_workshop/static/src/xml/ows_pos_customer_sidebar.xml',
|
||||||
|
'open_workshop/static/src/xml/ows_product_screen.xml',
|
||||||
|
],
|
||||||
|
},
|
||||||
'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 16.0 konzipiert.
|
Die App ist für den Einsatz in der Odoo-Version 17.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
|
||||||
11
controllers/pos_access.py
Normal file
11
controllers/pos_access.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Datei: controllers/pos_access.py
|
||||||
|
|
||||||
|
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, partner_id):
|
||||||
|
Machine = request.env['ows.machine'].sudo()
|
||||||
|
return Machine.get_access_list_grouped(partner_id)
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from . import ows_models
|
from . import ows_models
|
||||||
|
from . import pos_order
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,33 @@ 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)
|
||||||
|
_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):
|
class ResPartner(models.Model):
|
||||||
_inherit = 'res.partner'
|
_inherit = 'res.partner'
|
||||||
_logger.info("✅ ows ResPartner geladen")
|
_logger.info("✅ ows ResPartner geladen")
|
||||||
|
|
@ -141,32 +168,49 @@ 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):
|
||||||
"""
|
"""
|
||||||
|
|
@ -284,6 +328,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'
|
||||||
_description = 'OWS: Benutzerdaten'
|
_description = 'OWS: Benutzerdaten'
|
||||||
|
|
|
||||||
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
|
||||||
|
|
@ -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_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_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_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_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_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
|
||||||
|
|
||||||
|
|
|
||||||
|
3
static/src/css/pos.css
Normal file
3
static/src/css/pos.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.ows-sidebar { width: 220px; }
|
||||||
|
.order-entry:hover { cursor: pointer; }
|
||||||
|
.order-entry.selected { background-color: #007bff; color: white; }
|
||||||
132
static/src/js/machine_access_sidebar.js
Normal file
132
static/src/js/machine_access_sidebar.js
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
/* 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');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
44
static/src/js/ows_pos_customer_sidebar.js
Normal file
44
static/src/js/ows_pos_customer_sidebar.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
// @odoo-module
|
||||||
|
|
||||||
|
import { Component } from "@odoo/owl";
|
||||||
|
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||||
|
|
||||||
|
export class OwsPosCustomerSidebar extends Component {
|
||||||
|
static template = "open_workshop.OwsPosCustomerSidebar";
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.pos = usePos();
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrder() {
|
||||||
|
this.pos.add_new_order();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCurrentOrder() {
|
||||||
|
const order = this.pos.get_order();
|
||||||
|
if (order) {
|
||||||
|
this.pos.removeOrder(order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openTicketScreen() {
|
||||||
|
this.pos.showScreen("TicketScreen");
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilteredOrderList() {
|
||||||
|
return this.pos.get_order_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDate(order) {
|
||||||
|
const date = new Date(order.creationDate || order.creation_date || Date.now());
|
||||||
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
getPartner(order) {
|
||||||
|
return order.get_partner()?.name || "Kein Kunde";
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOrder(order) {
|
||||||
|
this.pos.set_order(order);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
static/src/js/product_screen_template_patch.js
Normal file
16
static/src/js/product_screen_template_patch.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// @odoo-module
|
||||||
|
|
||||||
|
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
|
||||||
|
import { registry } from "@web/core/registry";
|
||||||
|
import { OwsPosCustomerSidebar } from "@open_workshop/js/ows_pos_customer_sidebar";
|
||||||
|
|
||||||
|
export class OwsProductScreen extends ProductScreen {
|
||||||
|
static template = "open_workshop.ProductScreen";
|
||||||
|
static components = {
|
||||||
|
...ProductScreen.components,
|
||||||
|
OwsPosCustomerSidebar,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.category("pos_screens").remove("ProductScreen");
|
||||||
|
registry.category("pos_screens").add("ProductScreen", OwsProductScreen);
|
||||||
72
static/src/xml/ows_briefing_details.xml
Normal file
72
static/src/xml/ows_briefing_details.xml
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?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>
|
||||||
64
static/src/xml/ows_briefing_details_edit.xml
Normal file
64
static/src/xml/ows_briefing_details_edit.xml
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?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>
|
||||||
88
static/src/xml/ows_machine_sidebar.xml
Normal file
88
static/src/xml/ows_machine_sidebar.xml
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve">
|
||||||
|
<t t-name="MachineAccessSidebar">
|
||||||
|
<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>
|
||||||
23
static/src/xml/ows_pos_customer_sidebar.xml
Normal file
23
static/src/xml/ows_pos_customer_sidebar.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?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 t-esc="getDate(order)"/> – <t t-esc="getPartner(order)"/></div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
70
static/src/xml/ows_pos_order_selector.xml
Normal file
70
static/src/xml/ows_pos_order_selector.xml
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?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>
|
||||||
45
static/src/xml/ows_product_screen.xml
Normal file
45
static/src/xml/ows_product_screen.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve">
|
||||||
|
<t t-name="open_workshop.ProductScreen" owl="1">
|
||||||
|
<div class="product-screen d-flex h-100 bg-100" t-att-class="{ 'd-none': !props.isShown }">
|
||||||
|
<div class="custompane d-flex flex-column border-end bg-200">
|
||||||
|
<OwsPosCustomerSidebar />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="!ui.isSmall || pos.mobile_pane === 'left'"
|
||||||
|
t-att-class="{'flex-grow-1': ui.isSmall}"
|
||||||
|
class="leftpane d-flex flex-column border-end bg-200" >
|
||||||
|
<OrderWidget lines="currentOrder.orderlines" t-slot-scope="scope"
|
||||||
|
total="env.utils.formatCurrency(currentOrder.get_total_with_tax())"
|
||||||
|
tax="!env.utils.floatIsZero(currentOrder.get_total_tax()) and env.utils.formatCurrency(currentOrder.get_total_tax()) or ''">
|
||||||
|
<t t-set="line" t-value="scope.line" />
|
||||||
|
<Orderline line="line.getDisplayData()"
|
||||||
|
t-on-click="() => this.selectLine(line)"
|
||||||
|
class="{ ...line.getDisplayClasses(), 'selected' : line.selected }"/>
|
||||||
|
<t t-set-slot="details" />
|
||||||
|
</OrderWidget>
|
||||||
|
<div class="pads border-top">
|
||||||
|
<div class="control-buttons d-flex flex-wrap border-bottom overflow-hidden bg-300" t-if="!ui.isSmall">
|
||||||
|
<t t-foreach="controlButtons" t-as="cb" t-key="cb.name">
|
||||||
|
<t t-component="cb.component" t-key="cb.name"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="subpads d-flex">
|
||||||
|
<ActionpadWidget partner="partner" actionName="constructor.numpadActionName" actionType="'payment'" onClickMore.bind="displayAllControlPopup" />
|
||||||
|
<Numpad buttons="getNumpadButtons()" onClick.bind="onNumpadClick" class="'w-100'"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rightpane overflow-auto d-flex flex-grow-1 flex-column bg-300 w-60" t-if="!ui.isSmall || pos.mobile_pane === 'right'">
|
||||||
|
<ProductsWidget />
|
||||||
|
<t t-if="ui.isSmall">
|
||||||
|
<div class="product-reminder d-flex justify-content-center align-items-center py-1 text-bg-warning bg-opacity-50 fw-bolder"
|
||||||
|
t-if="currentOrder.get_selected_orderline() and currentOrder.hasJustAddedProduct" t-key="animationKey" >
|
||||||
|
<span><t t-esc="selectedOrderlineQuantity"/> <t t-esc="selectedOrderlineDisplayName"/> <t t-esc="selectedOrderlineTotal"/></span>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
from . import test_res_partner
|
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,6 +1,8 @@
|
||||||
## Testausführung
|
## 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
|
# 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.common import TransactionCase
|
||||||
from odoo.tests import tagged
|
from odoo.tests import tagged
|
||||||
|
|
|
||||||
9
views/assets.xml
Normal file
9
views/assets.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!--<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>-->
|
||||||
|
|
||||||
38
views/machine_area_views.xml
Normal file
38
views/machine_area_views.xml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!-- 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">tree,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">
|
||||||
|
<tree>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="color_hex" widget="color_picker"/>
|
||||||
|
</tree>
|
||||||
|
</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" widget="color_picker"/>
|
||||||
|
</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">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field name="machine_id"/>
|
||||||
|
<field name="product_id" domain="[('categ_id.name', '=', 'Maschinennutzung')]"/>
|
||||||
|
</tree>
|
||||||
|
</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">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field name="machine_id"/>
|
||||||
|
<field name="training_id" domain="[('categ_id.name', 'in', ['Einweisungen', 'Kurse'])]"/>
|
||||||
|
</tree>
|
||||||
|
</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">tree</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">tree</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>
|
||||||
55
views/machine_views.xml
Normal file
55
views/machine_views.xml
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<!-- 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">
|
||||||
|
<tree>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="code"/>
|
||||||
|
<field name="area_id" widget="many2one_color"/>
|
||||||
|
<field name="product_names"/>
|
||||||
|
<field name="training_names"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</tree>
|
||||||
|
</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="name"/>
|
||||||
|
<field name="code"/>
|
||||||
|
<field name="area_id"/>
|
||||||
|
<field name="description"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<!-- Neue -->
|
||||||
|
<notebook>
|
||||||
|
<page string="Nutzungsprodukte">
|
||||||
|
<field name="product_ids" context="{'default_machine_id': active_id}">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field name="product_id" domain="[('categ_id.name', '=', 'Maschinennutzung')]" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Einweisungsprodukte">
|
||||||
|
<field name="training_ids" context="{'default_machine_id': active_id}">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field name="training_id" domain="[('categ_id.name', 'in', ['Einweisungen', 'Kurse'])]" />
|
||||||
|
</tree>
|
||||||
|
</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">tree,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">tree,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">
|
||||||
|
<tree>
|
||||||
|
<field name="product_id"/>
|
||||||
|
<field name="machine_id"/>
|
||||||
|
</tree>
|
||||||
|
</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">
|
||||||
|
<tree>
|
||||||
|
<field name="machine_id"/>
|
||||||
|
<field name="date_granted"/>
|
||||||
|
<field name="date_expiry"/>
|
||||||
|
<field name="granted_by_pos"/>
|
||||||
|
</tree>
|
||||||
|
</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">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"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue
Block a user