191 lines
8.0 KiB
Python
191 lines
8.0 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, Command, fields, models
|
|
from odoo.tools.misc import groupby
|
|
|
|
MAP_REPAIR_LINE_TYPE_TO_MOVE_LOCATIONS_FROM_REPAIR = {
|
|
'add': {'location_id': 'location_id', 'location_dest_id': 'location_dest_id'},
|
|
'remove': {'location_id': 'location_dest_id', 'location_dest_id': 'parts_location_id'},
|
|
'recycle': {'location_id': 'location_dest_id', 'location_dest_id': 'recycle_location_id'},
|
|
}
|
|
|
|
|
|
class StockMove(models.Model):
|
|
_inherit = 'stock.move'
|
|
|
|
repair_id = fields.Many2one('repair.order', check_company=True)
|
|
repair_line_type = fields.Selection([
|
|
('add', 'Add'),
|
|
('remove', 'Remove'),
|
|
('recycle', 'Recycle')
|
|
], 'Type', store=True, index=True)
|
|
|
|
@api.depends('repair_line_type')
|
|
def _compute_forecast_information(self):
|
|
moves_to_compute = self.filtered(lambda move: not move.repair_line_type or move.repair_line_type == 'add')
|
|
for move in (self - moves_to_compute):
|
|
move.forecast_availability = move.product_qty
|
|
move.forecast_expected_date = False
|
|
return super(StockMove, moves_to_compute)._compute_forecast_information()
|
|
|
|
@api.depends('repair_id.picking_type_id')
|
|
def _compute_picking_type_id(self):
|
|
remaining_moves = self
|
|
for move in self:
|
|
if move.repair_id:
|
|
move.picking_type_id = move.repair_id.picking_type_id
|
|
remaining_moves -= move
|
|
return super(StockMove, remaining_moves)._compute_picking_type_id()
|
|
|
|
def copy_data(self, default=None):
|
|
default = dict(default or {})
|
|
if 'repair_id' in default or self.repair_id:
|
|
default['sale_line_id'] = False
|
|
return super().copy_data(default)
|
|
|
|
@api.ondelete(at_uninstall=False)
|
|
def _unlink_if_draft_or_cancel(self):
|
|
self.filtered('repair_id')._action_cancel()
|
|
return super()._unlink_if_draft_or_cancel()
|
|
|
|
def unlink(self):
|
|
self._clean_repair_sale_order_line()
|
|
return super().unlink()
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
for vals in vals_list:
|
|
if not vals.get('repair_id') or 'repair_line_type' not in vals:
|
|
continue
|
|
repair_id = self.env['repair.order'].browse([vals['repair_id']])
|
|
vals['name'] = repair_id.name
|
|
src_location, dest_location = self._get_repair_locations(vals['repair_line_type'], repair_id)
|
|
if not vals.get('location_id'):
|
|
vals['location_id'] = src_location.id
|
|
if not vals.get('location_dest_id'):
|
|
vals['location_dest_id'] = dest_location.id
|
|
moves = super().create(vals_list)
|
|
repair_moves = self.env['stock.move']
|
|
for move in moves:
|
|
if not move.repair_id:
|
|
continue
|
|
move.group_id = move.repair_id.procurement_group_id.id
|
|
move.origin = move.name
|
|
move.picking_type_id = move.repair_id.picking_type_id.id
|
|
repair_moves |= move
|
|
|
|
if move.state == 'draft' and move.repair_id.state in ('confirmed', 'under_repair'):
|
|
move._check_company()
|
|
move._adjust_procure_method()
|
|
move._action_confirm()
|
|
move._trigger_scheduler()
|
|
repair_moves._create_repair_sale_order_line()
|
|
return moves
|
|
|
|
def write(self, vals):
|
|
res = super().write(vals)
|
|
repair_moves = self.env['stock.move']
|
|
moves_to_create_so_line = self.env['stock.move']
|
|
for move in self:
|
|
if not move.repair_id:
|
|
continue
|
|
# checks vals update
|
|
if 'repair_line_type' in vals or 'picking_type_id' in vals and move.product_id != move.repair_id.product_id:
|
|
move.location_id, move.location_dest_id = move._get_repair_locations(move.repair_line_type)
|
|
if not move.sale_line_id and 'sale_line_id' not in vals and move.repair_line_type == 'add':
|
|
moves_to_create_so_line |= move
|
|
if move.sale_line_id and ('repair_line_type' in vals or 'product_uom_qty' in vals):
|
|
repair_moves |= move
|
|
|
|
repair_moves._update_repair_sale_order_line()
|
|
moves_to_create_so_line._create_repair_sale_order_line()
|
|
return res
|
|
|
|
# Needed to also cancel the lastly added part
|
|
def _action_cancel(self):
|
|
self._clean_repair_sale_order_line()
|
|
return super()._action_cancel()
|
|
|
|
def _create_repair_sale_order_line(self):
|
|
if not self:
|
|
return
|
|
so_line_vals = []
|
|
for move in self:
|
|
if move.sale_line_id or move.repair_line_type != 'add' or not move.repair_id.sale_order_id:
|
|
continue
|
|
product_qty = move.product_uom_qty if move.repair_id.state != 'done' else move.quantity
|
|
so_line_vals.append({
|
|
'order_id': move.repair_id.sale_order_id.id,
|
|
'product_id': move.product_id.id,
|
|
'product_uom_qty': product_qty, # When relying only on so_line compute method, the sol quantity is only updated on next sol creation
|
|
'move_ids': [Command.link(move.id)],
|
|
})
|
|
if move.repair_id.under_warranty:
|
|
so_line_vals[-1]['price_unit'] = 0.0
|
|
elif move.price_unit:
|
|
so_line_vals[-1]['price_unit'] = move.price_unit
|
|
|
|
self.env['sale.order.line'].create(so_line_vals)
|
|
|
|
def _clean_repair_sale_order_line(self):
|
|
self.filtered(
|
|
lambda m: m.repair_id and m.sale_line_id
|
|
).mapped('sale_line_id').write({'product_uom_qty': 0.0})
|
|
|
|
def _update_repair_sale_order_line(self):
|
|
if not self:
|
|
return
|
|
moves_to_clean = self.env['stock.move']
|
|
moves_to_update = self.env['stock.move']
|
|
for move in self:
|
|
if not move.repair_id:
|
|
continue
|
|
if move.sale_line_id and move.repair_line_type != 'add':
|
|
moves_to_clean |= move
|
|
if move.sale_line_id and move.repair_line_type == 'add':
|
|
moves_to_update |= move
|
|
moves_to_clean._clean_repair_sale_order_line()
|
|
for sale_line, _ in groupby(moves_to_update, lambda m: m.sale_line_id):
|
|
sale_line.product_uom_qty = sum(sale_line.move_ids.mapped('product_uom_qty'))
|
|
|
|
def _is_consuming(self):
|
|
return super()._is_consuming() or (self.repair_id and self.repair_line_type == 'add')
|
|
|
|
def _get_repair_locations(self, repair_line_type, repair_id=False):
|
|
location_map = MAP_REPAIR_LINE_TYPE_TO_MOVE_LOCATIONS_FROM_REPAIR.get(repair_line_type)
|
|
if location_map:
|
|
if not repair_id:
|
|
self.repair_id.ensure_one()
|
|
repair_id = self.repair_id
|
|
location_id, location_dest_id = [repair_id[field] for field in location_map.values()]
|
|
else:
|
|
location_id, location_dest_id = False, False
|
|
return location_id, location_dest_id
|
|
|
|
def _get_source_document(self):
|
|
return self.repair_id or super()._get_source_document()
|
|
|
|
def _set_repair_locations(self):
|
|
moves_per_repair = self.filtered(lambda m: (m.repair_id and m.repair_line_type) is not False).grouped('repair_id')
|
|
if not moves_per_repair:
|
|
return
|
|
for moves in moves_per_repair.values():
|
|
grouped_moves = moves.grouped('repair_line_type')
|
|
for line_type, m in grouped_moves.items():
|
|
m.location_id, m.location_dest_id = m._get_repair_locations(line_type)
|
|
|
|
def _should_be_assigned(self):
|
|
res = super(StockMove, self)._should_be_assigned()
|
|
return bool(res and not (self.repair_id and self.repair_line_type))
|
|
|
|
def _create_extra_move(self):
|
|
if self.repair_id:
|
|
return self
|
|
return super(StockMove, self)._create_extra_move()
|
|
|
|
def _split(self, qty, restrict_partner_id=False):
|
|
# When setting the Repair Order as done with partially done moves, do not split these moves
|
|
if self.repair_id:
|
|
return []
|
|
return super(StockMove, self)._split(qty, restrict_partner_id)
|