Skip to content

Commit 8a27028

Browse files
committed
[14][ADD] module sale_production_state to display production state on sale order
1 parent 695cc91 commit 8a27028

10 files changed

Lines changed: 297 additions & 0 deletions

File tree

sale_production_state/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import models
2+
from . import tests
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2021 Akretion (http://www.akretion.com).
2+
# @author Benoît GUILLOT <benoit.guillot@akretion.com>
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
{
6+
"name": "Sale production State",
7+
"summary": "Show the production state on the sale order",
8+
"version": "14.0.1.0.0",
9+
"category": "Product",
10+
"website": "https://github.com/OCA/sale-workflow",
11+
"author": "Akretion, Odoo Community Association (OCA)",
12+
"license": "AGPL-3",
13+
"installable": True,
14+
"depends": ["sale_mrp"],
15+
"data": [
16+
"views/sale_order.xml",
17+
],
18+
"demo": [],
19+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import mrp_production
2+
from . import sale_order
3+
from . import sale_order_line
4+
from . import stock_rule
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2021 Akretion (http://www.akretion.com).
2+
# @author Benoît GUILLOT <benoit.guillot@akretion.com>
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from odoo import fields, models
6+
7+
8+
class MrpProduction(models.Model):
9+
_inherit = "mrp.production"
10+
11+
sale_line_ids = fields.Many2many(
12+
comodel_name="sale.order.line",
13+
relation="sale_line_production_rel",
14+
column1="production_id",
15+
column2="line_id",
16+
string="Sale order line",
17+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2021 Akretion (http://www.akretion.com).
2+
# @author Benoît GUILLOT <benoit.guillot@akretion.com>
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from odoo import api, fields, models
6+
7+
8+
class SaleOrder(models.Model):
9+
_inherit = "sale.order"
10+
11+
production_state = fields.Selection(
12+
selection=[
13+
("no", "No manufacturing"),
14+
("unprocessed", "Unprocessed"),
15+
("partially", "Partially processed"),
16+
("done", "Done"),
17+
],
18+
string="Manufacturing state",
19+
compute="_compute_production_state",
20+
store=True,
21+
)
22+
23+
@api.depends("order_line.production_state")
24+
def _compute_production_state(self):
25+
for order in self:
26+
production_states = [x.production_state for x in order.order_line]
27+
if all(x == "no" for x in production_states):
28+
order.production_state = "no"
29+
elif all(x in ["done", "no"] for x in production_states):
30+
order.production_state = "done"
31+
elif all(x in ["unprocessed", "no"] for x in production_states):
32+
order.production_state = "unprocessed"
33+
else:
34+
order.production_state = "partially"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2021 Akretion (http://www.akretion.com).
2+
# @author Benoît GUILLOT <benoit.guillot@akretion.com>
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from odoo import api, fields, models
6+
7+
8+
class SaleOrderLine(models.Model):
9+
_inherit = "sale.order.line"
10+
11+
production_state = fields.Selection(
12+
selection=[
13+
("no", "No manufacturing"),
14+
("unprocessed", "Unprocessed"),
15+
("partially", "Partially processed"),
16+
("done", "Done"),
17+
],
18+
string="Manufacturing state",
19+
compute="_compute_production_state",
20+
store=True,
21+
)
22+
production_ids = fields.Many2many(
23+
comodel_name="mrp.production",
24+
relation="sale_line_production_rel",
25+
column1="line_id",
26+
column2="production_id",
27+
string="Manufacturing orders",
28+
)
29+
30+
@api.depends("production_ids", "production_ids.state")
31+
def _compute_production_state(self):
32+
for line in self:
33+
if not line.production_ids:
34+
line.production_state = "no"
35+
elif all(x.state in ["done", "cancel"] for x in line.production_ids):
36+
line.production_state = "done"
37+
elif any(x.state == "done" for x in line.production_ids):
38+
line.production_state = "partially"
39+
else:
40+
line.production_state = "unprocessed"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2021 Akretion (http://www.akretion.com).
2+
# @author Benoît GUILLOT <benoit.guillot@akretion.com>
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from odoo import models
6+
7+
8+
class StockRule(models.Model):
9+
_inherit = "stock.rule"
10+
11+
def _prepare_mo_vals(
12+
self,
13+
product_id,
14+
product_qty,
15+
product_uom,
16+
location_id,
17+
name,
18+
origin,
19+
company_id,
20+
values,
21+
bom,
22+
):
23+
mo_vals = super()._prepare_mo_vals(
24+
product_id,
25+
product_qty,
26+
product_uom,
27+
location_id,
28+
name,
29+
origin,
30+
company_id,
31+
values,
32+
bom,
33+
)
34+
moves = values.get("move_dest_ids")
35+
line_ids = moves.sale_line_id
36+
while moves.move_dest_ids:
37+
moves = moves.move_dest_ids
38+
line_ids |= moves.sale_line_id
39+
mo_vals["sale_line_ids"] = line_ids and [(4, x.id) for x in line_ids] or False
40+
return mo_vals
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import test_production_state
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Copyright 2018 Akretion (http://www.akretion.com).
2+
# @author Benoît GUILLOT <benoit.guillot@akretion.com>
3+
# Copyright 2018 Camptocamp
4+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
5+
6+
from odoo.tests import Form, SavepointCase
7+
8+
9+
class TestSaleProductionState(SavepointCase):
10+
@classmethod
11+
def setUpClass(cls):
12+
super().setUpClass()
13+
route_manufacture_1 = cls.env.ref("mrp.route_warehouse0_manufacture")
14+
route_manufacture_2 = cls.env.ref("stock.route_warehouse0_mto")
15+
route_manufacture_2.active = True
16+
cls.product_1 = cls.env["product.product"].create(
17+
{
18+
"name": "Test sale production state product 1",
19+
"type": "product",
20+
"route_ids": [
21+
(4, route_manufacture_1.id),
22+
(4, route_manufacture_2.id),
23+
],
24+
}
25+
)
26+
cls.product = cls.env["product.product"].create(
27+
{
28+
"name": "Test sale production state product",
29+
"type": "product",
30+
}
31+
)
32+
cls.bom_1 = cls.env["mrp.bom"].create(
33+
{
34+
"product_tmpl_id": cls.product_1.product_tmpl_id.id,
35+
}
36+
)
37+
cls.env["mrp.bom.line"].create(
38+
{"bom_id": cls.bom_1.id, "product_id": cls.product.id, "product_qty": 1}
39+
)
40+
cls.product_2 = cls.env["product.product"].create(
41+
{
42+
"name": "Test sale production state product 2",
43+
"type": "product",
44+
"route_ids": [
45+
(4, route_manufacture_1.id),
46+
(4, route_manufacture_2.id),
47+
],
48+
}
49+
)
50+
cls.bom_2 = cls.env["mrp.bom"].create(
51+
{
52+
"product_tmpl_id": cls.product_2.product_tmpl_id.id,
53+
}
54+
)
55+
cls.env["mrp.bom.line"].create(
56+
{"bom_id": cls.bom_2.id, "product_id": cls.product.id, "product_qty": 1}
57+
)
58+
cls.partner = cls.env["res.partner"].create({"name": "Test client"})
59+
cls.order = cls.env["sale.order"].create(
60+
{
61+
"partner_id": cls.partner.id,
62+
"client_order_ref": "SO1",
63+
"order_line": [
64+
(
65+
0,
66+
0,
67+
{
68+
"product_id": cls.product_1.id,
69+
"product_uom_qty": 1,
70+
"price_unit": 1,
71+
},
72+
),
73+
(
74+
0,
75+
0,
76+
{
77+
"product_id": cls.product_2.id,
78+
"product_uom_qty": 1,
79+
"price_unit": 1,
80+
},
81+
),
82+
],
83+
}
84+
)
85+
86+
def test_no_production(self):
87+
self.assertEqual(self.order.order_line[0].production_state, "no")
88+
self.assertEqual(self.order.production_state, "no")
89+
90+
def test_unprocessed_production(self):
91+
self.order.action_confirm()
92+
self.assertEqual(self.order.order_line[0].production_state, "unprocessed")
93+
self.assertEqual(self.order.production_state, "unprocessed")
94+
self.assertTrue(self.order.order_line[0].production_ids)
95+
96+
def test_partially(self):
97+
self.order.action_confirm()
98+
mrp = self.order.order_line[0].production_ids
99+
mrp.action_confirm()
100+
mo_form = Form(mrp)
101+
mo_form.qty_producing = 1
102+
mrp = mo_form.save()
103+
mrp.button_mark_done()
104+
self.assertEqual(self.order.order_line[0].production_state, "done")
105+
self.assertEqual(self.order.production_state, "partially")
106+
107+
def test_production_done(self):
108+
self.order.action_confirm()
109+
for line in self.order.order_line:
110+
mrp = line.production_ids
111+
mrp.action_confirm()
112+
mo_form = Form(mrp)
113+
mo_form.qty_producing = 1
114+
mrp = mo_form.save()
115+
mrp.button_mark_done()
116+
self.assertEqual(self.order.production_state, "done")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<odoo>
3+
<record id="view_order_form_inherit_production_state" model="ir.ui.view">
4+
<field name="name">sale.order.form.sale.stock</field>
5+
<field name="model">sale.order</field>
6+
<field name="inherit_id" ref="sale.view_order_form" />
7+
<field name="arch" type="xml">
8+
<field name='date_order' position="after">
9+
<field name="production_state" readonly="True" />
10+
</field>
11+
</field>
12+
</record>
13+
14+
<record id="view_order_tree_inherit_production_state" model="ir.ui.view">
15+
<field name="name">sale.order.tree</field>
16+
<field name="inherit_id" ref="sale.view_order_tree" />
17+
<field name="model">sale.order</field>
18+
<field name="arch" type="xml">
19+
<field name="invoice_status" position="after">
20+
<field name="production_state" />
21+
</field>
22+
</field>
23+
</record>
24+
</odoo>

0 commit comments

Comments
 (0)