Skip to content

Commit aa7aabf

Browse files
[18.0][IMP] sale_product_pack: improve ergonomy for the user experience
- Convert the component lines unlink exception in JS - Also remove component lines in JS when a product pack is removed
1 parent 657af34 commit aa7aabf

File tree

10 files changed

+241
-58
lines changed

10 files changed

+241
-58
lines changed

sale_product_pack/README.rst

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
.. image:: https://odoo-community.org/readme-banner-image
2-
:target: https://odoo-community.org/get-involved?utm_source=readme
3-
:alt: Odoo Community Association
4-
51
=================
62
Sale Product Pack
73
=================
@@ -17,7 +13,7 @@ Sale Product Pack
1713
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
1814
:target: https://odoo-community.org/page/development-status
1915
:alt: Beta
20-
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
16+
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
2117
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
2218
:alt: License: AGPL-3
2319
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fproduct--pack-lightgray.png?logo=github
@@ -88,6 +84,7 @@ Authors
8884
* NaN·tic
8985
* ADHOC SA
9086
* Tecnativa
87+
* ACSONE SA/NV
9188

9289
Contributors
9390
------------
@@ -108,6 +105,7 @@ Contributors
108105
- `Acsone <https://www.acsone.eu/>`__:
109106

110107
- Maxime Franco
108+
- Stéphane Mangin <stephane.mangin@acsone.eu>
111109

112110
- `ADHOC SA <https://www.adhoc.com.ar>`__:
113111

sale_product_pack/__manifest__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# Copyright 2019 NaN (http://www.nan-tic.com) - Àngel Àlvarez
2+
# Copyright 2026 ACSONE SA/NV (<http://acsone.eu>)
23
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
34
{
45
"name": "Sale Product Pack",
5-
"version": "18.0.1.0.2",
6+
"version": "18.0.1.1.0",
67
"category": "Sales",
78
"summary": "This module allows you to sell product packs",
89
"website": "https://github.com/OCA/product-pack",
9-
"author": "NaN·tic, ADHOC SA, Tecnativa, Odoo Community Association (OCA)",
10+
"author": "NaN·tic, ADHOC SA, Tecnativa, ACSONE SA/NV,"
11+
" Odoo Community Association (OCA)",
1012
"maintainers": ["victoralmau"],
1113
"license": "AGPL-3",
1214
"depends": ["product_pack", "sale"],
@@ -15,5 +17,10 @@
1517
"demo/product_pack_line_demo.xml",
1618
"demo/sale_pack_demo.xml",
1719
],
20+
"assets": {
21+
"web.assets_backend": [
22+
"sale_product_pack/static/src/js/**/*.js",
23+
],
24+
},
1825
"installable": True,
1926
}

sale_product_pack/models/sale_order.py

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright 2019 Tecnativa - Ernesto Tejeda
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3-
from odoo import _, api, models
4-
from odoo.exceptions import UserError
3+
from odoo import models
54

65

76
class SaleOrder(models.Model):
@@ -13,31 +12,9 @@ def copy(self, default=None):
1312
pack_copied_lines = sale_copy.order_line.filtered(
1413
lambda line: line.pack_parent_line_id.order_id == self
1514
)
16-
pack_copied_lines.unlink()
15+
pack_copied_lines.with_context(pack_children_force_unlink=True).unlink()
1716
return sale_copy
1817

19-
@api.onchange("order_line")
20-
def check_pack_line_unlink(self):
21-
"""At least on embeded tree editable view odoo returns a recordset on
22-
_origin.order_line only when lines are unlinked and this is exactly
23-
what we need
24-
"""
25-
origin_line_ids = self._origin.order_line.ids
26-
line_ids = self.order_line.ids
27-
removed_line_ids = list(set(origin_line_ids) - set(line_ids))
28-
removed_line = self.env["sale.order.line"].browse(removed_line_ids)
29-
if removed_line.filtered(
30-
lambda x: x.pack_parent_line_id
31-
and not x.pack_parent_line_id.product_id.pack_modifiable
32-
):
33-
raise UserError(
34-
_(
35-
"You cannot delete this line because is part of a pack in"
36-
" this sale order. In order to delete this line you need to"
37-
" delete the pack itself"
38-
)
39-
)
40-
4118
def write(self, vals):
4219
if "order_line" in vals:
4320
to_delete_ids = [e[1] for e in vals["order_line"] if e[0] == 2]

sale_product_pack/models/sale_order_line.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
from odoo.exceptions import UserError
55
from odoo.fields import first
66

7+
IMMUTABLE_CHILD_FIELDS = [
8+
"product_id",
9+
"product_uom_qty",
10+
"product_uom",
11+
"price_unit",
12+
"discount",
13+
"name",
14+
"tax_id",
15+
]
16+
717

818
class SaleOrderLine(models.Model):
919
_inherit = "sale.order.line"
@@ -100,15 +110,28 @@ def write(self, vals):
100110
record.expand_pack_line(write=True)
101111
return res
102112

103-
@api.onchange(
104-
"product_id",
105-
"product_uom_qty",
106-
"product_uom",
107-
"price_unit",
108-
"discount",
109-
"name",
110-
"tax_id",
111-
)
113+
def unlink(self):
114+
# Avoid removing of a component line if the parent line is not also being
115+
# removed and the pack is not modifiable
116+
forcing_context = self.env.context.get("pack_children_force_unlink", False)
117+
if not forcing_context and self.filtered(
118+
lambda x: x.pack_parent_line_id
119+
and (
120+
x.pack_parent_line_id not in self
121+
or x.pack_parent_line_id in x.order_id.order_line
122+
)
123+
and not x.pack_parent_line_id.product_id.pack_modifiable
124+
):
125+
raise UserError(
126+
_(
127+
"You cannot delete this line because is part of a pack in"
128+
" this sale order. In order to delete this line you need to"
129+
" delete the pack itself"
130+
)
131+
)
132+
return super().unlink()
133+
134+
@api.onchange(*IMMUTABLE_CHILD_FIELDS)
112135
def check_pack_line_modify(self):
113136
"""Do not let to edit a sale order line if this one belongs to pack"""
114137
if self._origin.pack_parent_line_id and not self._origin.pack_modifiable:

sale_product_pack/readme/CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Daniel Reis \<<dreis@opensourceintegrators.com>\>
88
- [Acsone](https://www.acsone.eu/):
99
- Maxime Franco
10+
- Stéphane Mangin \<<stephane.mangin@acsone.eu>\>
1011
- [ADHOC SA](https://www.adhoc.com.ar):
1112
- Bruno Zanotti
1213
- Augusto Weiss

sale_product_pack/static/description/index.html

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
55
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
6-
<title>README.rst</title>
6+
<title>Sale Product Pack</title>
77
<style type="text/css">
88

99
/*
@@ -360,21 +360,16 @@
360360
</style>
361361
</head>
362362
<body>
363-
<div class="document">
363+
<div class="document" id="sale-product-pack">
364+
<h1 class="title">Sale Product Pack</h1>
364365

365-
366-
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
367-
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
368-
</a>
369-
<div class="section" id="sale-product-pack">
370-
<h1>Sale Product Pack</h1>
371366
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
372367
!! This file is generated by oca-gen-addon-readme !!
373368
!! changes will be overwritten. !!
374369
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
375370
!! source digest: sha256:d0034cc2b56fe98dabcd701ddded4a3adc81775fbf8857041c8b9ebd13eeb6ad
376371
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
377-
<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/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/product-pack/tree/18.0/sale_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-18-0/product-pack-18-0-sale_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=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
372+
<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/18.0/sale_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-18-0/product-pack-18-0-sale_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=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
378373
<p>This module adds <em>Product Pack</em> functionality to sales orders. You can
379374
choose a <em>Pack</em> in <em>sales order lines</em> and see different behaviors
380375
depending on “Pack type” and “Pack component price” fields options
@@ -394,7 +389,7 @@ <h1>Sale Product Pack</h1>
394389
</ul>
395390
</div>
396391
<div class="section" id="usage">
397-
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
392+
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
398393
<p>To use this module, you need to:</p>
399394
<ol class="arabic simple">
400395
<li>Go to <em>Sales &gt; Products &gt; Products</em>, create or select a product and
@@ -412,7 +407,7 @@ <h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
412407
</ol>
413408
</div>
414409
<div class="section" id="known-issues-roadmap">
415-
<h2><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h2>
410+
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
416411
<ul class="simple">
417412
<li>If this module is installed and stock module is installed too, when
418413
you create a Sale order for a <em>Non detailed</em> Pack and you confirm it,
@@ -422,25 +417,26 @@ <h2><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h2>
422417
</ul>
423418
</div>
424419
<div class="section" id="bug-tracker">
425-
<h2><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h2>
420+
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
426421
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/product-pack/issues">GitHub Issues</a>.
427422
In case of trouble, please check there if your issue has already been reported.
428423
If you spotted it first, help us to smash it by providing a detailed and welcomed
429424
<a class="reference external" href="https://github.com/OCA/product-pack/issues/new?body=module:%20sale_product_pack%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
430425
<p>Do not contact contributors directly about support or help with technical issues.</p>
431426
</div>
432427
<div class="section" id="credits">
433-
<h2><a class="toc-backref" href="#toc-entry-4">Credits</a></h2>
428+
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
434429
<div class="section" id="authors">
435-
<h3><a class="toc-backref" href="#toc-entry-5">Authors</a></h3>
430+
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
436431
<ul class="simple">
437432
<li>NaN·tic</li>
438433
<li>ADHOC SA</li>
439434
<li>Tecnativa</li>
435+
<li>ACSONE SA/NV</li>
440436
</ul>
441437
</div>
442438
<div class="section" id="contributors">
443-
<h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
439+
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
444440
<ul class="simple">
445441
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
446442
<li>Ernesto Tejeda</li>
@@ -457,6 +453,7 @@ <h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
457453
</li>
458454
<li><a class="reference external" href="https://www.acsone.eu/">Acsone</a>:<ul>
459455
<li>Maxime Franco</li>
456+
<li>Stéphane Mangin &lt;<a class="reference external" href="mailto:stephane.mangin&#64;acsone.eu">stephane.mangin&#64;acsone.eu</a>&gt;</li>
460457
</ul>
461458
</li>
462459
<li><a class="reference external" href="https://www.adhoc.com.ar">ADHOC SA</a>:<ul>
@@ -468,7 +465,7 @@ <h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
468465
</ul>
469466
</div>
470467
<div class="section" id="maintainers">
471-
<h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
468+
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
472469
<p>This module is maintained by the OCA.</p>
473470
<a class="reference external image-reference" href="https://odoo-community.org">
474471
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
@@ -483,6 +480,5 @@ <h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
483480
</div>
484481
</div>
485482
</div>
486-
</div>
487483
</body>
488484
</html>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* Copyright 2026 ACSONE SA/NV */
2+
import {AlertDialog} from "@web/core/confirmation_dialog/confirmation_dialog";
3+
import {_t} from "@web/core/l10n/translation";
4+
import {patch} from "@web/core/utils/patch";
5+
import {StaticList} from "@web/model/relational_model/static_list";
6+
7+
patch(StaticList.prototype, {
8+
_canBeDeleted(record) {
9+
return (
10+
record.resModel === "sale.order.line" &&
11+
record.data.pack_parent_line_id &&
12+
!record.data.pack_modifiable
13+
);
14+
},
15+
16+
_alertNotUnlinkable(isMultiple = false) {
17+
const body = isMultiple
18+
? _t(
19+
"Cannot delete these lines because they are part of a non-modifiable pack."
20+
)
21+
: _t(
22+
"Cannot delete this line because it is part of a pack. Delete the pack itself."
23+
);
24+
25+
this.model.env.services.dialog.add(AlertDialog, {
26+
title: _t("Deletion not allowed"),
27+
body: body,
28+
});
29+
},
30+
31+
_deleteChildRecord(record) {
32+
// Find and delete all child lines that belong to this pack parent
33+
const childLines = this.records.filter(
34+
(r) =>
35+
r.data.pack_parent_line_id &&
36+
r.data.pack_parent_line_id[0] === record.resId
37+
);
38+
for (const childLine of childLines) {
39+
super.delete.call(this, childLine);
40+
}
41+
},
42+
43+
_deleteChildRecords(records) {
44+
for (const record of records) {
45+
this._deleteChildRecord(record);
46+
}
47+
},
48+
49+
async delete(record) {
50+
if (this._canBeDeleted(record)) {
51+
this._alertNotUnlinkable(false);
52+
return;
53+
}
54+
/* If authorized anf if this record is a packed product line, also delete existing child lines */
55+
this._deleteChildRecord(record);
56+
return super.delete(...arguments);
57+
},
58+
59+
async deleteRecords(records) {
60+
if (records.some((record) => this._canBeDeleted(record))) {
61+
this._alertNotUnlinkable(true);
62+
return;
63+
}
64+
/* If authorized and if some of these records are packed product lines, also delete existing child lines */
65+
this._deleteChildRecords(records);
66+
return super.deleteRecords(...arguments);
67+
},
68+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
22

33
from . import test_sale_product_pack
4+
from . import test_sale_order

0 commit comments

Comments
 (0)