summaryrefslogtreecommitdiff
path: root/addons/purchase_product_matrix/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/purchase_product_matrix/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/purchase_product_matrix/models')
-rw-r--r--addons/purchase_product_matrix/models/__init__.py3
-rw-r--r--addons/purchase_product_matrix/models/purchase.py160
2 files changed, 163 insertions, 0 deletions
diff --git a/addons/purchase_product_matrix/models/__init__.py b/addons/purchase_product_matrix/models/__init__.py
new file mode 100644
index 00000000..c05845f2
--- /dev/null
+++ b/addons/purchase_product_matrix/models/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import purchase
diff --git a/addons/purchase_product_matrix/models/purchase.py b/addons/purchase_product_matrix/models/purchase.py
new file mode 100644
index 00000000..141becfa
--- /dev/null
+++ b/addons/purchase_product_matrix/models/purchase.py
@@ -0,0 +1,160 @@
+# -*- 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 PurchaseOrder(models.Model):
+ _inherit = 'purchase.order'
+
+ report_grids = fields.Boolean(string="Print Variant Grids", default=True, help="If set, the matrix of configurable products will be shown on the report of this 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 po_lines,
+ the js doesn't have access to the 41st 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(store=False, help="Technical storage of grid. \nIf grid_update, will be loaded on the PO. \nIf not, represents the matrix to open.")
+
+ @api.onchange('grid_product_tmpl_id')
+ def _set_grid_up(self):
+ if self.grid_product_tmpl_id:
+ self.grid_update = False
+ self.grid = json.dumps(self._get_matrix(self.grid_product_tmpl_id))
+
+ def _must_delete_date_planned(self, field_name):
+ return super()._must_delete_date_planned(field_name) or field_name == "grid"
+
+ @api.onchange('grid')
+ def _apply_grid(self):
+ 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_po_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)
+ # TODO replace the check on product_id by a first check on the ptavs and pnavs?
+ # and only create/require variant after no line has been found ???
+ order_lines = self.order_line.filtered(lambda line: (line._origin or line).product_id == product and (line._origin or line).product_no_variant_attribute_value_ids == no_variant_attribute_values)
+
+ # if product variant already exist in order lines
+ old_qty = sum(order_lines.mapped('product_qty'))
+ qty = cell['qty']
+ diff = qty - old_qty
+ 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 PO state = draft/sent
+ self.order_line -= order_lines
+ else:
+ order_lines.update({'product_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 purchase lines."))
+ else:
+ order_lines[0].product_qty = qty
+ order_lines[0]._onchange_quantity()
+ # 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:
+ if not default_po_line_vals:
+ OrderLine = self.env['purchase.order.line']
+ default_po_line_vals = OrderLine.default_get(OrderLine._fields.keys())
+ last_sequence = self.order_line[-1:].sequence
+ if last_sequence:
+ default_po_line_vals['sequence'] = last_sequence
+ new_lines.append((0, 0, dict(
+ default_po_line_vals,
+ product_id=product.id,
+ product_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_quantity()
+ return res
+
+ def _get_matrix(self, product_template):
+ def has_ptavs(line, sorted_attr_ids):
+ 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)
+ 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_qty'))
+ })
+ return matrix
+
+ def get_report_matrixes(self):
+ """Reporting method."""
+ matrixes = []
+ if self.report_grids:
+ grid_configured_templates = self.order_line.filtered('is_configurable_product').product_template_id
+ # TODO is configurable product and product_variant_count > 1
+ # configurable products are only configured through the matrix in purchase, so no need to check product_add_mode.
+ for template in grid_configured_templates:
+ if len(self.order_line.filtered(lambda line: line.product_template_id == template)) > 1:
+ matrixes.append(self._get_matrix(template))
+ return matrixes
+
+
+class PurchaseOrderLine(models.Model):
+ _inherit = "purchase.order.line"
+
+ product_template_id = fields.Many2one('product.template', string='Product Template', related="product_id.product_tmpl_id", domain=[('purchase_ok', '=', True)])
+ is_configurable_product = fields.Boolean('Is the product configurable?', related="product_template_id.has_configurable_attributes")
+ product_template_attribute_value_ids = fields.Many2many(related='product_id.product_template_attribute_value_ids', readonly=True)
+ product_no_variant_attribute_value_ids = fields.Many2many('product.template.attribute.value', string='Product attribute values that do not create variants', ondelete='restrict')
+
+ def _get_product_purchase_description(self, product):
+ name = super(PurchaseOrderLine, self)._get_product_purchase_description(product)
+ for no_variant_attribute_value in self.product_no_variant_attribute_value_ids:
+ name += "\n" + no_variant_attribute_value.attribute_id.name + ': ' + no_variant_attribute_value.name
+
+ return name