mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-08-19 01:34:43 -05:00
[PATCH] Upstream patch - 24082022
This commit is contained in:
@@ -424,7 +424,12 @@ class ProductProduct(models.Model):
|
||||
For convenience the template is copied instead and its first variant is
|
||||
returned.
|
||||
"""
|
||||
return self.product_tmpl_id.copy(default=default).product_variant_id
|
||||
# copy variant is disabled in https://github.com/flectra/flectra/pull/38303
|
||||
# this returns the first possible combination of variant to make it
|
||||
# works for now, need to be fixed to return product_variant_id if it's
|
||||
# possible in the future
|
||||
template = self.product_tmpl_id.copy(default=default)
|
||||
return template.product_variant_id or template._create_first_product_variant()
|
||||
|
||||
@api.model
|
||||
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
|
||||
|
||||
@@ -975,6 +975,18 @@ class ProductTemplate(models.Model):
|
||||
'product_template_attribute_value_ids': [(6, 0, combination._without_no_variant_attributes().ids)]
|
||||
})
|
||||
|
||||
def _create_first_product_variant(self, log_warning=False):
|
||||
"""Create if necessary and possible and return the first product
|
||||
variant for this template.
|
||||
|
||||
:param log_warning: whether a warning should be logged on fail
|
||||
:type log_warning: bool
|
||||
|
||||
:return: the first product variant or none
|
||||
:rtype: recordset of `product.product`
|
||||
"""
|
||||
return self._create_product_variant(self._get_first_possible_combination(), log_warning)
|
||||
|
||||
@tools.ormcache('self.id', 'frozenset(filtered_combination.ids)')
|
||||
def _get_variant_id_for_combination(self, filtered_combination):
|
||||
"""See `_get_variant_for_combination`. This method returns an ID
|
||||
|
||||
@@ -213,6 +213,31 @@ class TestVariants(common.TestProductCommon):
|
||||
self.assertEqual(variant_copy.name, 'Test Copy (copy) (copy)')
|
||||
self.assertEqual(len(variant_copy.product_variant_ids), 2)
|
||||
|
||||
def test_dynamic_variants_copy(self):
|
||||
self.color_attr = self.env['product.attribute'].create({'name': 'Color', 'create_variant': 'dynamic'})
|
||||
self.color_attr_value_r = self.env['product.attribute.value'].create({'name': 'Red', 'attribute_id': self.color_attr.id})
|
||||
self.color_attr_value_b = self.env['product.attribute.value'].create({'name': 'Blue', 'attribute_id': self.color_attr.id})
|
||||
|
||||
# test copy of variant with dynamic attribute
|
||||
template_dyn = self.env['product.template'].create({
|
||||
'name': 'Test Dynamical',
|
||||
'attribute_line_ids': [(0, 0, {
|
||||
'attribute_id': self.color_attr.id,
|
||||
'value_ids': [(4, self.color_attr_value_r.id), (4, self.color_attr_value_b.id)],
|
||||
})]
|
||||
})
|
||||
|
||||
self.assertEqual(len(template_dyn.product_variant_ids), 0)
|
||||
self.assertEqual(template_dyn.name, 'Test Dynamical')
|
||||
|
||||
variant_dyn = template_dyn._create_product_variant(template_dyn._get_first_possible_combination())
|
||||
self.assertEqual(len(template_dyn.product_variant_ids), 1)
|
||||
|
||||
variant_dyn_copy = variant_dyn.copy()
|
||||
template_dyn_copy = variant_dyn_copy.product_tmpl_id
|
||||
self.assertEqual(len(template_dyn_copy.product_variant_ids), 1)
|
||||
self.assertEqual(template_dyn_copy.name, 'Test Dynamical (copy)')
|
||||
|
||||
def test_standard_price(self):
|
||||
""" Ensure template values are correctly (re)computed depending on the context """
|
||||
one_variant_product = self.product_1
|
||||
|
||||
@@ -37,7 +37,12 @@ class XlsxCreatorCase(common.HttpCase):
|
||||
}
|
||||
|
||||
def _mock_write(self, row, column, value, style=None):
|
||||
self.worksheet[row, column] = str(value)
|
||||
if isinstance(value, float):
|
||||
decimal_places = style.num_format[::-1].find('.')
|
||||
style_format = "{:." + str(decimal_places) + "f}"
|
||||
self.worksheet[row, column] = style_format.format(value)
|
||||
else:
|
||||
self.worksheet[row, column] = str(value)
|
||||
|
||||
def make(self, values, context=None):
|
||||
return self.model.with_context(**(context or {})).create(values)
|
||||
@@ -79,6 +84,7 @@ class XlsxCreatorCase(common.HttpCase):
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestGroupedExport(XlsxCreatorCase):
|
||||
model_name = 'export.group_operator'
|
||||
# pylint: disable=bad-whitespace
|
||||
|
||||
def test_int_sum_max(self):
|
||||
values = [
|
||||
@@ -125,12 +131,12 @@ class TestGroupedExport(XlsxCreatorCase):
|
||||
['Int Sum' ,'Float Min'],
|
||||
['10 (2)' ,'111.00'],
|
||||
[' 111.0 (1)','111.00'],
|
||||
['10' ,'111.0'],
|
||||
['10' ,'111.00'],
|
||||
[' 222.0 (1)','222.00'],
|
||||
['10' ,'222.0'],
|
||||
['10' ,'222.00'],
|
||||
['20 (1)' ,'333.00'],
|
||||
[' 333.0 (1)','333.00'],
|
||||
['20' ,'333.0'],
|
||||
['20' ,'333.00'],
|
||||
])
|
||||
|
||||
def test_float_avg(self):
|
||||
@@ -145,12 +151,12 @@ class TestGroupedExport(XlsxCreatorCase):
|
||||
['Int Sum' ,'Float Avg'],
|
||||
['10 (2)' ,'150.00'],
|
||||
[' 100.0 (1)','100.00'],
|
||||
['10' ,'100.0'],
|
||||
['10' ,'100.00'],
|
||||
[' 200.0 (1)','200.00'],
|
||||
['10' ,'200.0'],
|
||||
['10' ,'200.00'],
|
||||
['20 (1)' ,'300.00'],
|
||||
[' 300.0 (1)','300.00'],
|
||||
['20' ,'300.0'],
|
||||
['20' ,'300.00'],
|
||||
])
|
||||
|
||||
def test_float_avg_nested(self):
|
||||
@@ -167,12 +173,12 @@ class TestGroupedExport(XlsxCreatorCase):
|
||||
['10 (3)' ,'300.00'],
|
||||
[' 20 (1)' ,'600.00'],
|
||||
[' 600.0 (1)','600.00'],
|
||||
['10' ,'600.0'],
|
||||
['10' ,'600.00'],
|
||||
[' 30 (2)' ,'150.00'],
|
||||
[' 100.0 (1)','100.00'],
|
||||
['10' ,'100.0'],
|
||||
['10' ,'100.00'],
|
||||
[' 200.0 (1)','200.00'],
|
||||
['10' ,'200.0'],
|
||||
['10' ,'200.00'],
|
||||
])
|
||||
|
||||
def test_float_avg_nested_no_value(self):
|
||||
@@ -189,11 +195,11 @@ class TestGroupedExport(XlsxCreatorCase):
|
||||
['10 (3)' ,'0.00'],
|
||||
[' 20 (1)' ,'0.00'],
|
||||
[' Undefined (1)','0.00'],
|
||||
['10' ,'0.0'],
|
||||
['10' ,'0.00'],
|
||||
[' 30 (2)' ,'0.00'],
|
||||
[' Undefined (2)','0.00'],
|
||||
['10' ,'0.0'],
|
||||
['10' ,'0.0'],
|
||||
['10' ,'0.00'],
|
||||
['10' ,'0.00'],
|
||||
])
|
||||
|
||||
def test_date_max(self):
|
||||
@@ -367,13 +373,13 @@ class TestGroupedExport(XlsxCreatorCase):
|
||||
['Int Sum', 'Float Monetary'],
|
||||
['1 (1)','60739.200'],
|
||||
[' 60739.2 (1)','60739.200'],
|
||||
['1','60739.2'],
|
||||
['1','60739.20'],
|
||||
['2 (1)','2.000'],
|
||||
[' 2.0 (1)','2.000'],
|
||||
['2','2.0'],
|
||||
['2','2.00'],
|
||||
['3 (1)','1000.000'],
|
||||
[' 1000.0 (1)','1000.000'],
|
||||
['3','1000.0'],
|
||||
['3','1000.00'],
|
||||
])
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
|
||||
@@ -34,10 +34,10 @@ import flectra
|
||||
import flectra.modules.registry
|
||||
from flectra.api import call_kw, Environment
|
||||
from flectra.modules import get_module_path, get_resource_path
|
||||
from flectra.tools import image_process, topological_sort, html_escape, pycompat, ustr, apply_inheritance_specs, lazy_property, float_repr
|
||||
from flectra.tools import image_process, topological_sort, html_escape, pycompat, ustr, apply_inheritance_specs, lazy_property
|
||||
from flectra.tools.mimetypes import guess_mimetype
|
||||
from flectra.tools.translate import _
|
||||
from flectra.tools.misc import str2bool, xlsxwriter, file_open
|
||||
from flectra.tools.misc import str2bool, xlsxwriter, file_open, get_lang
|
||||
from flectra.tools.safe_eval import safe_eval, time
|
||||
from flectra import http, tools
|
||||
from flectra.http import content_disposition, dispatch_rpc, request, serialize_exception as _serialize_exception, Response
|
||||
@@ -777,6 +777,10 @@ class ExportXlsxWriter:
|
||||
self.datetime_style = self.workbook.add_format({'text_wrap': True, 'num_format': 'yyyy-mm-dd hh:mm:ss'})
|
||||
self.worksheet = self.workbook.add_worksheet()
|
||||
self.value = False
|
||||
decimal_separator = get_lang(request.env).decimal_point
|
||||
self.float_format = f'0{decimal_separator}00'
|
||||
decimal_places = [res['decimal_places'] for res in request.env['res.currency'].search_read([], ['decimal_places'])]
|
||||
self.monetary_format = f'0{decimal_separator}{max(decimal_places or [2]) * "0"}'
|
||||
|
||||
if row_count > self.worksheet.xls_rowmax:
|
||||
raise UserError(_('There are too many rows (%s rows, limit: %s) to export as Excel 2007-2013 (.xlsx) format. Consider splitting the export.') % (row_count, self.worksheet.xls_rowmax))
|
||||
@@ -824,6 +828,8 @@ class ExportXlsxWriter:
|
||||
cell_style = self.datetime_style
|
||||
elif isinstance(cell_value, datetime.date):
|
||||
cell_style = self.date_style
|
||||
elif isinstance(cell_value, float):
|
||||
cell_style.set_num_format(self.float_format)
|
||||
elif isinstance(cell_value, (list, tuple)):
|
||||
cell_value = pycompat.to_text(cell_value)
|
||||
self.write(row, column, cell_value, cell_style)
|
||||
@@ -859,26 +865,16 @@ class GroupExportXlsxWriter(ExportXlsxWriter):
|
||||
|
||||
label = '%s%s (%s)' % (' ' * group_depth, label, group.count)
|
||||
self.write(row, column, label, self.header_bold_style)
|
||||
if any(f.get('type') == 'monetary' for f in self.fields[1:]):
|
||||
|
||||
decimal_places = [res['decimal_places'] for res in group._model.env['res.currency'].search_read([], ['decimal_places'])]
|
||||
decimal_places = max(decimal_places) if decimal_places else 2
|
||||
for field in self.fields[1:]: # No aggregates allowed in the first column because of the group title
|
||||
column += 1
|
||||
aggregated_value = aggregates.get(field['name'])
|
||||
# Float fields may not be displayed properly because of float
|
||||
# representation issue with non stored fields or with values
|
||||
# that, even stored, cannot be rounded properly and it is not
|
||||
# acceptable to display useless digits (i.e. monetary)
|
||||
#
|
||||
# non stored field -> we force 2 digits
|
||||
# stored monetary -> we force max digits of installed currencies
|
||||
if isinstance(aggregated_value, float):
|
||||
if field.get('type') == 'monetary':
|
||||
aggregated_value = float_repr(aggregated_value, decimal_places)
|
||||
elif not field.get('store'):
|
||||
aggregated_value = float_repr(aggregated_value, 2)
|
||||
self.write(row, column, str(aggregated_value if aggregated_value is not None else ''), self.header_bold_style)
|
||||
if field.get('type') == 'monetary':
|
||||
self.header_bold_style.set_num_format(self.monetary_format)
|
||||
elif field.get('type') == 'float':
|
||||
self.header_bold_style.set_num_format(self.float_format)
|
||||
else:
|
||||
aggregated_value = str(aggregated_value if aggregated_value is not None else '')
|
||||
self.write(row, column, aggregated_value, self.header_bold_style)
|
||||
return row + 1, 0
|
||||
|
||||
|
||||
|
||||
@@ -317,18 +317,6 @@ class ProductTemplate(models.Model):
|
||||
|
||||
return combination_info
|
||||
|
||||
def _create_first_product_variant(self, log_warning=False):
|
||||
"""Create if necessary and possible and return the first product
|
||||
variant for this template.
|
||||
|
||||
:param log_warning: whether a warning should be logged on fail
|
||||
:type log_warning: bool
|
||||
|
||||
:return: the first product variant or none
|
||||
:rtype: recordset of `product.product`
|
||||
"""
|
||||
return self._create_product_variant(self._get_first_possible_combination(), log_warning)
|
||||
|
||||
def _get_image_holder(self):
|
||||
"""Returns the holder of the image to use as default representation.
|
||||
If the product template has an image it is the product template,
|
||||
|
||||
Reference in New Issue
Block a user