Skip to content
Snippets Groups Projects
Commit 37200abf authored by Arnold Moyaux's avatar Arnold Moyaux
Browse files

[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.
parent 6c09ffb6
Branches
Tags
No related merge requests found
Showing with 131 additions and 66 deletions
# -*- 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)
# -*- 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'
......
<?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'), '&amp;', ('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>
\ No newline at end of file
......@@ -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"/>
......
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mrp_product_produce
# -*- 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment