# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import json from collections import defaultdict from datetime import date from markupsafe import Markup from odoo import models, fields, api, _, SUPERUSER_ID from odoo.exceptions import UserError from odoo.tools.sql import column_exists, create_column class StockQuantPackage(models.Model): _inherit = "stock.quant.package" @api.depends('quant_ids', 'package_type_id') def _compute_weight(self): if self.env.context.get('picking_id'): package_weights = defaultdict(float) # Ordering by qty_done prevents the default ordering by groupby fields that can inject multiple Left Joins in the resulting query. res_groups = self.env['stock.move.line'].read_group( [('result_package_id', 'in', self.ids), ('product_id', '!=', False), ('picking_id', '=', self.env.context['picking_id'])], ['id:count'], ['result_package_id', 'product_id', 'product_uom_id', 'qty_done'], lazy=False, orderby='qty_done asc' ) for res_group in res_groups: product_id = self.env['product.product'].browse(res_group['product_id'][0]) product_uom_id = self.env['uom.uom'].browse(res_group['product_uom_id'][0]) package_weights[res_group['result_package_id'][0]] += ( res_group['__count'] * product_uom_id._compute_quantity(res_group['qty_done'], product_id.uom_id) * product_id.weight ) for package in self: weight = package.package_type_id.base_weight or 0.0 if self.env.context.get('picking_id'): package.weight = weight + package_weights[package.id] else: for quant in package.quant_ids: weight += quant.quantity * quant.product_id.weight package.weight = weight def _get_default_weight_uom(self): return self.env['product.template']._get_weight_uom_name_from_ir_config_parameter() def _compute_weight_uom_name(self): for package in self: package.weight_uom_name = self.env['product.template']._get_weight_uom_name_from_ir_config_parameter() def _compute_weight_is_kg(self): self.weight_is_kg = False uom_id = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter() if uom_id == self.env.ref('uom.product_uom_kgm'): self.weight_is_kg = True self.weight_uom_rounding = uom_id.rounding weight = fields.Float(compute='_compute_weight', digits='Stock Weight', help="Total weight of all the products contained in the package.") weight_uom_name = fields.Char(string='Weight unit of measure label', compute='_compute_weight_uom_name', readonly=True, default=_get_default_weight_uom) weight_is_kg = fields.Boolean("Technical field indicating whether weight uom is kg or not (i.e. lb)", compute="_compute_weight_is_kg") weight_uom_rounding = fields.Float("Technical field indicating weight's number of decimal places", compute="_compute_weight_is_kg") shipping_weight = fields.Float(string='Shipping Weight', help="Total weight of the package.") class StockPicking(models.Model): _inherit = 'stock.picking' def _auto_init(self): if not column_exists(self.env.cr, "stock_picking", "weight"): # In order to speed up module installation when dealing with hefty data # We create the column weight manually, but the computation will be skipped # Therefore we do the computation in a query by getting weight sum from stock moves create_column(self.env.cr, "stock_picking", "weight", "numeric") self.env.cr.execute(""" WITH computed_weight AS ( SELECT SUM(weight) AS weight_sum, picking_id FROM stock_move WHERE picking_id IS NOT NULL GROUP BY picking_id ) UPDATE stock_picking SET weight = weight_sum FROM computed_weight WHERE stock_picking.id = computed_weight.picking_id; """) return super()._auto_init() @api.depends('move_line_ids', 'move_line_ids.result_package_id') def _compute_packages(self): packages = { res["picking_id"][0]: set(res["result_package_id"]) for res in self.env["stock.move.line"].read_group( [("picking_id", "in", self.ids), ("result_package_id", "!=", False)], ["result_package_id:array_agg"], ["picking_id"], lazy=False, orderby="picking_id asc", ) } for picking in self: picking.package_ids = list(packages.get(picking.id, [])) @api.depends('move_line_ids', 'move_line_ids.result_package_id', 'move_line_ids.product_uom_id', 'move_line_ids.qty_done') def _compute_bulk_weight(self): picking_weights = defaultdict(float) # Ordering by qty_done prevents the default ordering by groupby fields that can inject multiple Left Joins in the resulting query. res_groups = self.env['stock.move.line'].read_group( [('picking_id', 'in', self.ids), ('product_id', '!=', False), ('result_package_id', '=', False)], ['id:count'], ['picking_id', 'product_id', 'product_uom_id', 'qty_done'], lazy=False, orderby='qty_done asc' ) products_by_id = { product_res['id']: (product_res['uom_id'][0], product_res['weight']) for product_res in self.env['product.product'].with_context(active_test=False).search_read( [('id', 'in', list(set(grp["product_id"][0] for grp in res_groups)))], ['uom_id', 'weight']) } for res_group in res_groups: uom_id, weight = products_by_id[res_group['product_id'][0]] uom = self.env['uom.uom'].browse(uom_id) product_uom_id = self.env['uom.uom'].browse(res_group['product_uom_id'][0]) picking_weights[res_group['picking_id'][0]] += ( res_group['__count'] * product_uom_id._compute_quantity(res_group['qty_done'], uom) * weight ) for picking in self: picking.weight_bulk = picking_weights[picking.id] @api.depends('move_line_ids.result_package_id', 'move_line_ids.result_package_id.shipping_weight', 'weight_bulk') def _compute_shipping_weight(self): for picking in self: # if shipping weight is not assigned => default to calculated product weight picking.shipping_weight = ( picking.weight_bulk + sum(pack.shipping_weight or pack.weight for pack in picking.package_ids.sudo()) ) def _get_default_weight_uom(self): return self.env['product.template']._get_weight_uom_name_from_ir_config_parameter() def _compute_weight_uom_name(self): for package in self: package.weight_uom_name = self.env['product.template']._get_weight_uom_name_from_ir_config_parameter() carrier_price = fields.Float(string="Shipping Cost") delivery_type = fields.Selection(related='carrier_id.delivery_type', readonly=True) carrier_id = fields.Many2one("delivery.carrier", string="Carrier", check_company=True) weight = fields.Float(compute='_cal_weight', digits='Stock Weight', store=True, help="Total weight of the products in the picking.", compute_sudo=True) carrier_tracking_ref = fields.Char(string='Tracking Reference', copy=False) carrier_tracking_url = fields.Char(string='Tracking URL', compute='_compute_carrier_tracking_url') weight_uom_name = fields.Char(string='Weight unit of measure label', compute='_compute_weight_uom_name', readonly=True, default=_get_default_weight_uom) package_ids = fields.Many2many('stock.quant.package', compute='_compute_packages', string='Packages') weight_bulk = fields.Float('Bulk Weight', compute='_compute_bulk_weight', help="Total weight of products which are not in a package.") shipping_weight = fields.Float("Weight for Shipping", compute='_compute_shipping_weight', help="Total weight of packages and products not in a package. Packages with no shipping weight specified will default to their products' total weight. This is the weight used to compute the cost of the shipping.") is_return_picking = fields.Boolean(compute='_compute_return_picking') return_label_ids = fields.One2many('ir.attachment', compute='_compute_return_label') destination_country_code = fields.Char(related='partner_id.country_id.code', string="Destination Country") @api.depends('carrier_id', 'carrier_tracking_ref') def _compute_carrier_tracking_url(self): for picking in self: picking.carrier_tracking_url = picking.carrier_id.get_tracking_link(picking) if picking.carrier_id and picking.carrier_tracking_ref else False @api.depends('carrier_id', 'move_ids_without_package') def _compute_return_picking(self): for picking in self: if picking.carrier_id and picking.carrier_id.can_generate_return: picking.is_return_picking = any(m.origin_returned_move_id for m in picking.move_ids_without_package) else: picking.is_return_picking = False def _compute_return_label(self): for picking in self: if picking.carrier_id: picking.return_label_ids = self.env['ir.attachment'].search([('res_model', '=', 'stock.picking'), ('res_id', '=', picking.id), ('name', 'like', '%s%%' % picking.carrier_id.get_return_label_prefix())]) else: picking.return_label_ids = False def get_multiple_carrier_tracking(self): self.ensure_one() try: return json.loads(self.carrier_tracking_url) except (ValueError, TypeError): return False @api.depends('move_ids.weight') def _cal_weight(self): for picking in self: picking.weight = sum(move.weight for move in picking.move_ids if move.state != 'cancel') def _carrier_exception_note(self, exception): self.ensure_one() line_1 = _("Exception occurred with respect to carrier on the transfer") line_2 = _("Manual actions might be needed.") line_3 = _("Exception:") return Markup('
{line_3} {exception}