mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
ADD module mrp_request_workcenter_cycle
This commit is contained in:
2
mrp_request_workcenter_cycle/__init__.py
Normal file
2
mrp_request_workcenter_cycle/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import wizards
|
||||
27
mrp_request_workcenter_cycle/__manifest__.py
Normal file
27
mrp_request_workcenter_cycle/__manifest__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright 2020 David BEAL @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
{
|
||||
"name": "MRP Request Workcenter Cycle",
|
||||
"version": "12.0.1.0.0",
|
||||
"category": "Manufacturing",
|
||||
"license": "AGPL-3",
|
||||
"author": "Akretion, Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/manufacture/tree/12.0"
|
||||
"/mrp_request_workcenter_cycle",
|
||||
"depends": [
|
||||
"mrp_production_request",
|
||||
],
|
||||
"development_status": "Alpha",
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"views/product.xml",
|
||||
"views/request.xml",
|
||||
],
|
||||
"demo": [
|
||||
"demo/workcenter.xml",
|
||||
"demo/request.xml",
|
||||
],
|
||||
"maintainers": ["bealdav"],
|
||||
"installable": True,
|
||||
}
|
||||
13
mrp_request_workcenter_cycle/demo/request.xml
Normal file
13
mrp_request_workcenter_cycle/demo/request.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="mrp.production.request" id="wood_panel_request">
|
||||
<field name="product_id" ref="mrp.product_product_wood_panel"/>
|
||||
<field name="bom_id" ref="mrp.mrp_bom_wood_panel"/>
|
||||
</record>
|
||||
|
||||
<function model="mrp.production.request" name="populate_qty_by_workcenter">
|
||||
<value eval="[ref('mrp_request_workcenter_cycle.wood_panel_request')]"/>
|
||||
</function>
|
||||
|
||||
</odoo>
|
||||
24
mrp_request_workcenter_cycle/demo/workcenter.xml
Normal file
24
mrp_request_workcenter_cycle/demo/workcenter.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="mrp.workcenter" id="oven1">
|
||||
<field name="name">Oven 1</field>
|
||||
</record>
|
||||
<record model="mrp.workcenter" id="oven2">
|
||||
<field name="name">Oven 2</field>
|
||||
</record>
|
||||
|
||||
<record model="product.workcenter.quantity" id="wood_panel_oven1">
|
||||
<field name="product_id" ref="mrp.product_product_wood_panel"/>
|
||||
<field name="workcenter_id" ref="oven1"/>
|
||||
<field name="product_qty">110</field>
|
||||
<field name="workcenter_cycle_no">1</field>
|
||||
</record>
|
||||
<record model="product.workcenter.quantity" id="wood_panel_oven2">
|
||||
<field name="product_id" ref="mrp.product_product_wood_panel"/>
|
||||
<field name="workcenter_id" ref="oven2"/>
|
||||
<field name="product_qty">80</field>
|
||||
<field name="workcenter_cycle_no">2</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
4
mrp_request_workcenter_cycle/models/__init__.py
Normal file
4
mrp_request_workcenter_cycle/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import product
|
||||
from . import product_workcenter_quantity
|
||||
from . import request
|
||||
from . import request_workcenter
|
||||
16
mrp_request_workcenter_cycle/models/product.py
Normal file
16
mrp_request_workcenter_cycle/models/product.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright 2020 David BEAL @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
qty_by_workcenter_ids = fields.One2many(
|
||||
comodel_name="product.workcenter.quantity",
|
||||
inverse_name="product_id",
|
||||
groups="mrp_production_request.group_mrp_production_request_user",
|
||||
string="Capacity by workcenter",
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright 2020 David BEAL @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ProductWorkcenterQuantity(models.Model):
|
||||
_name = "product.workcenter.quantity"
|
||||
_description = "Quantity that can be produced with a workcenter cycle"
|
||||
|
||||
workcenter_id = fields.Many2one(comodel_name="mrp.workcenter", string="Workcenter")
|
||||
product_id = fields.Many2one(comodel_name="product.product", string="Product")
|
||||
product_qty = fields.Float(
|
||||
default=1, help="Quantity of the product that the workcenter use in a cycle."
|
||||
)
|
||||
workcenter_cycle_no = fields.Float(
|
||||
string="Cycle Number",
|
||||
default=1,
|
||||
help="Default number of cycles for the workcenter set for "
|
||||
"Manufacturing Request of this product.\n",
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"product_workcenter_unique",
|
||||
"UNIQUE(workcenter_id,product_id)",
|
||||
"Workcenter field must be unique by product",
|
||||
)
|
||||
]
|
||||
120
mrp_request_workcenter_cycle/models/request.py
Normal file
120
mrp_request_workcenter_cycle/models/request.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# Copyright 2020 David BEAL @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from math import floor
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
|
||||
class MrpProductionRequest(models.Model):
|
||||
_inherit = "mrp.production.request"
|
||||
|
||||
qty_by_workcenter_ids = fields.One2many(
|
||||
comodel_name="mrp.request.workcenter",
|
||||
inverse_name="request_id",
|
||||
string="Workcenters cycles number",
|
||||
)
|
||||
workcenter_lines_count = fields.Integer(
|
||||
compute="_compute_workcenter_lines_count",
|
||||
help="Technical field: size of qty_by_workcenter_ids field",
|
||||
)
|
||||
auto_product_qty = fields.Float(
|
||||
compute="_compute_auto_product_qty",
|
||||
store=True,
|
||||
compute_sudo=True,
|
||||
digits=dp.get_precision("Product Unit of Measure"),
|
||||
)
|
||||
target_quantity = fields.Float(
|
||||
digits=dp.get_precision("Product Unit of Measure"),
|
||||
help="This is your initial quantity defined before any cycle adjustement.",
|
||||
)
|
||||
|
||||
@api.depends("qty_by_workcenter_ids")
|
||||
def _compute_auto_product_qty(self):
|
||||
for rec in self:
|
||||
if rec.qty_by_workcenter_ids:
|
||||
if rec.product_uom_id and rec.product_uom_id != rec.product_id.uom_id:
|
||||
raise UserError(
|
||||
_(
|
||||
"Computing quantity with different units is "
|
||||
"not supported for now."
|
||||
)
|
||||
)
|
||||
qty = 0
|
||||
for qty_by in rec.qty_by_workcenter_ids:
|
||||
qty += qty_by.product_qty * qty_by.workcenter_cycle_no
|
||||
rec.auto_product_qty = qty
|
||||
# need sudo to write in a not computed field in computed method
|
||||
rec.sudo().write({"product_qty": qty})
|
||||
else:
|
||||
rec.auto_product_qty = 0
|
||||
|
||||
@api.onchange("product_id")
|
||||
def _onchange_product_id(self):
|
||||
super()._onchange_product_id()
|
||||
self.populate_qty_by_workcenter()
|
||||
|
||||
@api.onchange("target_quantity")
|
||||
def _onchange_target_quantity(self):
|
||||
self.populate_qty_by_workcenter()
|
||||
|
||||
def populate_qty_by_workcenter(self):
|
||||
for rec in self:
|
||||
cycle_factor = 1
|
||||
if rec.target_quantity and rec.product_id.qty_by_workcenter_ids:
|
||||
# this factor allow us to compute the total quantity
|
||||
# allocated by machine
|
||||
cycle_factor = rec.target_quantity / sum(
|
||||
x.product_qty * x.workcenter_cycle_no
|
||||
for x in rec.product_id.qty_by_workcenter_ids
|
||||
)
|
||||
rec.qty_by_workcenter_ids = [(5, 0, 0)]
|
||||
rec.qty_by_workcenter_ids = [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"workcenter_cycle_no": x.workcenter_cycle_no * cycle_factor,
|
||||
"workcenter_id": x.workcenter_id.id,
|
||||
"request_id": rec.id,
|
||||
"product_qty": x.product_qty,
|
||||
},
|
||||
)
|
||||
for x in rec.product_id.qty_by_workcenter_ids
|
||||
]
|
||||
|
||||
def _compute_workcenter_lines_count(self):
|
||||
for rec in self:
|
||||
rec.workcenter_lines_count = len(rec.qty_by_workcenter_ids)
|
||||
|
||||
def button_create_mo_by_workcenter(self):
|
||||
for rec in self:
|
||||
if rec.mrp_production_ids:
|
||||
raise UserError(_("MO already exists"))
|
||||
if rec.qty_by_workcenter_ids:
|
||||
for qty_by in rec.qty_by_workcenter_ids:
|
||||
qty = floor(qty_by.workcenter_cycle_no)
|
||||
while qty:
|
||||
mo_qty = qty_by.product_qty
|
||||
self._get_mo_from_request(mo_qty, qty_by.workcenter_id)
|
||||
qty -= 1
|
||||
rest = qty_by.workcenter_cycle_no - floor(
|
||||
qty_by.workcenter_cycle_no
|
||||
)
|
||||
if rest:
|
||||
mo_qty = qty_by.product_qty * rest
|
||||
self._get_mo_from_request(mo_qty, qty_by.workcenter_id)
|
||||
|
||||
@api.model
|
||||
def _get_mo_from_request(self, mo_qty, workcenter):
|
||||
wiz = (
|
||||
self.env["mrp.production.request.create.mo"]
|
||||
.with_context(active_ids=[self.id], active_model="mrp.production.request")
|
||||
.create({})
|
||||
)
|
||||
wiz.compute_product_line_ids()
|
||||
wiz.mo_qty = mo_qty
|
||||
wiz.product_uom_id = self.product_id.uom_id.id
|
||||
wiz.with_context(workcenter=workcenter.name).create_mo()
|
||||
32
mrp_request_workcenter_cycle/models/request_workcenter.py
Normal file
32
mrp_request_workcenter_cycle/models/request_workcenter.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Copyright 2020 David BEAL @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpRequestWorkcenter(models.Model):
|
||||
_name = "mrp.request.workcenter"
|
||||
_description = "Workcenters attached to Production Request"
|
||||
|
||||
request_id = fields.Many2one(
|
||||
comodel_name="mrp.production.request", string="Production Request"
|
||||
)
|
||||
workcenter_id = fields.Many2one(
|
||||
comodel_name="mrp.workcenter", string="Workcenter", required=True
|
||||
)
|
||||
workcenter_cycle_no = fields.Float(
|
||||
string="Cycles",
|
||||
default="1",
|
||||
help="A cycle is complete ended process for the machine.\n"
|
||||
"Multiple cycles are several machines of the same kind in parallel "
|
||||
"or the same machine used several times.",
|
||||
)
|
||||
product_qty = fields.Float(string="Products by cycle")
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"request_workcenter_unique",
|
||||
"UNIQUE(workcenter_id,request_id)",
|
||||
"Workcenter field must be unique by manufacturing request",
|
||||
)
|
||||
]
|
||||
8
mrp_request_workcenter_cycle/readme/CONFIGURE.rst
Normal file
8
mrp_request_workcenter_cycle/readme/CONFIGURE.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
To configure a product to automatically compute Manufacturing Requests quantity you need to:
|
||||
|
||||
#. Create some workcenters: go to Manufacturing settings, tick work order and click on Workcenter.
|
||||
#. Go to the products variants.
|
||||
#. Go to the *Inventory* tab.
|
||||
#. Fill **Capacity by workcenter** section.
|
||||
|
||||
.. figure:: ../static/description/settings.png
|
||||
3
mrp_request_workcenter_cycle/readme/CONTRIBUTORS.rst
Normal file
3
mrp_request_workcenter_cycle/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
Akretion:
|
||||
|
||||
* David Beal <david.beal@akretion.com>
|
||||
7
mrp_request_workcenter_cycle/readme/DESCRIPTION.rst
Normal file
7
mrp_request_workcenter_cycle/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
This module extends the functionality of Manufacturing Request (MR) to allow you
|
||||
to:
|
||||
|
||||
* compute MR quantity from workcenter capacity: this capacity is defined by product
|
||||
* create manufacturing orders according to selected workcenters
|
||||
|
||||
Note: this module use Workcenters without Work Orders.
|
||||
1
mrp_request_workcenter_cycle/readme/ROADMAP.rst
Normal file
1
mrp_request_workcenter_cycle/readme/ROADMAP.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Support heterogenous units between product and manufacturing request
|
||||
12
mrp_request_workcenter_cycle/readme/USAGE.rst
Normal file
12
mrp_request_workcenter_cycle/readme/USAGE.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
To use this module, you need to:
|
||||
|
||||
#. Go to *Manufacturing > Manufacturing Requests*.
|
||||
#. Create a manufacturing request using the product configured with workcenter (Wood Panel in demo data)
|
||||
#. Fill Target Quantity field and save MR
|
||||
#. Click on 'Request Approval' button
|
||||
#. 'Approve' the Manufacturing Request
|
||||
#. Click on 'Create Manufacturing Orders' button
|
||||
|
||||
Check created MOs
|
||||
|
||||
.. figure:: ../static/description/request.png
|
||||
@@ -0,0 +1,4 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_prd_wkcenter_qty_user,product.workcenter.qty.user,model_product_workcenter_quantity,base.group_user,1,0,0,0
|
||||
access_prd_wkcenter_qty_manager,product.workcenter.qty.manager,model_product_workcenter_quantity,mrp_production_request.group_mrp_production_request_manager,1,1,1,1
|
||||
access_produc_request_wkcenter_qty_user,production.request.workcenter.user,model_mrp_request_workcenter,mrp_production_request.group_mrp_production_request_user,1,1,1,1
|
||||
|
BIN
mrp_request_workcenter_cycle/static/description/request.png
Normal file
BIN
mrp_request_workcenter_cycle/static/description/request.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
BIN
mrp_request_workcenter_cycle/static/description/settings.png
Normal file
BIN
mrp_request_workcenter_cycle/static/description/settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
1
mrp_request_workcenter_cycle/tests/__init__.py
Normal file
1
mrp_request_workcenter_cycle/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_cycle
|
||||
31
mrp_request_workcenter_cycle/tests/test_cycle.py
Normal file
31
mrp_request_workcenter_cycle/tests/test_cycle.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# copyright 2020 David BEAL @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class TestMrpRequestCycle(SavepointCase):
|
||||
def setUp(self, *args, **kwargs):
|
||||
super().setUp(*args, **kwargs)
|
||||
|
||||
def test_pool_of_mo(self):
|
||||
product = self.env.ref("mrp.product_product_wood_panel")
|
||||
request = self.env["mrp.production.request"].create(
|
||||
{
|
||||
"product_id": product.id,
|
||||
"bom_id": self.env["mrp.bom"]
|
||||
.search([("product_tmpl_id", "=", product.product_tmpl_id.id)], limit=1)
|
||||
.id,
|
||||
"name": "test",
|
||||
}
|
||||
)
|
||||
request.populate_qty_by_workcenter()
|
||||
assert request.auto_product_qty == 270
|
||||
assert request.product_qty == 270
|
||||
request.button_to_approve()
|
||||
request.button_approved()
|
||||
request.button_create_mo_by_workcenter()
|
||||
assert request.mrp_production_count == 3
|
||||
qties = [x.product_qty for x in request.mrp_production_ids]
|
||||
qties.sort()
|
||||
assert qties == [80, 80, 110]
|
||||
24
mrp_request_workcenter_cycle/views/product.xml
Normal file
24
mrp_request_workcenter_cycle/views/product.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="product_product_form_view_bom_button">
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="mrp.product_product_form_view_bom_button"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='traceability']" position="after">
|
||||
<group name="manufacturing">
|
||||
<group string="Capacity by workcenter" groups="mrp_production_request.group_mrp_production_request_user">
|
||||
<field name="qty_by_workcenter_ids" nolabel="1">
|
||||
<tree editable="bottom">
|
||||
<field name="workcenter_id"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="workcenter_cycle_no"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
47
mrp_request_workcenter_cycle/views/request.xml
Normal file
47
mrp_request_workcenter_cycle/views/request.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_mrp_production_request_form">
|
||||
<field name="model">mrp.production.request</field>
|
||||
<field name="inherit_id" ref="mrp_production_request.view_mrp_production_request_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header/button[@name='%(mrp_production_request.mrp_production_request_create_mo_action)d']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|', '|', ('workcenter_lines_count', '>', 0), ('state', '!=', 'approved'), ('mrp_production_count', '>', 0)]}</attribute>
|
||||
<attribute name="states">False</attribute>
|
||||
<attribute name="class">oe_highlight</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//header/button[@name='%(mrp_production_request.mrp_production_request_create_mo_action)d']" position="after">
|
||||
<button name="button_create_mo_by_workcenter" type="object"
|
||||
string="Create Manufacturing Orders" class="oe_highlight"
|
||||
title="Manufacturing Orders'll be created according to workcenters"
|
||||
attrs="{'invisible': ['|', '|', ('workcenter_lines_count', '=', 0), ('state', '!=', 'approved'), ('mrp_production_count', '>', 0)]}"/>
|
||||
</xpath>
|
||||
<field name="product_qty" position="after">
|
||||
<field name="auto_product_qty" invisible="1"/>
|
||||
</field>
|
||||
<field name="product_qty" position="attributes">
|
||||
<attribute name="attrs">{'readonly': ['|', ('state', 'not in', 'draft'), ('workcenter_lines_count', '>', 0)]}</attribute>
|
||||
</field>
|
||||
|
||||
<xpath expr="//page[@name='extra']" position="after">
|
||||
<page name="workcenter" string="Workcenters">
|
||||
<group name="capacity" col="4">
|
||||
<group colspan="2">
|
||||
<field name="target_quantity"/>
|
||||
<field name="workcenter_lines_count" attrs="{'invisible': 1}"/>
|
||||
<field name="qty_by_workcenter_ids" nolabel="1" attrs="{'readonly': [('state', '!=', 'draft')]}" colspan="2">
|
||||
<tree editable="bottom">
|
||||
<field name="workcenter_id"/>
|
||||
<field name="workcenter_cycle_no"/>
|
||||
<field name="product_qty"/>
|
||||
</tree>
|
||||
</field>
|
||||
<div title="">One manufacturing order is created by cycle.</div>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
1
mrp_request_workcenter_cycle/wizards/__init__.py
Normal file
1
mrp_request_workcenter_cycle/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import request_create_mo
|
||||
16
mrp_request_workcenter_cycle/wizards/request_create_mo.py
Normal file
16
mrp_request_workcenter_cycle/wizards/request_create_mo.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright 2020 David BEAL @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class MrpProductionRequestCreateMo(models.TransientModel):
|
||||
_inherit = "mrp.production.request.create.mo"
|
||||
|
||||
def _prepare_manufacturing_order(self):
|
||||
vals = super()._prepare_manufacturing_order()
|
||||
vals["origin"] = "%s %s" % (
|
||||
vals.get("origin") or "",
|
||||
self.env.context.get("workcenter", ""),
|
||||
)
|
||||
return vals
|
||||
Reference in New Issue
Block a user