1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
# 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()
@api.depends('repair_id', 'repair_id.location_dest_id')
def _compute_location_dest_id(self):
ids_to_super = set()
for move in self:
if move.repair_id and move.repair_line_type:
move.location_dest_id = move.repair_id[
MAP_REPAIR_LINE_TYPE_TO_MOVE_LOCATIONS_FROM_REPAIR[move.repair_line_type]['location_dest_id']
]
else:
ids_to_super.add(move.id)
return super(StockMove, self.browse(ids_to_super))._compute_location_dest_id()
def copy_data(self, default=None):
default = dict(default or {})
vals_list = super().copy_data(default=default)
for move, vals in zip(self, vals_list):
if 'repair_id' in default or move.repair_id:
vals['sale_line_id'] = False
return vals_list
@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
no_repair_moves = moves - repair_moves
draft_repair_moves = repair_moves.filtered(lambda m: m.state == 'draft' and m.repair_id.state in ('confirmed', 'under_repair'))
other_repair_moves = repair_moves - draft_repair_moves
draft_repair_moves._check_company()
draft_repair_moves._adjust_procure_method()
res = draft_repair_moves._action_confirm()
res._trigger_scheduler()
confirmed_repair_moves = (res | other_repair_moves)
confirmed_repair_moves._create_repair_sale_order_line()
return (confirmed_repair_moves | no_repair_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.repair_line_type:
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
def action_add_from_catalog_repair(self):
repair_order = self.env['repair.order'].browse(self.env.context.get('order_id'))
return repair_order.action_add_from_catalog()
# 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
'product_uom': move.product_uom.id,
'move_ids': [Command.link(move.id)],
'qty_delivered': move.quantity if move.state == 'done' else 0.0,
})
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):
if self.repair_id:
return False
return super()._should_be_assigned()
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)
def action_show_details(self):
action = super().action_show_details()
if self.repair_line_type == 'recycle':
action['context'].update({'show_quant': False, 'show_destination_location': True})
return action
|