Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FW][FIX] stock: fix text product label reports with special characters #155853

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 37 additions & 3 deletions addons/stock/report/product_label_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from odoo import _, models
from odoo.exceptions import UserError

import markupsafe

class ReportProductLabel(models.AbstractModel):
_name = 'report.stock.label_product_product_view'
Expand All @@ -22,11 +23,44 @@ def _get_report_values(self, docids, data):
quantity_by_product = defaultdict(list)
for p, q in data.get('quantity_by_product').items():
product = Product.browse(int(p))
quantity_by_product[product].append((product.barcode, q))
default_code_markup = markupsafe.Markup(product.default_code) if product.default_code else ''
product_info = {
'barcode': markupsafe.Markup(product.barcode),
'quantity': q,
'display_name_markup': markupsafe.Markup(product.display_name),
'default_code': (default_code_markup[:15], default_code_markup[15:30])
}
quantity_by_product[product].append(product_info)
if data.get('custom_barcodes'):
# we expect custom barcodes to be: {product: [(barcode, qty_of_barcode)]}
for product, barcodes_qtys in data.get('custom_barcodes').items():
quantity_by_product[Product.browse(int(product))] += (barcodes_qtys)
product = Product.browse(int(product))
default_code_markup = markupsafe.Markup(product.default_code) if product.default_code else ''
for barcode_qty in barcodes_qtys:
quantity_by_product[product].append({
'barcode': markupsafe.Markup(barcode_qty[0]),
'quantity': barcode_qty[1],
'display_name_markup': markupsafe.Markup(product.display_name),
'default_code': (default_code_markup[:15], default_code_markup[15:30])
}
)
data['quantity'] = quantity_by_product

return data


class ReportLotLabel(models.AbstractModel):
_name = 'report.stock.label_lot_template_view'
_description = 'Lot Label Report'

def _get_report_values(self, docids, data):
lots = self.env['stock.lot'].browse(docids)
lot_list = []
for lot in lots:
lot_list.append({
'display_name_markup': markupsafe.Markup(lot.product_id.display_name),
'name': markupsafe.Markup(lot.name),
'lot_record': lot
})
return {
'docs': lot_list,
}
33 changes: 16 additions & 17 deletions addons/stock/report/product_templates.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@
<template id="label_product_product_view">
<t t-foreach="quantity.items()" t-as="barcode_and_qty_by_product">
<t t-set="product" t-value="barcode_and_qty_by_product[0]"/>
<t t-foreach="barcode_and_qty_by_product[1]" t-as="barcode_and_qty">
<t t-set="barcode" t-value="barcode_and_qty[0]"/>
<t t-foreach="range(barcode_and_qty[1])" t-as="qty">
<t t-foreach="barcode_and_qty_by_product[1]" t-as="product_info">
<t t-set="barcode" t-value="product_info['barcode']"/>
<t t-foreach="range(product_info['quantity'])" t-as="qty">
<t t-translation="off">
^XA
^FT100,80^A0N,40,30^FD<t t-esc="product.display_name"/>^FS
^XA^CI28
^FT100,80^A0N,40,30^FD<t t-out="product_info['display_name_markup']"/>^FS
<t t-if="product.default_code and len(product.default_code) &gt; 15">
^FT100,115^A0N,30,24^FD<t t-esc="product.default_code[:15]"/>^FS
^FT100,150^A0N,30,24^FD<t t-esc="product.default_code[15:30]"/>^FS
^FT100,115^A0N,30,24^FD<t t-out="product_info['default_code'][0]"/>^FS
^FT100,150^A0N,30,24^FD<t t-out="product_info['default_code'][1]"/>^FS
</t>
<t t-else="">
^FT100,150^A0N,30,24^FD<t t-esc="product.default_code"/>^FS
^FT100,150^A0N,30,24^FD<t t-out="product_info['default_code'][0]"/>^FS
</t>
<t t-if="price_included">
^FO600,100,1
^CI28
<t t-if="product.currency_id.position == 'after'">
^A0N,66,48^FH^FD<t t-esc="product.lst_price if 'lst_price' in product else product.list_price" t-options='{"widget": "float", "precision": 2}'/><t t-esc="product.currency_id.symbol"/>^FS
</t>
Expand All @@ -30,7 +29,7 @@
<t t-if="barcode">
^FO100,160^BY3
^BCN,100,Y,N,N
^FD<t t-esc="barcode"/>^FS
^FD<t t-out="barcode"/>^FS
</t>
^XZ
</t>
Expand All @@ -42,24 +41,24 @@
<template id="label_lot_template_view">
<t t-foreach="docs" t-as="lot">
<t t-translation="off">
^XA
^XA^CI28
^FO100,50
^A0N,44,33^FD<t t-out="lot.product_id.display_name"/>^FS
^A0N,44,33^FD<t t-out="lot['display_name_markup']"/>^FS
^FO100,100
^A0N,44,33^FDLN/SN: <t t-out="lot.name"/>^FS
^A0N,44,33^FDLN/SN: <t t-out="lot['name']"/>^FS
<t t-if="env.user.has_group('stock.group_stock_lot_print_gs1')">
<t t-if="lot.product_id.valid_ean" t-set="final_barcode" t-value="'01' + '0' * (14 - len(lot.product_id.barcode)) + lot.product_id.barcode"/>
Copy link
Contributor

Choose a reason for hiding this comment

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

@clesgow My fix has changed a bit because of this new part. I modified the test so that it should also cover this part.

<t t-if="lot['lot_record'].product_id.valid_ean" t-set="final_barcode" t-value="'01' + '0' * (14 - len(lot['lot_record'].product_id.barcode)) + lot['lot_record'].product_id.barcode"/>
<!-- TODO: must keep lot/sn as last value in barcode because we cannot pad '0's without changing lot/sn name until we can scan in FNC1. -->
<t t-if="lot.product_id.tracking == 'lot'" name="datamatrix_lot" t-set="final_barcode" t-value="(final_barcode or '') + '10' + lot.name"/>
<t t-elif="lot.product_id.tracking == 'serial'" t-set="final_barcode" t-value="(final_barcode or '') + '21' + lot.name"/>
<t t-if="lot['lot_record'].product_id.tracking == 'lot'" name="datamatrix_lot" t-set="final_barcode" t-value="(final_barcode or '') + '10' + lot['name']"/>
<t t-elif="lot['lot_record'].product_id.tracking == 'serial'" t-set="final_barcode" t-value="(final_barcode or '') + '21' + lot['name']"/>
^FO425,150^BY3
^BXN,8,200
^FD<t t-out="final_barcode"/>^FS
</t>
<t t-else="" name="code128_barcode">
^FO100,150^BY3
^BCN,100,Y,N,N
^FD<t t-out="lot.name"/>^FS
^FD<t t-out="lot['name']"/>^FS
</t>
^XZ
</t>
Expand Down
49 changes: 40 additions & 9 deletions addons/stock/tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ def setUpClass(cls):
cls.supplier_location = cls.env['stock.location'].browse(cls.ModelDataObj._xmlid_to_res_id('stock.stock_location_suppliers'))
cls.stock_location = cls.env['stock.location'].browse(cls.ModelDataObj._xmlid_to_res_id('stock.stock_location_stock'))

cls.product1 = cls.env['product.product'].create({
'name': 'Mellohi"',
'type': 'product',
'categ_id': cls.env.ref('product.product_category_all').id,
'tracking': 'lot',
'default_code': 'C4181234""154654654654',
'barcode': 'scan""me'
})

product_form = Form(cls.env['product.product'])
product_form.detailed_type = 'product'
product_form.name = 'Product'
Expand All @@ -39,25 +48,47 @@ def get_report_forecast(self, product_template_ids=False, product_variant_ids=Fa


class TestReports(TestReportsCommon):
def test_reports(self):
product1 = self.env['product.product'].create({
'name': 'Mellohi',
'default_code': 'C418',

def test_product_label_reports(self):
""" Test that all the special characters are correctly rendered for the product name, the default code and the barcode.
In this test we test that the double quote is rendered correctly.
"""
report = self.env.ref('stock.label_product_product')
target = b'\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FDscan""me^FS\n^XZ\n\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FDscan""me^FS\n^XZ\n'
rendering, qweb_type = report._render_qweb_text(self.product1.product_tmpl_id.id, {'quantity_by_product': {self.product1.product_tmpl_id.id: 2}, 'active_model': 'product.template'})
self.assertEqual(target, rendering.replace(b' ', b''), 'Product name, default code or barcode is not correctly rendered, make sure the quotes are escaped correctly')
self.assertEqual(qweb_type, 'text', 'the report type is not good')

def test_product_label_custom_barcode_reports(self):
""" Test that the custom barcodes are correctly rendered with special characters."""
report = self.env.ref('stock.label_product_product')
target = b'\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FD123"barcode^FS\n^XZ\n\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FD123"barcode^FS\n^XZ\n\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FDbarcode"456^FS\n^XZ\n\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FDbarcode"456^FS\n^XZ\n'
rendering, qweb_type = report._render_qweb_text(self.product1.product_tmpl_id.id, {'custom_barcodes': {self.product1.product_tmpl_id.id: [('123"barcode', 2), ('barcode"456', 2)]}, 'quantity_by_product': {}, 'active_model': 'product.template'})
self.assertEqual(target, rendering.replace(b' ', b''), 'Custom barcodes are most likely not corretly rendered, make sure the quotes are escaped correctly')
self.assertEqual(qweb_type, 'text', 'the report type is not good')

def test_reports_with_special_characters(self):
product_test = self.env['product.product'].create({
'name': 'Mellohi"',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
'tracking': 'lot',
'barcode': 'scan_me'
'default_code': 'C4181234""154654654654',
'barcode': '9745213796142'
})

lot1 = self.env['stock.lot'].create({
'name': 'Volume-Beta',
'product_id': product1.id,
'name': 'Volume-Beta"',
'product_id': product_test.id,
'company_id': self.env.company.id,
})
#add group to the user
self.env.user.groups_id += self.env.ref('stock.group_stock_lot_print_gs1')
report = self.env.ref('stock.label_lot_template')
target = b'\n\n^XA\n^FO100,50\n^A0N,44,33^FD[C418]Mellohi^FS\n^FO100,100\n^A0N,44,33^FDLN/SN:Volume-Beta^FS\n^FO100,150^BY3\n^BCN,100,Y,N,N\n^FDVolume-Beta^FS\n^XZ\n'
target = b'\n\n^XA^CI28\n^FO100,50\n^A0N,44,33^FD[C4181234""154654654654]Mellohi"^FS\n^FO100,100\n^A0N,44,33^FDLN/SN:Volume-Beta"^FS\n\n^FO425,150^BY3\n^BXN,8,200\n^FD010974521379614210Volume-Beta"^FS\n^XZ\n'

rendering, qweb_type = report._render_qweb_text(lot1.id)
self.assertEqual(target, rendering.replace(b' ', b''), 'The rendering is not good')
self.assertEqual(target, rendering.replace(b' ', b''), 'The rendering is not good, make sure quotes are correctly escaped')
self.assertEqual(qweb_type, 'text', 'the report type is not good')

def test_report_quantity_1(self):
Expand Down