diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/purchase_stock/models/purchase.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/purchase_stock/models/purchase.py')
| -rw-r--r-- | addons/purchase_stock/models/purchase.py | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py new file mode 100644 index 00000000..a6c2a1eb --- /dev/null +++ b/addons/purchase_stock/models/purchase.py @@ -0,0 +1,559 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import api, fields, models, SUPERUSER_ID, _ +from odoo.tools.float_utils import float_compare, float_round +from datetime import datetime +from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError + +from odoo.addons.purchase.models.purchase import PurchaseOrder as Purchase + + +class PurchaseOrder(models.Model): + _inherit = 'purchase.order' + + @api.model + def _default_picking_type(self): + return self._get_picking_type(self.env.context.get('company_id') or self.env.company.id) + + incoterm_id = fields.Many2one('account.incoterms', 'Incoterm', states={'done': [('readonly', True)]}, help="International Commercial Terms are a series of predefined commercial terms used in international transactions.") + + picking_count = fields.Integer(compute='_compute_picking', string='Picking count', default=0, store=True) + picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False, store=True) + + picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=Purchase.READONLY_STATES, required=True, default=_default_picking_type, domain="['|', ('warehouse_id', '=', False), ('warehouse_id.company_id', '=', company_id)]", + help="This will determine operation type of incoming shipment") + default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type', + help="Technical field used to display the Drop Ship Address", readonly=True) + group_id = fields.Many2one('procurement.group', string="Procurement Group", copy=False) + is_shipped = fields.Boolean(compute="_compute_is_shipped") + effective_date = fields.Datetime("Effective Date", compute='_compute_effective_date', store=True, copy=False, + help="Completion date of the first receipt order.") + on_time_rate = fields.Float(related='partner_id.on_time_rate', compute_sudo=False) + + @api.depends('order_line.move_ids.picking_id') + def _compute_picking(self): + for order in self: + pickings = order.order_line.mapped('move_ids.picking_id') + order.picking_ids = pickings + order.picking_count = len(pickings) + + @api.depends('picking_ids.date_done') + def _compute_effective_date(self): + for order in self: + pickings = order.picking_ids.filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'internal' and x.date_done) + order.effective_date = min(pickings.mapped('date_done'), default=False) + + @api.depends('picking_ids', 'picking_ids.state') + def _compute_is_shipped(self): + for order in self: + if order.picking_ids and all(x.state in ['done', 'cancel'] for x in order.picking_ids): + order.is_shipped = True + else: + order.is_shipped = False + + @api.onchange('picking_type_id') + def _onchange_picking_type_id(self): + if self.picking_type_id.default_location_dest_id.usage != 'customer': + self.dest_address_id = False + + @api.onchange('company_id') + def _onchange_company_id(self): + p_type = self.picking_type_id + if not(p_type and p_type.code == 'incoming' and (p_type.warehouse_id.company_id == self.company_id or not p_type.warehouse_id)): + self.picking_type_id = self._get_picking_type(self.company_id.id) + + # -------------------------------------------------- + # CRUD + # -------------------------------------------------- + + def write(self, vals): + if vals.get('order_line') and self.state == 'purchase': + for order in self: + pre_order_line_qty = {order_line: order_line.product_qty for order_line in order.mapped('order_line')} + res = super(PurchaseOrder, self).write(vals) + if vals.get('order_line') and self.state == 'purchase': + for order in self: + to_log = {} + for order_line in order.order_line: + if pre_order_line_qty.get(order_line, False) and float_compare(pre_order_line_qty[order_line], order_line.product_qty, precision_rounding=order_line.product_uom.rounding) > 0: + to_log[order_line] = (order_line.product_qty, pre_order_line_qty[order_line]) + if to_log: + order._log_decrease_ordered_quantity(to_log) + return res + + # -------------------------------------------------- + # Actions + # -------------------------------------------------- + + def button_approve(self, force=False): + result = super(PurchaseOrder, self).button_approve(force=force) + self._create_picking() + return result + + def button_cancel(self): + for order in self: + for move in order.order_line.mapped('move_ids'): + if move.state == 'done': + raise UserError(_('Unable to cancel purchase order %s as some receptions have already been done.') % (order.name)) + # If the product is MTO, change the procure_method of the closest move to purchase to MTS. + # The purpose is to link the po that the user will manually generate to the existing moves's chain. + if order.state in ('draft', 'sent', 'to approve', 'purchase'): + for order_line in order.order_line: + order_line.move_ids._action_cancel() + if order_line.move_dest_ids: + move_dest_ids = order_line.move_dest_ids + if order_line.propagate_cancel: + move_dest_ids._action_cancel() + else: + move_dest_ids.write({'procure_method': 'make_to_stock'}) + move_dest_ids._recompute_state() + + for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'): + pick.action_cancel() + + order.order_line.write({'move_dest_ids':[(5,0,0)]}) + + return super(PurchaseOrder, self).button_cancel() + + def action_view_picking(self): + """ This function returns an action that display existing picking orders of given purchase order ids. When only one found, show the picking immediately. + """ + result = self.env["ir.actions.actions"]._for_xml_id('stock.action_picking_tree_all') + # override the context to get rid of the default filtering on operation type + result['context'] = {'default_partner_id': self.partner_id.id, 'default_origin': self.name, 'default_picking_type_id': self.picking_type_id.id} + pick_ids = self.mapped('picking_ids') + # choose the view_mode accordingly + if not pick_ids or len(pick_ids) > 1: + result['domain'] = "[('id','in',%s)]" % (pick_ids.ids) + elif len(pick_ids) == 1: + res = self.env.ref('stock.view_picking_form', False) + form_view = [(res and res.id or False, 'form')] + if 'views' in result: + result['views'] = form_view + [(state,view) for state,view in result['views'] if view != 'form'] + else: + result['views'] = form_view + result['res_id'] = pick_ids.id + return result + + def _prepare_invoice(self): + invoice_vals = super()._prepare_invoice() + invoice_vals['invoice_incoterm_id'] = self.incoterm_id.id + return invoice_vals + + # -------------------------------------------------- + # Business methods + # -------------------------------------------------- + + def _log_decrease_ordered_quantity(self, purchase_order_lines_quantities): + + def _keys_in_sorted(move): + """ sort by picking and the responsible for the product the + move. + """ + return (move.picking_id.id, move.product_id.responsible_id.id) + + def _keys_in_groupby(move): + """ group by picking and the responsible for the product the + move. + """ + return (move.picking_id, move.product_id.responsible_id) + + def _render_note_exception_quantity_po(order_exceptions): + order_line_ids = self.env['purchase.order.line'].browse([order_line.id for order in order_exceptions.values() for order_line in order[0]]) + purchase_order_ids = order_line_ids.mapped('order_id') + move_ids = self.env['stock.move'].concat(*rendering_context.keys()) + impacted_pickings = move_ids.mapped('picking_id')._get_impacted_pickings(move_ids) - move_ids.mapped('picking_id') + values = { + 'purchase_order_ids': purchase_order_ids, + 'order_exceptions': order_exceptions.values(), + 'impacted_pickings': impacted_pickings, + } + return self.env.ref('purchase_stock.exception_on_po')._render(values=values) + + documents = self.env['stock.picking']._log_activity_get_documents(purchase_order_lines_quantities, 'move_ids', 'DOWN', _keys_in_sorted, _keys_in_groupby) + filtered_documents = {} + for (parent, responsible), rendering_context in documents.items(): + if parent._name == 'stock.picking': + if parent.state == 'cancel': + continue + filtered_documents[(parent, responsible)] = rendering_context + self.env['stock.picking']._log_activity(_render_note_exception_quantity_po, filtered_documents) + + def _get_destination_location(self): + self.ensure_one() + if self.dest_address_id: + return self.dest_address_id.property_stock_customer.id + return self.picking_type_id.default_location_dest_id.id + + @api.model + def _get_picking_type(self, company_id): + picking_type = self.env['stock.picking.type'].search([('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)]) + if not picking_type: + picking_type = self.env['stock.picking.type'].search([('code', '=', 'incoming'), ('warehouse_id', '=', False)]) + return picking_type[:1] + + def _prepare_picking(self): + if not self.group_id: + self.group_id = self.group_id.create({ + 'name': self.name, + 'partner_id': self.partner_id.id + }) + if not self.partner_id.property_stock_supplier.id: + raise UserError(_("You must set a Vendor Location for this partner %s", self.partner_id.name)) + return { + 'picking_type_id': self.picking_type_id.id, + 'partner_id': self.partner_id.id, + 'user_id': False, + 'date': self.date_order, + 'origin': self.name, + 'location_dest_id': self._get_destination_location(), + 'location_id': self.partner_id.property_stock_supplier.id, + 'company_id': self.company_id.id, + } + + def _create_picking(self): + StockPicking = self.env['stock.picking'] + for order in self.filtered(lambda po: po.state in ('purchase', 'done')): + if any(product.type in ['product', 'consu'] for product in order.order_line.product_id): + order = order.with_company(order.company_id) + pickings = order.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel')) + if not pickings: + res = order._prepare_picking() + picking = StockPicking.with_user(SUPERUSER_ID).create(res) + else: + picking = pickings[0] + moves = order.order_line._create_stock_moves(picking) + moves = moves.filtered(lambda x: x.state not in ('done', 'cancel'))._action_confirm() + seq = 0 + for move in sorted(moves, key=lambda move: move.date): + seq += 5 + move.sequence = seq + moves._action_assign() + picking.message_post_with_view('mail.message_origin_link', + values={'self': picking, 'origin': order}, + subtype_id=self.env.ref('mail.mt_note').id) + return True + + def _add_picking_info(self, activity): + """Helper method to add picking info to the Date Updated activity when + vender updates date_planned of the po lines. + """ + validated_picking = self.picking_ids.filtered(lambda p: p.state == 'done') + if validated_picking: + activity.note += _("<p>Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.</p>") % validated_picking[0].name + elif not self.picking_ids: + activity.note += _("<p>Corresponding receipt not found.</p>") + else: + activity.note += _("<p>Those dates have been updated accordingly on the receipt %s.</p>") % self.picking_ids[0].name + + def _create_update_date_activity(self, updated_dates): + activity = super()._create_update_date_activity(updated_dates) + self._add_picking_info(activity) + + def _update_update_date_activity(self, updated_dates, activity): + # remove old picking info to update it + note_lines = activity.note.split('<p>') + note_lines.pop() + activity.note = '<p>'.join(note_lines) + super()._update_update_date_activity(updated_dates, activity) + self._add_picking_info(activity) + + @api.model + def _get_orders_to_remind(self): + """When auto sending reminder mails, don't send for purchase order with + validated receipts.""" + return super()._get_orders_to_remind().filtered(lambda p: not p.effective_date) + + +class PurchaseOrderLine(models.Model): + _inherit = 'purchase.order.line' + + qty_received_method = fields.Selection(selection_add=[('stock_moves', 'Stock Moves')]) + + move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, copy=False) + orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint') + move_dest_ids = fields.One2many('stock.move', 'created_purchase_line_id', 'Downstream Moves') + product_description_variants = fields.Char('Custom Description') + propagate_cancel = fields.Boolean('Propagate cancellation', default=True) + + def _compute_qty_received_method(self): + super(PurchaseOrderLine, self)._compute_qty_received_method() + for line in self.filtered(lambda l: not l.display_type): + if line.product_id.type in ['consu', 'product']: + line.qty_received_method = 'stock_moves' + + @api.depends('move_ids.state', 'move_ids.product_uom_qty', 'move_ids.product_uom') + def _compute_qty_received(self): + super(PurchaseOrderLine, self)._compute_qty_received() + for line in self: + if line.qty_received_method == 'stock_moves': + total = 0.0 + # In case of a BOM in kit, the products delivered do not correspond to the products in + # the PO. Therefore, we can skip them since they will be handled later on. + for move in line.move_ids.filtered(lambda m: m.product_id == line.product_id): + if move.state == 'done': + if move.location_dest_id.usage == "supplier": + if move.to_refund: + total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom) + elif move.origin_returned_move_id and move.origin_returned_move_id._is_dropshipped() and not move._is_dropshipped_returned(): + # Edge case: the dropship is returned to the stock, no to the supplier. + # In this case, the received quantity on the PO is set although we didn't + # receive the product physically in our stock. To avoid counting the + # quantity twice, we do nothing. + pass + elif ( + move.location_dest_id.usage == "internal" + and move.to_refund + and move.location_dest_id + not in self.env["stock.location"].search( + [("id", "child_of", move.warehouse_id.view_location_id.id)] + ) + ): + total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom) + else: + total += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom) + line._track_qty_received(total) + line.qty_received = total + + @api.model_create_multi + def create(self, vals_list): + lines = super(PurchaseOrderLine, self).create(vals_list) + lines.filtered(lambda l: l.order_id.state == 'purchase')._create_or_update_picking() + return lines + + def write(self, values): + for line in self.filtered(lambda l: not l.display_type): + # PO date_planned overrides any PO line date_planned values + if values.get('date_planned'): + new_date = fields.Datetime.to_datetime(values['date_planned']) + self._update_move_date_deadline(new_date) + result = super(PurchaseOrderLine, self).write(values) + if 'product_qty' in values: + self.filtered(lambda l: l.order_id.state == 'purchase')._create_or_update_picking() + return result + + def unlink(self): + self.move_ids._action_cancel() + + ppg_cancel_lines = self.filtered(lambda line: line.propagate_cancel) + ppg_cancel_lines.move_dest_ids._action_cancel() + + not_ppg_cancel_lines = self.filtered(lambda line: not line.propagate_cancel) + not_ppg_cancel_lines.move_dest_ids.write({'procure_method': 'make_to_stock'}) + not_ppg_cancel_lines.move_dest_ids._recompute_state() + + return super().unlink() + + # -------------------------------------------------- + # Business methods + # -------------------------------------------------- + + def _update_move_date_deadline(self, new_date): + """ Updates corresponding move picking line deadline dates that are not yet completed. """ + moves_to_update = self.move_ids.filtered(lambda m: m.state not in ('done', 'cancel')) + if not moves_to_update: + moves_to_update = self.move_dest_ids.filtered(lambda m: m.state not in ('done', 'cancel')) + for move in moves_to_update: + move.date_deadline = new_date + relativedelta(days=move.company_id.po_lead) + + def _create_or_update_picking(self): + for line in self: + if line.product_id and line.product_id.type in ('product', 'consu'): + # Prevent decreasing below received quantity + if float_compare(line.product_qty, line.qty_received, line.product_uom.rounding) < 0: + raise UserError(_('You cannot decrease the ordered quantity below the received quantity.\n' + 'Create a return first.')) + + if float_compare(line.product_qty, line.qty_invoiced, line.product_uom.rounding) == -1: + # If the quantity is now below the invoiced quantity, create an activity on the vendor bill + # inviting the user to create a refund. + line.invoice_lines[0].move_id.activity_schedule( + 'mail.mail_activity_data_warning', + note=_('The quantities on your purchase order indicate less than billed. You should ask for a refund.')) + + # If the user increased quantity of existing line or created a new line + pickings = line.order_id.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel') and x.location_dest_id.usage in ('internal', 'transit', 'customer')) + picking = pickings and pickings[0] or False + if not picking: + res = line.order_id._prepare_picking() + picking = self.env['stock.picking'].create(res) + + moves = line._create_stock_moves(picking) + moves._action_confirm()._action_assign() + + def _get_stock_move_price_unit(self): + self.ensure_one() + line = self[0] + order = line.order_id + price_unit = line.price_unit + price_unit_prec = self.env['decimal.precision'].precision_get('Product Price') + if line.taxes_id: + qty = line.product_qty or 1 + price_unit = line.taxes_id.with_context(round=False).compute_all( + price_unit, currency=line.order_id.currency_id, quantity=qty, product=line.product_id, partner=line.order_id.partner_id + )['total_void'] + price_unit = float_round(price_unit / qty, precision_digits=price_unit_prec) + if line.product_uom.id != line.product_id.uom_id.id: + price_unit *= line.product_uom.factor / line.product_id.uom_id.factor + if order.currency_id != order.company_id.currency_id: + price_unit = order.currency_id._convert( + price_unit, order.company_id.currency_id, self.company_id, self.date_order or fields.Date.today(), round=False) + return price_unit + + def _prepare_stock_moves(self, picking): + """ Prepare the stock moves data for one order line. This function returns a list of + dictionary ready to be used in stock.move's create() + """ + self.ensure_one() + res = [] + if self.product_id.type not in ['product', 'consu']: + return res + + qty = 0.0 + price_unit = self._get_stock_move_price_unit() + outgoing_moves, incoming_moves = self._get_outgoing_incoming_moves() + for move in outgoing_moves: + qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + for move in incoming_moves: + qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + + move_dests = self.move_dest_ids + if not move_dests: + move_dests = self.move_ids.move_dest_ids.filtered(lambda m: m.state != 'cancel' and not m.location_dest_id.usage == 'supplier') + + if not move_dests: + qty_to_attach = 0 + qty_to_push = self.product_qty - qty + else: + move_dests_initial_demand = self.product_id.uom_id._compute_quantity( + sum(move_dests.filtered(lambda m: m.state != 'cancel' and not m.location_dest_id.usage == 'supplier').mapped('product_qty')), + self.product_uom, rounding_method='HALF-UP') + qty_to_attach = move_dests_initial_demand - qty + qty_to_push = self.product_qty - move_dests_initial_demand + + if float_compare(qty_to_attach, 0.0, precision_rounding=self.product_uom.rounding) > 0: + product_uom_qty, product_uom = self.product_uom._adjust_uom_quantities(qty_to_attach, self.product_id.uom_id) + res.append(self._prepare_stock_move_vals(picking, price_unit, product_uom_qty, product_uom)) + if float_compare(qty_to_push, 0.0, precision_rounding=self.product_uom.rounding) > 0: + product_uom_qty, product_uom = self.product_uom._adjust_uom_quantities(qty_to_push, self.product_id.uom_id) + extra_move_vals = self._prepare_stock_move_vals(picking, price_unit, product_uom_qty, product_uom) + extra_move_vals['move_dest_ids'] = False # don't attach + res.append(extra_move_vals) + return res + + def _prepare_stock_move_vals(self, picking, price_unit, product_uom_qty, product_uom): + self.ensure_one() + product = self.product_id.with_context(lang=self.order_id.dest_address_id.lang or self.env.user.lang) + description_picking = product._get_description(self.order_id.picking_type_id) + if self.product_description_variants: + description_picking += "\n" + self.product_description_variants + date_planned = self.date_planned or self.order_id.date_planned + return { + # truncate to 2000 to avoid triggering index limit error + # TODO: remove index in master? + 'name': (self.name or '')[:2000], + 'product_id': self.product_id.id, + 'date': date_planned, + 'date_deadline': date_planned + relativedelta(days=self.order_id.company_id.po_lead), + 'location_id': self.order_id.partner_id.property_stock_supplier.id, + 'location_dest_id': (self.orderpoint_id and not (self.move_ids | self.move_dest_ids)) and self.orderpoint_id.location_id.id or self.order_id._get_destination_location(), + 'picking_id': picking.id, + 'partner_id': self.order_id.dest_address_id.id, + 'move_dest_ids': [(4, x) for x in self.move_dest_ids.ids], + 'state': 'draft', + 'purchase_line_id': self.id, + 'company_id': self.order_id.company_id.id, + 'price_unit': price_unit, + 'picking_type_id': self.order_id.picking_type_id.id, + 'group_id': self.order_id.group_id.id, + 'origin': self.order_id.name, + 'description_picking': description_picking, + 'propagate_cancel': self.propagate_cancel, + 'warehouse_id': self.order_id.picking_type_id.warehouse_id.id, + 'product_uom_qty': product_uom_qty, + 'product_uom': product_uom.id, + } + + @api.model + def _prepare_purchase_order_line_from_procurement(self, product_id, product_qty, product_uom, company_id, values, po): + line_description = '' + if values.get('product_description_variants'): + line_description = values['product_description_variants'] + supplier = values.get('supplier') + res = self._prepare_purchase_order_line(product_id, product_qty, product_uom, company_id, supplier, po) + # We need to keep the vendor name set in _prepare_purchase_order_line. To avoid redundancy + # in the line name, we add the line_description only if different from the product name. + # This way, we shoud not lose any valuable information. + if line_description and product_id.name != line_description: + res['name'] += '\n' + line_description + res['move_dest_ids'] = [(4, x.id) for x in values.get('move_dest_ids', [])] + res['orderpoint_id'] = values.get('orderpoint_id', False) and values.get('orderpoint_id').id + res['propagate_cancel'] = values.get('propagate_cancel') + res['product_description_variants'] = values.get('product_description_variants') + return res + + def _create_stock_moves(self, picking): + values = [] + for line in self.filtered(lambda l: not l.display_type): + for val in line._prepare_stock_moves(picking): + values.append(val) + line.move_dest_ids.created_purchase_line_id = False + + return self.env['stock.move'].create(values) + + def _find_candidate(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values): + """ Return the record in self where the procument with values passed as + args can be merged. If it returns an empty record then a new line will + be created. + """ + description_picking = '' + if values.get('product_description_variants'): + description_picking = values['product_description_variants'] + lines = self.filtered( + lambda l: l.propagate_cancel == values['propagate_cancel'] + and ((values['orderpoint_id'] and not values['move_dest_ids']) and l.orderpoint_id == values['orderpoint_id'] or True) + ) + + # In case 'product_description_variants' is in the values, we also filter on the PO line + # name. This way, we can merge lines with the same description. To do so, we need the + # product name in the context of the PO partner. + if lines and values.get('product_description_variants'): + partner = self.mapped('order_id.partner_id')[:1] + product_lang = product_id.with_context( + lang=partner.lang, + partner_id=partner.id, + ) + name = product_lang.display_name + if product_lang.description_purchase: + name += '\n' + product_lang.description_purchase + lines = lines.filtered(lambda l: l.name == name + '\n' + description_picking) + if lines: + return lines[0] + + return lines and lines[0] or self.env['purchase.order.line'] + + def _get_outgoing_incoming_moves(self): + outgoing_moves = self.env['stock.move'] + incoming_moves = self.env['stock.move'] + + for move in self.move_ids.filtered(lambda r: r.state != 'cancel' and not r.scrapped and self.product_id == r.product_id): + if move.location_dest_id.usage == "supplier" and move.to_refund: + outgoing_moves |= move + elif move.location_dest_id.usage != "supplier": + if not move.origin_returned_move_id or (move.origin_returned_move_id and move.to_refund): + incoming_moves |= move + + return outgoing_moves, incoming_moves + + def _update_date_planned(self, updated_date): + move_to_update = self.move_ids.filtered(lambda m: m.state not in ['done', 'cancel']) + if not self.move_ids or move_to_update: # Only change the date if there is no move done or none + super()._update_date_planned(updated_date) + if move_to_update: + self._update_move_date_deadline(updated_date) + + @api.model + def _update_qty_received_method(self): + """Update qty_received_method for old PO before install this module.""" + self.search([])._compute_qty_received_method() |
