From 484210c5d358f74fd8446512db7b6eb5f6bf4faf Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Fri, 18 Aug 2023 13:01:08 +0000 Subject: [PATCH] [IMP] sale: ease catalog inheritance closes odoo/odoo#132341 Signed-off-by: Victor Feyens (vfe) --- addons/sale/controllers/catalog.py | 31 +++++----- addons/sale/models/sale_order.py | 14 +++++ addons/sale/models/sale_order_line.py | 56 +++++++++++++++++++ .../src/js/product_catalog/kanban_model.js | 1 - .../src/js/product_catalog/kanban_record.xml | 5 +- addons/sale_stock/models/sale_order_line.py | 25 +++++++++ .../sale_order_line/sale_order_line.js | 11 ++++ 7 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 addons/sale_stock/static/src/product_catalog/sale_order_line/sale_order_line.js diff --git a/addons/sale/controllers/catalog.py b/addons/sale/controllers/catalog.py index 91670e9403b..f17587d6fbd 100644 --- a/addons/sale/controllers/catalog.py +++ b/addons/sale/controllers/catalog.py @@ -30,27 +30,22 @@ class CatalogController(Controller): ): if(product.id not in product_ids): continue - if (len(lines) > 1) or lines[0].product_uom != product.uom_id: - price_unit = order.pricelist_id._get_product_price( - product=product, - quantity=1.0, - currency=order.currency_id, - date=order.date_order, - ) - sale_order_line_info[product.id] = dict( - readOnly=True, - price=price_unit, - ) - else: - line = lines[0] - sale_order_line_info[product.id] = dict( - quantity=line.product_uom_qty, - price=line.price_unit, - ) + + sale_order_line_info[product.id] = request.env['sale.order.line'].browse( + line.id for line in lines # groupby gives a list and not a recordset + )._get_catalog_info() + product_ids.remove(product.id) + default_data = request.env['sale.order.line']._get_catalog_info() + default_data['readOnly'] = order._is_readonly() + sale_order_line_info.update({ - id: dict(price=price) for id, price in order.pricelist_id._get_products_price( + id: { + **default_data, + 'price': price, + } + for id, price in order.pricelist_id._get_products_price( quantity=1.0, products=request.env['product.product'].browse(product_ids), currency=order.currency_id, diff --git a/addons/sale/models/sale_order.py b/addons/sale/models/sale_order.py index 703ca3f474c..0bb65c9a66c 100644 --- a/addons/sale/models/sale_order.py +++ b/addons/sale/models/sale_order.py @@ -1658,3 +1658,17 @@ class SaleOrder(models.Model): # Override for correct taxcloud computation # when using coupon and delivery return True + + #=== TOOLING ===# + + def _is_readonly(self): + """ Return Whether the sale order is read-only or not based on the state or the lock status. + + A sale order is considered read-only if its state is 'cancel' or if the sale order is + locked. + + :return: Whether the sale order is read-only or not. + :rtype: bool + """ + self.ensure_one() + return self.state == 'cancel' or self.locked diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py index 4ad5c4c99fc..1ab06528b22 100644 --- a/addons/sale/models/sale_order_line.py +++ b/addons/sale/models/sale_order_line.py @@ -1188,6 +1188,62 @@ class SaleOrderLine(models.Model): product_catalog_digits=order.order_line._fields['price_unit'].get_digits(order.env), ) + def _get_catalog_info(self): + """ Return information about sale order lines in `self`. + + If `self` is empty, this method returns only the default value(s) needed for the product + catalog. In this case, the quantity that equals 0. + + Otherwise, it returns a quantity and a price based on the product of the SOL(s) and whether + the product is read-only or not. + + A product is considered read-only if the order is considered read-only (see + ``SaleOrder._is_readonly`` for more details) or if `self` contains multiple records. + + Note: This method cannot be called with multiple records that have different products linked. + + :raise odoo.exceptions.ValueError: ``len(self.product_id) != 1`` + :rtype: dict + :return: A dict with the following structure: + { + 'quantity': float, + 'price': float, + 'readOnly': bool, + } + """ + if len(self) == 1: + return { + 'quantity': self.product_uom_qty, + 'price': self.price_unit, + 'readOnly': self.order_id._is_readonly(), + } + elif self: + self.product_id.ensure_one() + order_line = self[0] + order = order_line.order_id + return { + 'readOnly': True, + 'price': order.pricelist._get_product_price( + product=order_line.product_id, + quantity=1.0, + currency=order.currency_id, + date=order.date_order, + ), + 'quantity': sum( + self.mapped( + lambda line: line.product_uom._compute_quantity( + qty=line.product_uom_qty, + to_unit=line.product_id.uom_id, + ) + ) + ), + } + else: + return { + 'quantity': 0, + # price will be computed in batch with pricelist utils so not given here + } + #=== TOOLING ===# def _convert_to_sol_currency(self, amount, currency): diff --git a/addons/sale/static/src/js/product_catalog/kanban_model.js b/addons/sale/static/src/js/product_catalog/kanban_model.js index 9f3ff0e7050..889f4842ee4 100644 --- a/addons/sale/static/src/js/product_catalog/kanban_model.js +++ b/addons/sale/static/src/js/product_catalog/kanban_model.js @@ -23,7 +23,6 @@ export class ProductCatalogKanbanModel extends RelationalModel { }); for (const record of result.records) { record.productCatalogData = saleOrderLinesInfo[record.id]; - record.productCatalogData.quantity = record.productCatalogData.quantity || 0; } return result; } diff --git a/addons/sale/static/src/js/product_catalog/kanban_record.xml b/addons/sale/static/src/js/product_catalog/kanban_record.xml index ccc37639ae1..a54b48ddd63 100644 --- a/addons/sale/static/src/js/product_catalog/kanban_record.xml +++ b/addons/sale/static/src/js/product_catalog/kanban_record.xml @@ -11,10 +11,7 @@ t-att-class="{'text-bg-primary': productCatalogData.quantity || productCatalogData.readOnly}"> - + diff --git a/addons/sale_stock/models/sale_order_line.py b/addons/sale_stock/models/sale_order_line.py index 869bd0be0e5..8a9d521c8f7 100644 --- a/addons/sale_stock/models/sale_order_line.py +++ b/addons/sale_stock/models/sale_order_line.py @@ -331,7 +331,32 @@ class SaleOrderLine(models.Model): raise UserError(_('The ordered quantity cannot be decreased below the amount already delivered. Instead, create a return in your inventory.')) super(SaleOrderLine, self)._update_line_quantity(values) + #=== HOOKS ===# + def _get_action_add_from_catalog_extra_context(self, order): extra_context = super()._get_action_add_from_catalog_extra_context(order) extra_context.update(warehouse=order.warehouse_id.id) return extra_context + + def _get_catalog_info(self): + """ Override of `sale` to add the delivered quantity. + + :rtype: dict + :return: A dict with the following structure: + { + 'deliveredQty': float, + 'quantity': float, + 'price': float, + 'readOnly': bool, + } + """ + res = super()._get_catalog_info() + res['deliveredQty'] = sum( + self.mapped( + lambda line: line.product_uom._compute_quantity( + qty=line.qty_delivered, + to_unit=line.product_id.uom_id, + ) + ) + ) + return res diff --git a/addons/sale_stock/static/src/product_catalog/sale_order_line/sale_order_line.js b/addons/sale_stock/static/src/product_catalog/sale_order_line/sale_order_line.js new file mode 100644 index 00000000000..e99e2cbbfba --- /dev/null +++ b/addons/sale_stock/static/src/product_catalog/sale_order_line/sale_order_line.js @@ -0,0 +1,11 @@ +/** @odoo-module */ + +import { ProductCatalogSOL } from "@sale/js/product_catalog/sale_order_line/sale_order_line"; +import { patch } from "@web/core/utils/patch"; + +patch(ProductCatalogSOL, { + props: { + ...ProductCatalogSOL.props, + deliveredQty: Number, + }, +});