Skip to content

[IMP]stock_ux: Add label transfer report and template#884

Open
jcadhoc wants to merge 3 commits intoingadhoc:19.0from
adhoc-dev:19.0-t-64865-jc
Open

[IMP]stock_ux: Add label transfer report and template#884
jcadhoc wants to merge 3 commits intoingadhoc:19.0from
adhoc-dev:19.0-t-64865-jc

Conversation

@jcadhoc
Copy link
Copy Markdown
Contributor

@jcadhoc jcadhoc commented Mar 10, 2026

No description provided.

Copilot AI review requested due to automatic review settings March 10, 2026 18:12
@roboadhoc
Copy link
Copy Markdown
Collaborator

Pull request status dashboard

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR incorpora la impresión de etiquetas para transferencias (pickings) agregando un flujo de wizard desde el formulario de stock.picking, junto con templates/reportes (ZPL/PDF) y un campo informativo en el picking para mostrar un resumen relacionado.

Changes:

  • Se añade una acción y botón en stock.picking para abrir un wizard de impresión de etiquetas, con una vista específica y una grilla editable de líneas/cantidades.
  • Se extiende el wizard product.label.layout para soportar un formato ZPL personalizado y generar el archivo ZPL para descarga.
  • Se incorporan nuevos report actions/templates (ZPL/PDF) y se ajusta el manifest para cargar los nuevos recursos.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
stock_ux/wizards/stock_operation_wizard_views.xml Agrega la vista del wizard y el tree de líneas editable para impresión ZPL.
stock_ux/wizards/stock_label_type.py Extiende product.label.layout para picking y genera ZPL/adjunto descargable; define líneas transient y validaciones.
stock_ux/views/stock_picking_voucher_views.xml Añade acción/botón “Print Labels” en picking y muestra el campo vouchers en la vista árbol.
stock_ux/models/stock_picking.py Introduce el campo computado vouchers para mostrar un resumen en pickings.
stock_ux/report/label_transfer_reports.xml Define paperformat y acciones de reporte para etiquetas (ZPL/PDF).
stock_ux/report/label_transfer_template.xml Añade templates QWeb para ZPL y PDF.
stock_ux/report/label_transfer_template.xml.bak Archivo backup añadido junto al template (no referenciado en manifest).
stock_ux/report/ir.action.reports.xml Remueve la definición antigua del action report ZPL de este archivo.
stock_ux/manifest.py Registra los nuevos XML de vistas y reportes para que se carguen.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread stock_ux/wizards/stock_label_type.py Outdated
Comment on lines +39 to +56
def _get_line_quantities(self):
"""
Get editable quantities from line_ids.
Workaround: TransientModel doesn't persist Many2one correctly,
so we match lines to moves by position.
"""
quantity_map = {}
if not self.line_ids or not self.picking_id:
return quantity_map

moves = self.picking_id.move_ids.filtered(lambda m: m.quantity > 0).sorted("id")
lines = self.line_ids.sorted("id")

for idx, line in enumerate(lines):
if idx < len(moves):
quantity_map[moves[idx].id] = int(line.move_quantity or 0)

return quantity_map
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_line_quantities() está mapeando cantidades por posición (líneas ordenadas por id vs moves ordenados por id), lo que puede desalinearse con facilidad (orden original de move_ids, borrado/creación de líneas, etc.) y terminar asignando cantidades al movimiento equivocado. Dado que stock.picking.zpl.lines ya tiene move_id, el mapeo debería basarse en line.move_id.id en vez de depender del índice.

Copilot uses AI. Check for mistakes.
Comment thread stock_ux/wizards/stock_label_type.py Outdated
Comment on lines +202 to +207
max_qty = max(line.move_id.quantity or 0, line.move_id.product_uom_qty or 0)
if max_qty > 0 and line.move_quantity > max_qty:
raise exceptions.ValidationError(
f"La cantidad a imprimir ({line.move_quantity}) no puede ser mayor "
f"que la cantidad disponible ({max_qty})."
)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En la constraint _check_move_quantity, la condición if max_qty > 0 and line.move_quantity > max_qty permite imprimir cantidades > 0 cuando max_qty es 0 (porque no entra al raise). Eso rompe la validación para movimientos sin cantidad disponible. Además, el mensaje se arma con f-string y no queda traducible; en este módulo se suele usar _() para errores de usuario.

Copilot uses AI. Check for mistakes.
Comment thread stock_ux/models/stock_picking.py Outdated
Comment on lines +27 to +40
vouchers = fields.Char(
string="Labels",
compute="_compute_vouchers",
help="Summary of printed labels for this transfer",
)

def _compute_vouchers(self):
"""Compute vouchers field - can be extended for tracking printed labels"""
for rec in self:
# Simple implementation - can be extended to track actual prints
if rec.state == "done" and rec.date_done:
rec.vouchers = f"Completed {rec.date_done.strftime('%d/%m/%Y')}"
else:
rec.vouchers = ""
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El campo computado vouchers no declara @api.depends, por lo que la cache puede quedar desactualizada cuando cambien state o date_done en el mismo entorno/transaction. Debería agregarse @api.depends('state', 'date_done') (o los campos que correspondan) para asegurar invalidación/recompute correcto.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +37
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ZPL Template for Thermal Printers - Simple 2 labels per page like v18 -->
<template id="custom_barcode_transfer_template_view_zpl">
<t t-foreach="docs" t-as="o">
<t t-foreach="o.line_ids" t-as="line">
^XA
^CI28
^LH0,0
^FO20,10,0

^FO250,10
^A0N,20,25^FD<t t-esc="o.env.company.name or ''\"/>^FS

^FO10,40
^A0N,40,30
^TBN,360,40
^FD<t t-esc="line.move_id.product_id.display_name or ''\"/>^FS

^FO10,90
^BY3
^BCN,60,Y,N,N,A
^FD<t t-esc="line.move_id.product_id.barcode or line.move_id.product_id.default_code or ''\"/>^FS
^FX Nueva etiqueta
^LH445,0
^FO20,10,0
^FO250,10
^A0N,20,25^FD<t t-esc="o.env.company.name or ''\"/>^FS
^FO10,40
^A0N,40,30
^TBN,360,40
^FD<t t-esc="line.move_id.product_id.display_name or ''\"/>^FS
^FO10,90
^BY3
^BCN,60,Y,N,N,A
^FD<t t-esc="line.move_id.product_id.barcode or line.move_id.product_id.default_code or ''\"/>^FS
^XZ
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Se está agregando label_transfer_template.xml.bak al módulo y, además de ser un “backup”, contiene QWeb/XML inválido (por ejemplo ''\"/>). Aunque no esté en el manifest, suele ser mejor no versionar estos archivos dentro del addon para evitar confusiones y posibles validaciones/lints sobre XML. Sugerencia: eliminarlo del módulo o moverlo fuera del path de datos del addon.

Copilot uses AI. Check for mistakes.
Comment thread stock_ux/wizards/stock_label_type.py Outdated
Comment on lines +136 to +147
def _create_zpl_attachment(self, zpl_content, filename):
"""Create attachment and return download action"""
import base64

attachment = self.env["ir.attachment"].create(
{
"name": filename,
"datas": base64.b64encode(zpl_content.encode("utf-8")),
"mimetype": "text/plain",
"res_model": self._name,
"res_id": self.id,
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_create_zpl_attachment() crea un ir.attachment por cada impresión y lo vincula a un TransientModel (res_model = self._name, res_id = self.id). Como los transients se purgan pero los attachments no tienen FK, esto puede dejar muchos adjuntos huérfanos en la base con el tiempo. Alternativas: evitar crear attachment y devolver el contenido vía ir.actions.report (qweb-text) / streaming, o bien vincular el attachment al stock.picking (cuando exista) y/o implementar una limpieza explícita de estos adjuntos.

Copilot uses AI. Check for mistakes.
Comment thread stock_ux/models/stock_picking.py Outdated
Comment on lines +33 to +40
def _compute_vouchers(self):
"""Compute vouchers field - can be extended for tracking printed labels"""
for rec in self:
# Simple implementation - can be extended to track actual prints
if rec.state == "done" and rec.date_done:
rec.vouchers = f"Completed {rec.date_done.strftime('%d/%m/%Y')}"
else:
rec.vouchers = ""
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En _compute_vouchers() se arma un string con f-string ("Completed ...") que queda sin traducir y además formatea la fecha con strftime, ignorando locale/formatos de Odoo. Para textos visibles al usuario suele convenir usar _() y helpers de formato (format_date/format_datetime) para respetar idioma y configuración regional.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +11
<record id="action_product_label_layout_picking" model="ir.actions.act_window">
<field name="name">Print Transfer Labels</field>
<field name="res_model">product.label.layout</field>
<field name="view_mode">form</field>
<field name="view_id" ref="product_label_layout_form_view_picking"/>
<field name="target">new</field>
<field name="binding_model_id" ref="stock.model_stock_picking"/>
</record>
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

La acción action_product_label_layout_picking está vinculada al modelo (binding_model_id) pero no limita binding_view_types. Eso hace probable que aparezca también en la vista lista y pueda ejecutarse con múltiples pickings seleccionados, lo que hoy deja el wizard inconsistente (ver manejo de active_ids). Para evitarlo, convendría restringirla a form (binding_view_types) o forzar single-record en el wizard.

Copilot uses AI. Check for mistakes.
Comment thread stock_ux/wizards/stock_label_type.py Outdated
Comment on lines 26 to 36
if active_model == "stock.picking":
move_ids = self.env[active_model].browse(active_ids).mapped("move_ids").filtered(lambda x: x.quantity > 0)
active_ids = self.env.context.get("active_ids") or self.env.context.get("active_id")
picking = self.env[active_model].browse(active_ids)
rec["picking_id"] = picking.id if picking else False

# Create lines for each move with quantity > 0
move_ids = picking.mapped("move_ids").filtered(lambda x: x.quantity > 0)
rec["line_ids"] = [
Command.create({"move_id": x.id, "move_quantity": x.quantity, "move_uom_id": x.product_uom.id})
for x in move_ids
]
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En default_get, si el wizard se abre desde una lista con múltiples pickings seleccionados (active_ids), browse(active_ids) devuelve un recordset y aquí se termina guardando solo picking.id (primer registro) pero line_ids se arma con movimientos de todos los pickings. Eso deja el wizard en un estado inconsistente y luego _generate_zpl_content() solo imprime para self.picking_id (el primero). Sería mejor limitar explícitamente a un solo picking (p. ej. validar len(picking)==1 y lanzar UserError si no) o soportar múltiples pickings de forma coherente (sin perder la relación).

Copilot uses AI. Check for mistakes.
Comment thread stock_ux/wizards/stock_label_type.py Outdated
Comment on lines +97 to +107
for move in self.picking_id.move_ids.filtered(lambda m: m.quantity > 0):
product = move.product_id
product_name = product.display_name or ""
barcode = product.barcode or product.default_code or ""
quantity = quantity_map.get(move.id, int(max(move.quantity or 0, move.product_uom_qty or 0)))

for _ in range(quantity):
label_counter += 1
is_first_of_pair = label_counter % 2 != 0
zpl_output += self._generate_zpl_label(product_name, barcode, is_first_of_pair)

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En _generate_zpl_content()/_generate_zpl_from_products() se fuerza int(...) para la cantidad de etiquetas. Si el usuario ingresa decimales en move_quantity (es Float) o si el movimiento tiene cantidades no enteras, se va a truncar silenciosamente (p. ej. 1.9 → 1) y se imprimen menos etiquetas de las esperadas. Conviene usar un campo Integer para la cantidad de etiquetas o, como mínimo, validar que la cantidad sea un entero (y/o decidir una estrategia explícita de redondeo).

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +36
<template id="custom_barcode_transfer_template_view_zpl">
<t t-foreach="docs" t-as="o"><t t-foreach="o.line_ids" t-as="line">^XA
^CI28
^LH0,0
^FO20,10,0

^FO250,10
^A0N,20,25^FD<t t-out="o.env.company.name or ''"/>^FS

^FO10,40
^A0N,40,30
^TBN,360,40
^FD<t t-out="line.move_id.product_id.display_name or ''"/>^FS

^FO10,90
^BY3
^BCN,60,Y,N,N,A
^FD<t t-out="line.move_id.product_id.barcode or line.move_id.product_id.default_code or ''"/>^FS
^FX Nueva etiqueta
^LH445,0
^FO20,10,0
^FO250,10
^A0N,20,25^FD<t t-out="o.env.company.name or ''"/>^FS
^FO10,40
^A0N,40,30
^TBN,360,40
^FD<t t-out="line.move_id.product_id.display_name or ''"/>^FS
^FO10,90
^BY3
^BCN,60,Y,N,N,A
^FD<t t-out="line.move_id.product_id.barcode or line.move_id.product_id.default_code or ''"/>^FS
^XZ
</t></t>
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El template ZPL quedó “minificado” en una sola línea con tags QWeb anidados (<t t-foreach...>), lo que hace muy difícil mantenerlo y revisar diffs (y es fácil introducir XML inválido sin verlo). Conviene reindentarlo/estructurarlo en múltiples líneas para que sea legible y editable sin romper el XML/QWeb.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants