[NEW] Addons creation - product_rental_web_bookings

This commit is contained in:
Stéphan Sainléger
2022-04-19 11:57:06 +02:00
parent 0f3a9c761f
commit 62059b6676
10 changed files with 1914 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
{
"name": "Product Rental Web Bookings",
"category": "Product",
"version": "14.0.1.0",
"summary": "Book products on several rental periods through website",
"author": "Elabore",
"website": "https://elabore.coop/",
"installable": True,
"application": True,
"auto_install": False,
"description": """
=======================
Product Rental Bookings
=======================
This module allows Odoo website visitors to rent products on several periods.
Installation
============
Just install product_rental_bookings, all dependencies will be installed by default.
Known issues / Roadmap
======================
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/elabore-coop/.../issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Elabore: `Icon <https://elabore.coop/web/image/res.company/1/logo?unique=f3db262>`_.
Contributors
------------
* Stéphan Sainléger <https://github.com/stephansainleger>
Funders
-------
The development of this module has been financially supported by:
* Elabore (https://elabore.coop)
Maintainer
----------
This module is maintained by ELABORE.
""",
"depends": ["base", "product_rental_bookings"],
"data": [
"views/product_template.xml",
"views/website_templates.xml",
],
"qweb": [],
}

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import website_rental
from . import customer_portal

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from odoo import http
from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.http import request
class CustomerPortal(CustomerPortal):
def _prepare_portal_layout_values(self):
values = super(CustomerPortal, self)._prepare_portal_layout_values()
rental_order_detail = request.env["rental.product.order"].sudo()
rental_order_count = rental_order_detail.search_count(
[("customer_name", "=", request.env.user.partner_id.id)]
)
values.update(
{
"rental_order_count": rental_order_count,
}
)
return values
@http.route("/order", type="http", auth="public", website=True)
def order(self, page=1, date_begin=None, date_end=None, sortby=None):
values = self._prepare_portal_layout_values()
order_id = (
request.env["rental.product.order"]
.sudo()
.search([("customer_name", "=", request.env.user.partner_id.id)])
)
searchbar_sortings = {
"date": {"label": ("Start Date"), "order": "from_date desc"},
"date1": {"label": ("End Date"), "order": "to_date desc"},
"stage": {"label": ("Stage"), "order": "state"},
}
# default sortby order
if not sortby:
sortby = "date"
sort_order = searchbar_sortings[sortby]["order"]
values.update(
{
"order_id": order_id,
"page_name": "rental_order",
"searchbar_sortings": searchbar_sortings,
"sortby": sortby,
}
)
return request.render("product_rental_bookings.rental_order", values)

View File

@@ -0,0 +1,566 @@
# -*- coding: utf-8 -*-
import json
from datetime import datetime
from odoo import http
from odoo.addons.payment.controllers.portal import PaymentProcessing
from odoo.http import request
from odoo.osv import expression
class WebsiteRental(http.Controller):
@http.route("/search-product", type="http", auth="public", website=True)
def search_product(self, **post):
return request.render(
"product_rental_bookings.product_search", {"post_data": post}
)
def convert_float_to_hh_mm(self, session_id):
start_date = "{0:02.0f}:{1:02.0f}".format(
*divmod(session_id.start_time * 60, 60)
)
end_date = "{0:02.0f}:{1:02.0f}".format(*divmod(session_id.end_time * 60, 60))
start_date_fmt = (
start_date.split(":")[0] + ":" + start_date.split(":")[1] + ":" + "00"
)
end_date_fmt = (
end_date.split(":")[0] + ":" + end_date.split(":")[1] + ":" + "00"
)
return start_date_fmt, end_date_fmt
@http.route("/booking_form", type="http", auth="public", website=True)
def booking_form(self, **post):
product_list = []
if (
post
and post.get("based_on")
and post.get("class_type")
and post["location"]
):
if post.get("based_on") == "per_session":
rent_session_id = (
request.env["session.config"]
.sudo()
.browse(int(post.get("session_type")))
)
request.session["session_type"] = rent_session_id.name
request.session["session_type_id"] = rent_session_id.id
start_date, end_date = self.convert_float_to_hh_mm(rent_session_id)
date_from = datetime.strptime(
post["date_from"] + " " + start_date, "%d-%m-%Y %H:%M:%S"
)
date_to = datetime.strptime(
post["date_from"] + " " + end_date, "%d-%m-%Y %H:%M:%S"
)
else:
date_format = (
"%d-%m-%Y"
if post["date_from"] and post["date_to"]
else "%d-%m-%Y %H:%M"
)
date_from = datetime.strptime(
post["date_from"] if post["date_from"] else post["datetime_from"],
date_format,
)
date_to = datetime.strptime(
post["date_to"] + " 23:59:59"
if post["date_to"]
else post["datetime_to"] + ":00",
"%d-%m-%Y %H:%M:%S",
)
request.session["date_from"] = date_from
request.session["date_to"] = date_to
request.session["location"] = post["location"]
request.session["class_type"] = post["class_type"]
request.session["based_on"] = post["based_on"]
categ_ids = (
request.env["product.category"]
.sudo()
.search([("parent_id", "child_of", int(post["class_type"]))])
)
request.env.cr.execute(
"""
SELECT
propro.id
FROM product_product propro
JOIN product_template protmp ON protmp.id = propro.product_tmpl_id
WHERE
protmp.categ_id IN %s AND
propro.is_rental='t' AND
propro.id IN (
Select product_id from stock_quant where location_id = %s);
""",
(
tuple(categ_ids.ids),
post["location"],
),
)
product_data = request.env.cr.fetchall()
if product_data:
for products in product_data:
product = (
request.env["product.product"]
.sudo()
.search([("id", "in", products)])
)
available_products = (
request.env["stock.quant"]
.sudo()
.search(
[
("product_id", "=", product.id),
("quantity", ">", 0),
("location_id", "=", int(post.get("location"))),
]
)
)
if available_products.product_id and product.is_published:
product_list.append(available_products.product_id)
values = {
"product_id": product_list,
"location": post["location"],
"post_data": post,
}
return request.render("product_rental_bookings.product_search", values)
else:
return request.render(
"product_rental_bookings.product_search",
{"post_data": post, "error": True},
)
else:
return request.render(
"product_rental_bookings.product_search", {"post_data": post}
)
@http.route(
'/booking_form/create_quotation/<model("product.product"):product_id>',
type="http",
auth="public",
website=True,
)
def create_qutation(self, product_id, **post):
product_order = {
"product_id": product_id,
"post_data": request.session["location"],
}
if request.session.get("based_on") in ["per_hour", "per_session"]:
delta = request.session.get("date_from") - request.session.get("date_to")
hours = abs(delta.total_seconds() / 3600.0)
product_order.update({"total_hours": hours})
return request.render("product_rental_bookings.rental_product", product_order)
@http.route("/product_cart_update", type="http", auth="public", website=True)
def product_cart_update(self, **post):
product_order_list = []
rental_order_obj = request.env["rental.product.order"]
order_id = rental_order_obj.search(
[
("customer_name", "=", request.env.user.partner_id.id),
("state", "=", "draft"),
],
limit=1,
)
if post and post["product_id"]:
product_id = (
request.env["product.product"]
.sudo()
.search([("id", "=", post["product_id"])])
)
enter_hour = post.get("enter_hour")
if not enter_hour:
total_day = (
request.session.get("date_to") - request.session.get("date_from")
).days
product_order_list.append(
(
0,
0,
{
"product_id": product_id.id,
"price_based": "per_day",
"enter_days": total_day + 1,
"enter_hour": 0.0,
"qty_needed": int(post["qty_needed"]),
"price": product_id.rental_amount,
},
)
)
elif enter_hour and request.session["based_on"] == "per_session":
product_order_list.append(
(
0,
0,
{
"product_id": product_id.id,
"price_based": request.session.get("based_on"),
"enter_days": 0.0,
"enter_hour": float(post["enter_hour"]),
"qty_needed": int(post["qty_needed"]),
"price": product_id.rental_amount_per_session,
},
)
)
else:
product_order_list.append(
(
0,
0,
{
"product_id": product_id.id,
"price_based": request.session.get("based_on"),
"enter_days": 0.0,
"enter_hour": float(post["enter_hour"]),
"qty_needed": int(post["qty_needed"]),
"price": product_id.rental_amount_per_hour,
},
)
)
if order_id:
order_lines_product = []
for order_line in order_id.product_order_lines_ids:
order_lines_product.append(order_line.product_id.id)
order_id.write(
(
{
"product_order_lines_ids": product_order_list,
"is_hours": True
if request.session.get("based_on")
in ["per_session", "per_hour"]
else order_id.is_hours,
"is_days": True
if request.session.get("based_on") == "per_day"
else order_id.is_days,
}
)
)
return request.render(
"product_rental_bookings.rental_product_cart_update",
{
"product_order_id": order_id,
"location_id": request.session["location"],
},
)
else:
from_date, to_date = rental_order_obj.sudo().start_end_date_global(
request.session.get("date_from"), request.session.get("date_to")
)
order_id = rental_order_obj.sudo().create(
{
"customer_name": request.env.user.partner_id.id,
"from_date": rental_order_obj.sudo().convert_TZ_UTC(from_date),
"to_date": rental_order_obj.sudo().convert_TZ_UTC(to_date),
"state": "draft",
"is_true": True,
"product_order_lines_ids": product_order_list,
"location_id": request.session["location"],
"is_hours": True
if request.session.get("based_on")
in ["per_session", "per_hour"]
else order_id.is_hours,
"is_days": True
if request.session.get("based_on") == "per_day"
else order_id.is_days,
}
)
order_id.product_order_lines_ids._get_subtotal()
request.session["order_id"] = order_id.id
return request.render(
"product_rental_bookings.rental_product_cart_update",
{"product_order_id": order_id, "location_id": request.session["location"]},
)
@http.route("/view_cart", type="http", auth="public", website=True)
def view_cart(self, **post):
rental_product_order_obj = request.env["rental.product.order"]
if post:
order_id = rental_product_order_obj.sudo().search(
[("id", "=", post["rental_order"])]
)
else:
order_id = rental_product_order_obj.sudo().search(
[("id", "=", request.session.get("order_id"))]
)
request.session["order_id"] = order_id.id
return request.render(
"product_rental_bookings.rental_product_cart_update",
{"product_order_id": order_id},
)
def _get_rental_payment_values(self, order, **kwargs):
values = dict(
website_rental_order=order,
errors=[],
partner=order.customer_name.id,
order=order,
payment_action_id=request.env.ref("payment.action_payment_acquirer").id,
return_url="/rental/payment/validate",
bootstrap_formatting=True,
)
domain = expression.AND(
[
[
"&",
("state", "in", ["enabled", "test"]),
("company_id", "=", order.company_id.id),
],
[
"|",
("country_ids", "=", False),
("country_ids", "in", [order.customer_name.country_id.id]),
],
]
)
acquirers = request.env["payment.acquirer"].search(domain)
values["acquirers"] = [
acq
for acq in acquirers
if (acq.payment_flow == "form" and acq.view_template_id)
or (acq.payment_flow == "s2s" and acq.registration_view_template_id)
]
values["tokens"] = request.env["payment.token"].search(
[
("partner_id", "=", order.customer_name.id),
("acquirer_id", "in", acquirers.ids),
]
)
return values
@http.route(
"/rental/payment/validate", type="http", auth="public", website=True, csrf=False
)
def payment_validate(self, transaction_id=None, rental_order_id=None, **post):
"""Method that should be called by the server when receiving an update
for a transaction. State at this point :
- UDPATE ME
"""
# vals = {}
outstanding_info = False
order = (
request.env["rental.product.order"]
.sudo()
.browse(request.session.get("order_id"))
)
if order:
if not order.invoice_ids:
order.confirm()
invoice_id = order.contract_ids[0].create_invoice()
order.contract_ids[0].create_invoice()
invoices = order.mapped("invoice_ids").filtered(
lambda inv: inv.state == "posted"
)
for inv in invoices:
if inv.invoice_has_outstanding:
outstanding_info = json.loads(
inv.invoice_outstanding_credits_debits_widget
)
transaction_id = request.env["payment.transaction"].browse(
request.session.get("__website_sale_last_tx_id")
)
if outstanding_info:
credit_aml_id = False
if "content" in outstanding_info:
for item in outstanding_info["content"]:
credit_aml_id = outstanding_info["content"][0]["id"]
if credit_aml_id and inv.state == "posted":
inv.js_assign_outstanding_line(credit_aml_id)
assert order.id == request.session.get("order_id")
return request.render(
"product_rental_bookings.rental_order_comfirmation", {"order": order}
)
@http.route(
[
"/rental/payment/transaction/",
"/rental/payment/transaction/<int:so_id>",
"/rental/payment/transaction/<int:so_id>/<string:access_token>",
],
type="json",
auth="public",
website=True,
)
def payment_transaction(
self,
acquirer_id,
save_token=False,
so_id=None,
access_token=None,
token=None,
**kwargs
):
"""Json method that creates a payment.transaction, used to create a
transaction when the user clicks on 'pay now' button. After having
created the transaction, the event continues and the user is redirected
to the acquirer website.
:param int acquirer_id: id of a payment.acquirer record. If not set the
user is redirected to the checkout page
"""
# Ensure a payment acquirer is selected
if not acquirer_id:
return False
try:
acquirer_id = int(acquirer_id)
except:
return False
# Retrieve the sale order
if so_id:
env = request.env["rental.product.order"]
domain = [("id", "=", so_id)]
if access_token:
env = env.sudo()
domain.append(("access_token", "=", access_token))
order = env.search(domain, limit=1)
else:
order = (
request.env["rental.product.order"]
.sudo()
.browse(request.session.get("order_id"))
)
# Ensure there is something to proceed
assert order.customer_name.id != request.website.partner_id.id
# Create transaction
vals = {
"acquirer_id": acquirer_id,
"return_url": "/rental/payment/validate",
"currency_id": request.env.user.company_id.currency_id.id,
"amount": order.total_amount,
"partner_id": order.customer_name.id,
}
if save_token:
vals["type"] = "form_save"
if token:
vals["payment_token_id"] = int(token)
transaction = order._create_payment_transaction(vals)
last_tx_id = request.session.get("__website_sale_last_tx_id")
PaymentProcessing.add_payment_transaction(transaction)
request.session["__website_sale_last_tx_id"] = transaction.id
return transaction.render_rental_button(order)
@http.route("/checkout", type="http", auth="public", website=True, csrf=False)
def checkout(self, **post):
rental_order_id = (
request.env["rental.product.order"]
.sudo()
.search([("id", "=", request.session.get("order_id"))])
)
render_values = self._get_rental_payment_values(rental_order_id, **post)
if render_values["errors"]:
render_values.pop("acquirers", "")
render_values.pop("tokens", "")
return request.render("product_rental_bookings.checkout_process", render_values)
@http.route("/get_rate_details", type="json", auth="public", website=True)
def get_rate_details(self, units, product_id):
total_day = (
request.session.get("date_to").date()
- request.session.get("date_from").date()
).days
product_id = (
request.env["product.product"].sudo().search([("id", "=", product_id)])
)
if units == "per_day":
product_details = {
"rate": product_id.rental_amount,
"total_days": total_day + 1,
"from_date": request.session.get("date_from"),
"to_date": request.session.get("date_to"),
}
return product_details
else:
product_details = {
"rate": product_id.rental_amount_per_hour
if units == "per_hour"
else product_id.rental_amount_per_session,
"from_date": request.session.get("date_from"),
"to_date": request.session.get("date_to"),
}
return product_details
@http.route("/product_ordel_line/remove", type="json", auth="user")
def remove_line(self, order_line_id, **kwrgs):
line_id = request.env["product.order.line"].browse(int(order_line_id))
order_id = line_id.product_order_id
line_id.sudo().unlink()
if not any(order_id.product_order_lines_ids):
order_id.sudo().unlink()
return {"order_id": order_id}
@http.route("/check/quantity", type="json", auth="public")
def quantity_check(self, quantity, product_id):
Product_obj = request.env["product.product"]
order_line_obj = request.env["product.order.line"]
product = Product_obj.sudo().search([("id", "=", product_id)])
rental_order_obj = request.env["rental.product.order"]
from_date, to_date = rental_order_obj.sudo().start_end_date_global(
request.session.get("date_from"), request.session.get("date_to")
)
start_date = datetime.strptime(
rental_order_obj.sudo().convert_TZ_UTC(from_date)
if request.env.user.tz
else from_date,
"%Y-%m-%d %H:%M:%S",
)
end_date = datetime.strptime(
rental_order_obj.sudo().convert_TZ_UTC(to_date)
if request.env.user.tz
else to_date,
"%Y-%m-%d %H:%M:%S",
)
order_line_ids = order_line_obj.sudo().search(
[
("product_order_id.state", "=", "confirm"),
("product_id", "=", product_id),
]
)
total_in_order_qty = 0
for order_line in order_line_ids:
if (
(start_date <= order_line.product_order_id.from_date <= end_date)
or (start_date <= order_line.product_order_id.to_date <= end_date)
and order_line.product_order_id.picking_count == 1
):
total_in_order_qty += (
order_line.qty_needed
if order_line.product_order_id.picking_count == 1
else 0
)
elif (
(
order_line.product_order_id.from_date
<= start_date
<= order_line.product_order_id.to_date
)
or (
order_line.product_order_id.from_date
<= end_date
<= order_line.product_order_id.to_date
)
and order_line.product_order_id.picking_count == 1
):
total_in_order_qty += (
order_line.qty_needed
if order_line.product_order_id.picking_count == 1
else 0
)
if product.sudo().qty_available and total_in_order_qty:
qty_available = product.sudo().qty_available - total_in_order_qty
else:
total_in_order_qty = order_line_ids.filtered(
lambda x: x.qty_needed if x.product_order_id.picking_count > 1 else 0
)
qty_available = product.sudo().qty_available + sum(
total_in_order_qty.mapped("qty_needed")
)
total_price_days = product.rental_amount
total_price_hour = product.rental_amount_per_hour
if not quantity or not qty_available >= quantity:
return False
return {
"product_price_days": total_price_days,
"product_price_hour": total_price_hour,
}

View File

@@ -0,0 +1,55 @@
.search-box{width:50%;background:#000000a3;border-radius:30px;margin:0 auto;margin-top:50px;margin-bottom:50px;padding:10px;font-size:16px;color:#fff;border: 3px solid;
border-color: black;}
.top-deals{padding:40px 0;background:#ededed}
.top-deals .col-xs-12{font-family:'Roboto',sans-serif;font-weight:700;font-size:40px;line-height:60px;color:#191919}
.top-deals .rline{width:100px;display:block;height:5px;background:#d42316;margin:0 auto;margin-top:20px}
.top-deals .box{background:#4ebab1;padding-bottom:150px;margin:0 auto;max-width:450px;margin-top:40px;-moz-box-shadow:1px 1px 5px #ccc;-webkit-box-shadow:1px 1px 5px #ccc;box-shadow:1px 1px 5px #ccc}
.top-deals .box .box-town-image{background:#4ebab1;width:50%;height:20px;}
.top-deals .box img{height:50%;width:50%;object-fit:cover}
.image{overflow:hidden;position:relative}
.heading-font{color:#fff;font-size:12px;line-height:18px;text-transform:uppercase;margin-top:65px;}
.heading-font{letter-spacing:0 !important}.text-transform{text-transform:uppercase}
.heading-font{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}ul{padding-left:20px}ul li{margin-bottom:8px}ol{padding-left:20px}ol li{margin-bottom:8px}ul.header-menu ul{margin-top:0}ul ol,ol ol,ol ul,ul ul{margin-top:8px}img{max-width:100%;height:auto}a{color:#6c98e1}a:hover{color:#6c98e1}a:focus{color:#6c98e1}a:active{color:#6c98e1}}
.image{overflow:hidden;position:relative}.rmv_txt_drctn:hover .image:before{opacity:1}
.special-offers .title{position:relative;padding:0 130px;margin-bottom:16px;text-align:center;font-weight:700;font-size:36px;line-height:normal;color:#fff}
.listing-car-items-units{margin:0 -15px}.listing-car-items .listing-car-item{padding:10px 15px}
.listing-car-items .listing-car-item .listing-car-item-inner{max-width:350px;width:auto;margin-bottom:30px;}
.listing-car-items .listing-car-item .car-title{margin-top:40px;color:#fff;overflow:hidden;text-transform:uppercase;font-size:14px;line-height:18px;font-weight:700;text-decoration:none;text-align:left}
.listing-car-items .listing-car-item .car-title .colored{color:#cc6119}
.listing-car-items .listing-car-item .price{position:relative;float:right;padding:6px 15px 5px 18px;margin-left:23px;background-color:#6c98e1;text-align:right}
.listing-car-items .listing-car-item .price .regular-price,.listing-car-items .listing-car-item .price .sale-price,
.listing-car-items .listing-car-item .price .normal-price{position:relative;z-index:6}.listing-car-items .listing-car-item .price .regular-price{font-size:13px;line-height:normal;text-decoration:line-through}
.listing-car-items .listing-car-item .price .normal-price,.listing-car-items .listing-car-item .price .sale-price{color:#fff;font-size:16px;line-height:20px;font-weight:700}.car-listing-row
.listing-car-item-meta .price .normal-price,.car-listing-row
.listing-car-item-meta .price .sale-price{color:#fff;font-size:16px;line-height:20px;font-weight:700}
.listing-car-items .listing-car-item .price .normal-price{line-height:36px}.listing-car-items .listing-car-item .price:before{position:absolute;content:'';display:block;top:0;left:-14px;height:100%;width:32px;background-color:#6c98e1;-webkit-transform:skew(-18deg,0deg);transform:skew(-18deg,0deg);z-index:5}
.listing-car-items .listing-car-item .listing-car-item-meta{border-radius: 15px;padding:3px 0px 19px 1px;width: 900px;background-color:#373c3f;text-align:left;vertical-align: middle;}
.title{margin-bottom:10px;font-weight:700;text-transform:uppercase;font-size:14px;line-height:10px}
.title{text-transform:uppercase;border-bottom:1px solid #ddd}
.title{font-size:16px;font-weight:400;color:#232628}
.title{position:relative;min-height:42px;text-transform:uppercase}
.car-meta-top{min-height:9px;padding-bottom:9px;border-bottom:1px solid #4e5254}.listing-car-items .listing-car-item
.listing-car-item-meta .car-meta-bottom ul{list-style:none;padding:0;margin:0;border-radius:37px}
.listing-car-items .listing-car-item .listing-car-item-meta .car-meta-bottom ul li{display:inline-block;margin-top:18px;margin-right:16px;margin-bottom:0;color:#c9c9c9;vertical-align:middle;line-height:13px}
.dp-in{display:inline-block;vertical-align:top;max-width:100%}.car-listing-tabs-unit .stm-listing-tabs{margin-top:20px}.car-listing-tabs-unit .car-listing-top-part{position:relative;}.car-listing-tabs-unit .car-listing-top-part:before{position:absolute;content:'';display:block;top:0;bottom:0;left:-2000px;right:-2000px;z-index:-1}
.stm-base-background-color{background-color:#cc6119 !important}.stm-base-color{color:#cc6119 !important}
.stm-image-links .stm-image-link .inner img{filter:grayscale(0%) !important;-webkit-filter:grayscale(0%) !important}img{-webkit-transform:translateZ(0)}.testimonials-carousel .owl-controls{display:none}.testimonial-unit
.image{margin-right:20px !important}.wpb_column .
.widget_car_location #stm-dealer-gmap{width:100%;height:253px}@media(max-width:440px){}
.vc_custom_1448532088457{margin-bottom: 0px !important;padding-top: 51px !important;padding-bottom: 40px !important;background-position: 0 0 !important;background-repeat: repeat !important;}
.car-listing-tabs-unit .car-listing-top-part:before {background-color: #232628}
.font-color{font-size:16px;font-weight:400;color:#000000}
.select{max-width:100px;}
.css_quantity {
max-width: 125px;
}.css_quantity input[name="add_qty"] {
text-align: center;
}
.kilometer{max-width:70px;}
.search_product{margin-top:8px; padding: 0 7px;}
.scroll_down {
width: 100%;
height: 300px;
}

View File

@@ -0,0 +1,228 @@
odoo.define('product_rental_bookings.main', function (require) {
"use strict";
var ajax = require('web.ajax');
$(document).ready(function () {
$('.total_days').hide();
var error = $('#error').val();
if (error == "True") {
$("#myModal").modal();
}
$('#date_from').datepicker({
minDate: new Date(),
dateFormat: "dd-mm-yy",
yearRange: new Date().getFullYear().toString() + ':' + new Date().getFullYear().toString(),
onClose: function (selectedDate) { $("#date_to").datepicker("option", "minDate", selectedDate); }
});
$('#date_to').datepicker({
dateFormat: "dd-mm-yy",
yearRange: new Date().getFullYear().toString() + ':' + new Date().getFullYear().toString(),
onClose: function (selectedDate) { $("#date_from").datepicker("option", "maxDate", selectedDate); }
});
$('#datetime_from').datetimepicker({
minDate: new Date(),
format: 'd-m-Y H:i',
onClose: function (selectedDate) {
$("#datetime_to").datetimepicker("option", "minDateTime", selectedDate);
}
});
$('#datetime_to').datetimepicker({
minDate: new Date(),
format: 'd-m-Y H:i',
onClose: function (selectedDate) {
$("#datetime_from").datetimepicker("option", "maxDateTime", selectedDate);
}
});
$('#datetime_from').on('change', function () {
var date_from = $('#datetime_from').val()
var date_to = $('#datetime_to').val()
if (date_from && date_to && date_from > date_to) {
alert('From Datetime is after To Datetime')
$('#datetime_from').val('')
}
});
$('#datetime_to').on('change', function () {
var date_from = $('#datetime_from').val()
var date_to = $('#datetime_to').val()
if (date_from && date_to && date_from > date_to) {
alert('To Datetime is before From Datetime')
$('#datetime_to').val('')
}
});
$('.rate_option').click(function () {
var select_value = $(this).val();
var qty = $(this).parent().parent().find('.qty').val()
var hour = $(this).parent().parent().find('.enter_hour').val()
var day = $(this).parent().parent().find('.total_day').val()
ajax.jsonRpc('/get_rate_details', 'call', {
'product_id': $(document).find('.product_id').val(),
'units': $(this).val(),
}).then(function (product_details) {
var rate = parseInt(product_details['rate'])
var total_day = parseInt(product_details['total_days'])
var from_date = (product_details['from_date'])
var to_date = (product_details['to_date'])
if (select_value == 'per_day') {
var html = '';
var day = '';
$('.total_hour').hide();
$('.enter_hour').hide();
$('.total_hours').hide();
$('.total_day').show();
$('.total_days').show();
html += "<td>" + rate + "</td>";
day += "<td>" + total_day + "</td>"
from_date += "<td>" + from_date + "</td>"
to_date += "<td>" + to_date + "</td>"
$('.rate_value').html(html);
$('.total_day').html(day)
$('.rate_value').addClass('rate_cls');
$('.total_day').addClass('day_cls');
$('.date_from').html(from_date)
$('.date_to').html(to_date)
calculate_total(rate * total_day);
$($('.calculate_total')).show()
$("input[name='qty_needed']").val(1)
}
else if (select_value == 'per_hour') {
$('.total_hour').show();
$('.enter_hour').show();
$('.total_hours').show();
$('.total_day').hide();
$('.total_days').hide();
calculate_total(rate * hour);
$($('.calculate_total')).show()
$("input[name='qty_needed']").val(1)
}
else if (select_value == 'per_session') {
$('.total_hour').show();
$('.enter_hour').show();
$('.total_hours').show();
$('.total_day').hide();
$('.total_days').hide();
calculate_total(rate);
$($('.calculate_total')).show()
$("input[name='qty_needed']").val(1)
}
});
});
$(".rate_option").trigger("click");
$('.quantity').on('change', function () {
var numberPattern = /\d+/g;
var rate_value = parseInt($('.rate_cls').html().match(numberPattern));
var hour = parseInt($('.enter_hour').val());
var total_day = parseInt($('.day_cls').html().match(numberPattern));
if ($(this).parent().parent().parent().find('.rate_option').val() == 'per_hour') {
calculate_total(rate_value * $(this).val() * hour);
}
});
// const total_amt = $('.calculate_total').html();
$("input[name='qty_needed']").on('change', function (e) {
var quantity = e.currentTarget.value;
var product_id = $("input[name='product_id']").val();
ajax.jsonRpc('/check/quantity', 'call', {
'product_id': parseInt(product_id),
'quantity': parseInt(quantity)
}).then(function (response) {
if (response == false) {
alert("Quantity Not Available.")
e.currentTarget.value = 1;
}
else {
if ($('.rate_option').val() == 'per_day') {
var total_day = ($('.total_day').html().replace('<td>', '')).replace('</td>', '');
$('.calculate_total').html(total_day * response.product_price_days * parseInt(quantity))
}
else {
var hour = $("input[name='enter_hour']").val()
$('.calculate_total').html(parseInt(hour) * response.product_price_hour * parseInt(quantity))
}
}
if (e.currentTarget.value == 0) {
$(".js_check_product").attr("disabled", true);
}
else {
$(".js_check_product").attr("disabled", false);
}
});
});
$('.js_delete_product').click(function () {
var ele = $(this)
var id = $(this).attr("id");
ajax.jsonRpc('/product_ordel_line/remove', 'call', {
'order_line_id': id,
}).then(function () {
ele.closest('tr').remove()
window.location.reload();
});
});
$('#based_on').click(function () {
var select_value = $(this).val();
if (select_value == 'per_hour') {
$('#session_selection').hide();
$('#for_hour_selection').show();
$('#for_day_selection').hide();
$("#datetime_from").prop("required", true);
$("#datetime_to").prop("required", true);
$("#date_from").prop("required", false);
$("#date_to").prop("required", false);
$("#session_type").prop("required", false);
$("#date_from").val('');
$("#date_to").val('');
}
else if (select_value == 'per_day') {
$('#session_selection').hide();
$('#for_hour_selection').hide();
$('#for_day_selection').show();
$("#to_date").show();
$("#date_from").prop("required", true);
$("#date_to").prop("required", true);
$("#datetime_from").prop("required", false);
$("#datetime_to").prop("required", false);
$("#session_type").prop("required", false);
$("#session_type").val('');
$("#datetime_from").val('');
$("#datetime_to").val('');
}
else {
$('#session_selection').show();
$('#for_hour_selection').hide();
$('#for_day_selection').show();
$("#date_from").prop("required", true);
$("#to_date").hide();
$("#date_to").prop("required", false);
$("#datetime_from").prop("required", false);
$("#datetime_to").prop("required", false);
$("#datetime_from").val('');
$("#datetime_to").val('');
}
});
});
function calculate_total(calculated_value) {
$('.calculate_total').html("<td>" + calculated_value + "</td>");
}
});

View File

@@ -0,0 +1,140 @@
//## Website Sale frontent design
//## ----------------------------
// Theming variables
$o-rental-wizard-thickness: 0.125rem;
$o-rental-wizard-dot-size: 0.625rem;
$o-rental-wizard-dot-active-glow: 0.25rem;
$o-rental-wizard-color-inner: white;
$o-rental-wizard-color-default: gray('200');
$o-rental-wizard-dot-active: theme-color('primary');
$o-rental-wizard-dot-completed: theme-color('success');
$o-rental-wizard-label-default: $text-muted;
$o-rental-wizard-label-active: $body-color;
$o-rental-wizard-label-completed: $success;
.progress-rental-wizard {
// Scoped variables
$tmp-dot-radius: ($o-rental-wizard-dot-size + $o-rental-wizard-thickness)*0.5;
$tmp-check-size: max($font-size-base, $o-rental-wizard-dot-size + $o-rental-wizard-thickness + $o-rental-wizard-dot-active-glow*2);
$tmp-check-pos: $o-rental-wizard-dot-size*0.5 - $tmp-check-size*0.5;
margin-top: $grid-gutter-width*0.5;
padding: 0 $grid-gutter-width*0.5;
@include media-breakpoint-up(md) {
padding: 0;
}
.progress-rental-wizard-step {
position: relative;
@include media-breakpoint-up(md) {
margin-top: $tmp-dot-radius + $o-rental-wizard-thickness*3.5;
float: left;
width: percentage(1/3);
.o_wizard_has_extra_step + & {
width: percentage(1/4);
}
}
@include media-breakpoint-down(sm) {
&.disabled, &.complete {
display:none;
}
}
.progress-rental-wizard-dot {
width: $o-rental-wizard-dot-size;
height: $o-rental-wizard-dot-size;
position: relative;
display: inline-block;
background-color: $o-rental-wizard-color-inner;
border-radius: 50%;
box-shadow: 0 0 0 $o-rental-wizard-thickness $o-rental-wizard-color-default;
@include media-breakpoint-up(md) {
@include o-position-absolute($left: 50%);
margin: (-$tmp-dot-radius) 0 0 (-$o-rental-wizard-dot-size*0.5);
}
}
.progress-rental-wizard-steplabel {
color: $o-rental-wizard-label-default;
margin: 5px 0 5px 5px;
font-size: $font-size-base;
display: inline-block;
@include media-breakpoint-up(md) {
display: block;
margin: (0.625rem + $tmp-dot-radius) 0 20px 0;
}
@include media-breakpoint-down(sm) {
margin-left: -15px;
font-size: 24px;
}
}
.progress-rental-wizard-bar {
height: $o-rental-wizard-thickness;
background-color: $o-rental-wizard-color-default;
}
&.active {
.progress-rental-wizard-dot {
animation: fadeIn 1s ease 0s 1 normal none running;
background: $o-rental-wizard-dot-active;
box-shadow: 0 0 0 ($o-rental-wizard-dot-active-glow - 0.0625rem) $o-rental-wizard-color-inner,
0 0 0 $o-rental-wizard-dot-active-glow rgba($o-rental-wizard-dot-active, 0.5);
}
.progress-rental-wizard-steplabel {
color: $o-rental-wizard-label-active;
font-weight: bolder;
}
}
&.complete {
.progress-rental-wizard-dot {
background: none;
box-shadow: none;
&:after {
@include o-position-absolute($tmp-check-pos, $left: $tmp-check-pos);
width: $tmp-check-size;
height: $tmp-check-size;
border-radius: 100%;
background: $o-rental-wizard-color-inner;
color: $o-rental-wizard-dot-completed;
text-align: center;
line-height: 1;
font-size: $tmp-check-size;
font-family: FontAwesome;
content: "\f058";
}
}
.progress-rental-wizard-steplabel {
color: $o-rental-wizard-label-completed;
}
&:hover:not(.disabled) {
.progress-rental-wizard-dot:after {
color: $o-rental-wizard-label-completed;
}
.progress-rental-wizard-steplabel {
color: $o-rental-wizard-label-active;
}
}
}
&.disabled {
cursor: default;
}
}
}

View File

@@ -0,0 +1,800 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="wishlist_frontend" inherit_id="web.assets_frontend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/product_rental_bookings/static/src/js/main.js" />
<link rel="stylesheet" href="/product_rental_bookings/static/src/css/index.css" />
<link rel="profile" href="http://gmpg.org/xfn/11" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />
<script src="http://code.jquery.com/jquery-1.9.1.js" />
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js" />
</xpath>
</template>
<template id="header_company_menu_11" inherit_id="website.navbar_nav" name="Search Product">
<xpath expr="//ul[@id='top_menu']/t" position="before">
<li class="nav-item search_product">
<a href="/search-product">Book Product</a>
</li>
</xpath>
</template>
<template id="product_search" name="Product Search">
<t t-set="no_footer">1</t>
<t t-call="website.layout">
<t t-if="error">
<input type="hidden" t-att-value="error" id="error" />
<div class="modal fade" id="myModal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"></button>
<h4 class="modal-title"></h4>
</div>
<div class="modal-body">
<p>Rent Product not available right Now</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</t>
<div id="wrap" class="oe_structure oe_empty">
<div id="myCarousel1513682046664" class="carousel slide s_banner" data-interval="1000000" style="height: 450px;">
<ol class="carousel-indicators"></ol>
<div class="carousel-inner">
<div class="item bg-alpha oe_img_bg">
<div class="container">
<div class="row">
<div class="col-xs-12">
<form class="form" name="bookingform" id="bookingform" action="/booking_form" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<input type="hidden" name="lang" value="en" />
<center>
<div class="search-box">
<div class="row">
<div class="col-md-12">
<div class="col-md-8">
<lable>Based On</lable>
<select class="form-control" id='based_on' name='based_on' required="1">
<t t-if="post_data.get('based_on') == 'per_day'">
<option t-att-value="per_day" t-att-selected="per_day">
Days
</option>
<option value="per_hour">Hours</option>
<option value="per_session">Session</option>
</t>
<t t-if="post_data.get('based_on') == 'per_hour'">
<option t-att-value="per_hour" t-att-selected="per_hour">
Hours
</option>
<option value="per_day">Days</option>
<option value="per_session">Session</option>
</t>
<t t-if="post_data.get('based_on') == 'per_session'">
<option t-att-value="per_session" t-att-selected="per_session">
Session
</option>
<option value="per_day">Days</option>
<option value="per_hour">Hours</option>
</t>
<t t-if="not post_data.get('based_on') or post_data.get('based_on') not in ['per_day', 'per_hour', 'per_session']">
<option t-if="request.env['ir.config_parameter'].sudo().get_param('enabled_day_rent')" value="per_day">Days</option>
<option t-if="request.env['ir.config_parameter'].sudo().get_param('enabled_hour_rent')" value="per_hour">Hours</option>
<option t-if="request.env['ir.config_parameter'].sudo().get_param('enabled_session_rent')" value="per_session">Session</option>
</t>
</select>
</div>
</div>
<div class="col-md-12" id="session_selection" style="display:None;">
<div class="col-md-8">
<lable>Session</lable>
<t t-set="session_type" t-value="request.env['session.config'].sudo().search([])" />
<select class="form-control" id='session_type' name='session_type' required="1">
<option value="">Select Session
</option>
<t t-foreach="session_type" t-as="session_type">
<t t-if="post_data.get('session_type')">
<option t-att-value="session_type.id" t-att-selected="session_type.id == int(post_data.get('session_type'))">
<t t-esc="session_type.name" />
</option>
</t>
<t t-else="">
<option t-att-value='session_type.id'>
<t t-esc="session_type.name" />
</option>
</t>
</t>
</select>
</div>
</div>
<div class="col-md-12" id="for_day_selection">
<div class="col-md-8">
<lable>From Date</lable>
<input id="date_from" class="form-control" placeholder="dd-mm-yyyy" required="1" t-att-value="post_data.get('date_from')" name="date_from" type="text" size="20" />
</div>
<div class="col-md-8" id="to_date">
<lable>To Date</lable>
<input id="date_to" class="form-control" placeholder="dd-mm-yyyy" required="1" t-att-value="post_data.get('date_to')" name="date_to" type="text" size="20" />
</div>
</div>
<div class="col-md-12" id="for_hour_selection" style="display:None;">
<div class="col-md-8">
<lable>From DateTime</lable>
<input id="datetime_from" class="form-control" placeholder="dd-mm-yyyy HH:MM" t-att-value="post_data.get('datetime_from')" name="datetime_from" type="text" size="20" />
</div>
<div class="col-md-8">
<lable>To DateTime</lable>
<input id="datetime_to" class="form-control" placeholder="dd-mm-yyyy HH:MM" t-att-value="post_data.get('datetime_to')" name="datetime_to" type="text" size="20" />
</div>
</div>
<br />
<div class="col-md-12">
<div class="col-md-8">
<lable>Type</lable>
<t t-set="class_type" t-value="request.env['product.category'].sudo().search([])" />
<select class="form-control" id='class_type' name='class_type' required="1">
<option value="">Select Product Categoty
</option>
<t t-foreach="class_type" t-as="class_type">
<t t-if="post_data.get('class_type')">
<option t-att-value="class_type.id" t-att-selected="class_type.id == int(post_data.get('class_type'))">
<t t-esc="class_type.name" />
</option>
</t>
<t t-else="">
<option t-att-value='class_type.id'>
<t t-esc="class_type.name" />
</option>
</t>
</t>
</select>
</div>
</div>
<div class="col-md-12">
<div class="col-md-8">
<lable>Location</lable>
<t t-set="location" t-value="request.env['stock.location'].sudo().search([('usage', '=', 'internal')])" />
<select name="location" id="location" class="form-control full-width" required="1">
<t t-foreach="location" t-as="location_id">
<t t-if="post_data.get('location')">
<option t-att-value="location_id.id" t-att-selected="location_id.id == int(post_data.get('location'))">
<t t-esc="location_id.complete_name" />
</option>
</t>
<t t-else="">
<option t-att-value='location_id.id'>
<t t-esc="location_id.complete_name" />
</option>
</t>
</t>
</select>
</div>
<div class="col-md-3"></div>
<div class="col-md-3"></div>
</div>
<div class="col-md-12"></div>
</div>
<center>
<div class="" style="margin-top:10px;">
<button class="btn btn-lg" id="search_button" name="search_button" style="background-color: #008CBA;border-color: #008CBA;">
Search
</button>
</div>
</center>
</div>
</center>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<t t-if="product_id">
<div class="">
<div id="result_div" class="listing-car-items col vc_custom_1448532088457" align="center">
<div class="container">
<div class="card-deck row">
<t t-foreach="product_id" t-as="product_id">
<div class="col-md-3">
<form class="myre card" name="qutation_create" t-att-id="product_id.id" style="max-height: 281px;margin-bottom: 10px;" t-att-action="'/booking_form/create_quotation/%s' % slug(product_id)" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<input type="hidden" t-att-value="product_id.id" name="product_id_text" class="product_id_text" />
<button class="btn book_now" type="submit" name="book_now" style="background-color: white;">
<div class="card-body">
<span t-field="product_id.image_1920" class="card-img-top" t-options="{'widget': 'image','style':'width: 100px;height: 100px;'}" />
<div class="card-body">
<h5 class="card-title">
<t t-esc="product_id.name" />
</h5>
<span class="card-text">
<span>
<t t-if="product_id.rental_amount and request.session['based_on'] == 'per_day'">
<b>
<t t-esc="request.env.user.company_id.currency_id.symbol" />
<t t-esc="product_id.rental_amount" />
/Day
</b>
</t>
</span>
</span>
<p class="card-text">
<span>
<t t-if="product_id.rental_amount_per_hour and request.session['based_on'] == 'per_hour'">
<b>
<t t-esc="request.env.user.company_id.currency_id.symbol" />
<t t-esc="product_id.rental_amount_per_hour" />
/Hour
</b>
</t>
</span>
</p>
<p class="card-text">
<span>
<t t-if="product_id.rental_amount_per_hour and request.session['based_on'] == 'per_session'">
<b>
<t t-esc="request.env.user.company_id.currency_id.symbol" />
<t t-esc="product_id.rental_amount_per_session" />
/Session
</b>
</t>
</span>
</p>
</div>
</div>
</button>
<br />
</form>
</div>
</t>
</div>
</div>
</div>
</div>
</t>
</t>
</template>
<template id="rental_product" name="Rental Product">
<t t-call="website.layout">
<div class="row">
<div class="col-sm-4"></div>
<div class="col-sm-3 mb8"></div>
<div class="col-sm-3"></div>
<div class="col-sm-2 text-right"></div>
</div>
<input type="hidden" t-att-value="product_id.id" class="product_id" />
<div class="row">
<div class="col-sm-2"></div>
<div class="col-sm-3 col-lg-4 col-lg-offset-1" id="product_details">
<form action="/product_cart_update" class="js_add_cart_variants" method="POST" onkeydown="return event.key != 'Enter';">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<input type="hidden" t-att-value="product_id.id" class="product_id_cart" name="product_id_cart" />
<div>
<t t-placeholder="select">
<input type="hidden" class="product_id" name="product_id" t-att-value="product_id.id" />
</t>
<table style="width:150%;">
<tbody class="font-color">
<tr>
<td>
<span itemprop="image" t-field="product_id.image_1920" t-options="{'widget': 'image', 'class': 'product_detail_img', 'alt-field': 'name', 'zoom': 'image'}" />
</td>
<td>
<h3 itemprop="name" t-field="product_id.name">Product Name</h3>
</td>
</tr>
<tr style="height:70px;">
<td>Date From</td>
<td class='date_from'>
<t t-esc="request.session['date_from']" />
</td>
<td>Date To</td>
<td class='date_to'>
<t t-esc="request.session['date_to']" />
</td>
</tr>
<tr style="height:70px;">
<td>Based On</td>
<td class="price_based">
<select class="form-control rate_option" readonly='1' style="pointer-events: none;">
<t t-if="request.session['based_on'] == 'per_day'">
<option value="per_day">
Days
</option>
</t>
<t t-if="request.session['based_on'] == 'per_hour'">
<option value="per_hour">
Hours
</option>
</t>
<t t-if="request.session['based_on'] == 'per_session'">
<option value="per_session">
Session
</option>
</t>
</select>
</td>
<td class="total_hours">Total Hours</td>
<td class="total_hours">
<input class='form-control kilometer enter_hour' id="entered_hour" type="text" readonly="1" t-att-value="total_hours" name="enter_hour" />
</td>
<td class="total_days">Total Days</td>
<td class="total_day" name="total_day"></td>
</tr>
<tr style="height:70px;" class="total_days">
<td class="total_days">Rate Per Day</td>
<td class="total_days" style="height:30px;">
<lable class="cls_per_day_amount">
<t t-esc="request.env.user.company_id.currency_id.symbol" />
<t t-esc="product_id.rental_amount" />
</lable>
</td>
</tr>
<tr style="height:70px;" class="total_hour">
<t t-if="request.session['based_on'] == 'per_session'">
<td class="enter_hour">Rate Per Session</td>
<td class="enter_hour" style="height:30px;">
<lable class="cls_per_day_amount">
<t t-esc="request.env.user.company_id.currency_id.symbol" />
<t t-esc="product_id.rental_amount_per_session" />
</lable>
</td>
<td>Session</td>
<td style="height:30px;">
<t t-esc="request.session['session_type']" />
</td>
</t>
<t t-else="">
<td class="enter_hour">Rate Per Hour</td>
<td class="enter_hour" style="height:30px;">
<lable class="cls_per_day_amount">
<t t-esc="request.env.user.company_id.currency_id.symbol" />
<t t-esc="product_id.rental_amount_per_hour" />
</lable>
</td>
</t>
</tr>
<tr style="height:70px;">
<td>Total Amount</td>
<td>
<t t-esc="request.env.user.company_id.currency_id.symbol" />
<span class="calculate_total" />
</td>
<td class="quantity_needed">Enter quantity</td>
<td class="quantity_needed">
<input class='form-control qty_needed' type="text" name="qty_needed" />
</td>
</tr>
</tbody>
</table>
<t t-if=" not request.env.user._is_public()">
<button class="btn btn-primary btn-lg mt8 js_check_product a-submit" type="submit" name="update_cart">Add To Cart
</button>
</t>
<t t-if="request.env.user._is_public()">
<a class="btn btn-primary" t-att-href="'/web/login?redirect=/booking_form/create_quotation/%s' % slug(product_id)">
Sign in
</a>
</t>
</div>
</form>
<div class="o_not_editable"></div>
<hr />
<p class="text-muted">
30-day money-back guarantee
<br />
<br />
</p>
</div>
</div>
</t>
</template>
<template id="rental_order" name="Rental Cart Lines">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True" />
<t t-call="portal.portal_searchbar">
<t t-set="title">Rental Order</t>
</t>
<div id="wrap">
<div class="container oe_website_sale">
<div class="row">
<div class="col-md-8 col-md-offset-2 oe_cart">
<table class="table table-striped table-condensed js_cart_lines" id="cart_product" style="margin-top:35px;">
<thead style="background: lavender;">
<tr>
<th>Order Number</th>
<th>From Date</th>
<th>To Date</th>
<th>State</th>
<th></th>
</tr>
</thead>
<tbody>
<t t-foreach="order_id" t-as="order">
<form name="Rental Order Form" id="rental_order_form" action="/view_cart" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<tr>
<td t-if="order.res_number">
<div>
<strong t-esc="order.res_number" />
</div>
</td>
<td t-if="order.from_date">
<div>
<strong t-esc="order.from_date" />
</div>
</td>
<td t-if="order.to_date">
<div>
<strong t-esc="order.to_date" />
</div>
</td>
<td t-if="order.state">
<div>
<strong t-esc="order.state" />
</div>
</td>
<td>
<input type="submit" name="View Cart" value="View Cart" class="btn-info" />
</td>
<input type="hidden" t-att-value="order.id" name="rental_order" class="order" />
</tr>
</form>
</t>
</tbody>
</table>
</div>
</div>
</div>
</div>
</t>
</template>
<template id="checkout_process" name="Checkout Process">
<t t-call="website.layout">
<div id="wrap">
<div class="container oe_website_sale" style="height: 450px;">
<div class="main_content" style="position: relative;margin-top:50px;">
<div style="float:left;width:60%;padding: 10px;">
<t t-call="product_rental_bookings.payment" />
</div>
<div style="float:left;width:40%;margin-top:50px;">
<table class="table table-striped">
<thead>
<tr>
<th>Product</th>
<th>Total Days/Hour</th>
<th>Quantity</th>
<th>Sub Total</th>
</tr>
</thead>
<tbody>
<t t-foreach="website_rental_order.product_order_lines_ids" t-as="product_line">
<tr>
<td>
<span t-esc="product_line.product_id.name" />
</td>
<td>
<t t-if="product_line.price_based == 'per_day'">
<span t-esc="int(product_line.enter_days)" />
Days
</t>
<t t-else="">
<span t-esc="product_line.enter_hour" />
Hours
</t>
</td>
<td>
<span t-esc="product_line.qty_needed" />
</td>
<td>
<!-- <t t-set="subtotal" t-value="product_line.sub_total * product_line.qty_needed"/>-->
<span t-esc="product_line.sub_total" />
<span t-esc="request.env.user.company_id.currency_id.symbol" />
</td>
</tr>
</t>
</tbody>
</table>
<div style="width: 100%;">
<span style="float:right;">
<strong>Total:-</strong>
<!-- <t t-esc="subtotal"/>AAA-->
<span t-esc="website_rental_order.total_amount" />
<span t-esc="request.env.user.company_id.currency_id.symbol" />
</span>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<template id="payment" name="Payment">
<t t-set="additional_title">Shop - Select Payment Acquirer</t>
<t t-set="no_footer">1</t>
<t>
<div class="col-xl">
<div class="wizard">
<div class="progress-rental-wizard">
<a class="no-decoration">
<div id="wizard-step10" class="progress-rental-wizard-step complete">
<div class="progress-rental-wizard-bar d-none d-md-block" />
<span class="progress-rental-wizard-dot d-none d-md-inline-block"></span>
<div class="text-center progress-rental-wizard-steplabel">
Review Order
</div>
</div>
</a>
<a class="no-decoration">
<div id="wizard-step40" class="progress-rental-wizard-step active">
<div class="progress-rental-wizard-bar d-none d-md-block" />
<span class="progress-rental-wizard-dot d-none d-md-inline-block"></span>
<div class="text-center progress-rental-wizard-steplabel">
Confirm Order
</div>
</div>
</a>
</div>
</div>
</div>
</t>
<div id="wrap">
<div class="container oe_website_sale py-2">
<div class="row">
<div class="col-12" t-if="errors">
<t t-foreach="errors" t-as="error">
<div class="alert alert-danger" t-if="error" role="alert">
<h4>
<t t-esc="error[0]" />
</h4>
<t t-esc="error[1]" />
</div>
</t>
</div>
<div class="col-12 col-xl order-xl-1 oe_cart">
<div id="payment_method" class="mt-3" t-if="(acquirers or tokens) and website_rental_order.total_amount">
<h3 class="mb24">Pay with</h3>
<t t-call="payment.payment_tokens_list">
<t t-set="mode" t-value="'payment'" />
<t t-set="submit_txt">Pay Now</t>
<t t-set="icon_right" t-value="1" />
<t t-set="icon_class" t-value="'fa-chevron-right'" />
<t t-set="submit_class" t-value="'btn btn-primary'" />
<t t-set="pms" t-value="tokens" />
<t t-set="form_action" t-value="'/rental/payment/validate'" />
<t t-set="prepare_tx_url" t-value="'/rental/payment/transaction/'" />
<t t-set="partner_id" t-value="partner" />
<t t-set="back_button_icon_class" t-value="'fa-chevron-left'" />
<t t-set="back_button_txt" t-value="'Return to Cart'" />
<t t-set="back_button_class" t-value="'btn btn-secondary'" />
<t t-set="back_button_link" t-value="'/view_cart'" />
</t>
</div>
<div t-if="not acquirers" class="mt-2">
<a role="button" class="btn-link" groups="base.group_system" t-attf-href="/web#return_label=Website&amp;action=#{payment_action_id}">
<i class="fa fa-arrow-right"></i>
Add payment acquirers
</a>
</div>
<div class="js_payment mt-3" t-if="not website_rental_order.total_amount" id="payment_method">
<form action="/rental/payment/validate" method="post" class="float-right">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<button class="btn btn-primary" type="submit" name="payment">
<span class="fa fa-chevron-right" />
Pay Now
</button>
</form>
</div>
</div>
</div>
</div>
<div class="oe_structure" id="oe_structure_website_sale_payment_2" />
</div>
</template>
<template id="rental_order_comfirmation">
<t t-call="website.layout">
<div class="container">
<center style="margin-top: 60px;height: 350px;">
<div>
<i class="fa fa-check-circle" style="font-size: 9.00rem;color: #29b329;" id="checkmark" />
</div>
<div class="main">
<h2>
<b>
Order #
<t t-esc="order.res_number" />
</b>
</h2>
</div>
<div style="margin-top: 50px;">
<h4>
<span>
Your Order has been confirmed
<br />
We will contact you soon
</span>
</h4>
</div>
</center>
</div>
</t>
</template>
<template id="rental_product_cart_update" name="Product Cart">
<t t-call="website.layout">
<div id="wrap">
<div class="container oe_website_sale">
<div class="row">
<div class="col-md-8 col-md-offset-2 oe_cart">
<div class="row">
<div class="col-md-12 o_website_sale_rightfit">
<form name="Checkout Form" id="checkout_form" action="/checkout" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<!-- <input type="hidden" name="location" t-att-value="request.session['location_id']"/>-->
<!-- <h1><t t-esc="request.session['location']"/>AAAA</h1>-->
<div class="row">
<t t-if="product_order_id">
<div class="col-xl">
<div class="wizard">
<div class="progress-rental-wizard">
<a class="no-decoration">
<div id="wizard-step10" class="progress-rental-wizard-step active">
<div class="progress-rental-wizard-bar d-none d-md-block" />
<span class="progress-rental-wizard-dot d-none d-md-inline-block"></span>
<div class="text-center progress-rental-wizard-steplabel">
Review Order
</div>
</div>
</a>
<a class="no-decoration">
<div id="wizard-step40" class="progress-rental-wizard-step">
<div class="progress-rental-wizard-bar d-none d-md-block" />
<span class="progress-rental-wizard-dot d-none d-md-inline-block"></span>
<div class="text-center progress-rental-wizard-steplabel">
Confirm Order
</div>
</div>
</a>
</div>
</div>
</div>
</t>
</div>
<h2 class="mb8 mt8">Product Cart</h2>
<t t-call="product_rental_bookings.cart_lines" />
<div class="clearfix" />
<input type="hidden" t-att-value="product_order_id" id="product_order_id_exist" />
<a role="button" href="/search-product" class="btn btn-secondary mb32 d-none d-xl-inline-block">
<span class="fa fa-chevron-left" />
<span class="">Rent More</span>
</a>
<t t-if="product_order_id">
<input type="submit" name="process_checkout" id="process_checkout" value="Process Checkout" class="btn-info mb16" style="float:right; line-height:1.5 ;font-size:1.09375rem; padding: 0.5rem 1rem; border-radius:0.3rem" />
</t>
<div class="oe_structure" />
</form>
</div>
</div>
</div>
</div>
</div>
<div class="oe_structure" />
</div>
</t>
</template>
<template id="cart_lines" name="Rental Cart Lines">
<table class="table table-striped table-condensed js_cart_lines" id="cart_product">
<thead>
<tr>
<th class="td-img">Product</th>
<th></th>
<th>Total Days/Hour</th>
<th class="text-center td-price">Price</th>
<th>Quantity</th>
<th>Total Price</th>
</tr>
</thead>
<tbody>
<t t-foreach="product_order_id.product_order_lines_ids" t-as="line">
<tr class="product_order_line">
<td>
<span t-field="line.product_id.image_1920" t-class="img rounded o_image_64_max" t-options="{'widget': 'image', 'class': 'img-rounded'}" />
</td>
<td t-if="line.product_id.name" class='td-product_name'>
<div>
<strong t-esc="line.product_id.with_context(display_default_code=False).display_name" />
</div>
</td>
<td t-if="line.price_based in ['per_hour','per_session']">
<div>
<strong t-esc="line.enter_hour" />
hours
</div>
</td>
<td t-else="">
<div>
<strong t-esc="int(line.enter_days)" />
days
</div>
</td>
<td t-if="line.price">
<div t-if="line.price_based == 'per_day'" style="display: inline-flex;">
<strong t-esc="line.product_id.rental_amount" />
<strong t-esc="request.env.user.company_id.currency_id.symbol" />
</div>
<div t-if="line.price_based == 'per_hour'" style="display: inline-flex;">
<strong t-esc="line.product_id.rental_amount_per_hour" />
<strong t-esc="request.env.user.company_id.currency_id.symbol" />
</div>
<div t-if="line.price_based == 'per_session'" style="display: inline-flex;">
<strong t-esc="line.product_id.rental_amount_per_session" />
<strong t-esc="request.env.user.company_id.currency_id.symbol" />
</div>
</td>
<td t-if="line.qty_needed">
<strong t-esc="line.qty_needed" />
</td>
<td t-if="line.sub_total">
<div style="display: inline-flex;">
<strong t-esc="line.sub_total" />
<strong t-esc="request.env.user.company_id.currency_id.symbol" />
</div>
</td>
<td class="td-action">
<a aria-label="Remove from cart" t-att-id="line.id" title="Remove from cart" class='js_delete_product no-decoration'>
<small>
<i class='fa fa-trash-o' style="font-size: 25px;"></i>
</small>
</a>
</td>
</tr>
</t>
</tbody>
</table>
</template>
<template id="product_rental_order" name="Product Order" inherit_id="portal.portal_my_home" priority="20">
<xpath expr="//div[hasclass('o_portal_docs')]" position="inside">
<t t-call="portal.portal_docs_entry">
<t t-set="title">Rental Orders</t>
<t t-set="url" t-value="'/order'" />
<t t-set="count" t-value="rental_order_count" />
</t>
</xpath>
</template>
<template id="portal_my_rental_orders" name="Rental Orders" inherit_id="portal.portal_breadcrumbs" priority="20">
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
<li t-if="page_name == 'rental_order'" t-attf-class="breadcrumb-item">
Rental Orders
</li>
</xpath>
</template>
</data>
</odoo>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="submenu_inherit" inherit_id="website.submenu" name="Submenu">
<xpath expr="//li[1]" position="replace">
<li t-if="submenu.is_visible and not has_visible_submenu" t-attf-class="#{item_class or ''}">
<a t-att-href="submenu.clean_url()" t-attf-class="#{link_class or ''} #{'active' if submenu.clean_url() and unslug_url(request.httprequest.path) == unslug_url(submenu.clean_url()) else ''}" role="menuitem" t-ignore="true" t-att-target="'_blank' if submenu.new_window else None">
<span t-field="submenu.name" />
</a>
</li>
</xpath>
</template>
</odoo>