feat(aspl_equipment_qrcode_generator): Fix QR-code generation and PDF rendering

- Move QR-code generation from report to wizard (before PDF generation)
- Add attachment=True to qr_code Binary field for filestore storage
- Simplify report class - only provides data, no QR generation
- Change template from t-field to <img> with base64 data URL for proper PDF rendering
- Fix JSONB field extraction (name, category) for de_DE/en_US
- Add equipment_ids field to wizard form (invisible)
- Apply changes to all 3 label formats (2x5, 2x7, 4x7)

QR-codes now display correctly in generated PDFs and contain equipment
details plus direct link to Odoo equipment record.
This commit is contained in:
Matthias Lotz 2025-12-11 21:53:21 +01:00
parent 550cdac1eb
commit e1feeb6d75
5 changed files with 101 additions and 88 deletions

View File

@ -6,7 +6,7 @@ from odoo import models, fields
class MaintenanceEquipment(models.Model):
_inherit = 'maintenance.equipment'
qr_code = fields.Binary("QR Code")
qr_code = fields.Binary("QR Code", attachment=True)
comp_serial_no = fields.Char("Inventory Serial No", tracking=True)
serial_no = fields.Char('Mfg. Serial Number', copy=False)

View File

@ -12,7 +12,7 @@
<div class="o_label_data">
<div class="text-center o_label_right_column">
<t t-if="equipment.qr_code">
<div t-field="equipment.qr_code" t-options="{'widget': 'image'}" style="width: 130px;margin-top: -40px;"/>
<img t-att-src="'data:image/png;base64,' + equipment.qr_code.decode('utf-8')" style="width: 130px;margin-top: -40px;"/>
</t>
</div>
<div class="text-left" style="line-height:normal;word-wrap: break-word;">
@ -40,7 +40,7 @@
</div>
<div class= "text-center o_label_right_column">
<t t-if="equipment.qr_code">
<div t-field="equipment.qr_code" t-options="{'widget': 'image'}" style="width:95px;padding:0px;margin-top:-10px"/>
<img t-att-src="'data:image/png;base64,' + equipment.qr_code.decode('utf-8')" style="width:95px;padding:0px;margin-top:-10px"/>
</t>
</div>
<div class="text-left o_label_left_column" style="line-height:normal;word-wrap: break-word;">
@ -66,19 +66,19 @@
<td style="width:25%;">Name</td>
<td style="width:5%">:</td>
<td colspan="2" style="width:70%;word-wrap: break-word;">
<t t-esc="equipment.name"/>
<span t-field="equipment.name"/>
</td>
</tr>
<tr>
<td style="width:25%">Model</td>
<td style="width:5%">:</td>
<td style="width:35%;word-wrap: break-word;">
<t t-esc="equipment.model"/>
<span t-field="equipment.model"/>
</td>
<td rowspan="5" style="width:35%">
<t t-if="equipment.name">
<div t-field="equipment.qr_code" t-options="{'widget': 'image'}"
style="width:30mm;height:10mm;"/>
<t t-if="equipment.qr_code">
<img t-att-src="'data:image/png;base64,' + equipment.qr_code.decode('utf-8')"
style="width:30mm;height:30mm;"/>
</t>
</td>
</tr>
@ -87,23 +87,22 @@
<td style="width:25%">Mfg Serial</td>
<td style="width:5%">:</td>
<td style="width:35%;font-size:10px;word-wrap: break-word;">
<t t-esc="equipment.serial_no"/>
<span t-field="equipment.serial_no"/>
</td>
</tr>
<tr>
<td style="width:25%">Serial</td>
<td style="width:5%">:</td>
<td style="width:35%;word-wrap: break-word;">
<t t-esc="equipment.comp_serial_no"/>
<span t-field="equipment.comp_serial_no"/>
</td>
</tr>
<tr>
<td style="width:25%">Warranty Date</td>
<td style="width:5%">:</td>
<td style="width:35%;word-wrap: break-word;">
<t t-esc="equipment.warranty_date"/>
<span t-field="equipment.warranty_date"/>
</td>
<!-- <td style="width:35%;"></td> -->
</tr>
<tr>
<td style="width:25%"></td>

View File

@ -1,84 +1,34 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import math
from io import BytesIO
import qrcode
from odoo import models
def generate_qr_code(value):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=20,
border=4,
)
qr.add_data(value)
qr.make(fit=True)
img = qr.make_image()
temp = BytesIO()
img.save(temp, format="PNG")
qr_img = base64.b64encode(temp.getvalue())
return qr_img
def _prepare_data(env, data):
equipment_label_layout_id = env['equipment.label.layout'].browse(data['equipment_label_layout_id'])
equipment_dict = {}
equipment_ids = equipment_label_layout_id.equipment_ids
for equipment in equipment_ids:
if not equipment.name:
continue
equipment_dict[equipment] = 1
combine_equipment_detail = ""
# Generate Equipment Redirect LInk
url = env['ir.config_parameter'].sudo().get_param('web.base.url')
menuId = env.ref('maintenance.menu_equipment_form').sudo().id
actionId = env.ref('maintenance.hr_equipment_action').sudo().id
equipment_link = url + '/web#id=' + str(equipment.id) + '&menu_id=' + str(menuId) + '&action=' + str(
actionId) + '&model=maintenance.equipment&view_type=form'
# Prepare main Equipment Detail
main_equipment_detail = ""
main_equipment_detail = main_equipment_detail.join(
"Name: " + str(equipment.name) + "\n" +
"Model: " + str(equipment.model) + "\n" +
"Mfg serial no: " + str(equipment.serial_no) + "\n"
"Warranty Exp. Date: " +str(equipment.warranty_date) + "\n"
"Category: " +str(equipment.category_id.name)
)
# main_equipment_detail = equipment_link + '\n' + '\n' + main_equipment_detail
# Prepare Child Equipment Detail
combine_equipment_detail = main_equipment_detail
combine_equipment_detail += '\n' + '\n' + equipment_link
# Generate Qr Code depends on Details
qr_image = generate_qr_code(combine_equipment_detail)
equipment.write({
'qr_code': qr_image
})
env.cr.commit()
page_numbers = (len(equipment_ids) - 1) // (equipment_label_layout_id.rows * equipment_label_layout_id.columns) + 1
dict_equipment = {
'rows': equipment_label_layout_id.rows,
'columns': equipment_label_layout_id.columns,
'page_numbers': page_numbers,
'equipment_data': equipment_dict
}
return dict_equipment
class ReportProductTemplateLabel(models.AbstractModel):
_name = 'report.aspl_equipment_qrcode_generator.maintenance_quip'
_description = 'Equipment QR-code Report'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, data)
def _get_report_values(self, docids, data=None):
"""
QR-Code-Generierung erfolgt im Wizard (equipment_label_layout.py).
Dieser Report rendert nur die bereits generierten QR-Codes.
"""
if not data:
return {}
equipment_label_layout_id = self.env['equipment.label.layout'].browse(data.get('equipment_label_layout_id'))
equipment_dict = {}
for equipment in equipment_label_layout_id.equipment_ids:
equipment_dict[equipment] = 1
page_numbers = (len(equipment_label_layout_id.equipment_ids) - 1) // (
equipment_label_layout_id.rows * equipment_label_layout_id.columns
) + 1
return {
'rows': equipment_label_layout_id.rows,
'columns': equipment_label_layout_id.columns,
'page_numbers': page_numbers,
'equipment_data': equipment_dict
}

View File

@ -16,6 +16,16 @@ class EquipmentLabelLayout(models.TransientModel):
rows = fields.Integer(compute='_compute_dimensions')
columns = fields.Integer(compute='_compute_dimensions')
@api.model
def default_get(self, fields_list):
"""Override to properly set equipment_ids from context"""
res = super().default_get(fields_list)
if self.env.context.get('active_ids'):
res['equipment_ids'] = [(6, 0, self.env.context.get('active_ids', []))]
elif self.env.context.get('default_equipment_ids'):
res['equipment_ids'] = [(6, 0, self.env.context.get('default_equipment_ids', []))]
return res
@api.depends('print_format')
def _compute_dimensions(self):
for wizard in self:
@ -28,9 +38,62 @@ class EquipmentLabelLayout(models.TransientModel):
def process_label(self):
# Generiere QR-Codes für alle Equipment VOR dem Report
self._generate_qr_codes()
xml_id = 'aspl_equipment_qrcode_generator.report_equipment_label'
data = {
'equipment_label_layout_id':self.id
'equipment_label_layout_id': self.id
}
return self.env.ref(xml_id).report_action(None, data=data)
# report_action benötigt die Equipment IDs als docids
return self.env.ref(xml_id).report_action(self.equipment_ids.ids, data=data)
def _generate_qr_codes(self):
"""Generiert QR-Codes für alle ausgewählten Equipment"""
import base64
from io import BytesIO
import qrcode
# Hole die base_url für die Equipment-Links
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
menu_id = self.env.ref('maintenance.menu_equipment_form').sudo().id
action_id = self.env.ref('maintenance.hr_equipment_action').sudo().id
for equipment in self.equipment_ids:
# Extrahiere Namen aus JSONB falls nötig
equipment_name = equipment.name
if isinstance(equipment_name, dict):
equipment_name = equipment_name.get('de_DE') or equipment_name.get('en_US') or str(equipment_name)
category_name = equipment.category_id.name if equipment.category_id else ""
if isinstance(category_name, dict):
category_name = category_name.get('de_DE') or category_name.get('en_US') or str(category_name)
# Erstelle Equipment-Link
equipment_link = f"{base_url}/web#id={equipment.id}&menu_id={menu_id}&action={action_id}&model=maintenance.equipment&view_type=form"
# Erstelle Equipment-Details für QR-Code
qr_data = f"Name: {equipment_name}\n"
qr_data += f"Model: {equipment.model or ''}\n"
qr_data += f"Mfg serial no: {equipment.serial_no or ''}\n"
qr_data += f"Warranty Exp. Date: {equipment.warranty_date or ''}\n"
qr_data += f"Category: {category_name}\n\n"
qr_data += equipment_link
# Generiere QR-Code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=20,
border=4,
)
qr.add_data(qr_data)
qr.make(fit=True)
img = qr.make_image()
temp = BytesIO()
img.save(temp, format="PNG")
qr_image = base64.b64encode(temp.getvalue())
# Speichere QR-Code im Equipment
equipment.write({'qr_code': qr_image})

View File

@ -7,6 +7,7 @@
<field name="arch" type="xml">
<form>
<group>
<field name="equipment_ids" invisible="1"/>
<group>
<field name="print_format" widget="radio"/>
</group>