forked from OCA/e-commerce
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[IMP] product_template_multi_link: Makes links bi-directional
fixes OCA#307
- Loading branch information
1 parent
1bed65c
commit 638ce27
Showing
15 changed files
with
813 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
product_template_multi_link/data/product_template_link_type.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<!-- Copyright 2019 ACSONE SA/NV | ||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> | ||
<odoo noupdate="1"> | ||
<record | ||
model="product.template.link.type" | ||
id="product_template_link_type_cross_selling" | ||
> | ||
<field name="name">Cross Selling</field> | ||
<field name="code">cross-selling</field> | ||
</record> | ||
<record | ||
model="product.template.link.type" | ||
id="product_template_link_type_up_selling" | ||
> | ||
<field name="name">Up Selling</field> | ||
<field name="code">up-selling</field> | ||
</record> | ||
</odoo> |
18 changes: 5 additions & 13 deletions
18
product_template_multi_link/demo/product_template_link.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,17 @@ | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<odoo> | ||
<record id="link_apple_1" model="product.template.link"> | ||
<record id="link_cross_selling_1" model="product.template.link"> | ||
<field | ||
name="product_template_id" | ||
name="left_product_tmpl_id" | ||
ref="product.product_product_7_product_template" | ||
/> | ||
<field | ||
name="linked_product_template_id" | ||
name="right_product_tmpl_id" | ||
ref="product.product_product_9_product_template" | ||
/> | ||
<field name="link_type">cross_sell</field> | ||
</record> | ||
<record id="link_apple_2" model="product.template.link"> | ||
<field | ||
name="product_template_id" | ||
ref="product.product_product_9_product_template" | ||
/> | ||
<field | ||
name="linked_product_template_id" | ||
ref="product.product_product_7_product_template" | ||
name="type_id" | ||
ref="product_template_multi_link.product_template_link_type_cross_selling" | ||
/> | ||
<field name="link_type">cross_sell</field> | ||
</record> | ||
</odoo> |
15 changes: 15 additions & 0 deletions
15
product_template_multi_link/demo/product_template_link_type.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<!-- Copyright 2019 ACSONE SA/NV | ||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> | ||
<odoo> | ||
<record | ||
model="product.template.link.type" | ||
id="product_template_link_type_demo_range" | ||
> | ||
<field name="is_symmetric" eval="0" /> | ||
<field name="name">Upper Range</field> | ||
<field name="inverse_name">Lower Range</field> | ||
<field name="code">upper-range</field> | ||
<field name="inverse_code">lower-range</field> | ||
</record> | ||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
from . import product_template | ||
from . import product_template_link | ||
from . import product_template_link_type |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 117 additions & 20 deletions
137
product_template_multi_link/models/product_template_link.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,143 @@ | ||
# Copyright 2017-Today GRAP (http://www.grap.coop). | ||
# @author Sylvain LE GAL <https://twitter.com/legalsylvain> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
from contextlib import contextmanager | ||
|
||
from odoo import fields, models | ||
from psycopg2.extensions import AsIs | ||
|
||
from odoo import _, api, fields, models | ||
from odoo.exceptions import ValidationError | ||
|
||
|
||
class ProductTemplateLink(models.Model): | ||
_name = "product.template.link" | ||
_order = "product_template_id, linked_product_template_id" | ||
_order = "left_product_tmpl_id, right_product_tmpl_id" | ||
_description = "Product link" | ||
|
||
_LINK_TYPE_SELECTION = [("cross_sell", "Cross-Sell"), ("up_sell", "Up-Sell")] | ||
|
||
product_template_id = fields.Many2one( | ||
left_product_tmpl_id = fields.Many2one( | ||
string="Source Product", | ||
comodel_name="product.template", | ||
required=True, | ||
ondelete="cascade", | ||
) | ||
|
||
linked_product_template_id = fields.Many2one( | ||
right_product_tmpl_id = fields.Many2one( | ||
string="Linked Product", | ||
comodel_name="product.template", | ||
required=True, | ||
ondelete="cascade", | ||
) | ||
|
||
link_type = fields.Selection( | ||
string="Link Type", | ||
selection=_LINK_TYPE_SELECTION, | ||
type_id = fields.Many2one( | ||
string="Link type", | ||
comodel_name="product.template.link.type", | ||
required=True, | ||
default="cross_sell", | ||
help="* Cross-Sell : suggest your customer to" | ||
" purchase an additional product\n" | ||
"* Up-Sell : suggest your customer to purchase a higher-end product," | ||
" an upgrade, etc.", | ||
ondelete="restrict", | ||
) | ||
|
||
sql_constraints = [ | ||
( | ||
"template_link_uniq", | ||
"unique (product_template_id, linked_product_template_id, link_type)", | ||
"The products and the link type combination must be unique", | ||
link_type_name = fields.Char(related="type_id.name") # left to right | ||
link_type_inverse_name = fields.Char( | ||
related="type_id.inverse_name" | ||
) # right to left | ||
|
||
@api.constrains("left_product_tmpl_id", "right_product_tmpl_id", "type_id") | ||
def _check_products(self): | ||
""" | ||
This method checks whether: | ||
- the two products are different | ||
- there is only one link between the same two templates for the same type | ||
:raise: ValidationError if not ok | ||
""" | ||
self.flush() # flush required since the method uses plain sql | ||
if any(rec.left_product_tmpl_id == rec.right_product_tmpl_id for rec in self): | ||
raise ValidationError( | ||
_("You can only create a link between 2 different products") | ||
) | ||
|
||
products = self.mapped("left_product_tmpl_id") + self.mapped( | ||
"right_product_tmpl_id" | ||
) | ||
] | ||
self.env.cr.execute( | ||
""" | ||
SELECT | ||
id, | ||
l2.duplicate or l3.duplicate | ||
FROM ( | ||
SELECT | ||
id, | ||
left_product_tmpl_id, | ||
right_product_tmpl_id, | ||
type_id | ||
FROM | ||
%s | ||
WHERE | ||
left_product_tmpl_id in %s | ||
AND right_product_tmpl_id in %s | ||
) as l1 | ||
LEFT JOIN LATERAL ( | ||
SELECT | ||
TRUE as duplicate | ||
FROM | ||
%s | ||
WHERE | ||
right_product_tmpl_id = l1.left_product_tmpl_id | ||
AND left_product_tmpl_id = l1.right_product_tmpl_id | ||
AND type_id = l1.type_id | ||
) l2 ON TRUE | ||
LEFT JOIN LATERAL ( | ||
SELECT | ||
TRUE as duplicate | ||
FROM | ||
%s | ||
WHERE | ||
left_product_tmpl_id = l1.left_product_tmpl_id | ||
AND right_product_tmpl_id = l1.right_product_tmpl_id | ||
AND type_id = l1.type_id | ||
AND id != l1.id | ||
) l3 ON TRUE | ||
""", | ||
( | ||
AsIs(self._table), | ||
tuple(products.ids), | ||
tuple(products.ids), | ||
AsIs(self._table), | ||
AsIs(self._table), | ||
), | ||
) | ||
res = self.env.cr.fetchall() | ||
is_duplicate_by_link_id = dict(res) | ||
if True in is_duplicate_by_link_id.values(): | ||
ids = [k for k, v in is_duplicate_by_link_id.items() if v] | ||
links = self.browse(ids) | ||
descrs = [] | ||
for l in links: | ||
descrs.append( | ||
u"{} <-> {} / {} <-> {}".format( | ||
l.left_product_tmpl_id.name, | ||
l.link_type_name, | ||
l.link_type_inverse_name, | ||
l.right_product_tmpl_id.name, | ||
) | ||
) | ||
links = "\n ".join(descrs) | ||
raise ValidationError( | ||
_( | ||
"Only one link with the same type is allowed between 2 " | ||
"products. \n %s" | ||
) | ||
% links | ||
) | ||
|
||
@contextmanager | ||
def _invalidate_links_on_product_template(self): | ||
yield | ||
self.env["product.template"].invalidate_cache(["product_template_link_ids"]) | ||
|
||
@api.model | ||
def create(self, vals_list): | ||
with self._invalidate_links_on_product_template(): | ||
return super().create(vals_list) | ||
|
||
def write(self, vals): | ||
with self._invalidate_links_on_product_template(): | ||
return super().write(vals) |
Oops, something went wrong.