Skip to content

Commit 68b796a

Browse files
committed
[19.0][ADD] website_sale_product_multiple_qty
1 parent 1121124 commit 68b796a

File tree

14 files changed

+1049
-0
lines changed

14 files changed

+1049
-0
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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+
5+
=================================
6+
Website Sale Product Multiple Qty
7+
=================================
8+
9+
..
10+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
11+
!! This file is generated by oca-gen-addon-readme !!
12+
!! changes will be overwritten. !!
13+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
14+
!! source digest: sha256:fae80e9f1799cbaea5d031c91bf0456af4741e0d2cf9edd5af4f30424377956f
15+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16+
17+
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
18+
:target: https://odoo-community.org/page/development-status
19+
:alt: Beta
20+
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
21+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
22+
:alt: License: AGPL-3
23+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
24+
:target: https://github.com/OCA/sale-workflow/tree/19.0/website_sale_product_multiple_qty
25+
:alt: OCA/sale-workflow
26+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
27+
:target: https://translation.odoo-community.org/projects/sale-workflow-19-0/sale-workflow-19-0-website_sale_product_multiple_qty
28+
:alt: Translate me on Weblate
29+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
30+
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=19.0
31+
:alt: Try me on Runboat
32+
33+
|badge1| |badge2| |badge3| |badge4| |badge5|
34+
35+
Website Sale Product Multiple Quantity
36+
======================================
37+
38+
This module extends the eCommerce flow to support **sales multiples**
39+
(packaging quantities) directly on the product page.
40+
41+
When a product has a *Sales Multiple UoM* configured, the quantity
42+
entered by the customer on the website is automatically rounded to a
43+
valid multiple according to the interaction type.
44+
45+
The rounding logic is applied dynamically when the customer:
46+
47+
- Opens the product page
48+
- Clicks the "+" (increase) button
49+
- Clicks the "–" (decrease) button
50+
- Manually enters a quantity
51+
52+
Rounding Rules
53+
--------------
54+
55+
The behavior is designed to be human-friendly and predictable:
56+
57+
- On page load: The default quantity is rounded **UP** to the nearest
58+
multiple.
59+
60+
- When clicking "+": The quantity is rounded **UP** to the next valid
61+
multiple.
62+
63+
- When clicking "–": The quantity is rounded **DOWN** to the previous
64+
valid multiple.
65+
66+
- When manually entering a quantity: The value is rounded **UP** to the
67+
nearest valid multiple.
68+
69+
It is possible to set the quantity to ``0`` if the user decreases the
70+
quantity below the first multiple.
71+
72+
Example
73+
-------
74+
75+
If a product is sold in multiples of 500:
76+
77+
- Entering ``1`` → becomes ``500``
78+
- Entering ``499`` → becomes ``500``
79+
- Entering ``501`` → becomes ``1000``
80+
- Clicking "–" from ``500`` → becomes ``0``
81+
- Clicking "+" from ``0`` → becomes ``500``
82+
83+
It is the responsibility of the user to configure compatible Units of
84+
Measure.
85+
86+
The Sales Multiple UoM must belong to the same UoM category as the
87+
product's sales UoM. Incorrect configuration (for example, mixing
88+
unrelated UoM categories) may lead to unexpected quantity conversions
89+
and rounding results.
90+
91+
The module assumes that Units of Measure are properly defined and
92+
conversion ratios are accurate.
93+
94+
**Table of contents**
95+
96+
.. contents::
97+
:local:
98+
99+
Usage
100+
=====
101+
102+
Usage
103+
=====
104+
105+
Configuration
106+
-------------
107+
108+
1. Go to *Sales → Products*.
109+
2. Open a product.
110+
3. Set a **Sales Multiple UoM** (for example, *Pack of 500*).
111+
112+
The selected UoM must belong to the same UoM category as the product's
113+
sales unit of measure.
114+
115+
Important
116+
---------
117+
118+
Ensure that the Sales Multiple UoM is correctly configured:
119+
120+
- It must belong to the same UoM category as the product's sales UoM.
121+
- Conversion ratios must be accurate.
122+
- The multiple should reflect the real packaging quantity.
123+
124+
Incorrect UoM configuration may result in unexpected rounding behavior.
125+
126+
Bug Tracker
127+
===========
128+
129+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
130+
In case of trouble, please check there if your issue has already been reported.
131+
If you spotted it first, help us to smash it by providing a detailed and welcomed
132+
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20website_sale_product_multiple_qty%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
133+
134+
Do not contact contributors directly about support or help with technical issues.
135+
136+
Credits
137+
=======
138+
139+
Authors
140+
-------
141+
142+
* Camptocamp SA
143+
144+
Contributors
145+
------------
146+
147+
- `Camptocamp <https://www.camptocamp.com>`__:
148+
149+
- Maksym Yankin <maksym.yankin@camptocamp.com>
150+
- Ivan Todorovich <ivan.todorovich@camptocamp.com>
151+
- Gaëtan Vaujour <gaetan.vaujour@camptocamp.com>
152+
153+
Maintainers
154+
-----------
155+
156+
This module is maintained by the OCA.
157+
158+
.. image:: https://odoo-community.org/logo.png
159+
:alt: Odoo Community Association
160+
:target: https://odoo-community.org
161+
162+
OCA, or the Odoo Community Association, is a nonprofit organization whose
163+
mission is to support the collaborative development of Odoo features and
164+
promote its widespread use.
165+
166+
.. |maintainer-yankinmax| image:: https://github.com/yankinmax.png?size=40px
167+
:target: https://github.com/yankinmax
168+
:alt: yankinmax
169+
170+
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
171+
172+
|maintainer-yankinmax|
173+
174+
This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/19.0/website_sale_product_multiple_qty>`_ project on GitHub.
175+
176+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import controllers
2+
from . import models
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright 2026 Camptocamp SA
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
{
4+
"name": "Website Sale Product Multiple Qty",
5+
"summary": "Allows setting a multiple quantity for products on the website.",
6+
"version": "19.0.1.0.0",
7+
"category": "Sales",
8+
"website": "https://github.com/OCA/sale-workflow",
9+
"author": "Camptocamp SA, Odoo Community Association (OCA)",
10+
"license": "AGPL-3",
11+
"installable": True,
12+
"depends": [
13+
# Odoo/core
14+
"website_sale",
15+
# OCA/sale-workflow
16+
"sale_product_multiple_qty",
17+
],
18+
"maintainers": ["yankinmax"],
19+
"data": [
20+
# Views
21+
"views/templates.xml",
22+
],
23+
"assets": {
24+
"web.assets_frontend": [
25+
"website_sale_product_multiple_qty/static/src/**/*.js",
26+
],
27+
},
28+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import variant
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2026 Camptocamp SA
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo.http import request, route
5+
6+
from odoo.addons.website_sale.controllers.variant import WebsiteSaleVariantController
7+
8+
9+
class WebsiteSaleRoundingVariantController(WebsiteSaleVariantController):
10+
@route(
11+
"/website_sale/get_combination_info",
12+
type="jsonrpc",
13+
auth="public",
14+
methods=["POST"],
15+
website=True,
16+
readonly=True,
17+
)
18+
def get_combination_info_website(
19+
self,
20+
product_template_id,
21+
product_id,
22+
combination,
23+
add_qty,
24+
uom_id=None,
25+
**kwargs,
26+
):
27+
if rounding := kwargs.get("multiple_rounding"):
28+
request.update_env(
29+
context=dict(request.env.context, multiple_rounding=rounding)
30+
)
31+
32+
return super().get_combination_info_website(
33+
product_template_id=product_template_id,
34+
product_id=product_id,
35+
combination=combination,
36+
add_qty=add_qty,
37+
uom_id=uom_id,
38+
**kwargs,
39+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import product_template
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2026 Camptocamp SA
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
from odoo import models
4+
5+
6+
class ProductTemplate(models.Model):
7+
_inherit = "product.template"
8+
9+
def _get_additionnal_combination_info(
10+
self, product_or_template, quantity, uom, date, website
11+
):
12+
# OVERRIDE: to update the combination info with the multiple related info
13+
combination_info = super()._get_additionnal_combination_info(
14+
product_or_template, quantity, uom, date, website
15+
)
16+
17+
if not product_or_template.sale_multiple_uom_id:
18+
return combination_info
19+
rounding = "UP"
20+
if to_rounding := self.env.context.get("multiple_rounding"):
21+
rounding = to_rounding
22+
rounded_qty = self.sale_multiple_uom_id._check_qty(
23+
quantity, self.uom_id, rounding_method=rounding
24+
)
25+
combination_info.update(
26+
{
27+
"is_multiple": 1,
28+
# The website expects an integer value as an input
29+
# ``website_sale::variant_mixin.js``
30+
# parseInt(parent.querySelector('input[name="add_qty"]').value).
31+
"multiple_qty": int(rounded_qty),
32+
}
33+
)
34+
return combination_info
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
requires = ["whool"]
3+
build-backend = "whool.buildapi"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- [Camptocamp](https://www.camptocamp.com):
2+
- Maksym Yankin \<<maksym.yankin@camptocamp.com>\>
3+
- Ivan Todorovich \<<ivan.todorovich@camptocamp.com>\>
4+
- Gaëtan Vaujour \<<gaetan.vaujour@camptocamp.com>\>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
Website Sale Product Multiple Quantity
2+
=======================================
3+
4+
This module extends the eCommerce flow to support **sales multiples**
5+
(packaging quantities) directly on the product page.
6+
7+
When a product has a *Sales Multiple UoM* configured, the quantity entered
8+
by the customer on the website is automatically rounded to a valid multiple
9+
according to the interaction type.
10+
11+
The rounding logic is applied dynamically when the customer:
12+
13+
- Opens the product page
14+
- Clicks the "+" (increase) button
15+
- Clicks the "–" (decrease) button
16+
- Manually enters a quantity
17+
18+
Rounding Rules
19+
--------------
20+
21+
The behavior is designed to be human-friendly and predictable:
22+
23+
* On page load:
24+
The default quantity is rounded **UP** to the nearest multiple.
25+
26+
* When clicking "+":
27+
The quantity is rounded **UP** to the next valid multiple.
28+
29+
* When clicking "–":
30+
The quantity is rounded **DOWN** to the previous valid multiple.
31+
32+
* When manually entering a quantity:
33+
The value is rounded **UP** to the nearest valid multiple.
34+
35+
It is possible to set the quantity to ``0`` if the user decreases
36+
the quantity below the first multiple.
37+
38+
Example
39+
-------
40+
41+
If a product is sold in multiples of 500:
42+
43+
- Entering ``1`` → becomes ``500``
44+
- Entering ``499`` → becomes ``500``
45+
- Entering ``501`` → becomes ``1000``
46+
- Clicking "–" from ``500`` → becomes ``0``
47+
- Clicking "+" from ``0`` → becomes ``500``
48+
49+
It is the responsibility of the user to configure compatible Units of Measure.
50+
51+
The Sales Multiple UoM must belong to the same UoM category as the product's
52+
sales UoM. Incorrect configuration (for example, mixing unrelated UoM
53+
categories) may lead to unexpected quantity conversions and rounding results.
54+
55+
The module assumes that Units of Measure are properly defined and
56+
conversion ratios are accurate.

0 commit comments

Comments
 (0)