-
-
Notifications
You must be signed in to change notification settings - Fork 498
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] website_sale_cart_quantity_shop: Add plus and minus buttons in …
…product list in cart
- Loading branch information
Showing
11 changed files
with
460 additions
and
0 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
setup/website_sale_cart_quantity_shop/odoo/addons/website_sale_cart_quantity_shop
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 @@ | ||
../../../../website_sale_cart_quantity_shop |
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,6 @@ | ||
import setuptools | ||
|
||
setuptools.setup( | ||
setup_requires=['setuptools-odoo'], | ||
odoo_addon=True, | ||
) |
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,59 @@ | ||
.. image:: https://pbs.twimg.com/profile_images/547133733149483008/0JKHr3Av_400x400.png | ||
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html | ||
:alt: License: AGPL-3 | ||
|
||
Website Sale Cart Quantity Shop | ||
======================================= | ||
|
||
- **Plus and Minus Buttons**: | ||
- Users can increase or decrease the quantity of a product by clicking the plus (+) or minus (−) buttons next to the quantity input field. | ||
- Clicking the plus button increments the quantity by 1. | ||
- Clicking the minus button decrements the quantity by 1, with a minimum value of 0. | ||
|
||
- **Direct Input**: | ||
- Users can manually enter the desired quantity directly into the input box in the middle of the buttons. | ||
- The input field validates and updates the quantity based on user input. | ||
|
||
- **Dynamic Updates**: | ||
- The quantity input field dynamically updates the cart when the quantity is changed using either the buttons or by direct input. | ||
- The system ensures that the quantity displayed is in sync with the quantity available in the cart. | ||
|
||
- **Visual Feedback**: | ||
- The input field's background color and text color change when the quantity matches the available stock, providing visual feedback to the user. | ||
|
||
Usage | ||
===== | ||
|
||
1. **Navigate to the Shop Page**: | ||
- Go to your shop or category page in the Odoo eCommerce interface. | ||
|
||
2. **Adjust Product Quantity**: | ||
- Use the plus (+) button to increase the quantity by 1. | ||
- Use the minus (−) button to decrease the quantity by 1, ensuring the quantity does not drop below 0. | ||
- Enter a specific quantity directly into the input field to set the desired amount. | ||
|
||
3. **Visual Feedback**: | ||
- Observe changes in the input field color to reflect the available stock. | ||
|
||
Configuration | ||
============= | ||
|
||
No additional configuration is required. The module integrates seamlessly with the existing product quantity functionality on the shop page. | ||
|
||
Bug Tracker | ||
=========== | ||
|
||
Bugs are tracked on `GitHub Issues <https://github.com/avanzosc/odoo-addons/issues>`_. If you encounter any issues, please check there to see if your issue has already been reported. If not, provide detailed feedback to help us resolve it. | ||
|
||
Credits | ||
======= | ||
|
||
Contributors | ||
------------ | ||
* Unai Beristain <[email protected]> | ||
|
||
Do not contact contributors directly about support or help with technical issues. | ||
|
||
License | ||
======= | ||
This project is licensed under the AGPL-3 License. For more details, please refer to the LICENSE file or visit <http://www.gnu.org/licenses/agpl-3.0-standalone.html>. |
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,5 @@ | ||
############################################################################### | ||
# For copyright and license notices, see __manifest__.py file in root directory | ||
############################################################################### | ||
from .hooks import pre_init_hook | ||
from . import controllers |
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,22 @@ | ||
# Copyright 2024 Unai Beristain - AvanzOSC | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
||
{ | ||
"name": "Website Sale Cart Quantity Shop", | ||
"summary": "Choose cart quantity from shop page", | ||
"category": "Website", | ||
"version": "14.0.1.1.0", | ||
"author": "AvanzOSC, Odoo Community Association (OCA)", | ||
"website": "https://github.com/OCA/e-commerce", | ||
"license": "AGPL-3", | ||
"depends": [ | ||
"stock", | ||
"website_sale", | ||
], | ||
"data": [ | ||
"views/assets.xml", | ||
"views/website_sale.xml", | ||
], | ||
"installable": True, | ||
"pre_init_hook": "pre_init_hook", | ||
} |
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,4 @@ | ||
############################################################################### | ||
# For copyright and license notices, see __manifest__.py file in root directory | ||
############################################################################### | ||
from . import website_sale |
93 changes: 93 additions & 0 deletions
93
website_sale_cart_quantity_shop/controllers/website_sale.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 |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# Copyright 2024 Unai Beristain - AvanzOSC | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
||
import json | ||
|
||
from odoo import fields | ||
from odoo.http import request, route | ||
|
||
from odoo.addons.website_sale.controllers.main import WebsiteSaleForm | ||
|
||
|
||
class WebsiteSaleForm(WebsiteSaleForm): | ||
@route( | ||
["/shop/cart/update_json_from_shop"], | ||
type="json", | ||
auth="public", | ||
methods=["POST"], | ||
website=True, | ||
csrf=False, | ||
) | ||
def cart_update_json_from_shop( | ||
self, product_id, line_id=None, add_qty=1, set_qty=0, display=True, **kw | ||
): | ||
|
||
sale_order = request.website.sale_get_order(force_create=True) | ||
|
||
if sale_order.state != "draft": | ||
request.session["sale_order_id"] = None | ||
sale_order = request.website.sale_get_order(force_create=True) | ||
|
||
product_custom_attribute_values = None | ||
if kw.get("product_custom_attribute_values"): | ||
product_custom_attribute_values = json.loads( | ||
kw.get("product_custom_attribute_values") | ||
) | ||
|
||
no_variant_attribute_values = None | ||
if kw.get("no_variant_attribute_values"): | ||
no_variant_attribute_values = json.loads( | ||
kw.get("no_variant_attribute_values") | ||
) | ||
|
||
value = sale_order._cart_update( | ||
product_id=product_id, | ||
line_id=line_id, | ||
add_qty=add_qty, | ||
set_qty=set_qty, | ||
product_custom_attribute_values=product_custom_attribute_values, | ||
no_variant_attribute_values=no_variant_attribute_values, | ||
) | ||
value["cart_quantity"] = sale_order.cart_quantity | ||
if not sale_order.cart_quantity: | ||
request.website.sale_reset() | ||
return value | ||
|
||
if not display: | ||
return value | ||
|
||
value["website_sale.cart_lines"] = request.env["ir.ui.view"]._render_template( | ||
"website_sale.cart_lines", | ||
{ | ||
"website_sale_order": sale_order, | ||
"date": fields.Date.today(), | ||
"suggested_products": sale_order._cart_accessories(), | ||
}, | ||
) | ||
value["website_sale.short_cart_summary"] = request.env[ | ||
"ir.ui.view" | ||
]._render_template( | ||
"website_sale.short_cart_summary", | ||
{ | ||
"website_sale_order": sale_order, | ||
}, | ||
) | ||
|
||
order_line = ( | ||
sale_order.sudo().order_line.filtered( | ||
lambda line: line.product_id.id == product_id | ||
) | ||
if sale_order and sale_order.order_line | ||
else [] | ||
) | ||
|
||
value["product_cart_qty"] = ( | ||
int(order_line[0].sudo().product_uom_qty) | ||
if order_line and order_line[0].product_uom_qty | ||
else 0 | ||
) | ||
|
||
product = request.env["product.product"].sudo().browse(product_id) | ||
value["product_available_qty"] = product.qty_available - product.outgoing_qty | ||
|
||
return value |
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,14 @@ | ||
# Copyright 2024 Unai Beristain - AvanzOSC | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
||
|
||
# Add to Cart button on product page | ||
# Necessary to know product_id to add to cart | ||
def pre_init_hook(cr): | ||
cr.execute( | ||
""" | ||
UPDATE ir_ui_view | ||
SET active = TRUE | ||
WHERE key = 'website_sale.products_add_to_cart' | ||
""" | ||
) |
179 changes: 179 additions & 0 deletions
179
website_sale_cart_quantity_shop/static/src/js/recalculate_product_qty.js
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,179 @@ | ||
// Copyright 2024 Unai Beristain - AvanzOSC | ||
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
odoo.define("website_sale_cart_quantity_shop.recalculate_product_qty", function ( | ||
require | ||
) { | ||
"use strict"; | ||
|
||
const publicWidget = require("web.public.widget"); | ||
const wSaleUtils = require("website_sale.utils"); | ||
|
||
// Define a flag to track if an RPC call is in progress | ||
let rpcInProgress = false; | ||
|
||
function isCategoryPage() { | ||
const url = window.location.href; | ||
return ( | ||
url.includes("/category/") || | ||
(url.includes("/shop") && !url.includes("/shop/")) || | ||
url.includes("/shop/page/") | ||
); | ||
} | ||
|
||
if (isCategoryPage()) { | ||
$(document).ready(function () { | ||
$("input.form-control.quantity").each(function () { | ||
var newValue = parseInt($(this).val().replace(",", "."), 10) || 0; | ||
$(this).val(newValue); | ||
$(this).data("oldValue", newValue); | ||
}); | ||
}); | ||
|
||
publicWidget.registry.WebsiteSale.include({ | ||
custom_add_qty: 0, | ||
changeTriggeredByButton: false, | ||
modifiedInputField: null, | ||
oldValue: 0, | ||
newValue: 0, | ||
|
||
start: function () { | ||
this._super.apply(this, arguments); | ||
var self = this; | ||
|
||
$(".fa.fa-plus") | ||
.parent() | ||
.click(function (event) { | ||
event.preventDefault(); | ||
var inputField = $(this) | ||
.parent() | ||
.siblings("input.form-control.quantity"); | ||
self.modifiedInputField = inputField; | ||
self.oldValue = parseInt(inputField.val(), 10) || 0; | ||
self.newValue = self.oldValue + 1; | ||
inputField.data("oldValue", self.newValue); | ||
self.custom_add_qty = 1; | ||
self.changeTriggeredByButton = true; | ||
self._onClickAdd(event); | ||
}); | ||
|
||
$(".fa.fa-minus") | ||
.parent() | ||
.click(function (event) { | ||
event.preventDefault(); | ||
var inputField = $(this) | ||
.parent() | ||
.siblings("input.form-control.quantity"); | ||
self.modifiedInputField = inputField; | ||
self.oldValue = parseInt(inputField.val(), 10) || 0; | ||
self.newValue = Math.max(self.oldValue - 1, 0); | ||
inputField.data("oldValue", self.newValue); | ||
self.custom_add_qty = -1; | ||
self.changeTriggeredByButton = true; | ||
self._onClickAdd(event); | ||
}); | ||
|
||
$("input.form-control.quantity").on("change", function (event) { | ||
self.modifiedInputField = $(this); | ||
|
||
if (self.changeTriggeredByButton) { | ||
self.changeTriggeredByButton = false; | ||
} else { | ||
self.oldValue = $(this).data("oldValue") || 0; | ||
self.newValue = | ||
parseInt($(this).val().replace(",", "."), 10) || 0; | ||
|
||
if (self.newValue < 0 || isNaN(self.newValue)) { | ||
self.newValue = 0; | ||
} | ||
|
||
$(this).val(self.newValue); | ||
$(this).data("oldValue", self.newValue); | ||
self.custom_add_qty = self.newValue - self.oldValue; | ||
self._onClickAdd(event); | ||
} | ||
}); | ||
}, | ||
|
||
_onClickAdd: function (ev) { | ||
this.isDynamic = true; | ||
this.pageType = $(ev.currentTarget).data("page-type"); | ||
this.targetEl = $(ev.currentTarget); | ||
this._super.apply(this, arguments); | ||
}, | ||
|
||
_submitForm: function () { | ||
if (rpcInProgress) { | ||
console.log( | ||
"An RPC call is already in progress. Skipping this call." | ||
); | ||
return Promise.resolve(); | ||
} | ||
|
||
rpcInProgress = true; | ||
|
||
const self = this; | ||
const params = this.rootProduct; | ||
params.add_qty = this.custom_add_qty; | ||
|
||
const $inputField = this.modifiedInputField; | ||
|
||
params.product_custom_attribute_values = JSON.stringify( | ||
params.product_custom_attribute_values | ||
); | ||
params.no_variant_attribute_values = JSON.stringify( | ||
params.no_variant_attribute_values | ||
); | ||
|
||
if (this.isBuyNow) { | ||
params.express = true; | ||
} | ||
|
||
return this._rpc({ | ||
route: "/shop/cart/update_json_from_shop", | ||
params: params, | ||
}) | ||
.then((data) => { | ||
self.oldValue = parseInt($inputField.val(), 10) || 0; | ||
self.newValue = parseInt(data.product_cart_qty, 10) || 0; | ||
$inputField.data("oldValue", self.newValue); | ||
$inputField.val(self.newValue); | ||
self.changeTriggeredByButton = true; | ||
|
||
if ( | ||
data.product_cart_qty == data.product_available_qty && | ||
data.product_cart_qty != 0 | ||
) { | ||
$inputField.css({ | ||
color: "white", | ||
"background-color": "black", | ||
"font-weight": "bold", | ||
}); | ||
} else { | ||
$inputField.css({ | ||
color: "black", | ||
"background-color": "white", | ||
"font-weight": "normal", | ||
}); | ||
} | ||
|
||
wSaleUtils.updateCartNavBar(data); | ||
const $navButton = $("header .o_wsale_my_cart").parent(); | ||
let el = $(); | ||
if (self.pageType === "product") { | ||
el = $("#o-carousel-product"); | ||
} | ||
if (self.pageType === "products") { | ||
el = self.targetEl.parents(".o_wsale_product_grid_wrapper"); | ||
} | ||
wSaleUtils.animateClone($navButton, el, 25, 40); | ||
|
||
rpcInProgress = false; | ||
}) | ||
.catch((error) => { | ||
console.error("Error occurred during RPC call:", error); | ||
rpcInProgress = false; | ||
}); | ||
}, | ||
}); | ||
} | ||
}); |
Oops, something went wrong.