add website_decimal_quantity
42
website_decimal_quantity/README.rst
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
|
||||||
|
Website Decimal Quantity
|
||||||
|
========================
|
||||||
|
This Module allows customers to set website-shop
|
||||||
|
product quantities in decimal.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
* To enable ordering in decimals, you must configure the ‘Allow Decimal Quantity’ field in the permitted units of measure categories.
|
||||||
|
|
||||||
|
Company
|
||||||
|
-------
|
||||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
* Developers: Vivek v16 @ cybrosys, Contact: odoo@cybrosys.com
|
||||||
|
Contacts
|
||||||
|
--------
|
||||||
|
* Mail Contact : odoo@cybrosys.com
|
||||||
|
* Website : https://cybrosys.com
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
-----------
|
||||||
|
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported.
|
||||||
|
|
||||||
|
Maintainer
|
||||||
|
==========
|
||||||
|
.. image:: https://cybrosys.com/images/logo.png
|
||||||
|
:target: https://cybrosys.com
|
||||||
|
|
||||||
|
This module is maintained by Cybrosys Technologies.
|
||||||
|
|
||||||
|
For support and more information, please visit `Our Website <https://cybrosys.com/>`__
|
||||||
|
|
||||||
|
Further information
|
||||||
|
===================
|
||||||
|
HTML Description: `<static/description/index.html>`__
|
||||||
23
website_decimal_quantity/__init__.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Vivek @ cybrosys,(odoo@cybrosys.com)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
#############################################################################
|
||||||
|
from . import controllers
|
||||||
|
from . import models
|
||||||
53
website_decimal_quantity/__manifest__.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Vivek @ cybrosys,(odoo@cybrosys.com)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
#############################################################################
|
||||||
|
{
|
||||||
|
"name": "Website Decimal Quantity",
|
||||||
|
"version": "16.0.1.0.0",
|
||||||
|
"category": "Website/Website",
|
||||||
|
"summary": "The app allows to select quantity in decimal for products in Website/Shop",
|
||||||
|
"description": """
|
||||||
|
The app allows to select quantity in decimal for products in Website/Shop
|
||||||
|
""",
|
||||||
|
"author": "Cybrosys Techno Solutions",
|
||||||
|
"company": "Cybrosys Techno Solutions",
|
||||||
|
"maintainer": "Cybrosys Techno Solutions",
|
||||||
|
"images": ["static/description/banner.png"],
|
||||||
|
"website": "https://www.cybrosys.com",
|
||||||
|
"depends": ["website_sale", "uom"],
|
||||||
|
"data": [
|
||||||
|
"security/ir.model.access.csv",
|
||||||
|
"views/uom_category_views.xml",
|
||||||
|
"views/website_sale_templates.xml",
|
||||||
|
"data/uom_category_data.xml",
|
||||||
|
],
|
||||||
|
"assets": {
|
||||||
|
"web.assets_frontend": [
|
||||||
|
"/website_decimal_quantity/static/src/js/variant_mixin.js",
|
||||||
|
"/website_decimal_quantity/static/src/js/website_sale.js",
|
||||||
|
"/website_decimal_quantity/static/src/js/website_sale_utils.js",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"installable": True,
|
||||||
|
"auto_install": False,
|
||||||
|
"application": False,
|
||||||
|
}
|
||||||
23
website_decimal_quantity/controllers/__init__.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Vivek @ cybrosys,(odoo@cybrosys.com)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
#############################################################################
|
||||||
|
from . import main
|
||||||
|
from . import website_sale_product_configurator
|
||||||
202
website_decimal_quantity/controllers/main.py
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Vivek @ cybrosys,(odoo@cybrosys.com)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
#############################################################################
|
||||||
|
from odoo import fields
|
||||||
|
from odoo import http
|
||||||
|
from odoo.http import request
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
from odoo.tools.json import scriptsafe as json_scriptsafe
|
||||||
|
from odoo.addons.payment import utils as payment_utils
|
||||||
|
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||||
|
|
||||||
|
|
||||||
|
class WebsiteSaleDecimal(WebsiteSale):
|
||||||
|
"""
|
||||||
|
WebsiteSaleDecimal extends WebsiteSale class to update the methods: cart, cart_update,
|
||||||
|
cart_update_json and cart_quantity.
|
||||||
|
"""
|
||||||
|
@http.route()
|
||||||
|
def cart(self, access_token=None, revive='', **post):
|
||||||
|
"""
|
||||||
|
Main cart management + abandoned cart revival
|
||||||
|
access_token: Abandoned cart SO access token
|
||||||
|
revive: Revival method when abandoned cart. Can be 'merge' or 'squash'
|
||||||
|
"""
|
||||||
|
order = request.website.sale_get_order()
|
||||||
|
if order and order.state != 'draft':
|
||||||
|
request.session['sale_order_id'] = None
|
||||||
|
order = request.website.sale_get_order()
|
||||||
|
request.session['website_sale_cart_quantity'] = \
|
||||||
|
round(sum(order.mapped('website_order_line.product_uom_qty')), 1)
|
||||||
|
|
||||||
|
values = {}
|
||||||
|
if access_token:
|
||||||
|
abandoned_order = request.env['sale.order'].sudo().search(
|
||||||
|
[('access_token', '=', access_token)], limit=1)
|
||||||
|
if not abandoned_order:
|
||||||
|
raise NotFound()
|
||||||
|
if abandoned_order.state != 'draft':
|
||||||
|
values.update({'abandoned_proceed': True})
|
||||||
|
elif revive == 'squash' or (
|
||||||
|
revive == 'merge' and not request.session.get(
|
||||||
|
'sale_order_id')):
|
||||||
|
request.session['sale_order_id'] = abandoned_order.id
|
||||||
|
return request.redirect('/shop/cart')
|
||||||
|
elif revive == 'merge':
|
||||||
|
abandoned_order.order_line.write(
|
||||||
|
{'order_id': request.session['sale_order_id']})
|
||||||
|
abandoned_order.action_cancel()
|
||||||
|
elif abandoned_order.id != request.session.get(
|
||||||
|
'sale_order_id'):
|
||||||
|
values.update({'access_token': abandoned_order.access_token})
|
||||||
|
|
||||||
|
values.update({
|
||||||
|
'website_sale_order': order,
|
||||||
|
'date': fields.Date.today(),
|
||||||
|
'suggested_products': [],
|
||||||
|
})
|
||||||
|
if order:
|
||||||
|
order.order_line.filtered(
|
||||||
|
lambda l: not l.product_id.active).unlink()
|
||||||
|
values['suggested_products'] = order._cart_accessories()
|
||||||
|
values.update(self._get_express_shop_payment_values(order))
|
||||||
|
|
||||||
|
if post.get('type') == 'popover':
|
||||||
|
# force no-cache so IE11 doesn't cache this XHR
|
||||||
|
return request.render("website_sale.cart_popover", values,
|
||||||
|
headers={'Cache-Control': 'no-cache'})
|
||||||
|
|
||||||
|
return request.render("website_sale.cart", values)
|
||||||
|
|
||||||
|
@http.route()
|
||||||
|
def cart_update(
|
||||||
|
self, product_id, add_qty=1, set_qty=0,
|
||||||
|
product_custom_attribute_values=None, no_variant_attribute_values=None,
|
||||||
|
express=False, **kwargs
|
||||||
|
):
|
||||||
|
"""This route is called when adding a product to cart (no options)."""
|
||||||
|
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)
|
||||||
|
|
||||||
|
if product_custom_attribute_values:
|
||||||
|
product_custom_attribute_values = \
|
||||||
|
json_scriptsafe.loads(product_custom_attribute_values)
|
||||||
|
|
||||||
|
if no_variant_attribute_values:
|
||||||
|
no_variant_attribute_values = \
|
||||||
|
json_scriptsafe.loads(no_variant_attribute_values)
|
||||||
|
|
||||||
|
sale_order._cart_update(
|
||||||
|
product_id=int(product_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,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
request.session['website_sale_cart_quantity'] = round(
|
||||||
|
sum(sale_order.mapped('website_order_line.product_uom_qty')), 1)
|
||||||
|
|
||||||
|
if express:
|
||||||
|
return request.redirect("/shop/checkout?express=1")
|
||||||
|
|
||||||
|
return request.redirect("/shop/cart")
|
||||||
|
|
||||||
|
@http.route()
|
||||||
|
def cart_update_json(
|
||||||
|
self, product_id, line_id=None, add_qty=None, set_qty=None,
|
||||||
|
display=True, product_custom_attribute_values=None,
|
||||||
|
no_variant_attribute_values=None, **kw
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
This route is called :
|
||||||
|
- When changing quantity from the cart.
|
||||||
|
- When adding a product from the wishlist.
|
||||||
|
- When adding a product to cart on the same page
|
||||||
|
(without redirection).
|
||||||
|
"""
|
||||||
|
order = request.website.sale_get_order(force_create=True)
|
||||||
|
if order.state != 'draft':
|
||||||
|
request.website.sale_reset()
|
||||||
|
if kw.get('force_create'):
|
||||||
|
order = request.website.sale_get_order(force_create=True)
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if product_custom_attribute_values:
|
||||||
|
product_custom_attribute_values = \
|
||||||
|
json_scriptsafe.loads(product_custom_attribute_values)
|
||||||
|
|
||||||
|
if no_variant_attribute_values:
|
||||||
|
no_variant_attribute_values = \
|
||||||
|
json_scriptsafe.loads(no_variant_attribute_values)
|
||||||
|
|
||||||
|
values = 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,
|
||||||
|
**kw
|
||||||
|
)
|
||||||
|
request.session['website_sale_cart_quantity'] = round(
|
||||||
|
sum(order.mapped('website_order_line.product_uom_qty')), 1)
|
||||||
|
|
||||||
|
if not order.cart_quantity:
|
||||||
|
request.website.sale_reset()
|
||||||
|
return values
|
||||||
|
|
||||||
|
values['cart_quantity'] = round(
|
||||||
|
sum(order.mapped('website_order_line.product_uom_qty')), 1)
|
||||||
|
values['minor_amount'] = payment_utils.to_minor_currency_units(
|
||||||
|
order.amount_total, order.currency_id
|
||||||
|
),
|
||||||
|
values['amount'] = order.amount_total
|
||||||
|
|
||||||
|
if not display:
|
||||||
|
return values
|
||||||
|
|
||||||
|
values['website_sale.cart_lines'] = \
|
||||||
|
request.env['ir.ui.view']._render_template(
|
||||||
|
"website_sale.cart_lines", {
|
||||||
|
'website_sale_order': order,
|
||||||
|
'date': fields.Date.today(),
|
||||||
|
'suggested_products': order._cart_accessories()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
values['website_sale.short_cart_summary'] = \
|
||||||
|
request.env['ir.ui.view']._render_template(
|
||||||
|
"website_sale.short_cart_summary", {'website_sale_order': order}
|
||||||
|
)
|
||||||
|
return values
|
||||||
|
|
||||||
|
@http.route()
|
||||||
|
def cart_quantity(self):
|
||||||
|
"""
|
||||||
|
This method updates the cart quantity count in the session based on the order's website order lines.
|
||||||
|
"""
|
||||||
|
if 'website_sale_cart_quantity' not in request.session:
|
||||||
|
return request.website.sale_get_order().mapped(
|
||||||
|
'website_order_line.product_uom_qty')
|
||||||
|
return request.session['website_sale_cart_quantity']
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Vivek @ cybrosys,(odoo@cybrosys.com)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
#############################################################################
|
||||||
|
import json
|
||||||
|
from odoo import http
|
||||||
|
from odoo.http import request
|
||||||
|
from odoo.addons.website_sale_product_configurator.controllers.main import \
|
||||||
|
WebsiteSale
|
||||||
|
|
||||||
|
|
||||||
|
class WebsiteSaleDecimal(WebsiteSale):
|
||||||
|
"""
|
||||||
|
WebsiteSaleDecimal extends WebsiteSale class to update the methods: cart_options_update_json.
|
||||||
|
"""
|
||||||
|
@http.route()
|
||||||
|
def cart_options_update_json(self, product_and_options, goto_shop=None,
|
||||||
|
lang=None, **kwargs):
|
||||||
|
"""This route is called when submitting the optional product modal.
|
||||||
|
The product without parent is the main product, the other are
|
||||||
|
options.
|
||||||
|
Options need to be linked to their parents with a unique ID.
|
||||||
|
The main product is the first product in the list and the options
|
||||||
|
need to be right after their parent.
|
||||||
|
product_and_options {
|
||||||
|
'product_id',
|
||||||
|
'product_template_id',
|
||||||
|
'quantity',
|
||||||
|
'parent_unique_id',
|
||||||
|
'unique_id',
|
||||||
|
'product_custom_attribute_values',
|
||||||
|
'no_variant_attribute_values'
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if lang:
|
||||||
|
request.website = request.website.with_context(lang=lang)
|
||||||
|
|
||||||
|
order = request.website.sale_get_order(force_create=True)
|
||||||
|
if order.state != 'draft':
|
||||||
|
request.session['sale_order_id'] = None
|
||||||
|
order = request.website.sale_get_order(force_create=True)
|
||||||
|
|
||||||
|
product_and_options = json.loads(product_and_options)
|
||||||
|
if product_and_options:
|
||||||
|
# The main product is the first, optional products are the rest
|
||||||
|
main_product = product_and_options[0]
|
||||||
|
value = order._cart_update(
|
||||||
|
product_id=main_product['product_id'],
|
||||||
|
add_qty=main_product['quantity'],
|
||||||
|
product_custom_attribute_values=main_product[
|
||||||
|
'product_custom_attribute_values'],
|
||||||
|
no_variant_attribute_values=main_product[
|
||||||
|
'no_variant_attribute_values'],
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
if value['line_id']:
|
||||||
|
# Link option with its parent iff line has been created.
|
||||||
|
option_parent = {main_product['unique_id']: value['line_id']}
|
||||||
|
for option in product_and_options[1:]:
|
||||||
|
parent_unique_id = option['parent_unique_id']
|
||||||
|
option_value = order._cart_update(
|
||||||
|
product_id=option['product_id'],
|
||||||
|
set_qty=option['quantity'],
|
||||||
|
linked_line_id=option_parent[parent_unique_id],
|
||||||
|
product_custom_attribute_values=option[
|
||||||
|
'product_custom_attribute_values'],
|
||||||
|
no_variant_attribute_values=option[
|
||||||
|
'no_variant_attribute_values'],
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
option_parent[option['unique_id']] = option_value['line_id']
|
||||||
|
request.session['website_sale_cart_quantity'] = round(
|
||||||
|
sum(order.mapped('website_order_line.product_uom_qty')), 1)
|
||||||
|
|
||||||
|
return str(round(
|
||||||
|
sum(order.mapped('website_order_line.product_uom_qty')), 1))
|
||||||
19
website_decimal_quantity/data/uom_category_data.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<!-- Set allow_decimal_quantity to True for Weight category -->
|
||||||
|
<record id="uom.product_uom_categ_kgm" model="uom.category">
|
||||||
|
<field name="allow_decimal_quantity">True</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Set allow_decimal_quantity to True for Volume category -->
|
||||||
|
<record id="uom.product_uom_categ_vol" model="uom.category">
|
||||||
|
<field name="allow_decimal_quantity">True</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Set allow_decimal_quantity to False for Unit category (default is False anyway) -->
|
||||||
|
<record id="uom.product_uom_categ_unit" model="uom.category">
|
||||||
|
<field name="allow_decimal_quantity">False</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
6
website_decimal_quantity/doc/RELEASE_NOTES.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
## Module <website_decimal_quantity>
|
||||||
|
|
||||||
|
#### 15.02.2023
|
||||||
|
#### Version 16.0.1.0.0
|
||||||
|
#### ADD
|
||||||
|
- Initial Commit for Website Decimal Quantity.
|
||||||
76
website_decimal_quantity/i18n/ca_ES.po
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * website_decimal_quantity
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2025-11-04 17:32+0000\n"
|
||||||
|
"PO-Revision-Date: 2025-11-04 17:32+0000\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: ca_ES\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model.fields,field_description:website_decimal_quantity.field_uom_category__allow_decimal_quantity
|
||||||
|
msgid "Allow Decimal Quantity"
|
||||||
|
msgstr "Permetre Quantitat Decimal"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model.fields,help:website_decimal_quantity.field_uom_category__allow_decimal_quantity
|
||||||
|
msgid ""
|
||||||
|
"If checked, products with this unit of measure category will allow decimal "
|
||||||
|
"quantities in the website shop."
|
||||||
|
msgstr ""
|
||||||
|
"Si està marcat, els productes amb aquesta categoria d'unitat de mesura "
|
||||||
|
"permetran quantitats decimals a la botiga del lloc web."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "It is forbidden to modify a sales order which is not in draft status."
|
||||||
|
msgstr "Està prohibit modificar una comanda de venda que no està en estat esborrany."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model,name:website_decimal_quantity.model_uom_category
|
||||||
|
msgid "Product UoM Categories"
|
||||||
|
msgstr "Categories d'UdM de Producte"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model,name:website_decimal_quantity.model_sale_order
|
||||||
|
msgid "Sales Order"
|
||||||
|
msgstr "Comanda de Venda"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "The given product does not exist therefore it cannot be added to cart."
|
||||||
|
msgstr "El producte indicat no existeix, per tant no es pot afegir al carret."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"The given product does not have a price therefore it cannot be added to "
|
||||||
|
"cart."
|
||||||
|
msgstr ""
|
||||||
|
"El producte indicat no té preu, per tant no es pot afegir al "
|
||||||
|
"carret."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_decimal_quantity.cart_popover_decimal
|
||||||
|
msgid "View Cart ("
|
||||||
|
msgstr "Veure Carret ("
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_decimal_quantity.cart_popover_decimal
|
||||||
|
msgid "item(s))"
|
||||||
|
msgstr "article(s))"
|
||||||
76
website_decimal_quantity/i18n/es.po
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * website_decimal_quantity
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2025-11-04 17:32+0000\n"
|
||||||
|
"PO-Revision-Date: 2025-11-04 17:32+0000\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: es\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model.fields,field_description:website_decimal_quantity.field_uom_category__allow_decimal_quantity
|
||||||
|
msgid "Allow Decimal Quantity"
|
||||||
|
msgstr "Permitir Cantidad Decimal"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model.fields,help:website_decimal_quantity.field_uom_category__allow_decimal_quantity
|
||||||
|
msgid ""
|
||||||
|
"If checked, products with this unit of measure category will allow decimal "
|
||||||
|
"quantities in the website shop."
|
||||||
|
msgstr ""
|
||||||
|
"Si está marcado, los productos con esta categoría de unidad de medida "
|
||||||
|
"permitirán cantidades decimales en la tienda del sitio web."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "It is forbidden to modify a sales order which is not in draft status."
|
||||||
|
msgstr "Está prohibido modificar un pedido de venta que no está en estado borrador."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model,name:website_decimal_quantity.model_uom_category
|
||||||
|
msgid "Product UoM Categories"
|
||||||
|
msgstr "Categorías de UdM de Producto"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model,name:website_decimal_quantity.model_sale_order
|
||||||
|
msgid "Sales Order"
|
||||||
|
msgstr "Pedido de Venta"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "The given product does not exist therefore it cannot be added to cart."
|
||||||
|
msgstr "El producto indicado no existe, por lo tanto no se puede añadir al carrito."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"The given product does not have a price therefore it cannot be added to "
|
||||||
|
"cart."
|
||||||
|
msgstr ""
|
||||||
|
"El producto indicado no tiene precio, por lo tanto no se puede añadir al "
|
||||||
|
"carrito."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_decimal_quantity.cart_popover_decimal
|
||||||
|
msgid "View Cart ("
|
||||||
|
msgstr "Ver Carrito ("
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_decimal_quantity.cart_popover_decimal
|
||||||
|
msgid "item(s))"
|
||||||
|
msgstr "artículo(s))"
|
||||||
76
website_decimal_quantity/i18n/gl_ES.po
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * website_decimal_quantity
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2025-11-04 17:32+0000\n"
|
||||||
|
"PO-Revision-Date: 2025-11-04 17:32+0000\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: gl_ES\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model.fields,field_description:website_decimal_quantity.field_uom_category__allow_decimal_quantity
|
||||||
|
msgid "Allow Decimal Quantity"
|
||||||
|
msgstr "Permitir Cantidade Decimal"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model.fields,help:website_decimal_quantity.field_uom_category__allow_decimal_quantity
|
||||||
|
msgid ""
|
||||||
|
"If checked, products with this unit of measure category will allow decimal "
|
||||||
|
"quantities in the website shop."
|
||||||
|
msgstr ""
|
||||||
|
"Se está marcado, os produtos con esta categoría de unidade de medida "
|
||||||
|
"permitirán cantidades decimais na tenda do sitio web."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "It is forbidden to modify a sales order which is not in draft status."
|
||||||
|
msgstr "Está prohibido modificar un pedido de venda que non está en estado borrador."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model,name:website_decimal_quantity.model_uom_category
|
||||||
|
msgid "Product UoM Categories"
|
||||||
|
msgstr "Categorías de UdM de Produto"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model,name:website_decimal_quantity.model_sale_order
|
||||||
|
msgid "Sales Order"
|
||||||
|
msgstr "Pedido de Venda"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "The given product does not exist therefore it cannot be added to cart."
|
||||||
|
msgstr "O produto indicado non existe, polo tanto non se pode engadir ao carriño."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"The given product does not have a price therefore it cannot be added to "
|
||||||
|
"cart."
|
||||||
|
msgstr ""
|
||||||
|
"O produto indicado non ten prezo, polo tanto non se pode engadir ao "
|
||||||
|
"carriño."
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_decimal_quantity.cart_popover_decimal
|
||||||
|
msgid "View Cart ("
|
||||||
|
msgstr "Ver Carriño ("
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_decimal_quantity.cart_popover_decimal
|
||||||
|
msgid "item(s))"
|
||||||
|
msgstr "artigo(s))"
|
||||||
71
website_decimal_quantity/i18n/website_decimal_quantity.pot
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * website_decimal_quantity
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2025-11-04 17:32+0000\n"
|
||||||
|
"PO-Revision-Date: 2025-11-04 17:32+0000\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model.fields,field_description:website_decimal_quantity.field_uom_category__allow_decimal_quantity
|
||||||
|
msgid "Allow Decimal Quantity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model.fields,help:website_decimal_quantity.field_uom_category__allow_decimal_quantity
|
||||||
|
msgid ""
|
||||||
|
"If checked, products with this unit of measure category will allow decimal "
|
||||||
|
"quantities in the website shop."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "It is forbidden to modify a sales order which is not in draft status."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model,name:website_decimal_quantity.model_uom_category
|
||||||
|
msgid "Product UoM Categories"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model:ir.model,name:website_decimal_quantity.model_sale_order
|
||||||
|
msgid "Sales Order"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "The given product does not exist therefore it cannot be added to cart."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_decimal_quantity/models/sale_order.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"The given product does not have a price therefore it cannot be added to "
|
||||||
|
"cart."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_decimal_quantity.cart_popover_decimal
|
||||||
|
msgid "View Cart ("
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: website_decimal_quantity
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_decimal_quantity.cart_popover_decimal
|
||||||
|
msgid "item(s))"
|
||||||
|
msgstr ""
|
||||||
23
website_decimal_quantity/models/__init__.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Vivek @ cybrosys,(odoo@cybrosys.com)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
#############################################################################
|
||||||
|
from . import sale_order
|
||||||
|
from . import uom_category
|
||||||
121
website_decimal_quantity/models/sale_order.py
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Vivek @ cybrosys,(odoo@cybrosys.com)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
#############################################################################
|
||||||
|
from odoo import api, models, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.http import request
|
||||||
|
|
||||||
|
|
||||||
|
class SaleOrder(models.Model):
|
||||||
|
_inherit = 'sale.order'
|
||||||
|
"""
|
||||||
|
Inherit the 'sale.order' model to overwrite the _compute_cart_info and _cart_update functions.
|
||||||
|
"""
|
||||||
|
@api.depends('order_line.product_uom_qty', 'order_line.product_id')
|
||||||
|
def _compute_cart_info(self):
|
||||||
|
"""
|
||||||
|
Making cart_quantity integer is avoided in order
|
||||||
|
to represent it in decimal values
|
||||||
|
"""
|
||||||
|
for order in self:
|
||||||
|
order.cart_quantity = sum(order.mapped
|
||||||
|
('website_order_line.product_uom_qty'))
|
||||||
|
order.only_services = all(line.product_id.type == 'service' for line
|
||||||
|
in order.website_order_line)
|
||||||
|
|
||||||
|
def _cart_update(self, product_id, line_id=None, add_qty=0, set_qty=0,
|
||||||
|
**kwargs):
|
||||||
|
""" Add or set product quantity, add_qty can be negative.
|
||||||
|
|
||||||
|
Making add_qty and set_qty integer are avoided in order
|
||||||
|
to represent them as decimal values.
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
if self.state != 'draft':
|
||||||
|
request.session.pop('sale_order_id', None)
|
||||||
|
request.session.pop('website_sale_cart_quantity', None)
|
||||||
|
raise UserError(_('It is forbidden to modify a sales order '
|
||||||
|
'which is not in draft status.'))
|
||||||
|
product = self.env['product.product'].browse(product_id).exists()
|
||||||
|
if not product or not product._is_add_to_cart_allowed():
|
||||||
|
raise UserError(_("The given product does not exist "
|
||||||
|
"therefore it cannot be added to cart."))
|
||||||
|
if product.lst_price == 0 and \
|
||||||
|
product.website_id.prevent_zero_price_sale:
|
||||||
|
raise UserError(_("The given product does not have a price "
|
||||||
|
"therefore it cannot be added to cart."))
|
||||||
|
if line_id is not False:
|
||||||
|
order_line = self._cart_find_product_line(product_id,
|
||||||
|
line_id, **kwargs)[:1]
|
||||||
|
else:
|
||||||
|
order_line = self.env['sale.order.line']
|
||||||
|
try:
|
||||||
|
if add_qty:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
add_qty = 1
|
||||||
|
try:
|
||||||
|
if set_qty:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
set_qty = 0
|
||||||
|
quantity = 0
|
||||||
|
if set_qty:
|
||||||
|
quantity = set_qty
|
||||||
|
elif add_qty is not None:
|
||||||
|
if order_line:
|
||||||
|
quantity = order_line.product_uom_qty + (add_qty or 0)
|
||||||
|
else:
|
||||||
|
quantity = add_qty or 0
|
||||||
|
if quantity > 0:
|
||||||
|
quantity, warning = self._verify_updated_quantity(
|
||||||
|
order_line,
|
||||||
|
product_id,
|
||||||
|
quantity,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# If the line will be removed anyway, there is no need to verify
|
||||||
|
# the requested quantity update.
|
||||||
|
warning = ''
|
||||||
|
if order_line and quantity <= 0:
|
||||||
|
# Remove zero or negative lines
|
||||||
|
order_line.unlink()
|
||||||
|
order_line = self.env['sale.order.line']
|
||||||
|
elif order_line:
|
||||||
|
# Update existing line
|
||||||
|
update_values = self._prepare_order_line_update_values(
|
||||||
|
order_line, quantity, **kwargs)
|
||||||
|
if update_values:
|
||||||
|
self._update_cart_line_values(order_line, update_values)
|
||||||
|
elif quantity >= 0:
|
||||||
|
# Create new line
|
||||||
|
order_line_values = self._prepare_order_line_values(
|
||||||
|
product_id, quantity, **kwargs)
|
||||||
|
order_line = self.env['sale.order.line'].sudo().create(
|
||||||
|
order_line_values)
|
||||||
|
return {
|
||||||
|
'line_id': order_line.id,
|
||||||
|
'quantity': quantity,
|
||||||
|
'option_ids': list(set(order_line.option_line_ids.filtered(
|
||||||
|
lambda l: l.order_id == order_line.order_id).ids)),
|
||||||
|
'warning': warning,
|
||||||
|
}
|
||||||
12
website_decimal_quantity/models/uom_category.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class UomCategory(models.Model):
|
||||||
|
_inherit = "uom.category"
|
||||||
|
|
||||||
|
allow_decimal_quantity = fields.Boolean(
|
||||||
|
string="Allow Decimal Quantity",
|
||||||
|
default=False,
|
||||||
|
help="If checked, products with this unit of measure category will allow decimal quantities in the website shop.",
|
||||||
|
)
|
||||||
3
website_decimal_quantity/security/ir.model.access.csv
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_uom_category_portal,uom.category.portal,uom.model_uom_category,base.group_portal,1,0,0,0
|
||||||
|
access_uom_category_user,uom.category.user,uom.model_uom_category,base.group_user,1,0,0,0
|
||||||
|
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 310 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 576 B |
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 653 B |
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 839 B |
|
After Width: | Height: | Size: 427 B |
|
After Width: | Height: | Size: 627 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 988 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 589 B |
BIN
website_decimal_quantity/static/description/assets/misc/logo.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 967 B |
BIN
website_decimal_quantity/static/description/assets/misc/star.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 5 KiB |
BIN
website_decimal_quantity/static/description/assets/modules/1.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
website_decimal_quantity/static/description/assets/modules/2.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
website_decimal_quantity/static/description/assets/modules/3.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
website_decimal_quantity/static/description/assets/modules/4.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
website_decimal_quantity/static/description/assets/modules/5.gif
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
website_decimal_quantity/static/description/assets/modules/6.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 122 KiB |
BIN
website_decimal_quantity/static/description/banner.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
website_decimal_quantity/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
612
website_decimal_quantity/static/description/index.html
Normal file
|
|
@ -0,0 +1,612 @@
|
||||||
|
<div style="background-color: #714B67; min-height: 600px; width: 100%; padding: 15px; position: relative;">
|
||||||
|
<!-- TITLE BAR -->
|
||||||
|
<div
|
||||||
|
style="border-bottom: 1px solid #875A7B; padding: 15px; display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<img src="assets/misc/cybrosys-logo.png" width="42" height="42" style="width: 42px; height: 42px;"/>
|
||||||
|
<div>
|
||||||
|
<div style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
|
||||||
|
class="mr-2">
|
||||||
|
<i class="fa fa-check mr-1"></i>Community
|
||||||
|
</div>
|
||||||
|
<div style="color: #875A7B; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
|
||||||
|
class="mr-2">
|
||||||
|
<i class="fa fa-check mr-1"></i>Enterprise
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END OF TITLE BAR -->
|
||||||
|
|
||||||
|
<!-- APP HERO -->
|
||||||
|
<h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;">Website
|
||||||
|
Decimal Quantity</h1>
|
||||||
|
<p style="color:#FFFFFF; padding: 8px 15px; text-align: center; font-size: 24px;">Select Decimal Product Quantity
|
||||||
|
from Website</p>
|
||||||
|
<!-- END OF APP HERO -->
|
||||||
|
<img src="assets/screenshots/hero.gif"
|
||||||
|
style="width: 75%; height: auto; position: absolute; margin-left: auto; margin-right: auto; top: 45%; left: 12%; right: auto;"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NAVIGATION SECTION -->
|
||||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px; margin-top: 300px;">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/compass.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Explore This
|
||||||
|
Module</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row my-4" style="font-family: 'Montserrat', sans-serif;">
|
||||||
|
<div class="col-sm-12 col-md-6 my-3">
|
||||||
|
<a href="#overview">
|
||||||
|
<div class="d-flex justify-content-between align-items-center"
|
||||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
|
||||||
|
<div>
|
||||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Overview</span>
|
||||||
|
<span
|
||||||
|
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">Learn
|
||||||
|
more about this
|
||||||
|
module</span>
|
||||||
|
</div>
|
||||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-6 my-3">
|
||||||
|
<a href="#configuration">
|
||||||
|
<div class="d-flex justify-content-between align-items-center"
|
||||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
|
||||||
|
<div>
|
||||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Configuration</span>
|
||||||
|
<span
|
||||||
|
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View configurations of this module</span>
|
||||||
|
</div>
|
||||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-md-6 my-3">
|
||||||
|
<a href="#features">
|
||||||
|
<div class="d-flex justify-content-between align-items-center"
|
||||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
|
||||||
|
<div>
|
||||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Features</span>
|
||||||
|
<span
|
||||||
|
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View features of this module</span>
|
||||||
|
</div>
|
||||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-6 my-3">
|
||||||
|
<a href="#screenshots">
|
||||||
|
<div class="d-flex justify-content-between align-items-center"
|
||||||
|
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
|
||||||
|
<div>
|
||||||
|
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Screenshots</span>
|
||||||
|
<span
|
||||||
|
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">
|
||||||
|
See key screenshots of this module
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<img src="assets/misc/right-arrow.png" width="36" height="36"/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END OF NAVIGATION SECTION -->
|
||||||
|
|
||||||
|
<!-- OVERVIEW SECTION -->
|
||||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="overview">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/pie-chart.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Overview
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
|
||||||
|
<div class="col-sm-12 py-4">
|
||||||
|
This module allows users to choose products from the website shop in decimal quantities. This can be helpful
|
||||||
|
for users who need to select products in decimal quantities rather than multiples of one.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END OF OVERVIEW SECTION -->
|
||||||
|
|
||||||
|
<!-- CONFIGURATION SECTION -->
|
||||||
|
<div class="d-flex align-items-center"
|
||||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"
|
||||||
|
id="configuration">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/star.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2"
|
||||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
|
||||||
|
Configuration
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row"
|
||||||
|
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
|
||||||
|
<div class="col-sm-12 py-4">
|
||||||
|
No additional configuration required.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END OF CONFIGURATION SECTION -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- FEATURES SECTION -->
|
||||||
|
<div class="d-flex align-items-center"
|
||||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"
|
||||||
|
id="features">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/features.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2"
|
||||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
|
||||||
|
Features
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row"
|
||||||
|
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
|
||||||
|
<div class="col-sm-12 col-md-6">
|
||||||
|
<div class="d-flex align-items-center"
|
||||||
|
style="margin-top: 40px; margin-bottom: 40px">
|
||||||
|
<img src="assets/misc/check-box.png" class="mr-2"/>
|
||||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Decimal Product Quantity</span>
|
||||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 450 !important; color: #282F33 !important; font-size: 1rem !important;">
|
||||||
|
Allows users to select product in decimal quantities from product and cart page in Website/Shop.</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center"
|
||||||
|
style="margin-top: 30px; margin-bottom: 30px">
|
||||||
|
<img src="assets/misc/check-box.png" class="mr-2"/>
|
||||||
|
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Simultaneous Price Calculation</span>
|
||||||
|
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 450 !important; color: #282F33 !important; font-size: 1rem !important;">
|
||||||
|
Simultaneous price calculation in the cart according to the selected decimal quantities.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END OF FEATURES SECTION -->
|
||||||
|
|
||||||
|
<!-- SCREENSHOTS SECTION -->
|
||||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="screenshots">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/pictures.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Screenshots
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
|
||||||
|
<div style="display: block; margin: 30px auto;">
|
||||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Decimal Product
|
||||||
|
Quantity in Product Page</h3>
|
||||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">Select product
|
||||||
|
quantities in decimal values from Product Page.</p>
|
||||||
|
<img src="assets/screenshots/1.png" class="img-thumbnail">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: block; margin: 30px auto;">
|
||||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Decimal Product
|
||||||
|
Quantity in Cart Page</h3>
|
||||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">Select product
|
||||||
|
quantities in decimal values from Cart Page.</p>
|
||||||
|
<img src="assets/screenshots/2.png" class="img-thumbnail">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: block; margin: 30px auto;">
|
||||||
|
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Simultaneous price
|
||||||
|
calculation in cart</h3>
|
||||||
|
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">Price is calculated in
|
||||||
|
the cart according to the decimal product values.</p>
|
||||||
|
<img src="assets/screenshots/3.png" class="img-thumbnail">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END OF SCREENSHOTS SECTION -->
|
||||||
|
|
||||||
|
<!-- RELATED PRODUCTS -->
|
||||||
|
<div class="d-flex align-items-center"
|
||||||
|
style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/categories.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2"
|
||||||
|
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
|
||||||
|
Related
|
||||||
|
Products
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div id="demo1" class="row carousel slide" data-ride="carousel">
|
||||||
|
<!-- The slideshow -->
|
||||||
|
<div class="carousel-inner" style="padding: 30px;">
|
||||||
|
<div class="carousel-item" style="min-height: 198.656px;">
|
||||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
|
||||||
|
style="float:left">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/export_stockinfo_xls/"
|
||||||
|
target="_blank">
|
||||||
|
<div style="border-radius:10px">
|
||||||
|
<img class="img img-responsive center-block"
|
||||||
|
style="border-radius: 0px;"
|
||||||
|
src="assets/modules/1.png">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
|
||||||
|
style="float:left">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/inventory_stock_dashboard_odoo/"
|
||||||
|
target="_blank">
|
||||||
|
<div style="border-radius:10px">
|
||||||
|
<img class="img img-responsive center-block"
|
||||||
|
style="border-radius: 0px;"
|
||||||
|
src="assets/modules/2.png">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
|
||||||
|
style="float:left">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/customer_product_qrcode/"
|
||||||
|
target="_blank">
|
||||||
|
<div style="border-radius:10px">
|
||||||
|
<img class="img img-responsive center-block"
|
||||||
|
style="border-radius: 0px;"
|
||||||
|
src="assets/modules/3.png">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="carousel-item active"
|
||||||
|
style="min-height: 198.656px;">
|
||||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
|
||||||
|
style="float:left">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/list_view_sticky_header/"
|
||||||
|
target="_blank">
|
||||||
|
<div style="border-radius:10px">
|
||||||
|
<img class="img img-responsive center-block"
|
||||||
|
style="border-radius: 0px;"
|
||||||
|
src="assets/modules/4.png">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
|
||||||
|
style="float:left">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/openai_product_tag_descrption/"
|
||||||
|
target="_blank">
|
||||||
|
<div style="border-radius:10px">
|
||||||
|
<img class="img img-responsive center-block"
|
||||||
|
style="border-radius: 0px;"
|
||||||
|
src="assets/modules/5.gif">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
|
||||||
|
style="float:left">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/16.0/advanced_vat_invoice/"
|
||||||
|
target="_blank">
|
||||||
|
<div style="border-radius:10px">
|
||||||
|
<img class="img img-responsive center-block"
|
||||||
|
style="border-radius: 0px;"
|
||||||
|
src="assets/modules/6.png">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Left and right controls -->
|
||||||
|
<a class="carousel-control-prev" href="#demo1" data-slide="prev"
|
||||||
|
style="width:35px; color:#000"> <span
|
||||||
|
class="carousel-control-prev-icon"><i
|
||||||
|
class="fa fa-chevron-left"
|
||||||
|
style="font-size:24px"></i></span>
|
||||||
|
</a> <a class="carousel-control-next" href="#demo1"
|
||||||
|
data-slide="next" style="width:35px; color:#000">
|
||||||
|
<span class="carousel-control-next-icon"><i
|
||||||
|
class="fa fa-chevron-right"
|
||||||
|
style="font-size:24px"></i></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END OF RELATED PRODUCTS -->
|
||||||
|
|
||||||
|
<!-- OUR SERVICES -->
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/star.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Our Services
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container my-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/cogs.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Odoo
|
||||||
|
Customization</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/wrench.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Odoo
|
||||||
|
Implementation</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/lifebuoy.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Odoo
|
||||||
|
Support</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/user.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Hire
|
||||||
|
Odoo
|
||||||
|
Developer</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/puzzle.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Odoo
|
||||||
|
Integration</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/update.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Odoo
|
||||||
|
Migration</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/consultation.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Odoo
|
||||||
|
Consultancy</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/training.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Odoo
|
||||||
|
Implementation</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
|
||||||
|
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;">
|
||||||
|
<img src="assets/icons/license.png" class="img-responsive" height="48px" width="48px">
|
||||||
|
</div>
|
||||||
|
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
|
||||||
|
Odoo
|
||||||
|
Licensing Consultancy</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- END OF END OF OUR SERVICES -->
|
||||||
|
|
||||||
|
<!-- OUR INDUSTRIES -->
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/corporate.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Our
|
||||||
|
Industries
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container my-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="my-4 d-flex flex-column justify-content-center"
|
||||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
|
||||||
|
<img src="assets/icons/trading-black.png" class="img-responsive mb-3" height="48px" width="48px">
|
||||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
|
||||||
|
Trading
|
||||||
|
</h5>
|
||||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
|
||||||
|
Easily procure
|
||||||
|
and
|
||||||
|
sell your products</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="my-4 d-flex flex-column justify-content-center"
|
||||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
|
||||||
|
<img src="assets/icons/pos-black.png" class="img-responsive mb-3" height="48px" width="48px">
|
||||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
|
||||||
|
POS
|
||||||
|
</h5>
|
||||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
|
||||||
|
Easy
|
||||||
|
configuration
|
||||||
|
and convivial experience</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="my-4 d-flex flex-column justify-content-center"
|
||||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
|
||||||
|
<img src="assets/icons/education-black.png" class="img-responsive mb-3" height="48px" width="48px">
|
||||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
|
||||||
|
Education
|
||||||
|
</h5>
|
||||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
|
||||||
|
A platform for
|
||||||
|
educational management</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="my-4 d-flex flex-column justify-content-center"
|
||||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
|
||||||
|
<img src="assets/icons/manufacturing-black.png" class="img-responsive mb-3" height="48px"
|
||||||
|
width="48px">
|
||||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
|
||||||
|
Manufacturing
|
||||||
|
</h5>
|
||||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
|
||||||
|
Plan, track and
|
||||||
|
schedule your operations</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="my-4 d-flex flex-column justify-content-center"
|
||||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
|
||||||
|
<img src="assets/icons/ecom-black.png" class="img-responsive mb-3" height="48px" width="48px">
|
||||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
|
||||||
|
E-commerce & Website
|
||||||
|
</h5>
|
||||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
|
||||||
|
Mobile
|
||||||
|
friendly,
|
||||||
|
awe-inspiring product pages</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="my-4 d-flex flex-column justify-content-center"
|
||||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
|
||||||
|
<img src="assets/icons/service-black.png" class="img-responsive mb-3" height="48px" width="48px">
|
||||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
|
||||||
|
Service Management
|
||||||
|
</h5>
|
||||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
|
||||||
|
Keep track of
|
||||||
|
services and invoice</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="my-4 d-flex flex-column justify-content-center"
|
||||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
|
||||||
|
<img src="assets/icons/restaurant-black.png" class="img-responsive mb-3" height="48px" width="48px">
|
||||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
|
||||||
|
Restaurant
|
||||||
|
</h5>
|
||||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
|
||||||
|
Run your bar or
|
||||||
|
restaurant methodically</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="my-4 d-flex flex-column justify-content-center"
|
||||||
|
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
|
||||||
|
<img src="assets/icons/hotel-black.png" class="img-responsive mb-3" height="48px" width="48px">
|
||||||
|
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
|
||||||
|
Hotel Management
|
||||||
|
</h5>
|
||||||
|
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
|
||||||
|
An
|
||||||
|
all-inclusive
|
||||||
|
hotel management application</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- END OF END OF OUR INDUSTRIES -->
|
||||||
|
|
||||||
|
<!-- SUPPORT -->
|
||||||
|
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mr-2"
|
||||||
|
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
|
||||||
|
<img src="assets/misc/customer-support.png"/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Support
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-6">
|
||||||
|
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;">
|
||||||
|
<div class="mr-4"
|
||||||
|
style="background-color: #714B67; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<img src="assets/misc/support.png" height="48" width="48" style="width: 42px; height: 42px;"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4>Need Help?</h4>
|
||||||
|
<p style="line-height: 100%;">Got questions or need help? Get in touch.</p>
|
||||||
|
<a href="mailto:odoo@cybrosys.com">
|
||||||
|
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">
|
||||||
|
odoo@cybrosys.com</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-6">
|
||||||
|
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;">
|
||||||
|
<div class="mr-4"
|
||||||
|
style="background-color: #2AC44D; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<img src="assets/misc/whatsapp.png" height="52" width="52" style="width: 52px; height: 52px;"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4>WhatsApp</h4>
|
||||||
|
<p style="line-height: 100%;">Say hi to us on WhatsApp!</p>
|
||||||
|
<a href="https://api.whatsapp.com/send?phone=918606827707">
|
||||||
|
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">+91 86068
|
||||||
|
27707</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 my-5 d-flex justify-content-center align-items-center">
|
||||||
|
<img src="assets/misc/logo.png" width="144" height="31"
|
||||||
|
style="width:144px; height: 31px; margin-top: 40px;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END OF SUPPORT -->
|
||||||
39
website_decimal_quantity/static/src/js/variant_mixin.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
odoo.define('website_decimal_qty.VariantMixin', function (require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var VariantMixin = require('sale.VariantMixin');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom implementation of the onClickAddCartJSON function in the
|
||||||
|
* sale.VariantMixin module. It allows for decimal quantities to be added to the cart
|
||||||
|
* by updating the input value with increments of 0.1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
VariantMixin.onClickAddCartJSON = function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
var $link = $(ev.currentTarget);
|
||||||
|
var $input = $link.closest('.input-group').find("input");
|
||||||
|
var min = parseFloat($input.data("min") || 0);
|
||||||
|
var max = parseFloat($input.data("max") || Infinity);
|
||||||
|
var allowDecimal = $input.data("allow-decimal");
|
||||||
|
|
||||||
|
// Replace comma with dot to support decimal separator
|
||||||
|
var inputValue = ($input.val() || 0).toString().replace(',', '.');
|
||||||
|
var previousQty = parseFloat(inputValue, 10);
|
||||||
|
|
||||||
|
// Convert allowDecimal to boolean - check for string 'true' or boolean true
|
||||||
|
var allowDecimals = (allowDecimal === true || allowDecimal === 'true');
|
||||||
|
|
||||||
|
// Use increment of 1 for unit-type products, 0.1 for weight/volume products
|
||||||
|
var increment = allowDecimals ? 0.1 : 1;
|
||||||
|
var quantity = ($link.has(".fa-minus").length ? -increment : increment) + previousQty;
|
||||||
|
var newQt = quantity > min ? (quantity < max ? quantity : max) : min;
|
||||||
|
|
||||||
|
if (newQt !== previousQty) {
|
||||||
|
// Round to integer if decimals not allowed, otherwise keep 1 decimal place
|
||||||
|
var newQty = allowDecimals ? newQt.toFixed(1) : Math.round(newQt).toString();
|
||||||
|
$input.val(newQty).trigger('change');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
144
website_decimal_quantity/static/src/js/website_sale.js
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
odoo.define('website_decimal_qty.website_sale', function (require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var publicWidget = require('web.public.widget');
|
||||||
|
var wSaleUtils = require('website_sale.utils');
|
||||||
|
const dom = require('web.dom');
|
||||||
|
require('website_sale.website_sale');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module extends the website_sale module to support decimal quantity input for adding products to the cart.
|
||||||
|
* It overrides the onClickAddCartJSON function from sale.VariantMixin to add support for decimal quantity input,
|
||||||
|
* and extends the WebsiteSale widget to handle updating cart quantities when a decimal quantity is entered.
|
||||||
|
*/
|
||||||
|
|
||||||
|
publicWidget.registry.WebsiteSale.include({
|
||||||
|
_updateRootProduct: function ($form, productId) {
|
||||||
|
// Override to handle comma as decimal separator
|
||||||
|
var $input = $form.find('input[name="add_qty"]');
|
||||||
|
var quantityInput = $input.val() || 1;
|
||||||
|
// Replace comma with dot to support decimal separator
|
||||||
|
var quantityValue = quantityInput.toString().replace(',', '.');
|
||||||
|
var quantity = parseFloat(quantityValue);
|
||||||
|
|
||||||
|
// Check if decimal quantities are allowed for this product
|
||||||
|
var allowDecimal = $input.data('allow-decimal');
|
||||||
|
// Convert to boolean - check for string 'true' or boolean true
|
||||||
|
var allowDecimals = (allowDecimal === true || allowDecimal === 'true');
|
||||||
|
|
||||||
|
if (!allowDecimals) {
|
||||||
|
// Round to nearest integer for products with unit-type UoM
|
||||||
|
quantity = Math.round(quantity);
|
||||||
|
// Update the input to show the rounded value
|
||||||
|
$input.val(quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rootProduct = {
|
||||||
|
product_id: productId,
|
||||||
|
quantity: quantity,
|
||||||
|
product_custom_attribute_values: this.getCustomVariantValues($form.find('.js_product')),
|
||||||
|
variant_values: this.getSelectedVariantValues($form.find('.js_product')),
|
||||||
|
no_variant_attribute_values: this.getNoVariantAttributeValues($form.find('.js_product'))
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_changeCartQuantity: function ($input, value, $dom_optional, line_id, productIDs) {
|
||||||
|
var self = this;
|
||||||
|
_.each($dom_optional, function (elem) {
|
||||||
|
$(elem).find('.js_quantity').text(value);
|
||||||
|
productIDs.push($(elem).find('span[data-product-id]').data('product-id'));
|
||||||
|
});
|
||||||
|
$input.data('update_change', true);
|
||||||
|
|
||||||
|
// Check if decimal quantities are allowed for this product
|
||||||
|
var allowDecimal = $input.data('allow-decimal');
|
||||||
|
// Convert to boolean - check for string 'true' or boolean true
|
||||||
|
var allowDecimals = (allowDecimal === true || allowDecimal === 'true');
|
||||||
|
|
||||||
|
if (!allowDecimals) {
|
||||||
|
// Round to nearest integer for products with unit-type UoM
|
||||||
|
value = Math.round(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._rpc({
|
||||||
|
route: "/shop/cart/update_json",
|
||||||
|
params: {
|
||||||
|
line_id: line_id,
|
||||||
|
product_id: parseInt($input.data('product-id'), 10),
|
||||||
|
set_qty: value
|
||||||
|
},
|
||||||
|
}).then(function (data) {
|
||||||
|
$input.data('update_change', false);
|
||||||
|
// Replace comma with dot to support decimal separator
|
||||||
|
var inputValue = $input.val().toString().replace(',', '.');
|
||||||
|
var check_value = parseFloat(inputValue);
|
||||||
|
|
||||||
|
if (isNaN(check_value)) {
|
||||||
|
check_value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply same rounding logic when checking
|
||||||
|
if (!allowDecimals) {
|
||||||
|
check_value = Math.round(check_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== check_value) {
|
||||||
|
$input.trigger('change');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!data.cart_quantity) {
|
||||||
|
return window.location = '/shop/cart';
|
||||||
|
}
|
||||||
|
wSaleUtils.updateCartNavBar(data);
|
||||||
|
$input.val(data.quantity);
|
||||||
|
|
||||||
|
$('.js_quantity[data-line-id='+line_id+']').val(data.quantity).text(data.quantity);
|
||||||
|
|
||||||
|
if (data.warning) {
|
||||||
|
var cart_alert = $('.oe_cart').parent().find('#data_warning');
|
||||||
|
if (cart_alert.length === 0) {
|
||||||
|
$('.oe_cart').prepend('<div class="alert alert-danger alert-dismissable" role="alert" id="data_warning">'+
|
||||||
|
'<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> ' + data.warning + '</div>');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cart_alert.html('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> ' + data.warning);
|
||||||
|
}
|
||||||
|
$input.val(data.quantity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onChangeCartQuantity: function (ev) {
|
||||||
|
var $input = $(ev.currentTarget);
|
||||||
|
if ($input.data('update_change')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Replace comma with dot to support decimal separator
|
||||||
|
var inputValue = ($input.val() || 0).toString().replace(',', '.');
|
||||||
|
var value = parseFloat(inputValue, 10);
|
||||||
|
if (isNaN(value)) {
|
||||||
|
value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if decimal quantities are allowed for this product
|
||||||
|
var allowDecimal = $input.data('allow-decimal');
|
||||||
|
// Convert to boolean - check for string 'true' or boolean true
|
||||||
|
var allowDecimals = (allowDecimal === true || allowDecimal === 'true');
|
||||||
|
|
||||||
|
if (!allowDecimals) {
|
||||||
|
// Round to nearest integer for products with unit-type UoM
|
||||||
|
value = Math.round(value);
|
||||||
|
// Update the input to show the rounded value
|
||||||
|
$input.val(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var $dom = $input.closest('tr');
|
||||||
|
// var default_price = parseFloat($dom.find('.text-danger > span.oe_currency_value').text());
|
||||||
|
var $dom_optional = $dom.nextUntil(':not(.optional_product.info)');
|
||||||
|
var line_id = parseInt($input.data('line-id'), 10);
|
||||||
|
var productIDs = [parseInt($input.data('product-id'), 10)];
|
||||||
|
this._changeCartQuantity($input, value, $dom_optional, line_id, productIDs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
98
website_decimal_quantity/static/src/js/website_sale_utils.js
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
odoo.define('website_decimal_quantity.utils', function (require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var utils = require('website_sale.utils');
|
||||||
|
var utilsDecimalQty = utils.updateCartNavBar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module overrides the updateCartNavBar function of the website_sale.utils module to provide
|
||||||
|
* support for decimal quantities in the cart.
|
||||||
|
* It replaces the cart quantity text with an animated icon and updates the cart summary and lines with the new data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
utils.updateCartNavBar = function(data) {
|
||||||
|
// Ensure cart_quantity is treated as a number for proper evaluation
|
||||||
|
var cartQty = parseFloat(data.cart_quantity) || 0;
|
||||||
|
// Round to 2 decimal places to avoid floating point precision issues
|
||||||
|
var displayQty = Math.round(cartQty * 100) / 100;
|
||||||
|
|
||||||
|
$(".my_cart_quantity")
|
||||||
|
.parents('li.o_wsale_my_cart').removeClass('d-none').end()
|
||||||
|
.addClass('o_mycart_zoom_animation').delay(300)
|
||||||
|
.queue(function () {
|
||||||
|
$(this)
|
||||||
|
.toggleClass('fa fa-warning', cartQty === 0)
|
||||||
|
.attr('title', data.warning)
|
||||||
|
.text(displayQty > 0 ? displayQty : '')
|
||||||
|
.removeClass('o_mycart_zoom_animation')
|
||||||
|
.dequeue();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".js_cart_lines").first().before(data['website_sale.cart_lines']).end().remove();
|
||||||
|
$(".js_cart_summary").replaceWith(data['website_sale.short_cart_summary']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override _addToCartInPage to handle decimal quantities correctly in the animation condition
|
||||||
|
*/
|
||||||
|
var originalAddToCart = utils.cartHandlerMixin.addToCart;
|
||||||
|
var originalAddToCartInPage = utils.cartHandlerMixin._addToCartInPage;
|
||||||
|
|
||||||
|
utils.cartHandlerMixin._addToCartInPage = function(params) {
|
||||||
|
var self = this;
|
||||||
|
params.force_create = true;
|
||||||
|
return this._rpc({
|
||||||
|
route: "/shop/cart/update_json",
|
||||||
|
params: params,
|
||||||
|
}).then(async function(data) {
|
||||||
|
console.log('Data received from cart update:', data);
|
||||||
|
|
||||||
|
// The response might not include cart_quantity if display=false or on error
|
||||||
|
// In that case, we need to make another call to get the total or calculate it
|
||||||
|
var currentQty = parseFloat($(".my_cart_quantity").text()) || 0;
|
||||||
|
var newQty;
|
||||||
|
|
||||||
|
if (data.cart_quantity !== undefined) {
|
||||||
|
newQty = parseFloat(data.cart_quantity) || 0;
|
||||||
|
} else {
|
||||||
|
// If cart_quantity is not in response, add the quantity that was just added
|
||||||
|
newQty = currentQty + parseFloat(params.add_qty || 0);
|
||||||
|
// Ensure we have the templates by making the call with display=true
|
||||||
|
if (!data['website_sale.cart_lines']) {
|
||||||
|
// Make another call to get the rendered templates
|
||||||
|
var displayData = await self._rpc({
|
||||||
|
route: "/shop/cart/update_json",
|
||||||
|
params: {
|
||||||
|
product_id: params.product_id,
|
||||||
|
line_id: data.line_id,
|
||||||
|
set_qty: data.quantity,
|
||||||
|
display: true,
|
||||||
|
force_create: true
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Merge the data
|
||||||
|
Object.assign(data, displayData);
|
||||||
|
newQty = parseFloat(displayData.cart_quantity) || newQty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.setItem('website_sale_cart_quantity', newQty);
|
||||||
|
|
||||||
|
console.log('Current Qty:', currentQty, 'New Qty:', newQty, 'Data:', data);
|
||||||
|
|
||||||
|
// Animate if quantity changed and is greater than 0
|
||||||
|
if (newQty > 0 && newQty !== currentQty) {
|
||||||
|
// Ensure data has cart_quantity for updateCartNavBar
|
||||||
|
if (data.cart_quantity === undefined) {
|
||||||
|
data.cart_quantity = newQty;
|
||||||
|
}
|
||||||
|
// No animation if the product's page images are hidden
|
||||||
|
if ($('div[data-image_width]').data('image_width') !== 'none') {
|
||||||
|
await utils.animateClone($('header .o_wsale_my_cart').first(), self.$itemImgContainer, 25, 40);
|
||||||
|
}
|
||||||
|
utils.updateCartNavBar(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
13
website_decimal_quantity/views/uom_category_views.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="uom_category_form_view_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">uom.category.form.inherit</field>
|
||||||
|
<field name="model">uom.category</field>
|
||||||
|
<field name="inherit_id" ref="uom.product_uom_categ_form_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="name" position="after">
|
||||||
|
<field name="allow_decimal_quantity"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
32
website_decimal_quantity/views/website_sale_templates.xml
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- This template replaces the default quantity selector with a text input field
|
||||||
|
that allows for decimal values. -->
|
||||||
|
<template id="product_quantity" inherit_id="website_sale.product_quantity" name="Product Quantity Decimal Support">
|
||||||
|
<xpath expr="//input[@name='add_qty']" position="attributes">
|
||||||
|
<attribute name="t-att-data-allow-decimal">product.uom_id.category_id.allow_decimal_quantity and 'true' or 'false'</attribute>
|
||||||
|
<attribute name="t-att-data-min">product.uom_id.category_id.allow_decimal_quantity and '0.1' or '1'</attribute>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="cart_lines_decimal" inherit_id="website_sale.cart_lines" name="Cart Lines Decimal Support">
|
||||||
|
<xpath expr="//input[hasclass('js_quantity','form-control','quantity')]" position="attributes">
|
||||||
|
<attribute name="t-att-data-allow-decimal">line.product_uom.category_id.allow_decimal_quantity and 'true' or 'false'</attribute>
|
||||||
|
<attribute name="t-att-data-min">line.product_uom.category_id.allow_decimal_quantity and '0.1' or '1'</attribute>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- This template replaces the default cart popover button with a button that
|
||||||
|
displays the total number of items in the cart with one decimal point precision. -->
|
||||||
|
<template id="cart_popover_decimal" inherit_id="website_sale.cart_popover"
|
||||||
|
name="Cart Popover">
|
||||||
|
<xpath expr="//a[hasclass('btn-primary')]" position="replace">
|
||||||
|
<a role="button" class="btn btn-primary" href="/shop/cart">
|
||||||
|
View Cart (
|
||||||
|
<span class="o_wsale_cart_quantity"
|
||||||
|
t-esc="round(sum(website_sale_order.mapped('website_order_line.product_uom_qty')), 1)"/>
|
||||||
|
item(s))
|
||||||
|
</a>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||