Skip to content

Commit

Permalink
[ADD] website_sale_cart_quantity_shop: Add plus and minus buttons in …
Browse files Browse the repository at this point in the history
…product list in cart
  • Loading branch information
unaiberis committed Aug 21, 2024
1 parent ec0a281 commit d34d819
Show file tree
Hide file tree
Showing 11 changed files with 462 additions and 0 deletions.
6 changes: 6 additions & 0 deletions setup/website_sale_cart_quantity_shop/setup.py
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,
)
59 changes: 59 additions & 0 deletions website_sale_cart_quantity_shop/README.rst
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>.
5 changes: 5 additions & 0 deletions website_sale_cart_quantity_shop/__init__.py
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
24 changes: 24 additions & 0 deletions website_sale_cart_quantity_shop/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Ooops
# Cetmix
# 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",
}
4 changes: 4 additions & 0 deletions website_sale_cart_quantity_shop/controllers/__init__.py
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 website_sale_cart_quantity_shop/controllers/website_sale.py
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)

Check warning on line 29 in website_sale_cart_quantity_shop/controllers/website_sale.py

View check run for this annotation

Codecov / codecov/patch

website_sale_cart_quantity_shop/controllers/website_sale.py#L28-L29

Added lines #L28 - L29 were not covered by tests

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

Check warning on line 54 in website_sale_cart_quantity_shop/controllers/website_sale.py

View check run for this annotation

Codecov / codecov/patch

website_sale_cart_quantity_shop/controllers/website_sale.py#L53-L54

Added lines #L53 - L54 were not covered by tests

if not display:
return value

Check warning on line 57 in website_sale_cart_quantity_shop/controllers/website_sale.py

View check run for this annotation

Codecov / codecov/patch

website_sale_cart_quantity_shop/controllers/website_sale.py#L57

Added line #L57 was not covered by tests

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
14 changes: 14 additions & 0 deletions website_sale_cart_quantity_shop/hooks.py
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'
"""
)
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;
});
},
});
}
});
Loading

0 comments on commit d34d819

Please sign in to comment.