[ADD] mrp_packaging_default: product packaging data in MRP

This module allows and encourages the use of packaging within MRP, both to manufacture products or to create kits.

@moduon MT-4506
This commit is contained in:
Jairo Llopis
2023-12-21 13:52:32 +00:00
parent 449893e75f
commit 5b75b1bdc8
20 changed files with 1224 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
=====================
MRP Default Packaging
=====================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:430d1de03606fea9f58a23276a8423eafb1a11f45a74b0bdb94cd84662d0ccf3
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github
:target: https://github.com/OCA/manufacture/tree/16.0/mrp_packaging_default
:alt: OCA/manufacture
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/manufacture-16-0/manufacture-16-0-mrp_packaging_default
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/manufacture&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module allows and encourages the use of packaging within MRP, both
to manufacture products or to create kits.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Use Cases / Context
===================
In certain businesses, it is important to know the packaging you will
use to manufacture a product. Example: food wholesale companies.
For example, imagine you want to create a food basket that includes,
among other things, 1 kg of cheese. It is not the same to put it in
small sliced and vacuum-packed 100g packages as it is to put a whole
cheese ball. Even when the product and the total weight are the same:
1kg of cheese.
If you are interested in this module, you may also be interested in
``sale_packaging_default``.
Configuration
=============
To see the effects of this module, you need to:
1. Go to *Settings*.
2. Activate *Inventory > Products > Product Packagings*.
3. Optionally, activate also *Units of Measure*. This is not required,
but if you are interested in this module, it's probably because you
use this.
4. Save.
Usage
=====
Some component products must exist. Those components will be later
included in the manufactured or kit product. Then, you'll notice the
module effects.
To create the component products:
1. Go to *Inventory > Products > Products*.
2. Create a product.
3. Configure its unit of measure (if you enabled that option).
4. Add some line(s) in *Inventory > Packaging*.
To use this module with **a kit of products**, you need to:
1. Go to *Inventory > Products > Products*.
2. Create a product that will be the kit.
3. Set *Product Type* "Consumable".
4. Configure its unit of measure (if you enabled that option).
5. Enable *Inventory > Operations > Routes > Manufacture*.
6. Click on *Bill of Materials* button and create a new one.
7. Set *BoM Type* "Kit".
8. Configure the rest of the BoM. When you configure the component
lines, use the new *Packaging* and *Packaging Qty* fields.
9. Go to *Inventory > Delivery Orders (three dots) > New > Planned
Transfer*.
10. Fill the *Delivery Address*.
11. Add one *Operations* line with the kit product you just created.
12. Click on *Mark as TODO*.
13. You will notice that the kit has been replaced by its components,
and each component line includes the packaging and its qty, just
like you configured them in the BoM.
To use it with **a manufactured product**, instead:
1. Go to *Inventory > Products > Products*.
2. Create a product; the one that will be manufactured.
3. Set *Product Type* "Storable Product".
4. Configure its unit of measure (if you enabled that option).
5. Enable *Inventory > Operations > Routes > Manufacture*.
6. Click on *Bill of Materials* button and create a new one.
7. Set *BoM Type* "Manufacture this product".
8. Configure the rest of the BoM. When you configure the component
lines, use the new *Packaging* and *Packaging Qty* fields.
9. Go back to the product form.
10. Click on *Reordering Rules* button and create a new one.
11. Set some minimal and maximal quantities.
12. Click on *Order Once*. If you don't see this button, you can also go
to *Inventory > Operations > Run Scheduler > Run Scheduler*.
13. Go to *Manufacturing > Operations > Manufacturing Orders*. You will
see a new MO created from the reordering rule. Open it.
14. See how the *Components* lines contain packaging information, just
like you defined it in the BoM. The same would happen if you created
the MO manually.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/manufacture/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/manufacture/issues/new?body=module:%20mrp_packaging_default%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Moduon
Contributors
------------
- Jairo Llopis (`Moduon <https://www.moduon.team/>`__)
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-rafaelbn| image:: https://github.com/rafaelbn.png?size=40px
:target: https://github.com/rafaelbn
:alt: rafaelbn
.. |maintainer-yajo| image:: https://github.com/yajo.png?size=40px
:target: https://github.com/yajo
:alt: yajo
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-rafaelbn| |maintainer-yajo|
This module is part of the `OCA/manufacture <https://github.com/OCA/manufacture/tree/16.0/mrp_packaging_default>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

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

View File

@@ -0,0 +1,19 @@
# Copyright 2023 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
{
"name": "MRP Default Packaging",
"summary": "Include packaging info in MRP by default",
"version": "16.0.1.0.0",
"development_status": "Alpha",
"category": "Manufacturing/Manufacturing",
"website": "https://github.com/OCA/manufacture",
"author": "Moduon, Odoo Community Association (OCA)",
"maintainers": ["rafaelbn", "yajo"],
"license": "LGPL-3",
"depends": ["mrp", "stock_move_packaging_qty"],
"data": [
"views/mrp_bom_view.xml",
"views/mrp_production_view.xml",
],
}

View File

View File

@@ -0,0 +1,2 @@
from . import mrp_bom_line
from . import stock_move

View File

@@ -0,0 +1,67 @@
# Copyright 2023 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import api, fields, models
class MrpBomLine(models.Model):
_inherit = "mrp.bom.line"
product_packaging_id = fields.Many2one(
comodel_name="product.packaging",
string="Packaging",
compute="_compute_product_packaging",
store=True,
readonly=False,
domain="[('product_id', '=', product_id)]",
check_company=True,
)
product_packaging_qty = fields.Float(
string="Packaging Qty.",
compute="_compute_product_packaging",
digits="Product Unit of Measure",
store=True,
readonly=False,
)
@api.depends("product_id", "product_qty", "product_uom_id")
def _compute_product_packaging(self):
"""Set the appropriate packaging for the product qty."""
for one in self:
one.product_packaging_id = (
one.product_id.packaging_ids._find_suitable_product_packaging(
one.product_qty, one.product_uom_id
)
)
if not one.product_packaging_id:
one.product_packaging_qty = 0
continue
uom_qty_per_package = (
one.product_packaging_id.product_uom_id._compute_quantity(
one.product_packaging_id.qty, one.product_uom_id
)
)
one.product_packaging_qty = (
one.product_packaging_id._check_qty(one.product_qty, one.product_uom_id)
/ uom_qty_per_package
)
@api.onchange("product_packaging_id", "product_packaging_qty")
def _onchange_product_packaging_set_qty(self):
"""When interactively setting a new packaging, set default qty values."""
if not self.product_packaging_id:
return
self.product_qty = (
self.product_packaging_qty
* self.product_uom_id._compute_quantity(
self.product_packaging_id.qty,
self.product_packaging_id.product_uom_id,
)
)
@api.onchange("product_id")
def _onchange_product_set_qty_from_packaging(self):
"""When interactively setting a new product, set default packaging values."""
default_packaging = self.product_id.packaging_ids[:1]
if default_packaging:
self.product_uom_id = default_packaging.product_uom_id
self.product_qty = default_packaging.qty

View File

@@ -0,0 +1,36 @@
# Copyright 2023 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import api, models
class StockMove(models.Model):
_inherit = "stock.move"
@api.model
def _packaging_vals_from_bom_line(self, vals):
"""Fill vals with packaging info from BoM line."""
try:
bom_line = self.env["mrp.bom.line"].browse(vals["bom_line_id"])
except KeyError:
# No BoM line, nothing to do
return
vals.update(
{
"product_packaging_id": bom_line.product_packaging_id.id,
"product_packaging_qty": bom_line.product_packaging_qty,
}
)
@api.model_create_multi
def create(self, vals_list):
"""Inherit packaging from BoM line."""
for vals in vals_list:
self._packaging_vals_from_bom_line(vals)
return super().create(vals_list)
def write(self, vals):
"""Inherit packaging from BoM line."""
self._packaging_vals_from_bom_line(vals)
return super().write(vals)

View File

@@ -0,0 +1,7 @@
To see the effects of this module, you need to:
1. Go to *Settings*.
2. Activate *Inventory > Products > Product Packagings*.
3. Optionally, activate also *Units of Measure*. This is not required, but if
you are interested in this module, it's probably because you use this.
4. Save.

View File

@@ -0,0 +1,10 @@
In certain businesses, it is important to know the packaging you will use to
manufacture a product. Example: food wholesale companies.
For example, imagine you want to create a food basket that includes, among
other things, 1 kg of cheese. It is not the same to put it in small sliced and
vacuum-packed 100g packages as it is to put a whole cheese ball. Even when the
product and the total weight are the same: 1kg of cheese.
If you are interested in this module, you may also be interested in
`sale_packaging_default`.

View File

@@ -0,0 +1 @@
- Jairo Llopis ([Moduon](https://www.moduon.team/))

View File

@@ -0,0 +1,2 @@
This module allows and encourages the use of packaging within MRP, both to
manufacture products or to create kits.

View File

@@ -0,0 +1,49 @@
Some component products must exist. Those components will be later included in the manufactured or kit product. Then, you'll notice the module effects.
To create the component products:
1. Go to *Inventory > Products > Products*.
2. Create a product.
3. Configure its unit of measure (if you enabled that option).
4. Add some line(s) in *Inventory > Packaging*.
To use this module with **a kit of products**, you need to:
1. Go to *Inventory > Products > Products*.
2. Create a product that will be the kit.
3. Set *Product Type* "Consumable".
4. Configure its unit of measure (if you enabled that option).
5. Enable *Inventory > Operations > Routes > Manufacture*.
7. Click on *Bill of Materials* button and create a new one.
8. Set *BoM Type* "Kit".
9. Configure the rest of the BoM. When you configure the component lines, use
the new *Packaging* and *Packaging Qty* fields.
10. Go to *Inventory > Delivery Orders (three dots) > New > Planned Transfer*.
11. Fill the *Delivery Address*.
12. Add one *Operations* line with the kit product you just created.
13. Click on *Mark as TODO*.
14. You will notice that the kit has been replaced by its components, and each
component line includes the packaging and its qty, just like you configured
them in the BoM.
To use it with **a manufactured product**, instead:
1. Go to *Inventory > Products > Products*.
2. Create a product; the one that will be manufactured.
3. Set *Product Type* "Storable Product".
4. Configure its unit of measure (if you enabled that option).
5. Enable *Inventory > Operations > Routes > Manufacture*.
7. Click on *Bill of Materials* button and create a new one.
8. Set *BoM Type* "Manufacture this product".
9. Configure the rest of the BoM. When you configure the component lines, use
the new *Packaging* and *Packaging Qty* fields.
10. Go back to the product form.
11. Click on *Reordering Rules* button and create a new one.
12. Set some minimal and maximal quantities.
13. Click on *Order Once*. If you don't see this button, you can also go to
*Inventory > Operations > Run Scheduler > Run Scheduler*.
14. Go to *Manufacturing > Operations > Manufacturing Orders*. You will see a
new MO created from the reordering rule. Open it.
15. See how the *Components* lines contain packaging information, just like you
defined it in the BoM. The same would happen if you created the MO
manually.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,512 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>MRP Default Packaging</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="mrp-default-packaging">
<h1 class="title">MRP Default Packaging</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:430d1de03606fea9f58a23276a8423eafb1a11f45a74b0bdb94cd84662d0ccf3
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/manufacture/tree/16.0/mrp_packaging_default"><img alt="OCA/manufacture" src="https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/manufacture-16-0/manufacture-16-0-mrp_packaging_default"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/manufacture&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows and encourages the use of packaging within MRP, both
to manufacture products or to create kits.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#use-cases-context" id="toc-entry-1">Use Cases / Context</a></li>
<li><a class="reference internal" href="#configuration" id="toc-entry-2">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-3">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="use-cases-context">
<h1><a class="toc-backref" href="#toc-entry-1">Use Cases / Context</a></h1>
<p>In certain businesses, it is important to know the packaging you will
use to manufacture a product. Example: food wholesale companies.</p>
<p>For example, imagine you want to create a food basket that includes,
among other things, 1 kg of cheese. It is not the same to put it in
small sliced and vacuum-packed 100g packages as it is to put a whole
cheese ball. Even when the product and the total weight are the same:
1kg of cheese.</p>
<p>If you are interested in this module, you may also be interested in
<tt class="docutils literal">sale_packaging_default</tt>.</p>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-2">Configuration</a></h1>
<p>To see the effects of this module, you need to:</p>
<ol class="arabic simple">
<li>Go to <em>Settings</em>.</li>
<li>Activate <em>Inventory &gt; Products &gt; Product Packagings</em>.</li>
<li>Optionally, activate also <em>Units of Measure</em>. This is not required,
but if you are interested in this module, its probably because you
use this.</li>
<li>Save.</li>
</ol>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-3">Usage</a></h1>
<p>Some component products must exist. Those components will be later
included in the manufactured or kit product. Then, youll notice the
module effects.</p>
<p>To create the component products:</p>
<ol class="arabic simple">
<li>Go to <em>Inventory &gt; Products &gt; Products</em>.</li>
<li>Create a product.</li>
<li>Configure its unit of measure (if you enabled that option).</li>
<li>Add some line(s) in <em>Inventory &gt; Packaging</em>.</li>
</ol>
<p>To use this module with <strong>a kit of products</strong>, you need to:</p>
<ol class="arabic simple">
<li>Go to <em>Inventory &gt; Products &gt; Products</em>.</li>
<li>Create a product that will be the kit.</li>
<li>Set <em>Product Type</em> “Consumable”.</li>
<li>Configure its unit of measure (if you enabled that option).</li>
<li>Enable <em>Inventory &gt; Operations &gt; Routes &gt; Manufacture</em>.</li>
<li>Click on <em>Bill of Materials</em> button and create a new one.</li>
<li>Set <em>BoM Type</em> “Kit”.</li>
<li>Configure the rest of the BoM. When you configure the component
lines, use the new <em>Packaging</em> and <em>Packaging Qty</em> fields.</li>
<li>Go to <em>Inventory &gt; Delivery Orders (three dots) &gt; New &gt; Planned
Transfer</em>.</li>
<li>Fill the <em>Delivery Address</em>.</li>
<li>Add one <em>Operations</em> line with the kit product you just created.</li>
<li>Click on <em>Mark as TODO</em>.</li>
<li>You will notice that the kit has been replaced by its components,
and each component line includes the packaging and its qty, just
like you configured them in the BoM.</li>
</ol>
<p>To use it with <strong>a manufactured product</strong>, instead:</p>
<ol class="arabic simple">
<li>Go to <em>Inventory &gt; Products &gt; Products</em>.</li>
<li>Create a product; the one that will be manufactured.</li>
<li>Set <em>Product Type</em> “Storable Product”.</li>
<li>Configure its unit of measure (if you enabled that option).</li>
<li>Enable <em>Inventory &gt; Operations &gt; Routes &gt; Manufacture</em>.</li>
<li>Click on <em>Bill of Materials</em> button and create a new one.</li>
<li>Set <em>BoM Type</em> “Manufacture this product”.</li>
<li>Configure the rest of the BoM. When you configure the component
lines, use the new <em>Packaging</em> and <em>Packaging Qty</em> fields.</li>
<li>Go back to the product form.</li>
<li>Click on <em>Reordering Rules</em> button and create a new one.</li>
<li>Set some minimal and maximal quantities.</li>
<li>Click on <em>Order Once</em>. If you dont see this button, you can also go
to <em>Inventory &gt; Operations &gt; Run Scheduler &gt; Run Scheduler</em>.</li>
<li>Go to <em>Manufacturing &gt; Operations &gt; Manufacturing Orders</em>. You will
see a new MO created from the reordering rule. Open it.</li>
<li>See how the <em>Components</em> lines contain packaging information, just
like you defined it in the BoM. The same would happen if you created
the MO manually.</li>
</ol>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/manufacture/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/manufacture/issues/new?body=module:%20mrp_packaging_default%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-6">Authors</a></h2>
<ul class="simple">
<li>Moduon</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-7">Contributors</a></h2>
<ul class="simple">
<li>Jairo Llopis (<a class="reference external" href="https://www.moduon.team/">Moduon</a>)</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainers</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/rafaelbn"><img alt="rafaelbn" src="https://github.com/rafaelbn.png?size=40px" /></a> <a class="reference external image-reference" href="https://github.com/yajo"><img alt="yajo" src="https://github.com/yajo.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/manufacture/tree/16.0/mrp_packaging_default">OCA/manufacture</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

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

View File

@@ -0,0 +1,262 @@
# Copyright 2023 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from odoo import Command
from odoo.tests.common import Form
from odoo.addons.mrp.tests.common import TestMrpCommon
class MrpPackagingDefaultCase(TestMrpCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Enable product packaging
cls.env.ref("base.group_user")._apply_group(
cls.env.ref("product.group_stock_packaging")
)
# Sandwich ingredients
cls.tomato_product = cls.env["product.product"].create(
{
"name": "Tomato",
"type": "product",
"list_price": 5.0,
"categ_id": cls.product_category.id,
"uom_id": cls.env.ref("uom.product_uom_kgm").id,
"uom_po_id": cls.env.ref("uom.product_uom_kgm").id,
"packaging_ids": [
Command.create({"name": "Box", "qty": 3}),
Command.create({"name": "Unit", "qty": 0.2}),
Command.create({"name": "Slice", "qty": 0.05}),
],
}
)
cls.lettuce_product = cls.env["product.product"].create(
{
"name": "Lettuce",
"type": "product",
"list_price": 1.0,
"categ_id": cls.product_category.id,
"uom_id": cls.env.ref("uom.product_uom_kgm").id,
"uom_po_id": cls.env.ref("uom.product_uom_kgm").id,
"packaging_ids": [
Command.create({"name": "Leaf", "qty": 0.05}),
],
}
)
cls.bread_product = cls.env["product.product"].create(
{
"name": "Bread",
"type": "product",
"list_price": 3.0,
"categ_id": cls.product_category.id,
"uom_id": cls.env.ref("uom.product_uom_kgm").id,
"uom_po_id": cls.env.ref("uom.product_uom_kgm").id,
"packaging_ids": [
Command.create({"name": "Slice", "qty": 0.1}),
],
}
)
def create_sandwich(self, cooked):
"""Create a sandwich product and its BoMs.
If the sandwich is cooked, then we sell it as a manufactured product.
Otherwise, it's a DIY sandwich; we sell the ingredients and you cook it
yourself.
"""
sandwich = self.env["product.product"].create(
{
"name": "Sandwich",
"type": "product" if cooked else "consu",
"list_price": 10.0 if cooked else 7.0,
"categ_id": self.product_category.id,
"uom_id": self.env.ref("uom.product_uom_unit").id,
"uom_po_id": self.env.ref("uom.product_uom_unit").id,
"route_ids": [
Command.link(self.warehouse_1.manufacture_pull_id.route_id.id)
],
}
)
bom_f = Form(self.env["mrp.bom"])
bom_f.product_tmpl_id = sandwich.product_tmpl_id
bom_f.type = "normal" if cooked else "phantom"
with bom_f.bom_line_ids.new() as line_f:
line_f.product_id = self.tomato_product
self.assertEqual(line_f.product_packaging_id.name, "Box")
self.assertEqual(line_f.product_packaging_qty, 1)
self.assertEqual(line_f.product_qty, 3)
line_f.product_packaging_id = self.tomato_product.packaging_ids[2]
line_f.product_packaging_qty = 2
self.assertEqual(line_f.product_packaging_id.name, "Slice")
self.assertEqual(line_f.product_qty, 0.1)
with bom_f.bom_line_ids.new() as line_f:
line_f.product_id = self.lettuce_product
self.assertEqual(line_f.product_packaging_id.name, "Leaf")
self.assertEqual(line_f.product_packaging_qty, 1)
self.assertEqual(line_f.product_qty, 0.05)
line_f.product_packaging_qty = 4
self.assertEqual(line_f.product_qty, 0.2)
with bom_f.bom_line_ids.new() as line_f:
line_f.product_id = self.bread_product
self.assertEqual(line_f.product_packaging_id.name, "Slice")
self.assertEqual(line_f.product_packaging_qty, 1)
self.assertEqual(line_f.product_qty, 0.1)
line_f.product_packaging_qty = 2
self.assertEqual(line_f.product_qty, 0.2)
bom_f.save()
return sandwich
def test_deliver_diy_sandwich(self):
"""Deliver a DIY sandwich.
To deliver a sandwich, we would usually create a sale order. However,
`sale` is not among this module's dependencies, so we create the
picking directly.
In product kits, the standard behavior es that when you confirm the
picking, the system replaces the kit with its components, linked to
each BoM line.
Here we just exercise that behavior and assert that the packaging data
matches the one that comes from the BoM.
"""
sandwich = self.create_sandwich(cooked=False)
picking_f = Form(self.env["stock.picking"].with_user(self.user_stock_user))
picking_f.partner_id = self.partner_1
picking_f.picking_type_id = self.warehouse_1.out_type_id
with picking_f.move_ids_without_package.new() as move_f:
move_f.product_id = sandwich
move_f.product_uom_qty = 2
picking = picking_f.save()
picking.action_confirm()
self.assertRecordValues(
picking.move_ids_without_package,
[
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[0].id,
"description_bom_line": "Sandwich - 1/3",
"product_id": self.tomato_product.id,
"product_packaging_id": self.tomato_product.packaging_ids[2].id,
"product_packaging_qty": 2.0,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.1,
},
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[1].id,
"description_bom_line": "Sandwich - 2/3",
"product_id": self.lettuce_product.id,
"product_packaging_id": self.lettuce_product.packaging_ids[0].id,
"product_packaging_qty": 4.0,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.2,
},
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[2].id,
"description_bom_line": "Sandwich - 3/3",
"product_id": self.bread_product.id,
"product_packaging_id": self.bread_product.packaging_ids[0].id,
"product_packaging_qty": 2,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.2,
},
],
)
def test_auto_procure_cooked_sandwich(self):
"""Procure a cooked sandwich.
This sandwich is a manufactured product. It has reordering rules, so
the procurement scheduler will create a manufacturing order.
We will just exercise that scenario and make sure the packaging data in
the manufacturing order matches the one in the BoM.
"""
sandwich = self.create_sandwich(cooked=True)
# Define a reordering rule for the cooked sandwich
rule_f = Form(self.env["stock.warehouse.orderpoint"])
rule_f.product_id = sandwich
rule_f.product_min_qty = 4
rule_f.product_max_qty = 10
rule_f.save()
# Run the stock scheduler
self.env["procurement.group"].run_scheduler()
# Check the created manufacturing order
mo = self.env["mrp.production"].search([("product_id", "=", sandwich.id)])
self.assertEqual(mo.state, "confirmed")
self.assertEqual(mo.qty_producing, 0)
self.assertEqual(mo.product_qty, 10)
self.assertEqual(mo.bom_id, sandwich.bom_ids)
self.assertRecordValues(
mo.move_raw_ids,
[
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[0].id,
"product_id": self.tomato_product.id,
"product_packaging_id": self.tomato_product.packaging_ids[2].id,
"product_packaging_qty": 2.0,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.1,
},
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[1].id,
"product_id": self.lettuce_product.id,
"product_packaging_id": self.lettuce_product.packaging_ids[0].id,
"product_packaging_qty": 4.0,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.2,
},
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[2].id,
"product_id": self.bread_product.id,
"product_packaging_id": self.bread_product.packaging_ids[0].id,
"product_packaging_qty": 2,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.2,
},
],
)
def test_manual_mo_cooked_sandwich(self):
"""Create a manufacturing order for a cooked sandwich, interactively."""
sandwich = self.create_sandwich(cooked=True)
# Create a manufacturing order
mo_f = Form(self.env["mrp.production"])
mo_f.product_id = sandwich
self.assertEqual(mo_f.bom_id, sandwich.bom_ids)
mo_f.product_qty = 10
mo = mo_f.save()
# Check the created manufacturing order
self.assertEqual(mo.state, "draft")
self.assertEqual(mo.qty_producing, 0)
self.assertEqual(mo.product_qty, 10)
self.assertEqual(mo.bom_id, sandwich.bom_ids)
self.assertRecordValues(
mo.move_raw_ids,
[
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[0].id,
"product_id": self.tomato_product.id,
"product_packaging_id": self.tomato_product.packaging_ids[2].id,
"product_packaging_qty": 2.0,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.1,
},
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[1].id,
"product_id": self.lettuce_product.id,
"product_packaging_id": self.lettuce_product.packaging_ids[0].id,
"product_packaging_qty": 4.0,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.2,
},
{
"bom_line_id": sandwich.bom_ids[0].bom_line_ids[2].id,
"product_id": self.bread_product.id,
"product_packaging_id": self.bread_product.packaging_ids[0].id,
"product_packaging_qty": 2,
"product_uom": self.env.ref("uom.product_uom_kgm").id,
"product_uom_qty": 0.2,
},
],
)

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 Moduon Team S.L.
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) -->
<data>
<record id="mrp_bom_form_view" model="ir.ui.view">
<field name="name">Product packaging</field>
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.mrp_bom_form_view" />
<field name="arch" type="xml">
<xpath
expr="//field[@name='bom_line_ids']/tree/field[@name='product_qty']"
position="before"
>
<field
name="product_packaging_id"
optional="show"
groups="product.group_stock_packaging"
/>
<field
name="product_packaging_qty"
optional="show"
groups="product.group_stock_packaging"
attrs="{'invisible': [('product_packaging_id', '=', False)]}"
/>
</xpath>
</field>
</record>
</data>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 Moduon Team S.L.
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) -->
<data>
<record id="mrp_production_form_view" model="ir.ui.view">
<field name="name">Product packaging</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view" />
<field name="arch" type="xml">
<xpath
expr="//field[@name='move_finished_ids']/tree/field[@name='product_uom_qty']"
position="before"
>
<field
name="product_packaging_id"
optional="show"
groups="product.group_stock_packaging"
/>
<field
name="product_packaging_qty"
optional="show"
groups="product.group_stock_packaging"
attrs="{'invisible': [('product_packaging_id', '=', False)]}"
/>
</xpath>
<xpath
expr="//field[@name='move_raw_ids']/tree/field[@name='product_uom_qty']"
position="before"
>
<field
name="product_packaging_id"
optional="show"
groups="product.group_stock_packaging"
/>
<field
name="product_packaging_qty"
optional="show"
groups="product.group_stock_packaging"
attrs="{'invisible': [('product_packaging_id', '=', False)]}"
/>
</xpath>
</field>
</record>
</data>