mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
Migrate and improve mrp_to_mto_with_stock to version 10
This commit is contained in:
@@ -7,9 +7,16 @@ MRP MTO with Stock
|
||||
==================
|
||||
|
||||
This module extends the functionality of Manufacturing to support the creation
|
||||
of procurements when there is no stock available. This allow you to pull from
|
||||
stock until the quantity on hand is zero, and then create a procurement
|
||||
to fulfill the MO requirements.
|
||||
of procurements only for a part of the raw material.
|
||||
It has 2 modes. The default one allow you to pull
|
||||
from stock until the quantity on hand is zero, and then create a procurement
|
||||
to fulfill the MO requirements. In this mode, the created procurements must
|
||||
be the ones fulfilling the MO that has generated it.
|
||||
The other mode is based on the forecast quantity. It will allow to pull from
|
||||
stock until the forecast quantity is zero and then create a procurement for
|
||||
the missing products. In this mode, there is no link between the procurement
|
||||
created and MO that has generated it. The procurement may be used to fulfill
|
||||
another MO.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
@@ -21,17 +28,23 @@ To configure this module, you need to:
|
||||
MTO/MTS Locations*. Any other location not specified here will have the
|
||||
standard behavior.
|
||||
|
||||
If you want to use the second mode, based on forecast quantity
|
||||
#. Go to the warehouse you want to follow this behaviour.
|
||||
#. In the view form go to the tab *Warehouse Configuration* and set the
|
||||
*MRP MTO with forecast stock*. You still need to configure the products
|
||||
like described in last step.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
To use this module, you need to:
|
||||
|
||||
#. Go to *Manufacturing* and create a Manufacturing Order.
|
||||
#. Click on *Confirm Production*.
|
||||
#. Click on *Check availability*.
|
||||
|
||||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||
:alt: Try me on Runbot
|
||||
:target: https://runbot.odoo-community.org/runbot/129/9.0
|
||||
:target: https://runbot.odoo-community.org/runbot/129/10.0
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
@@ -54,6 +67,7 @@ Contributors
|
||||
|
||||
* John Walsh <John.Walsh@interclean.com>
|
||||
* Lois Rilo <lois.rilo@eficent.com>
|
||||
* Florian da Costa <florian.dacosta@akretion.com>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
@@ -10,10 +10,14 @@
|
||||
"author": "John Walsh, Eficent, Odoo Community Association (OCA)",
|
||||
"website": "https://odoo-community.org/",
|
||||
"category": "Manufacturing",
|
||||
"version": "9.0.1.0.0",
|
||||
"version": "10.0.1.0.0",
|
||||
"license": "AGPL-3",
|
||||
"application": False,
|
||||
"installable": True,
|
||||
"depends": ["mrp"],
|
||||
"data": ['views/product_template_view.xml'],
|
||||
"data": [
|
||||
'views/product_template_view.xml',
|
||||
'views/stock_warehouse.xml',
|
||||
],
|
||||
"demo": ['demo/product.xml'],
|
||||
}
|
||||
128
mrp_mto_with_stock/demo/product.xml
Normal file
128
mrp_mto_with_stock/demo/product.xml
Normal file
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
|
||||
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
-->
|
||||
|
||||
<odoo> <data noupdate="1">
|
||||
|
||||
<record id="product_product_manufacture_1" model="product.product">
|
||||
<field name="name">TOP</field>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
<field name="standard_price">600.00</field>
|
||||
<field name="list_price">400.00</field>
|
||||
<field name="type">product</field>
|
||||
<field name="uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="product.product_uom_unit"/>
|
||||
<field name="description">TODO</field>
|
||||
<field name="default_code">MANUF</field>
|
||||
<field name="route_ids" eval="[(6, 0, [ref('stock.route_warehouse0_mto'), ref('mrp.route_warehouse0_manufacture')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_manufacture_1_1" model="product.product">
|
||||
<field name="name">Subproduct 1</field>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
<field name="standard_price">300.00</field>
|
||||
<field name="list_price">100.00</field>
|
||||
<field name="type">product</field>
|
||||
<field name="uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="product.product_uom_unit"/>
|
||||
<field name="description">TODO</field>
|
||||
<field name="default_code">MANUF 1-1</field>
|
||||
<field name="route_ids" eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"/>
|
||||
<field name="mrp_mts_mto_location_ids" eval="[(6, 0, [ref('stock.stock_location_stock')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_manufacture_1_2" model="product.product">
|
||||
<field name="name">Subproduct 2</field>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
<field name="standard_price">100.00</field>
|
||||
<field name="list_price">30.00</field>
|
||||
<field name="type">product</field>
|
||||
<field name="uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="product.product_uom_unit"/>
|
||||
<field name="description">TODO</field>
|
||||
<field name="default_code">MANUF 1-2</field>
|
||||
<field name="route_ids" eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"/>
|
||||
<field name="mrp_mts_mto_location_ids" eval="[(6, 0, [ref('stock.stock_location_stock')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_manufacture_1_1_1" model="product.product">
|
||||
<field name="name">Subproduct 1-1</field>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
<field name="standard_price">10.00</field>
|
||||
<field name="list_price">3.00</field>
|
||||
<field name="type">product</field>
|
||||
<field name="uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="product.product_uom_unit"/>
|
||||
<field name="description">TODO</field>
|
||||
<field name="default_code">MANUF 1-1-1</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="product_product_manufacture_1_2_1" model="product.product">
|
||||
<field name="name">Subproduct 2-1</field>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
<field name="standard_price">10.00</field>
|
||||
<field name="list_price">3.00</field>
|
||||
<field name="type">product</field>
|
||||
<field name="uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="product.product_uom_unit"/>
|
||||
<field name="description">TODO</field>
|
||||
<field name="default_code">MANUF 1-2-1</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="mrp_bom_manuf_1" model="mrp.bom">
|
||||
<field name="product_tmpl_id" ref="product_product_manufacture_1_product_template"/>
|
||||
<field name="product_uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_bom_line_manuf_1_1" model="mrp.bom.line">
|
||||
<field name="product_id" ref="product_product_manufacture_1_1"/>
|
||||
<field name="product_qty">5</field>
|
||||
<field name="product_uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="bom_id" ref="mrp_bom_manuf_1"/>
|
||||
</record>
|
||||
|
||||
<record id="mrp_bom_line_manuf_1_2" model="mrp.bom.line">
|
||||
<field name="product_id" ref="product_product_manufacture_1_2"/>
|
||||
<field name="product_qty">2</field>
|
||||
<field name="product_uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="bom_id" ref="mrp_bom_manuf_1"/>
|
||||
</record>
|
||||
|
||||
<record id="mrp_bom_manuf_1_1" model="mrp.bom">
|
||||
<field name="product_tmpl_id" ref="product_product_manufacture_1_1_product_template"/>
|
||||
<field name="product_uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_bom_line_manuf_1_1_1" model="mrp.bom.line">
|
||||
<field name="product_id" ref="product_product_manufacture_1_1_1"/>
|
||||
<field name="product_qty">2</field>
|
||||
<field name="product_uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="bom_id" ref="mrp_bom_manuf_1_1"/>
|
||||
</record>
|
||||
|
||||
<record id="mrp_bom_manuf_1_2" model="mrp.bom">
|
||||
<field name="product_tmpl_id" ref="product_product_manufacture_1_2_product_template"/>
|
||||
<field name="product_uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_bom_line_manuf_1_2_1" model="mrp.bom.line">
|
||||
<field name="product_id" ref="product_product_manufacture_1_2_1"/>
|
||||
<field name="product_qty">4</field>
|
||||
<field name="product_uom_id" ref="product.product_uom_unit"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="bom_id" ref="mrp_bom_manuf_1_2"/>
|
||||
</record>
|
||||
|
||||
|
||||
</data> </odoo>
|
||||
@@ -5,3 +5,4 @@
|
||||
|
||||
from . import mrp_production
|
||||
from . import product_template
|
||||
from . import stock_warehouse
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Copyright 2015 John Walsh
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import api, models
|
||||
from odoo import api, models
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -11,32 +11,69 @@ _logger = logging.getLogger(__name__)
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def _adjust_procure_method(self):
|
||||
# Si location => By pass method...
|
||||
super(MrpProduction, self)._adjust_procure_method()
|
||||
|
||||
@api.multi
|
||||
def action_assign(self):
|
||||
"""Reserves available products to the production order but also creates
|
||||
procurements for more items if we cannot reserve enough (MTO with
|
||||
stock).
|
||||
@returns list of ids"""
|
||||
@returns True"""
|
||||
# reserve all that is available (standard behaviour):
|
||||
res = super(MrpProduction, self).action_assign()
|
||||
# try to create procurements:
|
||||
for move in self.move_lines:
|
||||
if (move.state == 'confirmed' and move.location_id in
|
||||
move.product_id.mrp_mts_mto_location_ids):
|
||||
domain = [('product_id', '=', move.product_id.id),
|
||||
('move_dest_id', '=', move.id)]
|
||||
if move.group_id:
|
||||
domain.append(('group_id', '=', move.group_id.id))
|
||||
procurement = self.env['procurement.order'].search(domain)
|
||||
if not procurement:
|
||||
qty_to_procure = (move.remaining_qty -
|
||||
move.reserved_availability)
|
||||
proc_dict = self._prepare_mto_procurement(
|
||||
move, qty_to_procure)
|
||||
self.env['procurement.order'].create(proc_dict)
|
||||
move_obj = self.env['stock.move']
|
||||
for production in self:
|
||||
warehouse = production.location_src_id.get_warehouse()
|
||||
mto_with_no_move_dest_id = warehouse.mrp_mto_mts_forecast_qty
|
||||
for move in self.move_raw_ids:
|
||||
if (move.state == 'confirmed' and move.location_id in
|
||||
move.product_id.mrp_mts_mto_location_ids and not
|
||||
mto_with_no_move_dest_id):
|
||||
domain = [('product_id', '=', move.product_id.id),
|
||||
('move_dest_id', '=', move.id)]
|
||||
if move.group_id:
|
||||
domain.append(('group_id', '=', move.group_id.id))
|
||||
procurement = self.env['procurement.order'].search(domain)
|
||||
if not procurement:
|
||||
# We have to split the move because we can't have
|
||||
# a part of the move that have ancestors and not the
|
||||
# other else it won't ever be reserved.
|
||||
qty_to_procure = (move.remaining_qty -
|
||||
move.reserved_availability)
|
||||
if qty_to_procure < move.product_uom_qty:
|
||||
move.do_unreserve()
|
||||
new_move_id = move.split(
|
||||
qty_to_procure,
|
||||
restrict_lot_id=move.restrict_lot_id,
|
||||
restrict_partner_id=move.restrict_partner_id)
|
||||
new_move = move_obj.browse(
|
||||
new_move_id)
|
||||
move.action_assign()
|
||||
else:
|
||||
new_move = move
|
||||
|
||||
proc_dict = self._prepare_mto_procurement(
|
||||
new_move, qty_to_procure,
|
||||
mto_with_no_move_dest_id)
|
||||
self.env['procurement.order'].create(proc_dict)
|
||||
|
||||
if (move.state == 'confirmed' and move.location_id in
|
||||
move.product_id.mrp_mts_mto_location_ids and
|
||||
move.procure_method == 'make_to_stock' and
|
||||
mto_with_no_move_dest_id):
|
||||
qty_to_procure = production.get_mto_qty_to_procure(move)
|
||||
if qty_to_procure > 0.0:
|
||||
proc_dict = self._prepare_mto_procurement(
|
||||
move, qty_to_procure, mto_with_no_move_dest_id)
|
||||
proc_dict.pop('move_dest_id', None)
|
||||
self.env['procurement.order'].create(proc_dict)
|
||||
return res
|
||||
|
||||
def _prepare_mto_procurement(self, move, qty):
|
||||
def _prepare_mto_procurement(self, move, qty, mto_with_no_move_dest_id):
|
||||
"""Prepares a procurement for a MTO product."""
|
||||
origin = ((move.group_id and move.group_id.name + ":") or "") + \
|
||||
((move.name and move.name + ":") or "") + 'MTO -> Production'
|
||||
@@ -44,7 +81,7 @@ class MrpProduction(models.Model):
|
||||
route_ids = self.env.ref('stock.route_warehouse0_mto')
|
||||
warehouse_id = (move.warehouse_id.id or (move.picking_type_id and
|
||||
move.picking_type_id.warehouse_id.id or False))
|
||||
return {
|
||||
vals = {
|
||||
'name': move.name + ':' + str(move.id),
|
||||
'origin': origin,
|
||||
'company_id': move.company_id and move.company_id.id or False,
|
||||
@@ -53,9 +90,26 @@ class MrpProduction(models.Model):
|
||||
'product_qty': qty,
|
||||
'product_uom': move.product_uom.id,
|
||||
'location_id': move.location_id.id,
|
||||
'move_dest_id': move.id,
|
||||
'group_id': group_id,
|
||||
'route_ids': [(6, 0, route_ids.ids)],
|
||||
'warehouse_id': warehouse_id,
|
||||
'priority': move.priority,
|
||||
}
|
||||
if not mto_with_no_move_dest_id:
|
||||
vals['move_dest_id'] = move.id
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def get_mto_qty_to_procure(self, move):
|
||||
self.ensure_one()
|
||||
stock_location_id = move.location_id.id
|
||||
move_location = move.with_context(location=stock_location_id)
|
||||
virtual_available = move_location.product_id.virtual_available
|
||||
qty_available = move.product_id.uom_id._compute_quantity(
|
||||
virtual_available, move.product_uom)
|
||||
if qty_available >= 0:
|
||||
return 0.0
|
||||
else:
|
||||
if abs(qty_available) < move.product_uom_qty:
|
||||
return abs(qty_available)
|
||||
return move.product_uom_qty
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import fields, models
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
|
||||
16
mrp_mto_with_stock/models/stock_warehouse.py
Normal file
16
mrp_mto_with_stock/models/stock_warehouse.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StockWarehouse(models.Model):
|
||||
_inherit = 'stock.warehouse'
|
||||
|
||||
mrp_mto_mts_forecast_qty = fields.Boolean(
|
||||
string="MRP MTO with forecast stock",
|
||||
help="When you use Mrp_mto_with_stock, the procurement creation is "
|
||||
"based on reservable stock by default. Check this option if "
|
||||
"you prefer base it on the forecast stock. In this case, the "
|
||||
"created procurements won't be linked to the raw material moves")
|
||||
@@ -2,11 +2,11 @@
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp.tests.common import TransactionCase
|
||||
from openerp import fields
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestMrpMtoWithStock(TransactionCase):
|
||||
|
||||
def setUp(self, *args, **kwargs):
|
||||
super(TestMrpMtoWithStock, self).setUp(*args, **kwargs)
|
||||
self.production_model = self.env['mrp.production']
|
||||
@@ -15,55 +15,27 @@ class TestMrpMtoWithStock(TransactionCase):
|
||||
self.manufacture_route = self.env.ref(
|
||||
'mrp.route_warehouse0_manufacture')
|
||||
self.uom_unit = self.env.ref('product.product_uom_unit')
|
||||
self.warehouse = self.env.ref('stock.warehouse0')
|
||||
|
||||
self.product_fp = self.env['product.product'].create({
|
||||
'name': 'FP',
|
||||
'type': 'product',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'route_ids': [(4, self.manufacture_route.id)]
|
||||
})
|
||||
self.product_c1 = self.env['product.product'].create({
|
||||
'name': 'C1',
|
||||
'type': 'product',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'route_ids': [(4, self.manufacture_route.id)]
|
||||
})
|
||||
self.product_c2 = self.env['product.product'].create({
|
||||
'name': 'C2',
|
||||
'type': 'product',
|
||||
'uom_id': self.uom_unit.id,
|
||||
})
|
||||
self._update_product_qty(self.product_c2,
|
||||
self.stock_location_stock, 10)
|
||||
self.top_product = self.env.ref(
|
||||
'mrp_mto_with_stock.product_product_manufacture_1')
|
||||
self.subproduct1 = self.env.ref(
|
||||
'mrp_mto_with_stock.product_product_manufacture_1_1')
|
||||
self.subproduct2 = self.env.ref(
|
||||
'mrp_mto_with_stock.product_product_manufacture_1_2')
|
||||
self.subproduct_1_1 = self.env.ref(
|
||||
'mrp_mto_with_stock.product_product_manufacture_1_1_1')
|
||||
|
||||
self.bom_fp = self.env['mrp.bom'].create({
|
||||
'product_id': self.product_fp.id,
|
||||
'product_tmpl_id': self.product_fp.product_tmpl_id.id,
|
||||
'bom_line_ids': ([
|
||||
(0, 0, {
|
||||
'product_id': self.product_c1.id,
|
||||
'product_qty': 1,
|
||||
'product_uom': self.uom_unit.id
|
||||
}),
|
||||
(0, 0, {
|
||||
'product_id': self.product_c2.id,
|
||||
'product_qty': 1,
|
||||
'product_uom': self.uom_unit.id
|
||||
}),
|
||||
])
|
||||
})
|
||||
self.main_bom = self.env.ref(
|
||||
'mrp_mto_with_stock.mrp_bom_manuf_1')
|
||||
|
||||
self.bom_c1 = self.env['mrp.bom'].create({
|
||||
'product_id': self.product_c1.id,
|
||||
'product_tmpl_id': self.product_c1.product_tmpl_id.id,
|
||||
'bom_line_ids': ([(0, 0, {
|
||||
'product_id': self.product_c2.id,
|
||||
'product_qty': 1,
|
||||
'product_uom': self.uom_unit.id
|
||||
})])
|
||||
})
|
||||
self.product_c1.mrp_mts_mto_location_ids = [
|
||||
(6, 0, [self.stock_location_stock.id])]
|
||||
def _get_production_vals(self):
|
||||
return {
|
||||
'product_id': self.top_product.id,
|
||||
'product_qty': 1,
|
||||
'product_uom_id': self.uom_unit.id,
|
||||
'bom_id': self.main_bom.id,
|
||||
}
|
||||
|
||||
def _update_product_qty(self, product, location, quantity):
|
||||
"""Update Product quantity."""
|
||||
@@ -75,65 +47,112 @@ class TestMrpMtoWithStock(TransactionCase):
|
||||
product_qty.change_product_qty()
|
||||
return product_qty
|
||||
|
||||
def create_procurement(self, name, product):
|
||||
values = {
|
||||
'name': name,
|
||||
'date_planned': fields.Datetime.now(),
|
||||
'product_id': product.id,
|
||||
'product_qty': 4.0,
|
||||
'product_uom': product.uom_id.id,
|
||||
'warehouse_id': self.env.ref('stock.warehouse0').id,
|
||||
'location_id': self.stock_location_stock.id,
|
||||
'route_ids': [
|
||||
(4, self.env.ref('mrp.route_warehouse0_manufacture').id, 0)],
|
||||
}
|
||||
return self.env['procurement.order'].create(values)
|
||||
def test_manufacture_with_forecast_stock(self):
|
||||
"""
|
||||
Test Manufacture mto with stock based on forecast quantity
|
||||
and no link between sub assemblies MO's and Main MO raw material
|
||||
"""
|
||||
|
||||
def test_manufacture(self):
|
||||
self.warehouse.mrp_mto_mts_forecast_qty = True
|
||||
|
||||
procurement_fp = self.create_procurement('TEST/01', self.product_fp)
|
||||
production_fp = procurement_fp.production_id
|
||||
self.assertEqual(production_fp.state, 'confirmed')
|
||||
self._update_product_qty(self.subproduct1, self.stock_location_stock,
|
||||
2)
|
||||
self._update_product_qty(self.subproduct2, self.stock_location_stock,
|
||||
4)
|
||||
|
||||
production_fp.action_assign()
|
||||
self.assertEqual(production_fp.state, 'confirmed')
|
||||
self.production = self.production_model.create(
|
||||
self._get_production_vals())
|
||||
|
||||
procurement_c1 = self.env['procurement.order'].search(
|
||||
[('product_id', '=', self.product_c1.id),
|
||||
('move_dest_id', 'in', production_fp.move_lines.ids)], limit=1)
|
||||
self.assertEquals(len(procurement_c1), 1)
|
||||
# Create MO and check it create sub assemblie MO.
|
||||
self.production.action_assign()
|
||||
|
||||
procurement_c2 = self.env['procurement.order'].search(
|
||||
[('product_id', '=', self.product_c2.id),
|
||||
('move_dest_id', 'in', production_fp.move_lines.ids)], limit=1)
|
||||
self.assertEquals(len(procurement_c2), 0)
|
||||
self.assertEqual(self.production.availability, 'partially_available')
|
||||
|
||||
procurement_c1.run()
|
||||
production_c1 = procurement_c1.production_id
|
||||
self.assertEqual(production_c1.state, 'confirmed')
|
||||
self.assertEquals(self.subproduct1.virtual_available, 0)
|
||||
|
||||
production_c1.action_assign()
|
||||
self.assertEqual(production_c1.state, 'ready')
|
||||
procurement_subproduct1 = self.env['procurement.order'].search(
|
||||
[('product_id', '=', self.subproduct1.id),
|
||||
('group_id', '=', self.production.procurement_group_id.id)])
|
||||
|
||||
procurement_c2 = self.env['procurement.order'].search(
|
||||
[('product_id', '=', self.product_c2.id),
|
||||
('move_dest_id', 'in', production_c1.move_lines.ids)], limit=1)
|
||||
self.assertEquals(len(procurement_c2), 0)
|
||||
self.assertEquals(len(procurement_subproduct1), 1)
|
||||
self.assertEquals(procurement_subproduct1.product_qty, 3)
|
||||
|
||||
wizard = self.env['mrp.product.produce'].create({
|
||||
'product_id': self.product_c1.id,
|
||||
'product_qty': 1,
|
||||
})
|
||||
self.env['mrp.production'].action_produce(
|
||||
production_c1.id, 1, 'consume_produce', wizard)
|
||||
production_c1.refresh()
|
||||
self.assertEqual(production_fp.state, 'confirmed')
|
||||
production_sub1 = procurement_subproduct1.production_id
|
||||
self.assertEqual(production_sub1.state, 'confirmed')
|
||||
self.assertEqual(production_sub1.product_qty, 3)
|
||||
|
||||
wizard = self.env['mrp.product.produce'].create({
|
||||
'product_id': self.product_c1.id,
|
||||
'product_qty': 3,
|
||||
})
|
||||
self.env['mrp.production'].action_produce(
|
||||
production_c1.id, 3, 'consume_produce', wizard)
|
||||
production_c1.refresh()
|
||||
self.assertEqual(production_fp.state, 'ready')
|
||||
self._update_product_qty(self.subproduct1, self.stock_location_stock,
|
||||
7)
|
||||
|
||||
# Create second MO and check it does not create procurement
|
||||
self.production2 = self.production_model.create(
|
||||
self._get_production_vals())
|
||||
self.production2.action_assign()
|
||||
procurement_subproduct1_2 = self.env['procurement.order'].search(
|
||||
[('product_id', '=', self.subproduct1.id),
|
||||
('group_id', '=', self.production2.procurement_group_id.id)])
|
||||
self.assertEquals(len(procurement_subproduct1_2), 0)
|
||||
self.assertEquals(self.production2.availability, 'assigned')
|
||||
self.production2.do_unreserve()
|
||||
|
||||
self.assertEquals(self.subproduct1.virtual_available, 0)
|
||||
|
||||
self.production.action_assign()
|
||||
# We check if first MO is able to assign it self even if it has
|
||||
# previously generate procurements, it would not be the case in the
|
||||
# other mode (without mrp_mto_mts_reservable_stock on warehouse)
|
||||
self.assertEquals(self.production.availability, 'assigned')
|
||||
|
||||
self.assertEquals(self.subproduct1.virtual_available, 0)
|
||||
|
||||
def test_manufacture_with_reservable_stock(self):
|
||||
"""
|
||||
Test Manufacture mto with stock based on reservable stock
|
||||
and there is a link between sub assemblies MO's and Main MO raw
|
||||
materi al
|
||||
"""
|
||||
|
||||
self._update_product_qty(self.subproduct1, self.stock_location_stock,
|
||||
2)
|
||||
self._update_product_qty(self.subproduct2, self.stock_location_stock,
|
||||
4)
|
||||
|
||||
self.production = self.production_model.create(
|
||||
self._get_production_vals())
|
||||
|
||||
self._update_product_qty(self.subproduct_1_1,
|
||||
self.stock_location_stock, 50)
|
||||
|
||||
# Create MO and check it create sub assemblie MO.
|
||||
self.production.action_assign()
|
||||
self.assertEqual(self.production.state, 'confirmed')
|
||||
|
||||
procurement_sub1 = self.env['procurement.order'].search(
|
||||
[('product_id', '=', self.subproduct1.id),
|
||||
('move_dest_id', 'in', self.production.move_raw_ids.ids)])
|
||||
self.assertEquals(len(procurement_sub1), 1)
|
||||
|
||||
procurement_sub2 = self.env['procurement.order'].search(
|
||||
[('product_id', '=', self.subproduct2.id),
|
||||
('move_dest_id', 'in', self.production.move_raw_ids.ids)])
|
||||
self.assertEquals(len(procurement_sub2), 0)
|
||||
|
||||
production_sub1 = procurement_sub1.production_id
|
||||
self.assertEqual(production_sub1.product_qty, 3)
|
||||
production_sub1.action_assign()
|
||||
self.assertEqual(production_sub1.availability, 'assigned')
|
||||
|
||||
wizard_obj = self.env['mrp.product.produce']
|
||||
default_fields = ['lot_id', 'product_id', 'product_uom_id',
|
||||
'product_tracking', 'consume_line_ids',
|
||||
'production_id', 'product_qty', 'serial']
|
||||
wizard_vals = wizard_obj.with_context(active_id=production_sub1.id).\
|
||||
default_get(default_fields)
|
||||
|
||||
wizard = wizard_obj.create(wizard_vals)
|
||||
wizard.do_produce()
|
||||
self.assertTrue(production_sub1.check_to_done)
|
||||
self.assertEquals(self.subproduct1.qty_available, 2)
|
||||
production_sub1.button_mark_done()
|
||||
self.assertEquals(self.subproduct1.qty_available, 5)
|
||||
self.assertEqual(self.production.availability, 'assigned')
|
||||
|
||||
17
mrp_mto_with_stock/views/stock_warehouse.xml
Normal file
17
mrp_mto_with_stock/views/stock_warehouse.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Akretion
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_warehouse_mrp_with_stock" model="ir.ui.view">
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="inherit_id" ref="mrp.view_warehouse_inherited"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="manufacture_to_resupply" position="after">
|
||||
<field name="mrp_mto_mts_forecast_qty"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user