[REF] mrp_subcontracting: lot produced/consumed management
Currently, the subcontract picking present multiple difficulties: - The user has to introduce multiple time the lot produced. In register components button and then create a new move line with the show details operation button. - Same problem with the correction system. The user has to modify the lot in details operation and then go on the subcontract production and unlock and edit it. In order to facilitate those behavior. A move line will be automaticaly created during the register components process. Also the subcontract moves for components will be available in the detail operations tab.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
'data': [
|
||||
'data/mrp_subcontracting_data.xml',
|
||||
'views/stock_warehouse_views.xml',
|
||||
'views/stock_move_views.xml',
|
||||
'views/stock_picking_views.xml',
|
||||
'views/mrp_bom_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
|
||||
@@ -4,6 +4,7 @@ from . import mrp_bom
|
||||
from . import res_company
|
||||
from . import res_partner
|
||||
from . import stock_move
|
||||
from . import stock_move_line
|
||||
from . import stock_picking
|
||||
from . import stock_warehouse
|
||||
|
||||
|
||||
@@ -3,13 +3,36 @@
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = 'stock.move'
|
||||
|
||||
is_subcontract = fields.Boolean(copy=False)
|
||||
subcontract_components_ids = fields.One2many('stock.move.line',
|
||||
compute='_compute_subcontract_move_line_ids',
|
||||
inverse='_inverse_subcontract_move_line_ids',
|
||||
string='Subcontracted Components', readonly=False)
|
||||
|
||||
def action_show_details(self):
|
||||
action = super(StockMove, self).action_show_details()
|
||||
action['context'].update({
|
||||
'show_lots_m2o': True,
|
||||
'show_lots_text': False,
|
||||
})
|
||||
return action
|
||||
|
||||
def _compute_subcontract_move_line_ids(self):
|
||||
for move in self:
|
||||
if move.is_subcontract:
|
||||
move.subcontract_components_ids = move.move_orig_ids.production_id.move_raw_ids.move_line_ids
|
||||
|
||||
def _inverse_subcontract_move_line_ids(self):
|
||||
for move in self:
|
||||
if move.is_subcontract:
|
||||
(move.move_orig_ids.production_id.move_raw_ids.move_line_ids - move.subcontract_components_ids).unlink()
|
||||
|
||||
def _get_subcontract_bom(self):
|
||||
self.ensure_one()
|
||||
bom = self.env['mrp.bom']._bom_subcontract_find(
|
||||
@@ -35,6 +58,9 @@ class StockMove(models.Model):
|
||||
error_message += _('If there is well a BoM of type subcontracting defined, check if you have set the correct subcontractors on it.')
|
||||
raise UserError(error_message % (move.product_id.name, move.product_id.name))
|
||||
subcontract_details_per_picking[move.picking_id].append((move, bom))
|
||||
move.write({
|
||||
'is_subcontract': True,
|
||||
})
|
||||
for picking, subcontract_details in subcontract_details_per_picking.items():
|
||||
picking._subcontracted_produce(subcontract_details)
|
||||
return super(StockMove, self)._action_confirm(merge=merge, merge_into=merge_into)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class StockMoveLine(models.Model):
|
||||
_inherit = 'stock.move.line'
|
||||
|
||||
def write(self, values):
|
||||
move_lines_subcontracted = self.filtered(lambda ml: ml.move_id.is_subcontract)
|
||||
if not move_lines_subcontracted:
|
||||
return super(StockMoveLine, self).write(values)
|
||||
for ml in move_lines_subcontracted:
|
||||
candidates = ml.move_id.move_orig_ids.move_line_ids
|
||||
candidate = candidates.filtered(lambda c:
|
||||
c.qty_done == ml.qty_done and ml.product_uom_qty == c.product_uom_qty and
|
||||
ml.product_uom_id == c.product_uom_id and c.lot_id == ml.lot_id)
|
||||
candidate = candidate and candidate[0] or self.env['stock.move.line']
|
||||
candidate.write(values)
|
||||
return super(StockMoveLine, self).write(values)
|
||||
@@ -8,7 +8,6 @@ class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
display_action_record_components = fields.Boolean(compute='_compute_display_action_record_components')
|
||||
display_view_subcontracted_move_lines = fields.Boolean(compute='_compute_display_view_subcontracted_move_lines')
|
||||
|
||||
@api.depends('state')
|
||||
def _compute_display_action_record_components(self):
|
||||
@@ -29,26 +28,6 @@ class StockPicking(models.Model):
|
||||
continue
|
||||
picking.display_action_record_components = True
|
||||
|
||||
@api.depends('state')
|
||||
def _compute_display_view_subcontracted_move_lines(self):
|
||||
for picking in self:
|
||||
# Hide if not encoding state
|
||||
if picking.state in ('draft', 'cancel'):
|
||||
continue
|
||||
if not picking._is_subcontract():
|
||||
continue
|
||||
# Hide until state done if no move is tracked, if tracked until something was produced
|
||||
subcontracted_productions = picking._get_subcontracted_productions()
|
||||
subcontracted_moves = subcontracted_productions.mapped('move_raw_ids')
|
||||
subcontracted_moves |= subcontracted_productions.mapped('move_finished_ids')
|
||||
if all(subcontracted_move.has_tracking == 'none' for subcontracted_move in subcontracted_moves):
|
||||
if picking.state != 'done':
|
||||
continue
|
||||
# Hide if nothing was produced
|
||||
if all(subcontracted_move.quantity_done == 0 for subcontracted_move in subcontracted_moves):
|
||||
continue
|
||||
picking.display_view_subcontracted_move_lines = True
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Action methods
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -60,29 +39,18 @@ class StockPicking(models.Model):
|
||||
subcontracted_production.button_mark_done()
|
||||
return res
|
||||
|
||||
def action_view_subcontracted_move_lines(self):
|
||||
""" Returns a list view with the move lines of the subcontracted products. To find them, we
|
||||
look on the origin moves of the move lines of the picking if there is a manufacturing order.
|
||||
"""
|
||||
self.ensure_one()
|
||||
subcontracted_productions = self._get_subcontracted_productions()
|
||||
subcontracted_move_lines = self.env['stock.move.line']
|
||||
for subcontracted_production in subcontracted_productions:
|
||||
subcontracted_move_lines |= subcontracted_production.move_raw_ids.mapped('move_line_ids')
|
||||
subcontracted_move_lines |= subcontracted_production.move_finished_ids.mapped('move_line_ids')
|
||||
action = self.env.ref('stock.stock_move_line_action').read()[0]
|
||||
action['context'] = {}
|
||||
action['domain'] = [('id', 'in', subcontracted_move_lines.ids)]
|
||||
return action
|
||||
|
||||
def action_record_components(self):
|
||||
self.ensure_one()
|
||||
subcontracted_productions = self._get_subcontracted_productions()
|
||||
to_register = subcontracted_productions.filtered(lambda mo: mo.state not in ('to_close', 'done'))
|
||||
if to_register:
|
||||
mo = to_register[0]
|
||||
for move in self.move_lines:
|
||||
production = move.move_orig_ids.production_id
|
||||
if not production or production.state in ('done', 'to_close'):
|
||||
continue
|
||||
action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
|
||||
action['context'] = dict(self.env.context, active_id=mo.id)
|
||||
action['context'] = dict(
|
||||
self.env.context,
|
||||
active_id=production.id,
|
||||
default_subcontract_move_id=move.id
|
||||
)
|
||||
return action
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -188,7 +188,6 @@ class TestSubcontractingFlows(SavepointCase):
|
||||
|
||||
# Nothing should be tracked
|
||||
self.assertFalse(picking_receipt.display_action_record_components)
|
||||
self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
|
||||
|
||||
# Check the created manufacturing order
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
||||
@@ -218,9 +217,6 @@ class TestSubcontractingFlows(SavepointCase):
|
||||
picking_receipt.button_validate()
|
||||
self.assertEquals(mo.state, 'done')
|
||||
|
||||
# Now that the picking is done, the details stat button should be visible
|
||||
self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
|
||||
|
||||
# Available quantities should be negative at the subcontracting location for each components
|
||||
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
|
||||
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
|
||||
@@ -250,7 +246,6 @@ class TestSubcontractingFlows(SavepointCase):
|
||||
|
||||
# Nothing should be tracked
|
||||
self.assertFalse(picking_receipt.display_action_record_components)
|
||||
self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
|
||||
|
||||
# Pickings should directly be created
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
||||
@@ -275,9 +270,6 @@ class TestSubcontractingFlows(SavepointCase):
|
||||
picking_receipt.button_validate()
|
||||
self.assertEquals(mo.state, 'done')
|
||||
|
||||
# Now that the picking is done, the details stat button should be visible
|
||||
self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
|
||||
|
||||
# Available quantities should be negative at the subcontracting location for each components
|
||||
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
|
||||
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
|
||||
@@ -314,7 +306,6 @@ class TestSubcontractingFlows(SavepointCase):
|
||||
|
||||
# Nothing should be tracked
|
||||
self.assertFalse(picking_receipt.display_action_record_components)
|
||||
self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
|
||||
|
||||
# Pickings should directly be created
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
||||
@@ -336,9 +327,6 @@ class TestSubcontractingFlows(SavepointCase):
|
||||
picking_receipt.button_validate()
|
||||
self.assertEquals(mo.state, 'done')
|
||||
|
||||
# Now that the picking is done, the details stat button should be visible
|
||||
self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
|
||||
|
||||
# Available quantities should be negative at the subcontracting location for each components
|
||||
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
|
||||
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
|
||||
@@ -524,7 +512,6 @@ class TestSubcontractingTracking(TransactionCase):
|
||||
|
||||
# We should be able to call the 'record_components' button
|
||||
self.assertTrue(picking_receipt.display_action_record_components)
|
||||
self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
|
||||
|
||||
# Check the created manufacturing order
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_tracked.id)])
|
||||
@@ -571,8 +558,6 @@ class TestSubcontractingTracking(TransactionCase):
|
||||
|
||||
# We should not be able to call the 'record_components' button
|
||||
self.assertFalse(picking_receipt.display_action_record_components)
|
||||
# We should see the move lines stat button
|
||||
self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
|
||||
|
||||
picking_receipt.move_lines.quantity_done = 1
|
||||
picking_receipt.move_lines.move_line_ids.lot_name = 'lot00001test'
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_stock_move_operations_inherit_mrp_subcontracting" model="ir.ui.view">
|
||||
<field name="name">stock.move.operations.form.inherit.mrp.subcontracting</field>
|
||||
<field name="model">stock.move</field>
|
||||
<field name="inherit_id" ref="stock.view_stock_move_operations"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='move_line_ids']" position="after">
|
||||
<field name="is_subcontract" invisible="1"/>
|
||||
<h2 attrs="{'invisible': [('is_subcontract', '!=', True)]}">Components</h2>
|
||||
<field name="subcontract_components_ids" attrs="{'invisible': [('is_subcontract', '!=', True)], 'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}">
|
||||
<tree editable="bottom">
|
||||
<field name="tracking" invisible="1"/>
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="lot_produced_ids" widget="many2many_tags" context="{'default_product_id': parent.product_id}"/>
|
||||
<field name="qty_done"/>
|
||||
<field name="lot_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='move_line_ids']" position="before">
|
||||
<h2 attrs="{'invisible': [('is_subcontract', '!=', True)]}">Subcontracted Product</h2>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -5,14 +5,6 @@
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<field name="display_view_subcontracted_move_lines" invisible="1" />
|
||||
<button string="Subcontracted Moves" type="object"
|
||||
name="action_view_subcontracted_move_lines"
|
||||
class="oe_stat_button" icon="fa-exchange"
|
||||
attrs="{'invisible': [('display_view_subcontracted_move_lines', '=', False)]}" />
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//button[@name='button_validate'][hasclass('o_btn_validate')]" position="before">
|
||||
<field name="display_action_record_components" invisible="1" />
|
||||
<button name="action_record_components" class="oe_highlight" attrs="{'invisible': [('display_action_record_components', '=', False)]}" string="Record components" type="object"/>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mrp_product_produce
|
||||
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo.tools.float_utils import float_is_zero
|
||||
|
||||
class MrpProductProduce(models.TransientModel):
|
||||
_inherit = 'mrp.product.produce'
|
||||
|
||||
subcontract_move_id = fields.Many2one('stock.move', 'stock move from the subcontract picking')
|
||||
|
||||
def continue_production(self):
|
||||
action = super(MrpProductProduce, self).continue_production()
|
||||
action['context'] = dict(action['context'], default_subcontract_move_id=self.subcontract_move_id.id)
|
||||
return action
|
||||
|
||||
def _update_finished_move(self):
|
||||
""" After producing, set the move line on the subcontract picking. """
|
||||
res = super(MrpProductProduce, self)._update_finished_move()
|
||||
if self.subcontract_move_id:
|
||||
self.env['stock.move.line'].create({
|
||||
'move_id': self.subcontract_move_id.id,
|
||||
'picking_id': self.subcontract_move_id.picking_id.id,
|
||||
'product_id': self.product_id.id,
|
||||
'location_id': self.subcontract_move_id.location_id.id,
|
||||
'location_dest_id': self.subcontract_move_id.location_dest_id.id,
|
||||
'product_uom_qty': 0,
|
||||
'product_uom_id': self.product_uom_id.id,
|
||||
'qty_done': self.qty_producing,
|
||||
'lot_id': self.finished_lot_id and self.finished_lot_id.id,
|
||||
})
|
||||
if not self._get_todo(self.production_id):
|
||||
ml_reserved = self.subcontract_move_id.move_line_ids.filtered(lambda ml:
|
||||
float_is_zero(ml.qty_done, precision_rounding=ml.product_uom_id.rounding) and
|
||||
not float_is_zero(ml.product_uom_qty, precision_rounding=ml.product_uom_id.rounding))
|
||||
ml_reserved.unlink()
|
||||
for ml in self.subcontract_move_id.move_line_ids:
|
||||
ml.product_uom_qty = ml.qty_done
|
||||
self.subcontract_move_id._recompute_state()
|
||||
return res
|
||||
Reference in New Issue
Block a user