Skip to content

Commit 7835ef0

Browse files
dmitriypaulovivs-cetmix
authored andcommitted
[16.0][ADD] sale_optional_product_quantity: add option quantity computation
[16.0][ADD] sale_optional_product_quantity: add unmerged dep [16.0][FIX] sale_optional_product_quantity: fix default quantity assignment [16.0][FIX] sale_optional_product_quantity: fix quantity issue [FIX] sale_optional_product_quantity: fix logic according to requirements [FIX] sale_optional_product_quantity: add only options without configurator [REM] sale_optional_product_quantity: remove merged dependency
1 parent 018dadd commit 7835ef0

File tree

14 files changed

+731
-0
lines changed

14 files changed

+731
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
==============================
2+
Sale Optional Product Quantity
3+
==============================
4+
5+
..
6+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7+
!! This file is generated by oca-gen-addon-readme !!
8+
!! changes will be overwritten. !!
9+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10+
!! source digest: sha256:52d79306a3f06f4364ef7cd0f71946b5ca0a85376f8ed40d664832d29e7b7220
11+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12+
13+
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
14+
:target: https://odoo-community.org/page/development-status
15+
:alt: Beta
16+
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
17+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
18+
:alt: License: AGPL-3
19+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
20+
:target: https://github.com/OCA/sale-workflow/tree/16.0/sale_optional_product_quantity
21+
:alt: OCA/sale-workflow
22+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
23+
:target: https://translation.odoo-community.org/projects/sale-workflow-16-0/sale-workflow-16-0-sale_optional_product_quantity
24+
:alt: Translate me on Weblate
25+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
26+
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=16.0
27+
:alt: Try me on Runboat
28+
29+
|badge1| |badge2| |badge3| |badge4| |badge5|
30+
31+
This module allows to use quantities for optional products in quotations
32+
and sales orders. When a product that has optional products is added to
33+
a quotation optional product quantities will be updated accordingly.
34+
Please check description of the product_optional_product_quantity module
35+
for more details.
36+
37+
**Table of contents**
38+
39+
.. contents::
40+
:local:
41+
42+
Configuration
43+
=============
44+
45+
Check the configuration manual of the
46+
producty_optional_product_quantity. module for details.
47+
48+
Usage
49+
=====
50+
51+
Add a product that has optional products to quotation. Optional product
52+
quantities will be updated accordingly.
53+
54+
Bug Tracker
55+
===========
56+
57+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
58+
In case of trouble, please check there if your issue has already been reported.
59+
If you spotted it first, help us to smash it by providing a detailed and welcomed
60+
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_optional_product_quantity%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
61+
62+
Do not contact contributors directly about support or help with technical issues.
63+
64+
Credits
65+
=======
66+
67+
Authors
68+
-------
69+
70+
* Cetmix
71+
72+
Maintainers
73+
-----------
74+
75+
This module is maintained by the OCA.
76+
77+
.. image:: https://odoo-community.org/logo.png
78+
:alt: Odoo Community Association
79+
:target: https://odoo-community.org
80+
81+
OCA, or the Odoo Community Association, is a nonprofit organization whose
82+
mission is to support the collaborative development of Odoo features and
83+
promote its widespread use.
84+
85+
This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/16.0/sale_optional_product_quantity>`_ project on GitHub.
86+
87+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright 2024 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
from . import models
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2024 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
{
4+
"name": "Sale Optional Product Quantity",
5+
"version": "16.0.1.0.0",
6+
"category": "Sales Management",
7+
"summary": "Use optional product quantities in quotations and sales orders",
8+
"author": "Cetmix, Odoo Community Association (OCA)",
9+
"website": "https://github.com/OCA/sale-workflow",
10+
"license": "AGPL-3",
11+
"depends": [
12+
"product_optional_product_quantity",
13+
"sale_management",
14+
],
15+
"data": [],
16+
"installable": True,
17+
"auto_install": False,
18+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright 2024 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
from . import sale_order_option
4+
from . import sale_order_line
5+
from . import sale_order
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2024 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
from odoo import models
4+
5+
6+
class SaleOrder(models.Model):
7+
_inherit = "sale.order"
8+
9+
def _create_optional_line_if_not_exists(self, product_template, price_unit):
10+
"""Create optional product line if not exists"""
11+
self.ensure_one()
12+
sale_order_option_obj = self.env["sale.order.option"]
13+
if sale_order_option_obj.search_count(
14+
[
15+
("order_id", "=", self.id),
16+
("product_id.product_tmpl_id", "=", product_template.id),
17+
]
18+
):
19+
return
20+
return sale_order_option_obj.create(
21+
{
22+
"order_id": self.id,
23+
"price_unit": price_unit,
24+
"product_id": self.env["product.product"]
25+
.search(
26+
[
27+
("product_tmpl_id", "=", product_template.id),
28+
]
29+
)[0]
30+
.id,
31+
}
32+
)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright 2024 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
from odoo import api, models
4+
5+
6+
class SaleOrderLine(models.Model):
7+
_inherit = "sale.order.line"
8+
9+
@api.model_create_multi
10+
def create(self, vals_list):
11+
optional_quantity_enabled = self.env.user.has_group(
12+
"product_optional_product_quantity.group_product_optional_quantity"
13+
)
14+
if not optional_quantity_enabled:
15+
return super().create(vals_list)
16+
res = super().create(vals_list)
17+
for line in res:
18+
for product_tmpl in line._get_optional_products():
19+
product = self.env["product.product"].search(
20+
[("product_tmpl_id", "=", product_tmpl.id)]
21+
)[0]
22+
option = line.order_id._create_optional_line_if_not_exists(
23+
product_tmpl,
24+
product._get_tax_included_unit_price(
25+
line.company_id,
26+
line.order_id.currency_id,
27+
line.order_id.date_order,
28+
"sale",
29+
fiscal_position=line.order_id.fiscal_position_id,
30+
product_currency=line.currency_id,
31+
),
32+
)
33+
option._compute_quantity()
34+
if line._is_optional_product():
35+
line.order_id._create_optional_line_if_not_exists(
36+
line.product_template_id, line.price_unit
37+
)
38+
return res
39+
40+
@api.depends("product_template_id", "order_id.order_line")
41+
def _compute_product_uom_qty(self):
42+
optional_quantity_enabled = self.env.user.has_group(
43+
"product_optional_product_quantity.group_product_optional_quantity"
44+
)
45+
if not optional_quantity_enabled:
46+
return super()._compute_product_uom_qty()
47+
for line in self:
48+
order = line.order_id
49+
line_product_id = line.product_template_id.id
50+
order_lines = order.order_line.filtered(
51+
lambda x: line_product_id
52+
in (x.product_template_id.optional_product_ids.ids)
53+
)
54+
if not order_lines:
55+
line.product_uom_qty = line.product_uom_qty
56+
continue
57+
58+
multiplier = sum(
59+
order_lines.mapped("product_template_id")
60+
.mapped("product_optional_line_ids")
61+
.filtered(lambda x: x.optional_product_tmpl_id.id == line_product_id)
62+
.mapped("quantity")
63+
)
64+
qty = sum(order_lines.mapped("product_uom_qty"))
65+
line.product_uom_qty = qty * multiplier
66+
67+
def _is_optional_product(self):
68+
"""
69+
Check if product is optional.
70+
71+
Returns:
72+
bool: True if product is optional, False otherwise
73+
"""
74+
self.ensure_one()
75+
return bool(
76+
self.env["product.template"].search_count(
77+
[("optional_product_ids", "in", self.product_template_id.id)]
78+
)
79+
)
80+
81+
def _get_optional_products(self):
82+
"""
83+
Get product.template recordset with product templates
84+
related to current line's product as optional products.
85+
86+
Returns:
87+
product.template recordset: Optional products
88+
"""
89+
self.ensure_one()
90+
return (
91+
self.env["product.optional.line"]
92+
.search([("product_tmpl_id", "=", self.product_template_id.id)])
93+
.mapped("optional_product_tmpl_id")
94+
)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2024 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
from odoo import api, fields, models
4+
5+
6+
class SaleOrderOption(models.Model):
7+
_inherit = "sale.order.option"
8+
9+
quantity = fields.Float(
10+
compute="_compute_quantity",
11+
readonly=False,
12+
store=True,
13+
)
14+
15+
@api.depends(
16+
"order_id.order_line",
17+
"order_id.order_line.product_template_id",
18+
"order_id.order_line.product_uom_qty",
19+
)
20+
def _compute_quantity(self):
21+
"""
22+
Compute quantity based on optional product
23+
quantities configured in product templates
24+
from lines of Quotation/Sale Order.
25+
"""
26+
optional_quantity_enabled = self.env.user.has_group(
27+
"product_optional_product_quantity.group_product_optional_quantity"
28+
)
29+
if not optional_quantity_enabled:
30+
for option in self:
31+
option.quantity = option.quantity
32+
return
33+
for option in self:
34+
order = option.order_id
35+
option_product_id = option.product_id.product_tmpl_id.id
36+
order_lines = order.order_line.filtered(
37+
lambda x: option_product_id
38+
in (x.product_template_id.optional_product_ids.ids)
39+
)
40+
if not order_lines:
41+
option.quantity = 1
42+
continue
43+
44+
multiplier = sum(
45+
order_lines.mapped("product_template_id")
46+
.mapped("product_optional_line_ids")
47+
.filtered(lambda x: x.optional_product_tmpl_id.id == option_product_id)
48+
.mapped("quantity")
49+
)
50+
qty = sum(order_lines.mapped("product_uom_qty"))
51+
option.quantity = qty * multiplier
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Check the configuration manual of the producty_optional_product_quantity. module for details.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This module allows to use quantities for optional products in quotations and sales orders.
2+
When a product that has optional products is added to a quotation optional product quantities will be updated accordingly.
3+
Please check description of the product_optional_product_quantity module for more details.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a product that has optional products to quotation. Optional product quantities will be updated accordingly.

0 commit comments

Comments
 (0)