1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/industry_fsm_stock/models/project_task.py
2024-12-10 09:04:09 +07:00

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