ADD module mrp_request_workcenter_cycle

This commit is contained in:
David Beal
2020-12-09 21:36:28 +01:00
parent c93e0e4ce6
commit 5ddeb880f1
23 changed files with 423 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
from . import models
from . import wizards

View 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,
}

View 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>

View 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>

View File

@@ -0,0 +1,4 @@
from . import product
from . import product_workcenter_quantity
from . import request
from . import request_workcenter

View 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",
)

View File

@@ -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",
)
]

View 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()

View 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",
)
]

View 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

View File

@@ -0,0 +1,3 @@
Akretion:
* David Beal <david.beal@akretion.com>

View 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.

View File

@@ -0,0 +1 @@
* Support heterogenous units between product and manufacturing request

View 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

View File

@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_prd_wkcenter_qty_user product.workcenter.qty.user model_product_workcenter_quantity base.group_user 1 0 0 0
3 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
4 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -0,0 +1 @@
from . import test_cycle

View 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]

View 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>

View 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', '&gt;', 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>

View File

@@ -0,0 +1 @@
from . import request_create_mo

View 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