from odoo import api, fields, models from odoo.exceptions import ValidationError class SaleLineChangeOrder(models.TransientModel): _name = 'sale.line.change.order' _description = 'Sale Line Change Order' order_id = fields.Many2one('sale.order', string='Sale Order') line_ids = fields.One2many('sale.line.change.order.line', 'change_order_id', string='Change Lines') @api.model def default_get(self, fields): rec = super(SaleLineChangeOrder, self).default_get(fields) if 'order_id' in rec: order = self.env['sale.order'].browse(rec['order_id']) if not order: return rec line_model = self.env['sale.line.change.order.line'] rec['line_ids'] = [(0, 0, line_model.values_from_so_line(l)) for l in order.order_line] return rec @api.multi def apply(self): self.ensure_one() self.line_ids.apply() return True class SaleLineChangeOrderLine(models.TransientModel): _name = 'sale.line.change.order.line' change_order_id = fields.Many2one('sale.line.change.order') sale_line_id = fields.Many2one('sale.order.line', string='Sale Line') line_ordered_qty = fields.Float(string='Ordered Qty') line_delivered_qty = fields.Float(string='Delivered Qty') line_reserved_qty = fields.Float(string='Reserved Qty') line_date_planned = fields.Datetime(string='Planned Date') line_warehouse_id = fields.Many2one('stock.warehouse', string='Warehouse') line_route_id = fields.Many2one('stock.location.route', string='Route') def values_from_so_line(self, so_line): move_ids = so_line.move_ids reserved_qty = sum(move_ids.mapped('reserved_availability')) return { 'sale_line_id': so_line.id, 'line_ordered_qty': so_line.product_uom_qty, 'line_delivered_qty': so_line.qty_delivered, 'line_reserved_qty': reserved_qty, 'line_date_planned': so_line.date_planned, 'line_warehouse_id': so_line.warehouse_id.id, 'line_route_id': so_line.route_id.id, } def _apply(self): self._apply_clean_dropship() self._apply_clean_existing_moves() self._apply_new_values() self._apply_procurement() def _apply_clean_dropship(self): po_line_model = self.env['purchase.order.line'].sudo() po_lines = po_line_model.search([('sale_line_id', 'in', self.mapped('sale_line_id.id'))]) if po_lines and po_lines.filtered(lambda l: l.order_id.state != 'cancel'): names = ', '.join(po_lines.filtered(lambda l: l.order_id.state != 'cancel').mapped('order_id.name')) raise ValidationError('One or more lines has existing non-cancelled Purchase Orders associated: ' + names) def _apply_clean_existing_moves(self): moves = self.mapped('sale_line_id.move_ids').filtered(lambda m: m.state != 'done') moves._action_cancel() def _apply_new_values(self): for line in self: line.sale_line_id.write({ 'date_planned': line.line_date_planned, 'warehouse_id': line.line_warehouse_id.id, 'route_id': line.line_route_id.id, }) def _apply_procurement(self): self.mapped('sale_line_id')._action_launch_procurement_rule() def apply(self): changed_lines = self.filtered(lambda l: ( l.sale_line_id.warehouse_id != l.line_warehouse_id or l.sale_line_id.route_id != l.line_route_id)) changed_lines._apply()