Files
enterprise-addons/sale_stock_renting/models/stock_move.py
T

146 lines
7.2 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo import api, models
from odoo.fields import Command, Domain
class StockMove(models.Model):
_inherit = 'stock.move'
def _search_picking_for_assignation_domain(self):
""" This modifies the picking search domain for rental moves.
Modify the picking search domain to make sure that rental moves are on the
same picking as sale moves in case of hybrid rental orders, while still making
sure that modifying the SO line quantity is propagated to stock moves correctly.
"""
domain = super()._search_picking_for_assignation_domain()
rental_loc = self.company_id.rental_loc_id
if (
self.env['res.groups']._is_feature_enabled('sale_stock_renting.group_rental_stock_picking')
and rental_loc
and self.sale_line_id.order_id.is_rental_order
and self.location_dest_id.id in (rental_loc.id, rental_loc.location_id.id)
):
# optimize to make sure we have only to check the 'in' condition
domain = Domain(domain).optimize(self.env['stock.picking']).map_conditions(
lambda cond: Domain('location_dest_id', 'in', {rental_loc.id, rental_loc.location_id.id}.union(cond.value))
if cond.field_expr == 'location_dest_id' and cond.operator == 'in'
else cond
)
return domain
def _prepare_procurement_values(self):
res = super()._prepare_procurement_values()
if self.sale_line_id._are_rental_pickings_enabled() and self.sale_line_id.is_rental and self.sale_line_id.is_mto:
res['route_ids'] = self.rule_id.route_id
return res
@api.model
def _prepare_merge_moves_distinct_fields(self):
distinct_fields = super()._prepare_merge_moves_distinct_fields()
if any(sale_order.is_rental_order for sale_order in self.reference_ids.sale_ids.sudo()):
distinct_fields.remove('origin_returned_move_id')
distinct_fields.remove('procure_method')
return distinct_fields
def _action_assign(self, force_qty=False):
""" Assign the lot_id present on the SO line to the stock move lines for rental orders. """
super()._action_assign(force_qty=force_qty)
for product in self.product_id:
if not product.tracking == 'serial':
continue
moves = self.filtered(lambda m: m.product_id == product)
sale_lines = self.env['sale.order.line']
for move in moves:
sale_lines |= move._get_sale_order_lines()
if sale_lines.reserved_lot_ids:
free_reserved_lots = sale_lines.reserved_lot_ids.filtered(lambda s: s not in moves.move_line_ids.lot_id)
to_assign_move_lines = moves.move_line_ids.filtered(lambda l: l.lot_id not in sale_lines.reserved_lot_ids)
for line, lot in zip(to_assign_move_lines, free_reserved_lots):
quant = lot.quant_ids.filtered(lambda q: q.location_id == line.location_id and q.quantity == 1 and q.reserved_quantity == 0)
if quant:
line.lot_id = lot
def _action_done(self, cancel_backorder=False):
""" Correctly set the qty_delivered and qty_returned of rental order lines when using pickings."""
res = super()._action_done(cancel_backorder=cancel_backorder)
if (
not self.env.context.get('rental_direct_stock_move')
and self.env['res.groups']._is_feature_enabled('sale_stock_renting.group_rental_stock_picking')
):
returned_lot_ids, pickedup_lot_ids = (defaultdict(set), defaultdict(set))
for move in res:
if move.state != "done":
continue
if not move.sale_line_id.is_rental or move.product_id != move.sale_line_id.product_id:
continue
if move.location_id == move.company_id.rental_loc_id:
current_qty_returned = move.product_uom._compute_quantity(move.quantity, move.sale_line_id.product_uom_id, rounding_method='HALF-UP')
if move.sale_line_id.order_id.is_late:
move.sale_line_id._generate_delay_line(current_qty_returned)
move.sale_line_id.qty_returned += current_qty_returned
if move.picked and move.sale_line_id.tracking == 'serial':
# Synchronize the reserved lots with the picked lots.
# Ensure reserved lots include all picked lots and any extra reserved lots.
sol = move.sale_line_id
qty_to_keep_reserved = max(
int(sol.product_uom_qty), len(move.lot_ids), len(sol.reserved_lot_ids)
)
sol.reserved_lot_ids = (
move.lot_ids | sol.reserved_lot_ids
)[:qty_to_keep_reserved]
if move.location_id == move.company_id.rental_loc_id:
returned_lot_ids[move.sale_line_id.id].update(move.lot_ids.ids)
elif move.location_dest_id == move.company_id.rental_loc_id:
pickedup_lot_ids[move.sale_line_id.id].update(move.lot_ids.ids)
for sale_line_id, lot_ids in returned_lot_ids.items():
self.env['sale.order.line'].browse(sale_line_id).returned_lot_ids = [Command.link(lot_id) for lot_id in lot_ids]
for sale_line_id, lot_ids in pickedup_lot_ids.items():
self.env['sale.order.line'].browse(sale_line_id).pickedup_lot_ids = [Command.link(lot_id) for lot_id in lot_ids]
return res
def _compute_location_dest_id(self):
moves_to_super = self.env['stock.move']
for move in self:
if (
move.location_dest_id
and move.location_final_id
and move.sale_line_id
and not move.sale_line_id.is_rental
and move.sale_line_id.order_id.is_rental_order
and move.picking_type_id.code == 'outgoing'
and move.location_final_id._child_of(move.location_dest_id)
):
move.location_dest_id = move.location_final_id
else:
moves_to_super |= move
if moves_to_super:
super(StockMove, moves_to_super)._compute_location_dest_id()
@api.depends('sale_line_id.order_id.name')
def _compute_reference(self):
moves_with_reference = set()
for move in self:
if move.sale_line_id.is_rental and not move.picking_id:
move.reference = self.env._("Rental move: %(order)s", order=move.sale_line_id.order_id.name)
moves_with_reference.add(move.id)
super(StockMove, self - self.env['stock.move'].browse(moves_with_reference))._compute_reference()
def _set_rental_sm_qty(self):
self.ensure_one()
return self
def _is_incoming(self):
if (
self.company_id.rental_loc_id == self.location_id
and self.location_dest_id.usage == 'internal'
):
return True
return super()._is_incoming()