# Copyright 2026 Criptomart # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import api from odoo import fields from odoo import models class StockMoveLine(models.Model): _inherit = "stock.move.line" _order = "product_categ_id, product_id, picking_partner_id, id" product_categ_id = fields.Many2one( comodel_name="product.category", string="Product Category (Batch)", related="product_id.categ_id", store=True, readonly=True, ) product_default_code = fields.Char( string="Product Code", related="product_id.default_code", readonly=True, ) picking_partner_id = fields.Many2one( related="picking_id.partner_id", store=True, readonly=True, ) is_collected = fields.Boolean( string="Collected", default=False, copy=False, ) sale_ordered_qty = fields.Float( string="Ordered Quantity", digits="Product Unit of Measure", compute="_compute_sale_ordered_qty", ) move_demand_qty = fields.Float( string="Demanded Quantity", digits="Product Unit of Measure", compute="_compute_move_demand_qty", ) @api.depends("move_id.sale_ordered_qty", "move_id.product_uom", "product_uom_id") def _compute_sale_ordered_qty(self): for line in self: move = line.move_id if move.sale_ordered_qty and line.product_uom_id: line.sale_ordered_qty = move.product_uom._compute_quantity( move.sale_ordered_qty, line.product_uom_id, round=False ) else: line.sale_ordered_qty = 0.0 @api.depends("move_id.product_uom_qty", "move_id.product_uom", "product_uom_id") def _compute_move_demand_qty(self): """The demand of the whole move: every line of a split move shows it.""" for line in self: move = line.move_id if move.product_uom and line.product_uom_id: line.move_demand_qty = move.product_uom._compute_quantity( move.product_uom_qty, line.product_uom_id, round=False ) else: line.move_demand_qty = 0.0 def write(self, vals): res = super().write(vals) if vals.get("is_collected"): # Collecting a line is the operator saying the goods are in the # basket: its quantity must survive the reservation engine even if # it was never retyped. The demand is left alone, only a quantity # typed by hand adjusts it. lines = self.filtered( lambda line: not line.picked and line.state not in ("done", "cancel") ) if lines: lines.picked = True lines._freeze_manual_quantity(align_demand=False) return res