# Copyright 2018 Tecnativa - Sergio Teruel # Copyright 2019 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import _, api, fields, models from odoo.exceptions import UserError def _execute_onchanges(records, field_name): """Helper methods that executes all onchanges associated to a field.""" for onchange in records._onchange_methods.get(field_name, []): for record in records: onchange(record) class SaleOrder(models.Model): _inherit = "sale.order" def _create_elaboration_line(self, product, qty): """Create a sale order line from a elaboration product, search a line with the same elaboration product to add qty :param product: :param qty: :return: the sale order line record created """ SaleOrderLine = self.env["sale.order.line"] sol_for_product = self.order_line.filtered(lambda x: x.product_id == product)[ :1 ] if sol_for_product: sol_for_product.product_uom_qty += qty return sol_for_product sol = SaleOrderLine.new( {"order_id": self.id, "product_id": product.id, "is_elaboration": True} ) _execute_onchanges(sol, "product_id") sol.update({"product_uom_qty": qty}) _execute_onchanges(sol, "product_uom_qty") vals = sol._convert_to_write(sol._cache) if self.order_line: vals["sequence"] = self.order_line[-1].sequence + 1 return SaleOrderLine.sudo().create(vals) class SaleOrderLine(models.Model): _inherit = ["sale.order.line", "product.elaboration.mixin"] _name = "sale.order.line" date_order = fields.Datetime(related="order_id.date_order", string="Date") route_id = fields.Many2one(compute="_compute_route_id", store=True, readonly=False) elaboration_profile_id = fields.Many2one( comodel_name="product.elaboration.profile", compute="_compute_elaboration_profile_id", ) elaboration_price_unit = fields.Float( "Elab. Price", compute="_compute_elaboration_price_unit", store=True ) is_prepared = fields.Boolean( compute=lambda self: None, search="_search_is_prepared", help=("Dummy field to be able to find prepared lines"), ) @api.depends("product_id") def _compute_elaboration_profile_id(self): """Order of applicability: product profile > category profile > no profile""" self.elaboration_profile_id = False for line in self.filtered("product_id"): line.elaboration_profile_id = ( line.product_id.elaboration_profile_id or line.product_id.categ_id.elaboration_profile_id ) def get_elaboration_stock_route(self): self.ensure_one() return self.elaboration_ids.route_ids[:1] @api.depends("elaboration_ids") def _compute_route_id(self): for line in self: route_id = line.get_elaboration_stock_route() if route_id: line.route_id = route_id @api.depends("elaboration_ids", "order_id.pricelist_id") def _compute_elaboration_price_unit(self): for line in self: if not line.order_id.pricelist_id: line.elaboration_price_unit = 0 else: line.elaboration_price_unit = sum( line.order_id.pricelist_id._get_products_price( line.elaboration_ids.product_id, quantity=1, ).values() ) def _prepare_invoice_line(self, **optional_values): vals = super()._prepare_invoice_line(**optional_values) if self.is_elaboration: vals["name"] = f"{self.order_id.name} - {self.name}" return vals def _search_is_prepared(self, operator, value): if operator != "=": raise UserError( _("Unsupported operator %s for searching on is_prepared") % (operator,) ) moves = self.env["stock.move"].search( [ ( "state", "not in" if value else "in", [ "draft", "waiting", "confirmed", "partially_available", "assigned", ], ), ( "location_dest_id", "=", self.env.ref("stock.stock_location_customers").id, ), ] ) return [("move_ids", "in", moves.ids)]