Skip to content

Commit

Permalink
Merge PR #173 into 17.0
Browse files Browse the repository at this point in the history
Signed-off-by pedrobaeza
  • Loading branch information
OCA-git-bot committed Nov 4, 2024
2 parents 71fa9d2 + 32a9457 commit 3887561
Show file tree
Hide file tree
Showing 22 changed files with 355 additions and 80 deletions.
20 changes: 19 additions & 1 deletion product_pack/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Product Pack
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d6d79cfb1448cba3c8cc68f89101f6b53245f8a695effd2058ca881dac328748
!! source digest: sha256:6412314c32b470ebaa9ba73edd671e2513bf0b8c9faa4703dcb22faf76692670
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down Expand Up @@ -70,6 +70,21 @@ The options of this field are the followings:
components prices. The pack product will be the only one that has price
and this one will be the price set in the pack product.

+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+
| **Pack type** | **Show components on SO?** | **Sale price** | **Discount** | **Can be modified?** |
+=============================+=============================+=================================+=========================================+======================+
| **Detailed per components** | Yes, with their prices | Components + Pack | Applies to the price of the pack and | Yes, configurable |
| | | | the components | |
+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+
| **Detailed - Totalized** | Yes, with their prices at 0 | Components | Applies to the total (pack + components)| No |
+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+
| **Detailed - Ignored** | Yes, with their prices at 0 | Only Pack | Applies to the pack | No |
+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+
| **No detailed** | No | Components | Applies to the total (pack + components)| No |
+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+

**Note:** If pricelist enabled, Odoo will display the price according to the corresponding pricelist. In the case of a pricelist with discount policy "Show public price & discount to the customer" keep in mind that the "Non Detailed" and "Detailed - Totalized in main product" packs do not display the discount.

Known issues / Roadmap
======================

Expand Down Expand Up @@ -106,6 +121,9 @@ Contributors
* Juan José Scarafía
* Nicolas Mac Rouillon
* Katherine Zaoral
* Bruno Zanotti
* Augusto Weiss
* Nicolas Col
* `NaN·TIC <http://www.nan-tic.com>`_
* `Tecnativa <https://www.tecnativa.com>`_:

Expand Down
1 change: 1 addition & 0 deletions product_pack/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from . import product_pack_line
from . import product_product
from . import product_template
from . import product_pricelist
33 changes: 31 additions & 2 deletions product_pack/models/product_pack_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,35 @@ def _check_recursion(self):
)
pack_lines = pack_lines.mapped("product_id.pack_line_ids")

def get_price(self):
def _get_pack_line_price(self, pricelist, quantity, uom=None, date=False, **kwargs):
self.ensure_one()
return self.product_id.lst_price * self.quantity
if self.product_id._is_pack_to_be_handled():
price = pricelist._get_product_price(
self.product_id, quantity, uom=uom, date=date, **kwargs
)
else:
price = pricelist._compute_price_rule(
self.product_id, quantity, uom=uom, date=date, **kwargs
)[self.product_id.id][0]
return price * self.quantity

def _pack_line_price_compute(
self, price_type, uom=False, currency=False, company=False, date=False
):
packs, no_packs = self.product_id.split_pack_products()

pack_prices = {}
# If the component is a pack
for pack in packs:
pack_prices[pack.id] = pack.lst_price

# else
no_pack_prices = no_packs._price_compute(
price_type, uom, currency, company, date
)

prices = {**pack_prices, **no_pack_prices}
for line in self:
prices[line.product_id.id] *= line.quantity

return prices
51 changes: 51 additions & 0 deletions product_pack/models/product_pricelist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from odoo import models


class Pricelist(models.Model):
_inherit = "product.pricelist"

def _get_product_price(self, product, quantity, uom=None, date=False, **kwargs):
"""Compute the pricelist price for the specified pack product, qty & uom.
:returns: unit price of the pack product + components,
considering pricelist rules
"""
self and self.ensure_one()
if product._is_pack_to_be_handled():
# NOTE: This exception is to avoid adding the list price of the packs
# "totalized" and "non detailed". Should be removed to solve the issue #169.
if (
product.pack_type == "non_detailed"
or product.pack_component_price == "totalized"
):
pack_price = 0
else:
pack_price = self._compute_price_rule(
product, quantity, uom=uom, date=date, **kwargs
)[product.id][0]

for line in product.sudo().pack_line_ids:
pack_price += line._get_pack_line_price(
self, quantity, uom=uom, date=date, **kwargs
)
return pack_price
else:
return super()._get_product_price(
product=product, quantity=quantity, uom=uom, date=date, **kwargs
)

def _get_products_price(self, products, quantity, uom=None, date=False, **kwargs):
"""Compute the pricelist price for the specified pack product, qty & uom.
:returns: unit price of the pack product + components,
considering pricelist rules
"""
packs, no_packs = products.split_pack_products()
res = super()._get_products_price(
no_packs, quantity=quantity, uom=uom, date=date, **kwargs
)
for pack in packs:
res[pack.id] = self._get_product_price(
product=pack, quantity=quantity, uom=uom, date=date, **kwargs
)
return res
71 changes: 23 additions & 48 deletions product_pack/models/product_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,61 +26,36 @@ def get_pack_lines(self):
can be overloaded to introduce filtering function by date, etc..."""
return self.mapped("pack_line_ids")

def _is_pack_to_be_handled(self):
return self.product_tmpl_id._is_pack_to_be_handled()

def split_pack_products(self):
packs = self.filtered(lambda p: p.product_tmpl_id._is_pack_to_be_handled())
return packs, (self - packs)

def _price_compute(
self, price_type, uom=False, currency=False, company=False, date=False
):
packs, no_packs = self.split_pack_products()
prices = super(ProductProduct, no_packs)._price_compute(
price_type, uom, currency, company, date
)
for product in packs.with_context(prefetch_fields=False):
pack_price = 0.0
for pack_line in product.sudo().pack_line_ids:
pack_price += pack_line.get_price()
pricelist_id_or_name = self._context.get("pricelist")
# if there is a pricelist on the context the returned prices are on
# that currency but, if the pack product has a different currency
# it will be converted again by pp._compute_price_rule, so if
# that is the case we convert the amounts to the pack currency
if pricelist_id_or_name:
if isinstance(pricelist_id_or_name, list):
pricelist_id_or_name = pricelist_id_or_name[0]
if isinstance(pricelist_id_or_name, str):
pricelist_name_search = self.env["product.pricelist"].name_search(
pricelist_id_or_name, operator="=", limit=1
)
if pricelist_name_search:
pricelist = self.env["product.pricelist"].browse(
[pricelist_name_search[0][0]]
)
elif isinstance(pricelist_id_or_name, int):
pricelist = self.env["product.pricelist"].browse(
pricelist_id_or_name
)
if pricelist and pricelist.currency_id != product.currency_id:
pack_price = pricelist.currency_id._convert(
pack_price,
product.currency_id,
self.company_id or self.env.company,
fields.Date.today(),
)
prices[product.id] = pack_price
return prices

@api.depends("list_price", "price_extra")
def _compute_product_lst_price(self):
packs, no_packs = self.split_pack_products()
packs, no_packs = self.with_context(whole_pack_price=True).split_pack_products()
ret_val = super(ProductProduct, no_packs)._compute_product_lst_price()
to_uom = None
if "uom" in self._context:
to_uom = self.env["uom.uom"].browse([self._context["uom"]])
uom = self._context.get("uom", False)
if uom:
uom = self.env["uom.uom"].browse([uom])
for product in packs:
list_price = product._price_compute("list_price").get(product.id)
if to_uom:
list_price = product.uom_id._compute_price(list_price, to_uom)
# NOTE: This exception is to avoid adding the list price of the packs
# "totalized" and "non detailed". Should be removed to solve the issue #169.
if (
product.pack_type == "non_detailed"
or product.pack_component_price == "totalized"
):
list_price = 0
else:
list_price = product._price_compute("list_price", uom=uom).get(
product.id
)
list_price += sum(
product.pack_line_ids._pack_line_price_compute(
"list_price", uom=uom
).values()
)
product.lst_price = list_price + product.price_extra
return ret_val
2 changes: 2 additions & 0 deletions product_pack/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* Nicolas Mac Rouillon
* Katherine Zaoral
* Bruno Zanotti
* Augusto Weiss
* Nicolas Col
* `NaN·TIC <http://www.nan-tic.com>`_
* `Tecnativa <https://www.tecnativa.com>`_:

Expand Down
15 changes: 15 additions & 0 deletions product_pack/readme/USAGE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,18 @@ The options of this field are the followings:
* Ignored: will show each components but will not show
components prices. The pack product will be the only one that has price
and this one will be the price set in the pack product.

+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+
| **Pack type** | **Show components on SO?** | **Sale price** | **Discount** | **Can be modified?** |
+=============================+=============================+=================================+=========================================+======================+
| **Detailed per components** | Yes, with their prices | Components + Pack | Applies to the price of the pack and | Yes, configurable |
| | | | the components | |
+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+
| **Detailed - Totalized** | Yes, with their prices at 0 | Components | Applies to the total (pack + components)| No |
+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+
| **Detailed - Ignored** | Yes, with their prices at 0 | Only Pack | Applies to the pack | No |
+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+
| **No detailed** | No | Components | Applies to the total (pack + components)| No |
+-----------------------------+-----------------------------+---------------------------------+-----------------------------------------+----------------------+

**Note:** If pricelist enabled, Odoo will display the price according to the corresponding pricelist. In the case of a pricelist with discount policy "Show public price & discount to the customer" keep in mind that the "Non Detailed" and "Detailed - Totalized in main product" packs do not display the discount.
51 changes: 49 additions & 2 deletions product_pack/static/description/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<?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>
Expand Down Expand Up @@ -367,7 +366,7 @@ <h1 class="title">Product Pack</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d6d79cfb1448cba3c8cc68f89101f6b53245f8a695effd2058ca881dac328748
!! source digest: sha256:6412314c32b470ebaa9ba73edd671e2513bf0b8c9faa4703dcb22faf76692670
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/product-pack/tree/16.0/product_pack"><img alt="OCA/product-pack" src="https://img.shields.io/badge/github-OCA%2Fproduct--pack-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/product-pack-16-0/product-pack-16-0-product_pack"><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/product-pack&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 you to define a product as a <em>Product Pack</em>. Each
Expand Down Expand Up @@ -422,6 +421,51 @@ <h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
and this one will be the price set in the pack product.</li>
</ul>
</blockquote>
<table border="1" class="docutils">
<colgroup>
<col width="19%" />
<col width="19%" />
<col width="21%" />
<col width="27%" />
<col width="14%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head"><strong>Pack type</strong></th>
<th class="head"><strong>Show components on SO?</strong></th>
<th class="head"><strong>Sale price</strong></th>
<th class="head"><strong>Discount</strong></th>
<th class="head"><strong>Can be modified?</strong></th>
</tr>
</thead>
<tbody valign="top">
<tr><td><strong>Detailed per components</strong></td>
<td>Yes, with their prices</td>
<td>Components + Pack</td>
<td>Applies to the price of the pack and
the components</td>
<td>Yes, configurable</td>
</tr>
<tr><td><strong>Detailed - Totalized</strong></td>
<td>Yes, with their prices at 0</td>
<td>Components</td>
<td>Applies to the total (pack + components)</td>
<td>No</td>
</tr>
<tr><td><strong>Detailed - Ignored</strong></td>
<td>Yes, with their prices at 0</td>
<td>Only Pack</td>
<td>Applies to the pack</td>
<td>No</td>
</tr>
<tr><td><strong>No detailed</strong></td>
<td>No</td>
<td>Components</td>
<td>Applies to the total (pack + components)</td>
<td>No</td>
</tr>
</tbody>
</table>
<p><strong>Note:</strong> If pricelist enabled, Odoo will display the price according to the corresponding pricelist. In the case of a pricelist with discount policy “Show public price &amp; discount to the customer” keep in mind that the “Non Detailed” and “Detailed - Totalized in main product” packs do not display the discount.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
Expand Down Expand Up @@ -457,6 +501,9 @@ <h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<li>Juan José Scarafía</li>
<li>Nicolas Mac Rouillon</li>
<li>Katherine Zaoral</li>
<li>Bruno Zanotti</li>
<li>Augusto Weiss</li>
<li>Nicolas Col</li>
</ul>
</li>
<li><a class="reference external" href="http://www.nan-tic.com">NaN·TIC</a></li>
Expand Down
17 changes: 17 additions & 0 deletions product_pack/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,20 @@ def setUpClass(cls):
"name": "Company Pack 2",
}
cls.company_2 = cls.env["res.company"].create(vals)
cls.discount_pricelist = cls.env["product.pricelist"].create(
{
"name": "Discount",
"company_id": cls.env.company.id,
"item_ids": [
(
0,
0,
{
"applied_on": "3_global",
"compute_price": "percentage",
"percent_price": 10,
},
)
],
}
)
33 changes: 32 additions & 1 deletion product_pack/tests/test_product_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def test_get_pack_line_price(self):
30.0,
self.cpu_detailed.pack_line_ids.filtered(
lambda cmp: cmp.product_id == component.product_id
).get_price(),
)._pack_line_price_compute("list_price")[component.product_id.id],
)

def test_get_pack_lst_price(self):
Expand Down Expand Up @@ -117,3 +117,34 @@ def test_pack_modifiable(self):
pack.pack_type = "detailed"
pack.pack_component_price = "totalized"
self.assertTrue(pack.pack_modifiable_invisible)

def test_pack_price_with_pricelist_context(self):
# Apply pricelist by context only for product packs (no components)

# Pack Detailed
pack = self.env.ref("product_pack.product_pack_cpu_detailed_components")
price = pack.with_context(
whole_pack_price=True, pricelist=self.discount_pricelist.id
)._get_contextual_price()
self.assertEqual(price, 2601.675)

# Pack Totalized
pack = self.env.ref("product_pack.product_pack_cpu_detailed_totalized")
price = pack.with_context(
pricelist=self.discount_pricelist.id
)._get_contextual_price()
self.assertEqual(price, 2574.0)

# Pack Ignored
pack = self.env.ref("product_pack.product_pack_cpu_detailed_ignored")
price = pack.with_context(
pricelist=self.discount_pricelist.id
)._get_contextual_price()
self.assertEqual(price, 27.675)

# Pack Non detailed
pack = self.env.ref("product_pack.product_pack_cpu_non_detailed")
price = pack.with_context(
pricelist=self.discount_pricelist.id
)._get_contextual_price()
self.assertEqual(price, 2574.0)
Loading

0 comments on commit 3887561

Please sign in to comment.