2018-01-16 06:58:15 +01:00
# -*- coding: utf-8 -*-
2018-01-16 11:34:37 +01:00
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
2018-01-16 06:58:15 +01:00
from collections import defaultdict
import math
2018-01-16 11:34:37 +01:00
from flectra import api , fields , models , _
from flectra . addons import decimal_precision as dp
from flectra . exceptions import UserError
from flectra . tools import float_compare
2018-01-16 06:58:15 +01:00
class MrpProduction ( models . Model ) :
""" Manufacturing Orders """
_name = ' mrp.production '
_description = ' Manufacturing Order '
_date_name = ' date_planned_start '
_inherit = [ ' mail.thread ' , ' mail.activity.mixin ' ]
_order = ' date_planned_start asc,id '
@api.model
def _get_default_picking_type ( self ) :
return self . env [ ' stock.picking.type ' ] . search ( [
( ' code ' , ' = ' , ' mrp_operation ' ) ,
( ' warehouse_id.company_id ' , ' in ' , [ self . env . context . get ( ' company_id ' , self . env . user . company_id . id ) , False ] ) ] ,
limit = 1 ) . id
@api.model
def _get_default_location_src_id ( self ) :
location = False
if self . _context . get ( ' default_picking_type_id ' ) :
location = self . env [ ' stock.picking.type ' ] . browse ( self . env . context [ ' default_picking_type_id ' ] ) . default_location_src_id
if not location :
location = self . env . ref ( ' stock.stock_location_stock ' , raise_if_not_found = False )
return location and location . id or False
@api.model
def _get_default_location_dest_id ( self ) :
location = False
if self . _context . get ( ' default_picking_type_id ' ) :
location = self . env [ ' stock.picking.type ' ] . browse ( self . env . context [ ' default_picking_type_id ' ] ) . default_location_dest_id
if not location :
location = self . env . ref ( ' stock.stock_location_stock ' , raise_if_not_found = False )
return location and location . id or False
name = fields . Char (
' Reference ' , copy = False , readonly = True , default = lambda x : _ ( ' New ' ) )
origin = fields . Char (
' Source ' , copy = False ,
help = " Reference of the document that generated this production order request. " )
product_id = fields . Many2one (
' product.product ' , ' Product ' ,
domain = [ ( ' type ' , ' in ' , [ ' product ' , ' consu ' ] ) ] ,
readonly = True , required = True ,
states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } )
product_tmpl_id = fields . Many2one ( ' product.template ' , ' Product Template ' , related = ' product_id.product_tmpl_id ' )
product_qty = fields . Float (
' Quantity To Produce ' ,
default = 1.0 , digits = dp . get_precision ( ' Product Unit of Measure ' ) ,
readonly = True , required = True , track_visibility = ' onchange ' ,
states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } )
product_uom_id = fields . Many2one (
' product.uom ' , ' Product Unit of Measure ' ,
oldname = ' product_uom ' , readonly = True , required = True ,
states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } )
picking_type_id = fields . Many2one (
' stock.picking.type ' , ' Operation Type ' ,
default = _get_default_picking_type , required = True )
location_src_id = fields . Many2one (
' stock.location ' , ' Raw Materials Location ' ,
default = _get_default_location_src_id ,
readonly = True , required = True ,
states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } ,
help = " Location where the system will look for components. " )
location_dest_id = fields . Many2one (
' stock.location ' , ' Finished Products Location ' ,
default = _get_default_location_dest_id ,
readonly = True , required = True ,
states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } ,
help = " Location where the system will stock the finished products. " )
date_planned_start = fields . Datetime (
' Deadline Start ' , copy = False , default = fields . Datetime . now ,
index = True , required = True ,
states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } , oldname = " date_planned " )
date_planned_finished = fields . Datetime (
' Deadline End ' , copy = False , default = fields . Datetime . now ,
index = True ,
states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } )
date_start = fields . Datetime ( ' Start Date ' , copy = False , index = True , readonly = True )
date_finished = fields . Datetime ( ' End Date ' , copy = False , index = True , readonly = True )
bom_id = fields . Many2one (
' mrp.bom ' , ' Bill of Material ' ,
readonly = True , states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } ,
help = " Bill of Materials allow you to define the list of required raw materials to make a finished product. " )
routing_id = fields . Many2one (
' mrp.routing ' , ' Routing ' ,
readonly = True , compute = ' _compute_routing ' , store = True ,
help = " The list of operations (list of work centers) to produce the finished product. The routing "
" is mainly used to compute work center costs during operations and to plan future loads on "
" work centers based on production planning. " )
move_raw_ids = fields . One2many (
' stock.move ' , ' raw_material_production_id ' , ' Raw Materials ' , oldname = ' move_lines ' ,
copy = False , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ,
domain = [ ( ' scrapped ' , ' = ' , False ) ] )
move_finished_ids = fields . One2many (
' stock.move ' , ' production_id ' , ' Finished Products ' ,
copy = False , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ,
domain = [ ( ' scrapped ' , ' = ' , False ) ] )
finished_move_line_ids = fields . One2many (
' stock.move.line ' , compute = ' _compute_lines ' , inverse = ' _inverse_lines ' , string = " Finished Product "
)
workorder_ids = fields . One2many (
' mrp.workorder ' , ' production_id ' , ' Work Orders ' ,
copy = False , oldname = ' workcenter_lines ' , readonly = True )
workorder_count = fields . Integer ( ' # Work Orders ' , compute = ' _compute_workorder_count ' )
workorder_done_count = fields . Integer ( ' # Done Work Orders ' , compute = ' _compute_workorder_done_count ' )
move_dest_ids = fields . One2many ( ' stock.move ' , ' created_production_id ' ,
string = " Stock Movements of Produced Goods " )
state = fields . Selection ( [
( ' confirmed ' , ' Confirmed ' ) ,
( ' planned ' , ' Planned ' ) ,
( ' progress ' , ' In Progress ' ) ,
( ' done ' , ' Done ' ) ,
( ' cancel ' , ' Cancelled ' ) ] , string = ' State ' ,
copy = False , default = ' confirmed ' , track_visibility = ' onchange ' )
availability = fields . Selection ( [
( ' assigned ' , ' Available ' ) ,
( ' partially_available ' , ' Partially Available ' ) ,
( ' waiting ' , ' Waiting ' ) ,
( ' none ' , ' None ' ) ] , string = ' Materials Availability ' ,
compute = ' _compute_availability ' , store = True )
unreserve_visible = fields . Boolean (
' Allowed to Unreserve Inventory ' , compute = ' _compute_unreserve_visible ' ,
help = ' Technical field to check when we can unreserve ' )
post_visible = fields . Boolean (
' Allowed to Post Inventory ' , compute = ' _compute_post_visible ' ,
help = ' Technical field to check when we can post ' )
consumed_less_than_planned = fields . Boolean (
compute = ' _compute_consumed_less_than_planned ' ,
help = ' Technical field used to see if we have to display a warning or not when confirming an order. ' )
user_id = fields . Many2one ( ' res.users ' , ' Responsible ' , default = lambda self : self . _uid )
company_id = fields . Many2one (
' res.company ' , ' Company ' ,
default = lambda self : self . env [ ' res.company ' ] . _company_default_get ( ' mrp.production ' ) ,
required = True )
check_to_done = fields . Boolean ( compute = " _get_produced_qty " , string = " Check Produced Qty " ,
help = " Technical Field to see if we can show ' Mark as Done ' button " )
qty_produced = fields . Float ( compute = " _get_produced_qty " , string = " Quantity Produced " )
procurement_group_id = fields . Many2one (
' procurement.group ' , ' Procurement Group ' ,
copy = False )
propagate = fields . Boolean (
' Propagate cancel and split ' ,
help = ' If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too ' )
has_moves = fields . Boolean ( compute = ' _has_moves ' )
scrap_ids = fields . One2many ( ' stock.scrap ' , ' production_id ' , ' Scraps ' )
scrap_count = fields . Integer ( compute = ' _compute_scrap_move_count ' , string = ' Scrap Move ' )
priority = fields . Selection ( [ ( ' 0 ' , ' Not urgent ' ) , ( ' 1 ' , ' Normal ' ) , ( ' 2 ' , ' Urgent ' ) , ( ' 3 ' , ' Very Urgent ' ) ] , ' Priority ' ,
readonly = True , states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } , default = ' 1 ' )
is_locked = fields . Boolean ( ' Is Locked ' , default = True , copy = False )
show_final_lots = fields . Boolean ( ' Show Final Lots ' , compute = ' _compute_show_lots ' )
production_location_id = fields . Many2one ( ' stock.location ' , " Production Location " , related = ' product_id.property_stock_production ' )
@api.depends ( ' product_id.tracking ' )
def _compute_show_lots ( self ) :
for production in self :
production . show_final_lots = production . product_id . tracking != ' none '
def _inverse_lines ( self ) :
""" Little hack to make sure that when you change something on these objects, it gets saved """
pass
@api.depends ( ' move_finished_ids.move_line_ids ' )
def _compute_lines ( self ) :
for production in self :
production . finished_move_line_ids = production . move_finished_ids . mapped ( ' move_line_ids ' )
@api.multi
@api.depends ( ' bom_id.routing_id ' , ' bom_id.routing_id.operation_ids ' )
def _compute_routing ( self ) :
for production in self :
if production . bom_id . routing_id . operation_ids :
production . routing_id = production . bom_id . routing_id . id
else :
production . routing_id = False
@api.multi
@api.depends ( ' workorder_ids ' )
def _compute_workorder_count ( self ) :
data = self . env [ ' mrp.workorder ' ] . read_group ( [ ( ' production_id ' , ' in ' , self . ids ) ] , [ ' production_id ' ] , [ ' production_id ' ] )
count_data = dict ( ( item [ ' production_id ' ] [ 0 ] , item [ ' production_id_count ' ] ) for item in data )
for production in self :
production . workorder_count = count_data . get ( production . id , 0 )
@api.multi
@api.depends ( ' workorder_ids.state ' )
def _compute_workorder_done_count ( self ) :
data = self . env [ ' mrp.workorder ' ] . read_group ( [
( ' production_id ' , ' in ' , self . ids ) ,
( ' state ' , ' = ' , ' done ' ) ] , [ ' production_id ' ] , [ ' production_id ' ] )
count_data = dict ( ( item [ ' production_id ' ] [ 0 ] , item [ ' production_id_count ' ] ) for item in data )
for production in self :
production . workorder_done_count = count_data . get ( production . id , 0 )
@api.multi
@api.depends ( ' move_raw_ids.state ' , ' workorder_ids.move_raw_ids ' , ' bom_id.ready_to_produce ' )
def _compute_availability ( self ) :
for order in self :
if not order . move_raw_ids :
order . availability = ' none '
continue
if order . bom_id . ready_to_produce == ' all_available ' :
order . availability = any ( move . state not in ( ' assigned ' , ' done ' , ' cancel ' ) for move in order . move_raw_ids ) and ' waiting ' or ' assigned '
else :
move_raw_ids = order . move_raw_ids . filtered ( lambda m : m . product_qty )
partial_list = [ x . state in ( ' partially_available ' , ' assigned ' ) for x in move_raw_ids ]
assigned_list = [ x . state in ( ' assigned ' , ' done ' , ' cancel ' ) for x in move_raw_ids ]
order . availability = ( all ( assigned_list ) and ' assigned ' ) or ( any ( partial_list ) and ' partially_available ' ) or ' waiting '
@api.depends ( ' move_raw_ids ' , ' is_locked ' , ' state ' , ' move_raw_ids.quantity_done ' )
def _compute_unreserve_visible ( self ) :
for order in self :
already_reserved = order . is_locked and order . state not in ( ' done ' , ' cancel ' ) and order . mapped ( ' move_raw_ids.move_line_ids ' )
any_quantity_done = any ( [ m . quantity_done > 0 for m in order . move_raw_ids ] )
order . unreserve_visible = not any_quantity_done and already_reserved
@api.multi
@api.depends ( ' move_raw_ids.quantity_done ' , ' move_finished_ids.quantity_done ' , ' is_locked ' )
def _compute_post_visible ( self ) :
for order in self :
if order . product_tmpl_id . _is_cost_method_standard ( ) :
order . post_visible = order . is_locked and any ( ( x . quantity_done > 0 and x . state not in [ ' done ' , ' cancel ' ] ) for x in order . move_raw_ids | order . move_finished_ids )
else :
order . post_visible = order . is_locked and any ( ( x . quantity_done > 0 and x . state not in [ ' done ' , ' cancel ' ] ) for x in order . move_finished_ids )
@api.multi
@api.depends ( ' move_raw_ids.quantity_done ' , ' move_raw_ids.product_uom_qty ' )
def _compute_consumed_less_than_planned ( self ) :
for order in self :
order . consumed_less_than_planned = any ( order . move_raw_ids . filtered (
lambda move : float_compare ( move . quantity_done ,
move . product_uom_qty ,
precision_rounding = move . product_uom . rounding ) == - 1 )
)
@api.multi
@api.depends ( ' workorder_ids.state ' , ' move_finished_ids ' , ' is_locked ' )
def _get_produced_qty ( self ) :
for production in self :
done_moves = production . move_finished_ids . filtered ( lambda x : x . state != ' cancel ' and x . product_id . id == production . product_id . id )
qty_produced = sum ( done_moves . mapped ( ' quantity_done ' ) )
wo_done = True
if any ( [ x . state not in ( ' done ' , ' cancel ' ) for x in production . workorder_ids ] ) :
wo_done = False
production . check_to_done = production . is_locked and done_moves and ( qty_produced > = production . product_qty ) and ( production . state not in ( ' done ' , ' cancel ' ) ) and wo_done
production . qty_produced = qty_produced
return True
@api.multi
@api.depends ( ' move_raw_ids ' )
def _has_moves ( self ) :
for mo in self :
mo . has_moves = any ( mo . move_raw_ids )
@api.multi
def _compute_scrap_move_count ( self ) :
data = self . env [ ' stock.scrap ' ] . read_group ( [ ( ' production_id ' , ' in ' , self . ids ) ] , [ ' production_id ' ] , [ ' production_id ' ] )
count_data = dict ( ( item [ ' production_id ' ] [ 0 ] , item [ ' production_id_count ' ] ) for item in data )
for production in self :
production . scrap_count = count_data . get ( production . id , 0 )
_sql_constraints = [
( ' name_uniq ' , ' unique(name, company_id) ' , ' Reference must be unique per Company! ' ) ,
( ' qty_positive ' , ' check (product_qty > 0) ' , ' The quantity to produce must be positive! ' ) ,
]
@api.onchange ( ' product_id ' , ' picking_type_id ' , ' company_id ' )
def onchange_product_id ( self ) :
""" Finds UoM of changed product. """
if not self . product_id :
self . bom_id = False
else :
bom = self . env [ ' mrp.bom ' ] . _bom_find ( product = self . product_id , picking_type = self . picking_type_id , company_id = self . company_id . id )
if bom . type == ' normal ' :
self . bom_id = bom . id
else :
self . bom_id = False
self . product_uom_id = self . product_id . uom_id . id
return { ' domain ' : { ' product_uom_id ' : [ ( ' category_id ' , ' = ' , self . product_id . uom_id . category_id . id ) ] } }
@api.onchange ( ' picking_type_id ' )
def onchange_picking_type ( self ) :
location = self . env . ref ( ' stock.stock_location_stock ' )
self . location_src_id = self . picking_type_id . default_location_src_id . id or location . id
self . location_dest_id = self . picking_type_id . default_location_dest_id . id or location . id
2018-04-05 10:25:40 +02:00
@api.multi
def write ( self , vals ) :
res = super ( MrpProduction , self ) . write ( vals )
if ' date_planned_start ' in vals :
moves = ( self . mapped ( ' move_raw_ids ' ) + self . mapped ( ' move_finished_ids ' ) ) . filtered (
lambda r : r . state not in [ ' done ' , ' cancel ' ] )
moves . write ( {
' date_expected ' : vals [ ' date_planned_start ' ] ,
} )
return res
2018-01-16 06:58:15 +01:00
@api.model
def create ( self , values ) :
if not values . get ( ' name ' , False ) or values [ ' name ' ] == _ ( ' New ' ) :
if values . get ( ' picking_type_id ' ) :
values [ ' name ' ] = self . env [ ' stock.picking.type ' ] . browse ( values [ ' picking_type_id ' ] ) . sequence_id . next_by_id ( )
else :
values [ ' name ' ] = self . env [ ' ir.sequence ' ] . next_by_code ( ' mrp.production ' ) or _ ( ' New ' )
if not values . get ( ' procurement_group_id ' ) :
values [ ' procurement_group_id ' ] = self . env [ " procurement.group " ] . create ( { ' name ' : values [ ' name ' ] } ) . id
production = super ( MrpProduction , self ) . create ( values )
production . _generate_moves ( )
return production
@api.multi
def unlink ( self ) :
if any ( production . state != ' cancel ' for production in self ) :
raise UserError ( _ ( ' Cannot delete a manufacturing order not in cancel state ' ) )
return super ( MrpProduction , self ) . unlink ( )
def action_toggle_is_locked ( self ) :
self . ensure_one ( )
self . is_locked = not self . is_locked
return True
@api.multi
def _generate_moves ( self ) :
for production in self :
production . _generate_finished_moves ( )
factor = production . product_uom_id . _compute_quantity ( production . product_qty , production . bom_id . product_uom_id ) / production . bom_id . product_qty
boms , lines = production . bom_id . explode ( production . product_id , factor , picking_type = production . bom_id . picking_type_id )
production . _generate_raw_moves ( lines )
# Check for all draft moves whether they are mto or not
production . _adjust_procure_method ( )
production . move_raw_ids . _action_confirm ( )
return True
def _generate_finished_moves ( self ) :
move = self . env [ ' stock.move ' ] . create ( {
' name ' : self . name ,
' date ' : self . date_planned_start ,
' date_expected ' : self . date_planned_start ,
' product_id ' : self . product_id . id ,
' product_uom ' : self . product_uom_id . id ,
' product_uom_qty ' : self . product_qty ,
' location_id ' : self . product_id . property_stock_production . id ,
' location_dest_id ' : self . location_dest_id . id ,
' company_id ' : self . company_id . id ,
' production_id ' : self . id ,
' origin ' : self . name ,
' group_id ' : self . procurement_group_id . id ,
' propagate ' : self . propagate ,
' move_dest_ids ' : [ ( 4 , x . id ) for x in self . move_dest_ids ] ,
} )
move . _action_confirm ( )
return move
def _generate_raw_moves ( self , exploded_lines ) :
self . ensure_one ( )
moves = self . env [ ' stock.move ' ]
for bom_line , line_data in exploded_lines :
moves + = self . _generate_raw_move ( bom_line , line_data )
return moves
def _generate_raw_move ( self , bom_line , line_data ) :
quantity = line_data [ ' qty ' ]
# alt_op needed for the case when you explode phantom bom and all the lines will be consumed in the operation given by the parent bom line
alt_op = line_data [ ' parent_line ' ] and line_data [ ' parent_line ' ] . operation_id . id or False
if bom_line . child_bom_id and bom_line . child_bom_id . type == ' phantom ' :
return self . env [ ' stock.move ' ]
if bom_line . product_id . type not in [ ' product ' , ' consu ' ] :
return self . env [ ' stock.move ' ]
if self . routing_id :
routing = self . routing_id
else :
routing = self . bom_id . routing_id
if routing and routing . location_id :
source_location = routing . location_id
else :
source_location = self . location_src_id
2018-07-09 14:37:58 +02:00
original_quantity = ( self . product_qty - self . qty_produced ) or 1.0
2018-01-16 06:58:15 +01:00
data = {
' sequence ' : bom_line . sequence ,
' name ' : self . name ,
' date ' : self . date_planned_start ,
' date_expected ' : self . date_planned_start ,
' bom_line_id ' : bom_line . id ,
' product_id ' : bom_line . product_id . id ,
' product_uom_qty ' : quantity ,
' product_uom ' : bom_line . product_uom_id . id ,
' location_id ' : source_location . id ,
' location_dest_id ' : self . product_id . property_stock_production . id ,
' raw_material_production_id ' : self . id ,
' company_id ' : self . company_id . id ,
' operation_id ' : bom_line . operation_id . id or alt_op ,
' price_unit ' : bom_line . product_id . standard_price ,
' procure_method ' : ' make_to_stock ' ,
' origin ' : self . name ,
' warehouse_id ' : source_location . get_warehouse ( ) . id ,
' group_id ' : self . procurement_group_id . id ,
' propagate ' : self . propagate ,
' unit_factor ' : quantity / original_quantity ,
}
return self . env [ ' stock.move ' ] . create ( data )
@api.multi
def _adjust_procure_method ( self ) :
try :
mto_route = self . env [ ' stock.warehouse ' ] . _get_mto_route ( )
except :
mto_route = False
for move in self . move_raw_ids :
product = move . product_id
routes = product . route_ids + product . route_from_categ_ids
# TODO: optimize with read_group?
pull = self . env [ ' procurement.rule ' ] . search ( [ ( ' route_id ' , ' in ' , [ x . id for x in routes ] ) , ( ' location_src_id ' , ' = ' , move . location_id . id ) ,
( ' location_id ' , ' = ' , move . location_dest_id . id ) ] , limit = 1 )
if pull and ( pull . procure_method == ' make_to_order ' ) :
move . procure_method = pull . procure_method
elif not pull : # If there is no make_to_stock rule either
if mto_route and mto_route . id in [ x . id for x in routes ] :
move . procure_method = ' make_to_order '
@api.multi
def _update_raw_move ( self , bom_line , line_data ) :
quantity = line_data [ ' qty ' ]
self . ensure_one ( )
move = self . move_raw_ids . filtered ( lambda x : x . bom_line_id . id == bom_line . id and x . state not in ( ' done ' , ' cancel ' ) )
if move :
if quantity > 0 :
move [ 0 ] . write ( { ' product_uom_qty ' : quantity } )
elif quantity < 0 : # Do not remove 0 lines
if move [ 0 ] . quantity_done > 0 :
raise UserError ( _ ( ' Lines need to be deleted, but can not as you still have some quantities to consume in them. ' ) )
move [ 0 ] . _action_cancel ( )
move [ 0 ] . unlink ( )
return move
else :
self . _generate_raw_move ( bom_line , line_data )
@api.multi
def action_assign ( self ) :
for production in self :
production . move_raw_ids . _action_assign ( )
return True
@api.multi
def open_produce_product ( self ) :
self . ensure_one ( )
action = self . env . ref ( ' mrp.act_mrp_product_produce ' ) . read ( ) [ 0 ]
return action
@api.multi
def button_plan ( self ) :
""" Create work orders. And probably do stuff, like things. """
orders_to_plan = self . filtered ( lambda order : order . routing_id and order . state == ' confirmed ' )
for order in orders_to_plan :
quantity = order . product_uom_id . _compute_quantity ( order . product_qty , order . bom_id . product_uom_id ) / order . bom_id . product_qty
boms , lines = order . bom_id . explode ( order . product_id , quantity , picking_type = order . bom_id . picking_type_id )
order . _generate_workorders ( boms )
return orders_to_plan . write ( { ' state ' : ' planned ' } )
@api.multi
def _generate_workorders ( self , exploded_boms ) :
workorders = self . env [ ' mrp.workorder ' ]
2018-07-09 14:37:58 +02:00
original_one = False
2018-01-16 06:58:15 +01:00
for bom , bom_data in exploded_boms :
# If the routing of the parent BoM and phantom BoM are the same, don't recreate work orders, but use one master routing
if bom . routing_id . id and ( not bom_data [ ' parent_line ' ] or bom_data [ ' parent_line ' ] . bom_id . routing_id . id != bom . routing_id . id ) :
2018-07-09 14:37:58 +02:00
temp_workorders = self . _workorders_create ( bom , bom_data )
workorders + = temp_workorders
if temp_workorders : # In order to avoid two "ending work orders"
if original_one :
temp_workorders [ - 1 ] . next_work_order_id = original_one
original_one = temp_workorders [ 0 ]
2018-01-16 06:58:15 +01:00
return workorders
def _workorders_create ( self , bom , bom_data ) :
"""
: param bom : in case of recursive boms : we could create work orders for child
BoMs
"""
workorders = self . env [ ' mrp.workorder ' ]
bom_qty = bom_data [ ' qty ' ]
# Initial qty producing
if self . product_id . tracking == ' serial ' :
quantity = 1.0
else :
quantity = self . product_qty - sum ( self . move_finished_ids . mapped ( ' quantity_done ' ) )
quantity = quantity if ( quantity > 0 ) else 0
for operation in bom . routing_id . operation_ids :
# create workorder
cycle_number = math . ceil ( bom_qty / operation . workcenter_id . capacity ) # TODO: float_round UP
duration_expected = ( operation . workcenter_id . time_start +
operation . workcenter_id . time_stop +
cycle_number * operation . time_cycle * 100.0 / operation . workcenter_id . time_efficiency )
workorder = workorders . create ( {
' name ' : operation . name ,
' production_id ' : self . id ,
' workcenter_id ' : operation . workcenter_id . id ,
' operation_id ' : operation . id ,
' duration_expected ' : duration_expected ,
' state ' : len ( workorders ) == 0 and ' ready ' or ' pending ' ,
' qty_producing ' : quantity ,
' capacity ' : operation . workcenter_id . capacity ,
} )
if workorders :
workorders [ - 1 ] . next_work_order_id = workorder . id
workorders + = workorder
# assign moves; last operation receive all unassigned moves (which case ?)
moves_raw = self . move_raw_ids . filtered ( lambda move : move . operation_id == operation )
if len ( workorders ) == len ( bom . routing_id . operation_ids ) :
moves_raw | = self . move_raw_ids . filtered ( lambda move : not move . operation_id )
moves_finished = self . move_finished_ids . filtered ( lambda move : move . operation_id == operation ) #TODO: code does nothing, unless maybe by_products?
moves_raw . mapped ( ' move_line_ids ' ) . write ( { ' workorder_id ' : workorder . id } )
( moves_finished + moves_raw ) . write ( { ' workorder_id ' : workorder . id } )
workorder . _generate_lot_ids ( )
return workorders
@api.multi
def action_cancel ( self ) :
""" Cancels production order, unfinished stock moves and set procurement
orders in exception """
if any ( workorder . state == ' progress ' for workorder in self . mapped ( ' workorder_ids ' ) ) :
raise UserError ( _ ( ' You can not cancel production order, a work order is still in progress. ' ) )
for production in self :
production . workorder_ids . filtered ( lambda x : x . state != ' cancel ' ) . action_cancel ( )
finish_moves = production . move_finished_ids . filtered ( lambda x : x . state not in ( ' done ' , ' cancel ' ) )
raw_moves = production . move_raw_ids . filtered ( lambda x : x . state not in ( ' done ' , ' cancel ' ) )
( finish_moves | raw_moves ) . _action_cancel ( )
self . write ( { ' state ' : ' cancel ' , ' is_locked ' : True } )
return True
def _cal_price ( self , consumed_moves ) :
self . ensure_one ( )
return True
@api.multi
def post_inventory ( self ) :
for order in self :
moves_not_to_do = order . move_raw_ids . filtered ( lambda x : x . state == ' done ' )
moves_to_do = order . move_raw_ids . filtered ( lambda x : x . state not in ( ' done ' , ' cancel ' ) )
for move in moves_to_do . filtered ( lambda m : m . product_qty == 0.0 and m . quantity_done > 0 ) :
move . product_uom_qty = move . quantity_done
moves_to_do . _action_done ( )
moves_to_do = order . move_raw_ids . filtered ( lambda x : x . state == ' done ' ) - moves_not_to_do
order . _cal_price ( moves_to_do )
moves_to_finish = order . move_finished_ids . filtered ( lambda x : x . state not in ( ' done ' , ' cancel ' ) )
moves_to_finish . _action_done ( )
2018-07-09 14:37:58 +02:00
order . action_assign ( )
2018-01-16 06:58:15 +01:00
consume_move_lines = moves_to_do . mapped ( ' active_move_line_ids ' )
for moveline in moves_to_finish . mapped ( ' active_move_line_ids ' ) :
if moveline . product_id == order . product_id and moveline . move_id . has_tracking != ' none ' :
if any ( [ not ml . lot_produced_id for ml in consume_move_lines ] ) :
raise UserError ( _ ( ' You can not consume without telling for which lot you consumed it ' ) )
# Link all movelines in the consumed with same lot_produced_id false or the correct lot_produced_id
filtered_lines = consume_move_lines . filtered ( lambda x : x . lot_produced_id == moveline . lot_id )
moveline . write ( { ' consume_line_ids ' : [ ( 6 , 0 , [ x for x in filtered_lines . ids ] ) ] } )
else :
# Link with everything
moveline . write ( { ' consume_line_ids ' : [ ( 6 , 0 , [ x for x in consume_move_lines . ids ] ) ] } )
return True
@api.multi
def button_mark_done ( self ) :
self . ensure_one ( )
for wo in self . workorder_ids :
if wo . time_ids . filtered ( lambda x : ( not x . date_end ) and ( x . loss_type in ( ' productive ' , ' performance ' ) ) ) :
raise UserError ( _ ( ' Work order %s is still running ' ) % wo . name )
self . post_inventory ( )
moves_to_cancel = ( self . move_raw_ids | self . move_finished_ids ) . filtered ( lambda x : x . state not in ( ' done ' , ' cancel ' ) )
moves_to_cancel . _action_cancel ( )
self . write ( { ' state ' : ' done ' , ' date_finished ' : fields . Datetime . now ( ) } )
return self . write ( { ' state ' : ' done ' } )
@api.multi
def do_unreserve ( self ) :
for production in self :
production . move_raw_ids . filtered ( lambda x : x . state not in ( ' done ' , ' cancel ' ) ) . _do_unreserve ( )
return True
@api.multi
def button_unreserve ( self ) :
self . ensure_one ( )
self . do_unreserve ( )
return True
@api.multi
def button_scrap ( self ) :
self . ensure_one ( )
return {
' name ' : _ ( ' Scrap ' ) ,
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
' res_model ' : ' stock.scrap ' ,
' view_id ' : self . env . ref ( ' stock.stock_scrap_form_view2 ' ) . id ,
' type ' : ' ir.actions.act_window ' ,
' context ' : { ' default_production_id ' : self . id ,
' product_ids ' : ( self . move_raw_ids . filtered ( lambda x : x . state not in ( ' done ' , ' cancel ' ) ) | self . move_finished_ids . filtered ( lambda x : x . state == ' done ' ) ) . mapped ( ' product_id ' ) . ids ,
} ,
' target ' : ' new ' ,
}
@api.multi
def action_see_move_scrap ( self ) :
self . ensure_one ( )
action = self . env . ref ( ' stock.action_stock_scrap ' ) . read ( ) [ 0 ]
action [ ' domain ' ] = [ ( ' production_id ' , ' = ' , self . id ) ]
return action