summaryrefslogtreecommitdiff
path: root/addons/sale_product_matrix/models
diff options
context:
space:
mode:
Diffstat (limited to 'addons/sale_product_matrix/models')
-rw-r--r--addons/sale_product_matrix/models/__init__.py4
-rw-r--r--addons/sale_product_matrix/models/product_template.py21
-rw-r--r--addons/sale_product_matrix/models/sale_order.py164
3 files changed, 189 insertions, 0 deletions
diff --git a/addons/sale_product_matrix/models/__init__.py b/addons/sale_product_matrix/models/__init__.py
new file mode 100644
index 00000000..f2d32ff9
--- /dev/null
+++ b/addons/sale_product_matrix/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import sale_order
+from . import product_template
diff --git a/addons/sale_product_matrix/models/product_template.py b/addons/sale_product_matrix/models/product_template.py
new file mode 100644
index 00000000..8440b5b3
--- /dev/null
+++ b/addons/sale_product_matrix/models/product_template.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import api, models, fields
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ product_add_mode = fields.Selection([
+ ('configurator', 'Product Configurator'),
+ ('matrix', 'Order Grid Entry'),
+ ], string='Add product mode', default='configurator', help="Configurator: choose attribute values to add the matching \
+ product variant to the order.\nGrid: add several variants at once from the grid of attribute values")
+
+ def get_single_product_variant(self):
+ res = super(ProductTemplate, self).get_single_product_variant()
+ if self.has_configurable_attributes:
+ res['mode'] = self.product_add_mode
+ else:
+ res['mode'] = 'configurator'
+ return res
diff --git a/addons/sale_product_matrix/models/sale_order.py b/addons/sale_product_matrix/models/sale_order.py
new file mode 100644
index 00000000..6ce55c5d
--- /dev/null
+++ b/addons/sale_product_matrix/models/sale_order.py
@@ -0,0 +1,164 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import json
+from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
+
+
+class SaleOrder(models.Model):
+ _inherit = 'sale.order'
+
+ report_grids = fields.Boolean(
+ string="Print Variant Grids", default=True,
+ help="If set, the matrix of the products configurable by matrix will be shown on the report of the order.")
+
+ """ Matrix loading and update: fields and methods :
+
+ NOTE: The matrix functionality was done in python, server side, to avoid js
+ restriction. Indeed, the js framework only loads the x first lines displayed
+ in the client, which means in case of big matrices and lots of so_lines,
+ the js doesn't have access to the 41nth and following lines.
+
+ To force the loading, a 'hack' of the js framework would have been needed...
+ """
+
+ grid_product_tmpl_id = fields.Many2one(
+ 'product.template', store=False,
+ help="Technical field for product_matrix functionalities.")
+ grid_update = fields.Boolean(
+ default=False, store=False,
+ help="Whether the grid field contains a new matrix to apply or not.")
+ grid = fields.Char(
+ "Matrix local storage", store=False,
+ help="Technical local storage of grid. \nIf grid_update, will be loaded on the SO. \nIf not, represents the matrix to open.")
+
+ @api.onchange('grid_product_tmpl_id')
+ def _set_grid_up(self):
+ """Save locally the matrix of the given product.template, to be used by the matrix configurator."""
+ if self.grid_product_tmpl_id:
+ self.grid_update = False
+ self.grid = json.dumps(self._get_matrix(self.grid_product_tmpl_id))
+
+ @api.onchange('grid')
+ def _apply_grid(self):
+ """Apply the given list of changed matrix cells to the current SO."""
+ if self.grid and self.grid_update:
+ grid = json.loads(self.grid)
+ product_template = self.env['product.template'].browse(grid['product_template_id'])
+ dirty_cells = grid['changes']
+ Attrib = self.env['product.template.attribute.value']
+ default_so_line_vals = {}
+ new_lines = []
+ for cell in dirty_cells:
+ combination = Attrib.browse(cell['ptav_ids'])
+ no_variant_attribute_values = combination - combination._without_no_variant_attributes()
+
+ # create or find product variant from combination
+ product = product_template._create_product_variant(combination)
+ order_lines = self.order_line.filtered(
+ lambda line: line.product_id.id == product.id
+ and line.product_no_variant_attribute_value_ids.ids == no_variant_attribute_values.ids
+ )
+
+ # if product variant already exist in order lines
+ old_qty = sum(order_lines.mapped('product_uom_qty'))
+ qty = cell['qty']
+ diff = qty - old_qty
+ # TODO keep qty check? cannot be 0 because we only get cell changes ...
+ if diff and order_lines:
+ if qty == 0:
+ if self.state in ['draft', 'sent']:
+ # Remove lines if qty was set to 0 in matrix
+ # only if SO state = draft/sent
+ self.order_line -= order_lines
+ else:
+ order_lines.update({'product_uom_qty': 0.0})
+ else:
+ """
+ When there are multiple lines for same product and its quantity was changed in the matrix,
+ An error is raised.
+
+ A 'good' strategy would be to:
+ * Sets the quantity of the first found line to the cell value
+ * Remove the other lines.
+
+ But this would remove all business logic linked to the other lines...
+ Therefore, it only raises an Error for now.
+ """
+ if len(order_lines) > 1:
+ raise ValidationError(_("You cannot change the quantity of a product present in multiple sale lines."))
+ else:
+ order_lines[0].product_uom_qty = qty
+ # If we want to support multiple lines edition:
+ # removal of other lines.
+ # For now, an error is raised instead
+ # if len(order_lines) > 1:
+ # # Remove 1+ lines
+ # self.order_line -= order_lines[1:]
+ elif diff and not order_lines:
+ if not default_so_line_vals:
+ OrderLine = self.env['sale.order.line']
+ default_so_line_vals = OrderLine.default_get(OrderLine._fields.keys())
+ last_sequence = self.order_line[-1:].sequence
+ if last_sequence:
+ default_so_line_vals['sequence'] = last_sequence
+ new_lines.append((0, 0, dict(
+ default_so_line_vals,
+ product_id=product.id,
+ product_uom_qty=qty,
+ product_no_variant_attribute_value_ids=no_variant_attribute_values.ids)
+ ))
+ if new_lines:
+ res = False
+ self.update(dict(order_line=new_lines))
+ for line in self.order_line.filtered(lambda line: line.product_template_id == product_template):
+ res = line.product_id_change() or res
+ line._onchange_discount()
+ line._onchange_product_id_set_customer_lead()
+ return res
+
+ def _get_matrix(self, product_template):
+ """Return the matrix of the given product, updated with current SOLines quantities.
+
+ :param product.template product_template:
+ :return: matrix to display
+ :rtype dict:
+ """
+ def has_ptavs(line, sorted_attr_ids):
+ # TODO instead of sorting on ids, use odoo-defined order for matrix ?
+ ptav = line.product_template_attribute_value_ids.ids
+ pnav = line.product_no_variant_attribute_value_ids.ids
+ pav = pnav + ptav
+ pav.sort()
+ return pav == sorted_attr_ids
+ matrix = product_template._get_template_matrix(
+ company_id=self.company_id,
+ currency_id=self.currency_id,
+ display_extra_price=True)
+ if self.order_line:
+ lines = matrix['matrix']
+ order_lines = self.order_line.filtered(lambda line: line.product_template_id == product_template)
+ for line in lines:
+ for cell in line:
+ if not cell.get('name', False):
+ line = order_lines.filtered(lambda line: has_ptavs(line, cell['ptav_ids']))
+ if line:
+ cell.update({
+ 'qty': sum(line.mapped('product_uom_qty'))
+ })
+ return matrix
+
+ def get_report_matrixes(self):
+ """Reporting method.
+
+ :return: array of matrices to display in the report
+ :rtype: list
+ """
+ matrixes = []
+ if self.report_grids:
+ grid_configured_templates = self.order_line.filtered('is_configurable_product').product_template_id.filtered(lambda ptmpl: ptmpl.product_add_mode == 'matrix')
+ for template in grid_configured_templates:
+ if len(self.order_line.filtered(lambda line: line.product_template_id == template)) > 1:
+ # TODO do we really want the whole matrix even if there isn't a lot of lines ??
+ matrixes.append(self._get_matrix(template))
+ return matrixes