forked from Mapan/odoo17e
140 lines
7.6 KiB
Python
140 lines
7.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import models
|
|
from odoo.tools import float_compare, float_round
|
|
|
|
|
|
class Task(models.Model):
|
|
_inherit = "project.task"
|
|
|
|
def _prepare_materials_delivery(self):
|
|
""" Prepare the materials delivery
|
|
|
|
We validate the stock and generates/updates delivery order.
|
|
This method is called at the end of the action_fsm_validate method in industry_fsm_sale.
|
|
"""
|
|
for task in self.filtered(lambda x: x.allow_billable and x.sale_order_id):
|
|
exception = False
|
|
sale_line = self.env['sale.order.line'].sudo().search([('order_id', '=', task.sale_order_id.id), ('task_id', '=', task.id)])
|
|
for order_line in sale_line:
|
|
to_log = {}
|
|
total_qty = sum(order_line.move_ids.filtered(lambda p: p.state not in ['cancel']).mapped('product_uom_qty'))
|
|
if float_compare(order_line.product_uom_qty, total_qty, precision_rounding=order_line.product_uom.rounding) < 0:
|
|
to_log[order_line] = (order_line.product_uom_qty, total_qty)
|
|
|
|
if to_log:
|
|
exception = True
|
|
documents = self.env['stock.picking']._log_activity_get_documents(to_log, 'move_ids', 'UP')
|
|
documents = {k: v for k, v in documents.items() if k[0].state not in ['cancel', 'done']}
|
|
self.env['sale.order']._log_decrease_ordered_quantity(documents)
|
|
if not exception:
|
|
task.sudo()._validate_stock()
|
|
|
|
def _validate_stock(self):
|
|
self.ensure_one()
|
|
all_fsm_sn_moves = self.env['stock.move']
|
|
ml_to_create = []
|
|
for so_line in self.sale_order_id.order_line:
|
|
if not (so_line.task_id.is_fsm or so_line.project_id.is_fsm or so_line.fsm_lot_id):
|
|
continue
|
|
qty = so_line.product_uom_qty - so_line.qty_delivered
|
|
fsm_sn_moves = self.env['stock.move']
|
|
if not qty:
|
|
continue
|
|
for move in so_line.move_ids:
|
|
if move.state in ['done', 'cancel'] or move.quantity >= qty:
|
|
continue
|
|
fsm_sn_moves |= move
|
|
while move.move_orig_ids.filtered(lambda m: not m.picked or m.quantity < qty):
|
|
move = move.move_orig_ids
|
|
fsm_sn_moves |= move
|
|
for fsm_sn_move in fsm_sn_moves:
|
|
if not fsm_sn_move.move_line_ids:
|
|
ml_vals = fsm_sn_move._prepare_move_line_vals(quantity=0)
|
|
ml_vals['quantity'] = fsm_sn_move.product_uom_qty
|
|
ml_vals['lot_id'] = so_line.fsm_lot_id.id
|
|
ml_to_create.append(ml_vals)
|
|
else:
|
|
qty_done = 0
|
|
fsm_sn_move.move_line_ids.lot_id = so_line.fsm_lot_id
|
|
for move_line in fsm_sn_move.move_line_ids:
|
|
qty_done += move_line.quantity
|
|
missing_qty = fsm_sn_move.product_uom_qty - qty_done
|
|
if missing_qty > 0:
|
|
ml_vals = fsm_sn_move._prepare_move_line_vals(quantity=0)
|
|
ml_vals['quantity'] = missing_qty
|
|
ml_vals['lot_id'] = so_line.fsm_lot_id.id
|
|
ml_to_create.append(ml_vals)
|
|
quants = self.env['stock.quant']._gather(fsm_sn_move.product_id, fsm_sn_move.location_id, lot_id=so_line.fsm_lot_id)
|
|
if fsm_sn_move.product_id.tracking == "serial":
|
|
quants = quants.filtered(lambda q: q.quantity == 1.0)
|
|
ml_vals['location_id'] = quants[:1].location_id.id or fsm_sn_move.location_id.id
|
|
all_fsm_sn_moves |= fsm_sn_moves
|
|
self.env['stock.move.line'].create(ml_to_create)
|
|
for so_line in self.sale_order_id.order_line:
|
|
# set the quantity delivered of the sol to the quantity ordered for the product linked to the task
|
|
if so_line.task_id == self and so_line.product_id.service_policy not in ['delivered_timesheet', 'delivered_milestones']:
|
|
so_line.qty_delivered = so_line.product_uom_qty
|
|
|
|
def is_fsm_material_picking(picking, task):
|
|
""" this function returns if the picking is a picking ready to be validated. """
|
|
moves = picking.move_ids
|
|
while moves.move_dest_ids:
|
|
moves = moves.move_dest_ids
|
|
for move in moves:
|
|
sol = move.sale_line_id
|
|
if sol.fsm_lot_id:
|
|
continue
|
|
if not (sol.product_id != task.project_id.timesheet_product_id \
|
|
and sol != task.sale_line_id \
|
|
# On the last and, we check if the task is either done (and thus already done for the delivery) or the current one (and thus about to be validated)
|
|
# if not, we can not validate the delivery
|
|
and (sol.task_id == task or sol.task_id.fsm_done)):
|
|
return False
|
|
return True
|
|
|
|
pickings_to_do = self.sale_order_id.picking_ids.filtered(lambda p: p.state not in ['done', 'cancel'] and is_fsm_material_picking(p, self))
|
|
# set the quantity done as the initial demand before validating the pickings
|
|
for move in pickings_to_do.move_ids:
|
|
if move.state in ('done', 'cancel') or move in all_fsm_sn_moves:
|
|
continue
|
|
rounding = move.product_uom.rounding
|
|
if float_compare(move.quantity, move.product_uom_qty, precision_rounding=rounding) < 0:
|
|
qty_to_do = float_round(
|
|
move.product_uom_qty - move.quantity,
|
|
precision_rounding=rounding,
|
|
rounding_method='HALF-UP')
|
|
move.quantity = qty_to_do
|
|
pickings_to_do.with_context(skip_sms=True, cancel_backorder=True).button_validate()
|
|
|
|
def _fsm_ensure_sale_order(self):
|
|
"""Since we want to use the current user warehouse when using the FSM product kanban view, the SO must
|
|
be confirmed before adding any product trough the product kanban view.
|
|
We cannot indeed wait that the user actually adds a product trough the FSM product kanban view
|
|
to do so as there would be a risk that all the existing SOL (possibly added in a (pre)sale phase)
|
|
would get that user's default warehouse when the SO gets confirmed and the picking generated."""
|
|
sale_order = super()._fsm_ensure_sale_order()
|
|
if self.user_has_groups('project.group_project_user'):
|
|
sale_order = self.sale_order_id.sudo()
|
|
if sale_order.state == 'draft':
|
|
sale_order.action_confirm()
|
|
return sale_order
|
|
|
|
def _fsm_create_sale_order(self):
|
|
"""Since we want to use the current user warehouse when using the FSM product kanban view, the SO must
|
|
be confirmed before adding any product trough the product kanban view.
|
|
We cannot indeed wait that the user actually adds a product trough the FSM product kanban view
|
|
to do so as there would be a risk that all the existing SOL (possibly added in a (pre)sale phase)
|
|
would get that user's default warehouse when the SO gets confirmed and the picking generated."""
|
|
super()._fsm_create_sale_order()
|
|
sale_order = self.sale_order_id
|
|
if self.user_has_groups('project.group_project_user'):
|
|
sale_order = self.sale_order_id.sudo()
|
|
sale_order.action_confirm()
|
|
|
|
def action_fsm_view_material(self):
|
|
action = super(Task, self).action_fsm_view_material()
|
|
action['context'].update({"warehouse": self.env.user._get_default_warehouse_id().id})
|
|
return action
|