Browse Source

July 25: [FIX] Bug Fixed 'table_reservation_on_website'

pull/331/head
Cybrosys Technologies 9 months ago
parent
commit
319c4d5f32
  1. 4
      table_reservation_on_website/README.rst
  2. 29
      table_reservation_on_website/__manifest__.py
  3. 2
      table_reservation_on_website/controllers/__init__.py
  4. 70
      table_reservation_on_website/controllers/main.py
  5. 217
      table_reservation_on_website/controllers/table_reservation_on_website.py
  6. 155
      table_reservation_on_website/controllers/table_reservation_on_website_website_sale.py
  7. 10
      table_reservation_on_website/data/automatic_invoice.xml
  8. 18
      table_reservation_on_website/data/product_product_data.xml
  9. 11
      table_reservation_on_website/doc/RELEASE_NOTES.md
  10. 3
      table_reservation_on_website/models/__init__.py
  11. 54
      table_reservation_on_website/models/pos_config.py
  12. 42
      table_reservation_on_website/models/pos_session.py
  13. 28
      table_reservation_on_website/models/res_config_settings.py
  14. 42
      table_reservation_on_website/models/restaurant_floor.py
  15. 17
      table_reservation_on_website/models/restaurant_table.py
  16. 224
      table_reservation_on_website/models/table_reservation.py
  17. BIN
      table_reservation_on_website/static/description/assets/screenshots/1.png
  18. BIN
      table_reservation_on_website/static/description/assets/screenshots/10.png
  19. BIN
      table_reservation_on_website/static/description/assets/screenshots/11.png
  20. BIN
      table_reservation_on_website/static/description/assets/screenshots/12.png
  21. BIN
      table_reservation_on_website/static/description/assets/screenshots/13.png
  22. BIN
      table_reservation_on_website/static/description/assets/screenshots/14.png
  23. BIN
      table_reservation_on_website/static/description/assets/screenshots/15.png
  24. BIN
      table_reservation_on_website/static/description/assets/screenshots/16.png
  25. BIN
      table_reservation_on_website/static/description/assets/screenshots/17.png
  26. BIN
      table_reservation_on_website/static/description/assets/screenshots/18.png
  27. BIN
      table_reservation_on_website/static/description/assets/screenshots/19.png
  28. BIN
      table_reservation_on_website/static/description/assets/screenshots/2.png
  29. BIN
      table_reservation_on_website/static/description/assets/screenshots/20.png
  30. BIN
      table_reservation_on_website/static/description/assets/screenshots/21.png
  31. BIN
      table_reservation_on_website/static/description/assets/screenshots/3.png
  32. BIN
      table_reservation_on_website/static/description/assets/screenshots/4.png
  33. BIN
      table_reservation_on_website/static/description/assets/screenshots/5.png
  34. BIN
      table_reservation_on_website/static/description/assets/screenshots/6.png
  35. BIN
      table_reservation_on_website/static/description/assets/screenshots/7.png
  36. BIN
      table_reservation_on_website/static/description/assets/screenshots/8.png
  37. BIN
      table_reservation_on_website/static/description/assets/screenshots/9.png
  38. BIN
      table_reservation_on_website/static/description/assets/screenshots/hero.gif
  39. 221
      table_reservation_on_website/static/description/index.html
  40. 213
      table_reservation_on_website/static/src/app/booking_popup/createBookingPopup.js
  41. 134
      table_reservation_on_website/static/src/app/booking_popup/createBookingPopup.xml
  42. 227
      table_reservation_on_website/static/src/app/booking_popup/editBookingPopup.js
  43. 148
      table_reservation_on_website/static/src/app/booking_popup/editBookingPopup.xml
  44. 37
      table_reservation_on_website/static/src/app/screens/floor_screen/floor_screen.js
  45. 0
      table_reservation_on_website/static/src/app/screens/floor_screen/floor_screen.xml
  46. 10
      table_reservation_on_website/static/src/app/screens/product_screen/product_screen.js
  47. 63
      table_reservation_on_website/static/src/app/screens/reservation_screen/reservation_screen.js
  48. 89
      table_reservation_on_website/static/src/app/screens/reservation_screen/reservation_screen.xml
  49. 30
      table_reservation_on_website/static/src/js/FloorScreen.js
  50. 18
      table_reservation_on_website/static/src/js/PaymentScreen.js
  51. 72
      table_reservation_on_website/static/src/js/reservation.js
  52. 45
      table_reservation_on_website/static/src/js/reservation_floor.js
  53. 70
      table_reservation_on_website/static/src/js/table_reservation.js
  54. 18
      table_reservation_on_website/views/res_config_settings_views.xml
  55. 5
      table_reservation_on_website/views/restaurant_floor_views.xml
  56. 3
      table_reservation_on_website/views/restaurant_table_views.xml
  57. 200
      table_reservation_on_website/views/table_reservation_templates.xml
  58. 5
      table_reservation_on_website/views/table_reservation_views.xml

4
table_reservation_on_website/README.rst

@ -2,9 +2,9 @@
:target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html :target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3 :alt: License: LGPL-3
Table Reservation on Website Table Reservation On POS And Website
============================ ============================
This module allows to reserve tables in POS from website. This module allows to reserve tables in POS and website.
Configuration Configuration
============= =============

29
table_reservation_on_website/__manifest__.py

@ -20,8 +20,8 @@
# #
############################################################################### ###############################################################################
{ {
'name': 'Table Reservation on Website', 'name': 'Table Reservation On POS And Website',
'version': '17.0.1.0.2', 'version': '17.0.1.1.3',
'category': 'eCommerce,Point of Sale', 'category': 'eCommerce,Point of Sale',
'summary': 'Reserve tables in POS from website', 'summary': 'Reserve tables in POS from website',
'description': """This module enables to reserve tables in POS from website. 'description': """This module enables to reserve tables in POS from website.
@ -30,9 +30,10 @@
'company': 'Cybrosys Techno Solutions', 'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions', 'maintainer': 'Cybrosys Techno Solutions',
'website': 'https://www.cybrosys.com', 'website': 'https://www.cybrosys.com',
'depends': ['base', 'website_sale', 'pos_restaurant'], 'depends': ['pos_restaurant', 'base', 'website_sale', 'sale_management'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/automatic_invoice.xml',
'data/table_reservation_data.xml', 'data/table_reservation_data.xml',
'data/product_product_data.xml', 'data/product_product_data.xml',
'views/table_reservation_templates.xml', 'views/table_reservation_templates.xml',
@ -46,13 +47,29 @@
], ],
'assets': { 'assets': {
'point_of_sale._assets_pos': [ 'point_of_sale._assets_pos': [
'table_reservation_on_website/static/src/js/PaymentScreen.js', 'table_reservation_on_website/static/src/app/screens/floor_screen'
'table_reservation_on_website/static/src/js/FloorScreen.js', '/floor_screen.js',
'table_reservation_on_website/static/src/xml/FloorScreen.xml', 'table_reservation_on_website/static/src/app/screens/floor_screen'
'/floor_screen.xml',
'table_reservation_on_website/static/src/app/screens'
'/product_screen/product_screen.js',
'table_reservation_on_website/static/src/app/screens'
'/reservation_screen/reservation_screen.js',
'table_reservation_on_website/static/src/app/screens'
'/reservation_screen/reservation_screen.xml',
'table_reservation_on_website/static/src/app/booking_popup'
'/editBookingPopup.js',
'table_reservation_on_website/static/src/app/booking_popup'
'/editBookingPopup.xml',
'table_reservation_on_website/static/src/app/booking_popup'
'/createBookingPopup.js',
'table_reservation_on_website/static/src/app/booking_popup'
'/createBookingPopup.xml',
'table_reservation_on_website/static/src/scss/style.css', 'table_reservation_on_website/static/src/scss/style.css',
], ],
'web.assets_frontend': [ 'web.assets_frontend': [
'table_reservation_on_website/static/src/js/table_reservation.js', 'table_reservation_on_website/static/src/js/table_reservation.js',
'table_reservation_on_website/static/src/js/reservation.js',
'table_reservation_on_website/static/src/js/reservation_floor.js', 'table_reservation_on_website/static/src/js/reservation_floor.js',
], ],
}, },

2
table_reservation_on_website/controllers/__init__.py

@ -19,5 +19,5 @@
# If not, see <http://www.gnu.org/licenses/>. # If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################### ###############################################################################
from . import main from . import table_reservation_on_website_website_sale
from . import table_reservation_on_website from . import table_reservation_on_website

70
table_reservation_on_website/controllers/main.py

@ -1,70 +0,0 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Aysha Shalin (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from odoo import http
from odoo.exceptions import ValidationError
from odoo.http import request
from odoo.addons.website_sale.controllers.main import WebsiteSale
from odoo import SUPERUSER_ID
class WebsiteSalePayment(WebsiteSale):
""" For creating new record for table reservation """
@http.route('/shop/payment/validate', type='http', auth="public",
website=True, sitemap=False)
def shop_payment_validate(self, sale_order_id=None, **post):
""" Payment Validate page """
if sale_order_id is None:
order = request.website.sale_get_order()
if not order and 'sale_last_order_id' in request.session:
last_order_id = request.session['sale_last_order_id']
order = request.env['sale.order'].sudo().browse(
last_order_id).exists()
else:
order = request.env['sale.order'].sudo().browse(sale_order_id)
assert order.id == request.session.get('sale_last_order_id')
errors = self._get_shop_payment_errors(order)
if errors:
first_error = errors[0] # only display first error
error_msg = f"{first_error[0]}\n{first_error[1]}"
raise ValidationError(error_msg)
tx_sudo = order.get_portal_last_transaction() if order else order.env['payment.transaction']
if order:
reservation = request.env['table.reservation'].sudo().create({
"customer_id": request.env.user.partner_id.id,
"booked_tables_ids": order.tables_ids,
"floor_id": order.floors,
"date": order.date,
"starting_at": order.starting_at,
"ending_at": order.ending_at,
'booking_amount': order.booking_amount,
'state': 'reserved',
})
order.table_reservation_id = reservation.id
if not order or (order.amount_total and not tx_sudo):
return request.redirect('/shop')
if order and not order.amount_total and not tx_sudo:
order.with_context(send_email=True).with_user(SUPERUSER_ID).action_confirm()
return request.redirect(order.get_portal_url())
request.website.sale_reset()
if tx_sudo and tx_sudo.state == 'draft':
return request.redirect('/shop')
return request.redirect('/shop/confirmation')

217
table_reservation_on_website/controllers/table_reservation_on_website.py

@ -20,7 +20,8 @@
# #
############################################################################### ###############################################################################
from datetime import datetime, timedelta from datetime import datetime, timedelta
from odoo import http from odoo import http, _
from odoo.exceptions import ValidationError
from odoo.http import request from odoo.http import request
@ -65,11 +66,15 @@ class TableReservation(http.Controller):
'date', '=', datetime.strptime(kwargs.get('date'), 'date', '=', datetime.strptime(kwargs.get('date'),
"%Y-%m-%d")), ( "%Y-%m-%d")), (
'state', '=', 'reserved')]) 'state', '=', 'reserved')])
start_time_new = datetime.strptime(kwargs.get("start").strip(), "%H:%M").time() start_time_new = datetime.strptime(kwargs.get("start").strip(),
"%H:%M").time()
for rec in reserved: for rec in reserved:
start_at = datetime.strptime(rec.starting_at, "%H:%M").time() start_time = datetime.strptime(rec.starting_at, "%H:%M")
start_at = start_time - timedelta(
hours=int(rec.lead_time),
minutes=int((rec.lead_time % 1) * 100))
end_at = datetime.strptime(rec.ending_at, "%H:%M").time() end_at = datetime.strptime(rec.ending_at, "%H:%M").time()
if start_at <= start_time_new <= end_at: if start_at.time() <= start_time_new <= end_at:
for table in rec.booked_tables_ids: for table in rec.booked_tables_ids:
table_inbetween.append(table.id) table_inbetween.append(table.id)
data_tables = {} data_tables = {}
@ -94,58 +99,147 @@ class TableReservation(http.Controller):
def booking_confirm(self, **kwargs): def booking_confirm(self, **kwargs):
""" For booking tables """ """ For booking tables """
company = request.env.company company = request.env.company
list_tables = [rec for rec in kwargs.get("tables").split(',')] if kwargs.get("tables"):
record_tables = request.env['restaurant.table'].sudo().search( list_tables = [rec for rec in kwargs.get("tables").split(',')]
[('id', 'in', list_tables)]) record_tables = request.env['restaurant.table'].sudo().search(
amount = [rec.rate for rec in record_tables] [('id', 'in', list_tables)])
payment = request.env['ir.config_parameter'].sudo().get_param( amount = [rec.rate for rec in record_tables]
"table_reservation_on_website.reservation_charge") payment = request.env['ir.config_parameter'].sudo().get_param(
if payment: "table_reservation_on_website.reservation_charge")
table = request.env.ref( if payment:
'table_reservation_on_website' table = request.env.ref(
'.product_product_table_booking') 'table_reservation_on_website'
table.write({ '.product_product_table_booking').sudo()
'list_price': sum(amount) table.write({
}) 'list_price': sum(amount)
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) sale_order = request.website.sale_get_order(force_create=True)
sale_order.sudo().write({ if sale_order.state != 'draft':
'tables_ids': record_tables, request.session['sale_order_id'] = None
'floors': kwargs.get('floors'), sale_order = request.website.sale_get_order(
'date': kwargs.get('date'), force_create=True)
'starting_at': kwargs.get('start_time'), sale_order.sudo().write({
"ending_at": kwargs.get('end_time'), 'tables_ids': record_tables,
'booking_amount': sum(amount), 'floors': kwargs.get('floors'),
'order_line': [ 'date': kwargs.get('date'),
(0, 0, { 'starting_at': kwargs.get('start_time'),
'name': request.env.ref( "ending_at": kwargs.get('end_time'),
'table_reservation_on_website' 'booking_amount': sum(amount),
'.product_product_table_booking').name, 'order_line': [
'product_id': request.env.ref( (0, 0, {
'table_reservation_on_website' 'name': request.env.ref(
'.product_product_table_booking').id, 'table_reservation_on_website'
'product_uom_qty': 1, '.product_product_table_booking').name,
'price_unit': sum(amount), 'product_id': request.env.ref(
})], 'table_reservation_on_website'
}) '.product_product_table_booking').id,
sale_order.website_id = request.env['website'].sudo().search( 'product_uom_qty': 1,
[('company_id', '=', company.id)], limit=1) 'price_unit': sum(amount),
return request.redirect("/shop/cart") })],
else: })
request.env['table.reservation'].sudo().create({ sale_order.website_id = request.env['website'].sudo().search(
"customer_id": request.env.user.partner_id.id, [('company_id', '=', company.id)], limit=1)
"booked_tables_ids": record_tables, return request.redirect("/shop/cart")
"floor_id": kwargs.get('floors'), else:
"date": kwargs.get('date'), reservation = request.env['table.reservation'].sudo().create({
"starting_at": kwargs.get('start_time'), "customer_id": request.env.user.partner_id.id,
"ending_at": kwargs.get('end_time'), "booked_tables_ids": record_tables,
'booking_amount': 0, "floor_id": kwargs.get('floors'),
'state': 'reserved', "date": kwargs.get('date'),
}) "starting_at": kwargs.get('start_time'),
"ending_at": kwargs.get('end_time'),
'booking_amount': 0,
'state': 'reserved',
'type': 'website',
})
string = f'The reservation amount for the selected table is {reservation.booking_amount}.' if reservation.booking_amount > 0 else ''
list_of_tables = record_tables.mapped('name')
if len(list_of_tables) > 1:
tables_sentence = ', '.join(
list_of_tables[:-1]) + ', and ' + list_of_tables[-1]
else:
tables_sentence = list_of_tables[0]
final_sentence = string + " You have reserved " + tables_sentence + "."
request.env['mail.mail'].sudo().create({
'subject': "Table reservation",
'email_to': request.env.user.login,
'recipient_ids': [request.env.user.partner_id.id],
'body_html':
'<table border=0 cellpadding=0 cellspacing=0 '
'style="padding-top: 16px; background-color: '
'#F1F1F1; font-family:Verdana, Arial,sans-serif; '
'color: #454748; width: 100%; '
'border-collapse:separate;"><tr><td align=center>'
'<table border=0 cellpadding=0 cellspacing=0 width=590 style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">'
'<tbody>'
'<!-- HEADER -->'
'<tr>'
'<td align=center style="min-width: 590px;">'
'<table border=0 cellpadding=0 cellspacing=0 width=590 style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">'
'<tr>'
'<td valign=middle>'
'<span style="font-size: 10px;">'+reservation.sequence+'</span><br/>'
'<span style="font-size: 20px; font-weight: bold;">' + 'Table Reservation' + '</span>'
'</td>'
'<td valign="middle" align="right">'
'<img src="/logo.png?company=" + self.company_id.id + style="padding: 0px; margin: 0px; height: auto; width: 80px;"/>'
'</td>'
'</tr>'
'<tr>''<td colspan="2" style="text-align:center;">'
'<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>'
'</td>''</tr>'
'</table>'
'</td>'
'</tr>'
'<!-- CONTENT -->'
'<tr>'
'<td align="center" style="min-width: 590px;">'
'<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">'
'<tr>'
'<td valign="top" style="font-size: 13px;">'
'<div>'
'Dear' + ' ' + request.env.user.name + ',' '<br/>''<br/>'
'Your table booking at ' + request.env['restaurant.floor'].browse(int(kwargs.get('floors'))).name + ' ' + 'has been confirmed on '+str(reservation.date)+' for '+reservation.starting_at+' to '+reservation.ending_at + '.' + final_sentence +
'<br/>''<br/>'
'</span>'
'</div>'
'<br/>'
'Best regards''<br/>'
'</div>'
'</td>'
'</tr>'
'<tr>'
'<td style="text-align:center;">'
'<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>'
'</td>'
'</tr>'
'</table>'
'</td>'
'</tr>'
'<!-- FOOTER -->'
'<tr>'
'<td align="center" style="min-width: 590px;">'
'<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">'
'<tr>'
'<td valign="middle" align="left">'
+ request.env.company.name +
'<br/>'
+ request.env.company.phone +
'</td>'
'<td valign="middle" align="right">'
'<t t-if="%s" % +self.env.company_id.email>'
'<a href="mailto:%s % +request.env.company_id.email+" style="text-decoration:none; color: #5e6061;">'
+ request.env.company.email +
'</a>'
'</t>'
'<br/>'
'<t t-if="%s % +self.env.company.website+ ">'
'</table>'
}).send()
return request.render( return request.render(
"table_reservation_on_website.table_reserved_template") "table_reservation_on_website.table_reserved_template")
else:
raise ValidationError(_("Please select table."))
@http.route(['/table/reservation/pos'], type='json', auth='user', @http.route(['/table/reservation/pos'], type='json', auth='user',
website=True) website=True)
@ -170,6 +264,7 @@ class TableReservation(http.Controller):
'ending_at': end_time, 'ending_at': end_time,
'booking_amount': table.rate, 'booking_amount': table.rate,
'state': 'reserved', 'state': 'reserved',
'type': 'pos'
}) })
else: else:
request.env['table.reservation'].sudo().create({ request.env['table.reservation'].sudo().create({
@ -180,6 +275,7 @@ class TableReservation(http.Controller):
'ending_at': end_time, 'ending_at': end_time,
'booking_amount': 0, 'booking_amount': 0,
'state': 'reserved', 'state': 'reserved',
'type': 'pos'
}) })
return return
@ -188,17 +284,28 @@ class TableReservation(http.Controller):
def active_floor_tables(self, floor_id): def active_floor_tables(self, floor_id):
""" To get active floors """ """ To get active floors """
table_inbetween = [] table_inbetween = []
product_id = request.env.ref(
'table_reservation_on_website.'
'product_product_table_booking_pos')
for rec in request.env['pos.category'].sudo().search([]):
if rec:
product_id.pos_categ_ids = [(4, rec.id, 0)]
table_reservation = request.env['table.reservation'].sudo().search([( table_reservation = request.env['table.reservation'].sudo().search([(
'floor_id', "=", floor_id), ('date', '=', datetime.now().date()), 'floor_id', "=", floor_id), ('date', '=', datetime.now().date()),
('state', '=', 'reserved')]) ('state', '=', 'reserved')])
for rec in table_reservation: for rec in table_reservation:
start_at = datetime.strptime(rec.starting_at, "%H:%M").time() start_time = datetime.strptime(rec.starting_at, "%H:%M")
start_at = start_time - timedelta(
hours=int(rec.lead_time),
minutes=int((rec.lead_time % 1) * 100))
end_at = datetime.strptime(rec.ending_at, "%H:%M").time() end_at = datetime.strptime(rec.ending_at, "%H:%M").time()
now = ( now = (
(datetime.now() + timedelta(hours=5, (datetime.now() + timedelta(hours=5,
minutes=30)).time()).strftime( minutes=30)).time()).strftime(
"%H:%M") "%H:%M")
if start_at <= datetime.strptime(now, "%H:%M").time() <= end_at: if start_at.time() <= datetime.strptime(
now, "%H:%M").time() <= end_at:
for table in rec.booked_tables_ids: for table in rec.booked_tables_ids:
table_inbetween.append(table.id) table_inbetween.append(table.id)
return table_inbetween return table_inbetween

155
table_reservation_on_website/controllers/table_reservation_on_website_website_sale.py

@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Aysha Shalin (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from odoo import http
from odoo.exceptions import ValidationError
from odoo.http import request
from odoo.addons.website_sale.controllers.main import WebsiteSale
from odoo import SUPERUSER_ID
class WebsiteSalePayment(WebsiteSale):
""" For creating new record for table reservation """
@http.route('/shop/payment/validate', type='http', auth="public",
website=True, sitemap=False)
def shop_payment_validate(self, sale_order_id=None, **post):
""" Payment Validate page """
if sale_order_id is None:
order = request.website.sale_get_order()
if not order and 'sale_last_order_id' in request.session:
last_order_id = request.session['sale_last_order_id']
order = request.env['sale.order'].sudo().browse(
last_order_id).exists()
else:
order = request.env['sale.order'].sudo().browse(sale_order_id)
assert order.id == request.session.get('sale_last_order_id')
errors = self._get_shop_payment_errors(order)
if errors:
first_error = errors[0] # only display first error
error_msg = f"{first_error[0]}\n{first_error[1]}"
raise ValidationError(error_msg)
tx_sudo = order.get_portal_last_transaction() if order else order.env['payment.transaction']
if order.tables_ids:
reservation = request.env['table.reservation'].sudo().create({
"customer_id": request.env.user.partner_id.id,
"booked_tables_ids": order.tables_ids,
"floor_id": order.floors,
"date": order.date,
"starting_at": order.starting_at,
"ending_at": order.ending_at,
'booking_amount': order.booking_amount,
'state': 'reserved',
'type': 'website'
})
string = f'The reservation amount for the selected table is {order.order_line[0].price_unit}.' if order.order_line[0].price_unit > 0 else ''
list_of_tables = reservation.booked_tables_ids.mapped('name')
if len(list_of_tables) > 1:
tables_sentence = ', '.join(list_of_tables[:-1]) + ', and ' + \
list_of_tables[-1]
else:
tables_sentence = list_of_tables[0]
final_sentence = string + " You have reserved table " + tables_sentence + "."
request.env['mail.mail'].sudo().create({
'subject': "Table reservation",
'email_to': request.env.user.login,
'recipient_ids': [request.env.user.partner_id.id],
'body_html':
'<table border=0 cellpadding=0 cellspacing=0 '
'style="padding-top: 16px; background-color: '
'#F1F1F1; font-family:Verdana, Arial,sans-serif; '
'color: #454748; width: 100%; '
'border-collapse:separate;"><tr><td align=center>'
'<table border=0 cellpadding=0 cellspacing=0 width=590 style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">'
'<tbody>'
'<!-- HEADER -->'
'<tr>'
'<td align=center style="min-width: 590px;">'
'<table border=0 cellpadding=0 cellspacing=0 width=590 style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">'
'<tr>'
'<td valign=middle>'
'<span style="font-size: 10px;">'+reservation.sequence+'</span><br/>'
'<span style="font-size: 20px; font-weight: bold;">' + 'Table Reservation' + '</span>'
'</td>'
'<td valign="middle" align="right">'
'<img src="/logo.png?company=" + self.company_id.id + style="padding: 0px; margin: 0px; height: auto; width: 80px;"/>'
'</td>'
'</tr>'
'<tr>''<td colspan="2" style="text-align:center;">'
'<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>'
'</td>''</tr>'
'</table>'
'</td>'
'</tr>'
'<!-- CONTENT -->'
'<tr>'
'<td align="center" style="min-width: 590px;">'
'<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">'
'<tr>'
'<td valign="top" style="font-size: 13px;">'
'<div>'
'Dear' + ' ' + request.env.user.name + ',' '<br/>''<br/>'
'Your table booking at ' + request.env['restaurant.floor'].browse(int(order.floors)).name + ' ' + 'has been confirmed on '+str(reservation.date)+' for '+reservation.starting_at+' to '+reservation.ending_at + '.' + final_sentence +
'<br/>''<br/>'
'</span>'
'</div>'
'<br/>'
'Best regards''<br/>'
'</div>'
'</td>'
'</tr>'
'<tr>'
'<td style="text-align:center;">'
'<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>'
'</td>'
'</tr>'
'</table>'
'</td>'
'</tr>'
'<!-- FOOTER -->'
'<tr>'
'<td align="center" style="min-width: 590px;">'
'<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">'
'<tr>'
'<td valign="middle" align="left">'
+ request.env.company.name +
'<br/>'
+ request.env.company.phone +
'</td>'
'<td valign="middle" align="right">'
'<t t-if="%s" % +self.env.company_id.email>'
'<a href="mailto:%s % +request.env.company_id.email+" style="text-decoration:none; color: #5e6061;">'
+ request.env.company.email +
'</a>'
'</t>'
'<br/>'
'<t t-if="%s % +self.env.company.website+ ">'
'</table>'
}).send()
order.table_reservation_id = reservation.id
if not order or (order.amount_total and not tx_sudo):
return request.redirect('/shop')
if order and not order.amount_total and not tx_sudo:
order.with_context(send_email=True).with_user(SUPERUSER_ID).action_confirm()
return request.redirect(order.get_portal_url())
request.website.sale_reset()
if tx_sudo and tx_sudo.state == 'draft':
return request.redirect('/shop')
return request.redirect('/shop/confirmation')

10
table_reservation_on_website/data/automatic_invoice.xml

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="1">
<!--this record enable automatic invoice on online payment-->
<record id="automatic_invoice_online_payment" model="ir.config_parameter">
<field name="key">sale.automatic_invoice</field>
<field name="value">True</field>
</record>
</data>
</odoo>

18
table_reservation_on_website/data/product_product_data.xml

@ -5,11 +5,25 @@
<field name="name">Table Booking</field> <field name="name">Table Booking</field>
<field name="standard_price">0</field> <field name="standard_price">0</field>
<field name="list_price">0</field> <field name="list_price">0</field>
<field name="detailed_type">product</field> <field name="detailed_type">service</field>
<field name="uom_id" ref="uom.product_uom_unit"/> <field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/> <field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="description">Table Booking</field> <field name="description">Table Booking</field>
<field name="default_code">TB</field> <field name="default_code">TB</field>
<field name="image_1920" type="base64" file="table_reservation_on_website/static/img/table.png"/> <field name="image_1920" type="base64"
file="table_reservation_on_website/static/img/table.png"/>
</record>
<!-- To create new product Table Booking in POS -->
<record id="product_product_table_booking_pos" model="product.product">
<field name="name">Table Booking[POS]</field>
<field name="standard_price">0</field>
<field name="list_price">0</field>
<field name="detailed_type">service</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="description">Table Booking</field>
<field name="default_code">TB</field>
<field name="available_in_pos">true</field>
</record> </record>
</odoo> </odoo>

11
table_reservation_on_website/doc/RELEASE_NOTES.md

@ -3,14 +3,19 @@
#### 17.04.2024 #### 17.04.2024
#### Version 17.0.1.0.0 #### Version 17.0.1.0.0
##### ADD ##### ADD
- Initial commit for Table Reservation on Website - Initial commit for Table Reservation On POS And Website
#### 27.05.2024 #### 27.05.2024
#### Version 17.0.1.0.1 #### Version 17.0.1.0.1
##### UPDT ##### UPDATE
- Issue solved on the pos payment screen. - Issue solved on the pos payment screen.
#### 19.06.2024 #### 19.06.2024
#### Version 17.0.1.0.1 #### Version 17.0.1.0.2
##### BUGFIX ##### BUGFIX
- Fixed issues in table booking from website. - Fixed issues in table booking from website.
#### 25.07.2024
#### Version 17.0.1.1.3
##### BUGFIX
- Updated the module work flow.

3
table_reservation_on_website/models/__init__.py

@ -19,7 +19,10 @@
# If not, see <http://www.gnu.org/licenses/>. # If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################### ###############################################################################
from . import pos_config
from . import res_config_settings from . import res_config_settings
from . import restaurant_table from . import restaurant_table
from . import sale_order from . import sale_order
from . import table_reservation from . import table_reservation
from . import restaurant_floor
from . import pos_session

54
table_reservation_on_website/models/pos_config.py

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Aysha Shalin (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from odoo import fields, models
class PosConfig(models.Model):
""" Inherited model for adding new field to configuration settings
that allows to add lead time to reservations """
_inherit = 'pos.config'
has_lead_time = fields.Boolean(
string='Lead Time',
compute="_compute_has_lead_time",
help="Enable to set lead time for reservations")
has_reservation_charge = fields.Boolean(
string="Reservation Charge",
compute="_compute_has_reservation_charge",
help="Enable to apply charge for reservations.")
def _compute_has_lead_time(self):
""" To check whether lead time is enabled from settings """
if self.env['ir.config_parameter'].sudo().get_param(
'table_reservation_on_website.is_lead_time'):
self.has_lead_time = True
else:
self.has_lead_time = False
def _compute_has_reservation_charge(self):
""" To check whether reservation charge is enabled from settings """
if self.env['ir.config_parameter'].sudo().get_param(
'table_reservation_on_website.reservation_charge'):
self.has_reservation_charge = True
else:
self.has_reservation_charge = False

42
table_reservation_on_website/models/pos_session.py

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Aysha Shalin (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from odoo import models
class PosSession(models.Model):
"""inherited pos session to load the product product"""
_inherit = 'pos.session'
def _get_pos_ui_product_product(self, params):
"""load product to pos"""
result = super()._get_pos_ui_product_product(params)
product = self.env.ref('table_reservation_on_website'
'.product_product_table_booking')
data = product.read(fields=params['search_params']['fields'])
append_data = data[0]
append_data['categ'] = {'id': product.categ_id.id,
'name': product.categ_id.name,
'parent_id': product.categ_id.parent_id.id,
'parent': None}
result.append(append_data)
return result

28
table_reservation_on_website/models/res_config_settings.py

@ -32,14 +32,28 @@ class ResConfigSettings(models.TransientModel):
"reservation_on_" "reservation_on_"
"website.reservation" "website.reservation"
"_charge") "_charge")
refund = fields.Text(string="No Refund Notes", refund = fields.Text(string="Notes",
help="No refund notes to display in website") help="You can display this notes in Website table "
"booking")
is_lead_time = fields.Boolean(
string="Lead Time",
help="Enable to set lead time for reservations")
reservation_lead_time = fields.Float(
string="Reservation Lead Time",
help="The order should be reserved hours"
"before the booking start time.")
def set_values(self): def set_values(self):
""" To set the value for fields in config setting """ """ To set the value for fields in config setting """
res = super(ResConfigSettings, self).set_values()
self.env['ir.config_parameter'].set_param( self.env['ir.config_parameter'].set_param(
'table_reservation_on_website.refund', self.refund) 'table_reservation_on_website.refund', self.refund)
return super(ResConfigSettings, self).set_values() self.env['ir.config_parameter'].set_param(
'table_reservation_on_website.is_lead_time', self.is_lead_time)
self.env['ir.config_parameter'].sudo().set_param(
'table_reservation_on_website.reservation_lead_time',
self.reservation_lead_time)
return res
def get_values(self): def get_values(self):
""" To get the value in config settings """ """ To get the value in config settings """
@ -47,4 +61,12 @@ class ResConfigSettings(models.TransientModel):
refund = self.env['ir.config_parameter'].sudo().get_param( refund = self.env['ir.config_parameter'].sudo().get_param(
'table_reservation_on_website.refund') 'table_reservation_on_website.refund')
res.update(refund=refund if refund else False) res.update(refund=refund if refund else False)
is_lead_time = self.env['ir.config_parameter'].sudo().get_param(
'table_reservation_on_website.is_lead_time')
res.update(is_lead_time=is_lead_time if is_lead_time else False)
reservation_lead_time = self.env['ir.config_parameter'].sudo().get_param(
'table_reservation_on_website.reservation_lead_time')
res.update(
reservation_lead_time=reservation_lead_time if reservation_lead_time
else 0.0)
return res return res

42
table_reservation_on_website/models/restaurant_floor.py

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Aysha Shalin (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from odoo import api, fields, models
class RestaurantFloor(models.Model):
""" Inherit restaurant table for adding is_show_field field """
_inherit = 'restaurant.floor'
is_show_field = fields.Boolean(string='Show field',
compute='_compute_is_show_field',
help='Depends on the field value field '
'rate visibility is determined')
@api.depends('name')
def _compute_is_show_field(self):
"""Compute field rate visibility using this function"""
for record in self:
if record.env['ir.config_parameter'].get_param(
'table_reservation_on_website.reservation_charge'):
record.is_show_field = True
else:
record.is_show_field = False

17
table_reservation_on_website/models/restaurant_table.py

@ -19,7 +19,7 @@
# If not, see <http://www.gnu.org/licenses/>. # If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################### ###############################################################################
from odoo import fields, models from odoo import api, fields, models
class RestaurantTable(models.Model): class RestaurantTable(models.Model):
@ -28,3 +28,18 @@ class RestaurantTable(models.Model):
rate = fields.Float(string="Amount", rate = fields.Float(string="Amount",
help="Amount for reservation of this table") help="Amount for reservation of this table")
is_show_field = fields.Boolean(string='Show field',
compute='_compute_is_show_field',
help='Depends on the field value field '
'rate visibility is determined')
@api.depends('name')
def _compute_is_show_field(self):
"""Compute field rate visibility using this function"""
for record in self:
if record.env['ir.config_parameter'].get_param(
'table_reservation_on_website.reservation_charge'):
record.is_show_field = True
else:
record.is_show_field = False

224
table_reservation_on_website/models/table_reservation.py

@ -19,9 +19,9 @@
# If not, see <http://www.gnu.org/licenses/>. # If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################### ###############################################################################
from datetime import datetime from datetime import date, datetime, timedelta
import re import re
from odoo import _, api, fields, models from odoo import api, fields, models, _
from odoo.exceptions import UserError from odoo.exceptions import UserError
@ -51,12 +51,39 @@ class TableReservation(models.Model):
help="Ending time of reservation", help="Ending time of reservation",
required=True) required=True)
booking_amount = fields.Float(string="Booking Charge", booking_amount = fields.Float(string="Booking Charge",
help="Amount for booking", help="Amount for booking", store=True,
compute="_compute_booking_amount") compute="_compute_booking_amount")
state = fields.Selection([('draft', "Draft"), ('reserved', 'Reserved'), state = fields.Selection([('draft', "Draft"), ('reserved', 'Reserved'),
('done', "Done"), ("cancel", "Cancelled")], ('done', "Done"), ("cancel", "Cancelled")],
default='draft', string="Status", default='draft', string="Status",
help="State for records") help="State for records")
type = fields.Selection(string='Order Type',
selection=[('website', 'Website'), ('pos', 'POS')],
help="The type of Order")
lead_time = fields.Float(string='Lead time',
compute="_compute_lead_time",
store=True, readonly=False,
help="The order should be reserved hours before"
" the booking start time")
lead_time_computed = fields.Boolean(string="Lead Time Computed",
default=False)
order_name = fields.Char(string='Order Name',
help='pos order name')
@api.depends('lead_time_computed')
def _compute_lead_time(self):
""" Default lead time for reservations """
for rec in self:
if not rec.lead_time_computed:
is_lead_time = self.env['ir.config_parameter'].sudo().get_param(
'table_reservation_on_website.is_lead_time')
if is_lead_time and rec.lead_time == 0:
lead_times = self.env[
'ir.config_parameter'].sudo().get_param(
'table_reservation_on_website.reservation_lead_time')
if lead_times:
rec.lead_time = lead_times
rec.lead_time_computed = True
@api.model @api.model
def create(self, vals): def create(self, vals):
@ -86,9 +113,8 @@ class TableReservation(models.Model):
tables = self.env['restaurant.table'].search([('floor_id', '=', tables = self.env['restaurant.table'].search([('floor_id', '=',
self.floor_id.id)]) self.floor_id.id)])
table_inbetween = [] table_inbetween = []
reserved = self.search([('floor_id', '=', self.floor_id.id), ('date', reserved = self.search([('floor_id', '=', self.floor_id.id),
'=', ('date', '=', self.date),
self.date),
('state', '=', 'reserved')]) ('state', '=', 'reserved')])
if self.starting_at: if self.starting_at:
start_time_new = datetime.strptime( start_time_new = datetime.strptime(
@ -135,3 +161,189 @@ class TableReservation(models.Model):
self.write({ self.write({
'state': 'done' 'state': 'done'
}) })
def table_reservations(self):
""" To show reservations in pos product screen """
today = date.today()
reservations = self.search_read([('date', '>=', today),
('state', '=', 'reserved')])
return reservations
@api.model
def edit_reservations(self, booking_id, date, customer, start_time,
end_time, floor, table_ids, lead, order_name=None):
""" For editing reservations from pos """
time_float = 0
is_lead_time = self.env['ir.config_parameter'].sudo().get_param(
"table_reservation_on_website.is_lead_time")
default_lead_time = self.env['ir.config_parameter'].sudo().get_param(
"table_reservation_on_website.reservation_lead_time")
if is_lead_time:
if lead:
if isinstance(lead, str):
hours, minutes = map(int, lead.split(':'))
time_float = hours + minutes / 100.0
else:
time_float = lead
else:
time_float = default_lead_time
if isinstance(table_ids, str):
table_ids_list = [int(rec) for rec in table_ids.split(',')]
else:
table_ids_list = table_ids
reservation = self.browse(booking_id)
customer_id = self.env['res.partner'].browse(int(customer))
floor_id = self.env['restaurant.floor'].browse(floor)
reservation.update({
'lead_time': time_float,
'date': datetime.strptime(date, "%Y-%m-%d"),
'customer_id': customer_id.id,
'starting_at': start_time,
'order_name': order_name,
'ending_at': end_time,
'floor_id': floor_id.id,
'booked_tables_ids': [(6, 0, [rec for rec in table_ids_list])],
})
product_id = self.env.ref(
'table_reservation_on_website.'
'product_product_table_booking_pos')
return product_id.id
@api.model
def get_table_details(self, floor_id, date, start_time, end_time,
booked_table_id=None):
""" To get un-reserved table details """
table_inbetween = []
tables = self.env['restaurant.table'].sudo().search(
[('floor_id', '=', int(floor_id))])
reservations = self.env['table.reservation'].sudo().search(
[('floor_id', '=', int(floor_id)), (
'date', '=', datetime.strptime(date, "%Y-%m-%d")),
('state', '=', 'reserved')])
start_time = datetime.strptime(start_time, "%H:%M").time()
end_time = datetime.strptime(end_time, "%H:%M").time()
if reservations:
for rec in reservations:
starting_time = datetime.strptime(
rec.starting_at, "%H:%M")
reservation_start = starting_time - timedelta(
hours=int(rec.lead_time),
minutes=int((rec.lead_time % 1) * 100))
reservation_end = datetime.strptime(rec.ending_at,
"%H:%M").time()
if reservation_start.time() <= start_time <= reservation_end or reservation_start.time() <= end_time < reservation_end:
for table in rec.booked_tables_ids:
table_inbetween.append(table.id)
elif start_time <= reservation_start.time() <= end_time or start_time <= reservation_end < end_time:
for table in rec.booked_tables_ids:
table_inbetween.append(table.id)
data_tables = []
for rec in tables:
if rec.id not in table_inbetween:
data_tables.append({
'id': rec.id,
'name': rec.name
})
if booked_table_id:
for id in booked_table_id:
if self.env['restaurant.table'].browse(id).floor_id.id == int(
floor_id):
data_tables.append({
'id': id,
'name': self.env['restaurant.table'].browse(id).name
})
return data_tables
@api.model
def get_reservation_amount(self, table_id=None):
""" For fetching the reservation amount details of tables """
amount = 0
if table_id:
payment = self.env['ir.config_parameter'].sudo().get_param(
"table_reservation_on_website.reservation_charge")
table_id_list = [int(num) for num in table_id.split(',')]
tables = self.env['restaurant.table'].search([
('id', 'in', table_id_list)
])
if payment and table_id:
if table_id:
sum_amount = [rec.rate for rec in tables]
amount = sum(sum_amount)
else:
amount = amount
return amount
return amount
@api.model
def create_table_reservation(self, table_id, date, start_time, end_time,
partner, lead_time, floor_id, order_name=None):
""" For pos table booking """
time_float = 0
table_id_list = [int(num) for num in table_id.split(',')]
is_lead_time = self.env['ir.config_parameter'].sudo().get_param(
"table_reservation_on_website.is_lead_time")
default_lead_time = self.env['ir.config_parameter'].sudo().get_param(
"table_reservation_on_website.reservation_lead_time")
if is_lead_time:
if lead_time:
hours, minutes = map(int, lead_time.split(':'))
time_float = hours + minutes / 100.0
else:
time_float = default_lead_time
else:
time_float = time_float
partner_id = self.env['res.partner'].browse(int(partner))
self.env['table.reservation'].create({
'customer_id': partner_id.id,
'floor_id': floor_id,
'booked_tables_ids': [(6, 0, [rec for rec in table_id_list])],
'date': date,
'starting_at': start_time,
'ending_at': end_time,
'state': 'reserved',
'type': 'pos',
'lead_time': time_float,
'order_name': order_name,
})
product_id = self.env.ref(
'table_reservation_on_website.'
'product_product_table_booking_pos')
return product_id.id
@api.model
def get_avail_table(self, floor_id, date, start_time, end_time, table_ids):
"""To check if table is available while editing reservations from pos"""
table_ids_list = []
available = True
if table_ids and isinstance(table_ids, str):
table_ids_list = [int(rec) for rec in table_ids.split(',')]
reservations = self.env['table.reservation'].search([
('floor_id', '=', int(floor_id)),
('date', '=', datetime.strptime(date, "%Y-%m-%d")),
('booked_tables_ids', 'in', table_ids_list),
('state', '=', 'reserved')
])
start_time = datetime.strptime(start_time, "%H:%M").time()
end_time = datetime.strptime(end_time, "%H:%M").time()
if reservations:
for rec in reservations:
starting_time = datetime.strptime(
rec.starting_at, "%H:%M")
reservation_start = starting_time - timedelta(
hours=int(rec.lead_time),
minutes=int((rec.lead_time % 1) * 100))
reservation_end = datetime.strptime(rec.ending_at,
"%H:%M").time()
if reservation_start.time() <= start_time <= reservation_end:
available = False
elif start_time <= reservation_start.time() <= end_time:
available = False
return available
@api.model
def cancel_reservations(self, res_id):
""" Cancel reservations from pos screen """
res = self.browse(int(res_id))
res.update({
'state': 'cancel'
})

BIN
table_reservation_on_website/static/description/assets/screenshots/1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 108 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/10.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 167 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/11.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/12.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/13.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/14.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/15.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/17.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/18.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/19.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 58 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/20.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/21.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 58 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 85 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/5.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 88 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/6.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 69 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/7.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 154 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/8.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 51 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 105 KiB

BIN
table_reservation_on_website/static/description/assets/screenshots/hero.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 526 KiB

221
table_reservation_on_website/static/description/index.html

@ -26,14 +26,6 @@
style="background-color:#017E84 !important;font-size: 0.8rem !important; color:#fff !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important; min-width: 120px !important;"> style="background-color:#017E84 !important;font-size: 0.8rem !important; color:#fff !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important; min-width: 120px !important;">
Community Community
</div> </div>
<div class="text-center"
style="background-color:#875A7B !important; color:#fff !important;font-size: 0.8rem !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important;min-width: 120px !important;">
Enterprise
</div>
<div class="text-center"
style="background-color:#7C7BAD !important; color:#fff !important;font-size: 0.8rem !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important; min-width: 120px !important;">
Odoo.sh
</div>
</div> </div>
</div> </div>
</div> </div>
@ -42,10 +34,10 @@
style="margin: 80px 0px !important;"> style="margin: 80px 0px !important;">
<h1 style="font-size: 2.8rem;font-weight: 700; color: <h1 style="font-size: 2.8rem;font-weight: 700; color:
#1A202C;"> #1A202C;">
Table Reservation on Website</h1> Table Reservation On POS And Website</h1>
<p class="my-3 mb-4" <p class="my-3 mb-4"
style="max-width: 80%; font-weight: 400 !important; line-height: 32px; color: #718096;"> style="max-width: 80%; font-weight: 400 !important; line-height: 32px; color: #718096;">
Reserve Tables in POS through Website. Reserve POS Tables Through Website And Pos.
</p> </p>
<div style="width: 80%; margin-top: 3rem;"> <div style="width: 80%; margin-top: 3rem;">
<img src="assets/screenshots/hero.gif" class="img-responsive" width="100%" height="auto"> <img src="assets/screenshots/hero.gif" class="img-responsive" width="100%" height="auto">
@ -133,6 +125,23 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/17.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Lead Time.</h4>
<p class="m-0" style="color:#718096">
Enable the Lead Time if the Table should be Reserved a Certain Amount of Time before the Booking Start Time.
You can Edit the Time for Each Reservation Separately from POS.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;"> <div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div <div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
@ -146,6 +155,10 @@
<h4 class="mt-2" <h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important"> style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Add Reservation Amount for POS Tables in Floors.</h4> Add Reservation Amount for POS Tables in Floors.</h4>
<p class="m-0" style="color:#718096">
Go to Configuration -> Floor Plans, Select Floor and Add Reservation Amount for
Tables.
</p>
</div> </div>
</div> </div>
</div> </div>
@ -158,7 +171,8 @@
<div class="px-3"> <div class="px-3">
<h4 class="mt-2" <h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important"> style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Select Booking Date and Time.</h4> Booking Table from Website.</h4>
<p class="m-0" style="color:#718096">Select Booking Date and Time.</p>
</div> </div>
</div> </div>
</div> </div>
@ -190,19 +204,64 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/18.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Table is Reserved.</h4>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;"> <div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div <div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0"> <div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/7.png" class="img-responsive" width="100%" height="auto"> <img src="assets/screenshots/7.png" class="img-responsive" width="100%" height="auto">
</div> </div>
<div class="row justify-content-center p-3 w-100 m-0"> <div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Table Reservation in Backend.</h4>
<p class="m-0" style="color:#718096">
New Reservation will be created in the Backend. Go to Configuration -> Table Reservation.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/8.png" class="img-responsive" width="100%" height="auto"> <img src="assets/screenshots/8.png" class="img-responsive" width="100%" height="auto">
</div> </div>
<div class="px-3"> <div class="px-3">
<h4 class="mt-2" <h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important"> style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Table Reservation Details.</h4> Table Reservation Details.</h4>
<p class="m-0" style="color:#718096">
Order Type should be 'Website' if the Reservation is created from Website and 'POS' if it is created from POS.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/19.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Table Booking with Reservation Amount.</h4>
<p class="m-0" style="color:#718096">
If the Reservation Charge is Enabled then Booking Amount will be Displayed.
</p>
</div> </div>
</div> </div>
</div> </div>
@ -217,7 +276,7 @@
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important"> style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Cart.</h4> Cart.</h4>
<p class="m-0" style="color:#718096"> <p class="m-0" style="color:#718096">
Redirect to Cart page while Clicking the Button 'Booking Confirm'. Redirect to Cart page while Clicking the Button 'Booking Confirm' and Make the Payment.
</p> </p>
</div> </div>
</div> </div>
@ -233,7 +292,139 @@
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important"> style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Sale Order.</h4> Sale Order.</h4>
<p class="m-0" style="color:#718096"> <p class="m-0" style="color:#718096">
In sale order we can see the Table Reservation details. In Sale Order we can see the Table Reservation Details.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/15.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Book Table from POS.</h4>
<p class="m-0" style="color:#718096">
Click on 'Book table' to Display the Reservations.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/11.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Reservation Screen in POS.</h4>
<p class="m-0" style="color:#718096">
Displays Current and Upcoming Reservations in POS. You can Create and Edit reservations from here.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/12.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Create New Reservations.</h4>
<p class="m-0" style="color:#718096">
You can Create New Reservations by Clicking on 'Create' Button and Fill the Details.
Select any Floor to Choose the Available Tables of Corresponding Floor. If Reservation Charge is
not enabled, then you can Confirm your Booking on Clicking 'Confirm' Button.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/20.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Charge for Reservations from POS.</h4>
<p class="m-0" style="color:#718096">
If Reservation Charge is enabled, the Amount will be visible, and you need to Pay the Reservation
Amount for Booking by Clicking on 'Pay'.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/21.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Reservation Payment.</h4>
<p class="m-0" style="color:#718096">
Clicking on the Pay Button will create an Order in th POS for the Product 'Table Booking' with a
Unit Price equal to the Reservation Amount of the selected Tables, and you can then make the Payment.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/13.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Edit the Reservations.</h4>
<p class="m-0" style="color:#718096">
You can Edit Reservations by Clicking on 'Edit' Button and Edit the Details.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/14.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Cancel the Reservations.</h4>
<p class="m-0" style="color:#718096">
Able to Cancel the Reservations by clicking on 'Cancel' button.
</p>
</div>
</div>
</div>
<div class="col-lg-12 py-2" style="padding: 1rem 4rem !important;">
<div
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<div class="row justify-content-center p-3 w-100 m-0">
<img src="assets/screenshots/16.png" class="img-responsive" width="100%" height="auto">
</div>
<div class="px-3">
<h4 class="mt-2"
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important">
Displayed Reserved Label on Tables in POS Floor Screen.</h4>
<p class="m-0" style="color:#718096">
The Label 'Reserved' will be Visible on Tables in POS Floor Screen During the Reservation Period.
</p> </p>
</div> </div>
</div> </div>
@ -275,7 +466,7 @@
</div> </div>
<p class="m-0" <p class="m-0"
style=" color:#718096!important; font-size:1rem !important;line-height: 28px;"> style=" color:#718096!important; font-size:1rem !important;line-height: 28px;">
Initial Commit for Table Reservation on Website.</p> Initial Commit for Table Reservation On POS And Website.</p>
</div> </div>
</div> </div>
</div> </div>

213
table_reservation_on_website/static/src/app/booking_popup/createBookingPopup.js

@ -0,0 +1,213 @@
/**@odoo-module **/
import { AbstractAwaitablePopup } from "@point_of_sale/app/popup/abstract_awaitable_popup";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { usePos } from "@point_of_sale/app/store/pos_hook";
import { useService } from "@web/core/utils/hooks";
import { useState } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
export class createBookingPopup extends AbstractAwaitablePopup {
setup() {
super.setup();
this.orm = useService('orm');
this.pos = usePos();
this.popup = useService("popup");
this.state = useState({
customers: this.env.services.pos.partners,
partner: '',
floors: this.env.services.pos.floors,
floor: '',
date: '',
start_time: '',
end_time: '',
tables: [],
table: '',
amount: '',
lead_time: '',
Table: '',
table_details_header: false,
});
}
// Filter tables according to floor selected
async onSelectFloor(ev) {
this.state.amount = ''
const selectedFloorText = ev.target.options[ev.target.selectedIndex].text;
if (ev.target.options[ev.target.selectedIndex].text != 'Select Floor'){
var table_data = []
this.state.table_details_header = true
this.state.Table = ''
var floor_id = this.state.floor
var date = this.state.date
var start_time = this.state.start_time
var end_time = this.state.end_time
if (start_time > end_time){
this.popup.add(ConfirmPopup, {
title: _t("Error"),
body: _t("Start time can't be greater than end time."),
});
}
if ((start_time && end_time) && (start_time === end_time)) {
this.popup.add(ConfirmPopup, {
title: _t("Error"),
body: _t("Start time and end time can't be same."),
});
}
if (date && start_time && end_time){
var table_data = await this.orm.call('table.reservation', 'get_table_details', [
floor_id, date, start_time, end_time])
this.state.tables = table_data
}
}
}
// To Check selected date is valid one
async onChangeDate() {
var selectedDate = new Date(this.state.date);
const currentDate = new Date();
if (selectedDate < currentDate.setHours(0, 0, 0, 0)){
this.popup.add(ErrorPopup, {
title: _t("Invalid Date"),
body: _t("Please select a valid date."),
}).then(() => {
this.state.date = null;
});
}
this.onChangeTime()
}
// To check selected time is not past one
onChangeTime() {
let now = new Date();
let currentHours = now.getHours().toString().padStart(2, '0');
let currentMinutes = now.getMinutes().toString().padStart(2, '0');
let currentTime = `${currentHours}:${currentMinutes}`;
// Get the current date
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Months are zero-based
const day = String(currentDate.getDate()).padStart(2, '0');
// Format the date as YYYY-MM-DD
const formattedDate = `${year}-${month}-${day}`;
if (this.state.date == formattedDate){
if (this.state.start_time && this.state.start_time < currentTime) {
this.popup.add(ErrorPopup, {
title: _t("Invalid Time"),
body: _t("You can't select past time."),
}).then(() => {
this.state.start_time = null;
});
}
else if (this.state.end_time && this.state.end_time < currentTime) {
this.popup.add(ErrorPopup, {
title: _t("Invalid Time"),
body: _t("You can't select past time."),
}).then(() => {
this.state.end_time = null;
});
}
}
// Check start time is not greater than end time
if ((this.state.start_time && this.state.end_time) && (this.state.start_time > this.state.end_time)){
this.popup.add(ConfirmPopup, {
title: _t("Error"),
body: _t("Start time can't be greater than end time."),
}).then(() => {
this.state.start_time = null;
this.state.end_time = null;
});
}
// Check start and end time not same
if ((this.state.start_time && this.state.end_time) && (this.state.start_time === this.state.end_time)) {
this.popup.add(ConfirmPopup, {
title: _t("Error"),
body: _t("Start time and end time can't be same."),
}).then(() => {
this.state.start_time = null;
this.state.end_time = null;
});
}
}
// Select tables for booking
async onSelectTable(ev) {
var table_div = ev.target.closest('.card_table');
var tableId = table_div.getAttribute('data-id');
if (table_div.style.backgroundColor === 'green') {
table_div.style.backgroundColor = '#96ccd5';
this.state.Table = this.state.Table.split(',').filter(id => id !== tableId).join(',');
} else {
table_div.style.backgroundColor = 'green';
if (this.state.Table.length > 0) {
this.state.Table += ',' + tableId;
} else {
this.state.Table = tableId;
}
}
if (this.state.floor && this.state.Table !== '') {
var reservation_amount = await this.orm.call('table.reservation', 'get_reservation_amount', [this.state.Table]);
this.state.amount = reservation_amount;
} else {
this.state.amount = 0;
}
}
// Create new reservation
createReservation() {
this.onChangeTime()
if (this.state.partner && this.state.date && this.state.start_time && this.state.end_time
&& this.state.floor && this.state.Table) {
this.orm.call('table.reservation', 'create_table_reservation', [
this.state.Table, this.state.date, this.state.start_time, this.state.end_time,
this.state.partner, this.state.lead_time, this.state.floor])
location.reload()
}
else{
this.popup.add(ErrorPopup, {
title: _t("Alert"),
body: _t("Please fill all the required details."),
});
}
}
// Create new reservation and make payments if reservation charge enabled
async createReservationPayment(ev) {
this.onChangeTime()
if (this.state.start_time > this.state.end_time){
this.popup.add(ConfirmPopup, {
title: _t("Error"),
body: _t("Start time can't be greater than end time."),
});
}
if ((this.state.start_time && this.state.end_time) && (this.state.start_time === this.state.end_time)) {
this.popup.add(ConfirmPopup, {
title: _t("Error"),
body: _t("Start time and end time can't be same."),
});
}
if (this.state.partner && this.state.partner != 'Select Customer') {
if (this.state.date && this.state.start_time && this.state.end_time
&& this.state.floor && this.state.Table) {
var data = await this.orm.call('table.reservation', 'create_table_reservation', [
this.state.Table, this.state.date, this.state.start_time, this.state.end_time,
this.state.partner, this.state.lead_time, this.state.floor, this.pos.get_order().name])
this.cancel();
this.pos.showScreen('ProductScreen');
var product = this.pos.db.product_by_id[data]
product['lst_price'] = this.state.amount
this.pos.get_order().set_partner(this.pos.db.partner_by_id[parseInt(this.state.partner)])
this.pos.get_order().add_product(product, {
quantity: 1,
});
}
else{
this.popup.add(ErrorPopup, {
title: _t("Alert"),
body: _t("Please fill all the required details."),
});
}
}
else {
this.popup.add(ErrorPopup, {
title: _t("Alert"),
body: _t("Please fill all the required details."),
});
}
}
}
createBookingPopup.template = "createBookingPopup";

134
table_reservation_on_website/static/src/app/booking_popup/createBookingPopup.xml

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="createBookingPopup" owl="1">
<div class="popup product-line-popup">
<div class="modal-header">
<h4 class="modal-title title">
<span>Reserve Table</span>
</h4>
</div>
<main class="modal-body" style="height: 70vh; overflow-y: scroll;">
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="name">Customer</label>
<select name="partners"
class="form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-model="state.partner" id="partners" required="true">
<option>Select Customer</option>
<t t-foreach="state.customers" t-as="partner" t-key="partner.id">
<option t-att-value="partner.id">
<t t-out="partner.name"/>
</option>
</t>
</select>
<span style="position: absolute; left: 79px; top: 20px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="Date">Date</label>
<input type="date" class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-att-required="true" name="Date" id="date"
t-model="state.date" t-on-change="onChangeDate"/>
<span style="position: absolute; left: 48px; top: 96px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="starting time">Start Time</label>
<input type="time" class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-att-required="true" id="start_time" name="starting time"
t-model="state.start_time" t-on-change="onChangeTime"/>
<span style="position: absolute; left: 83px; top: 171px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="ending time">End Time</label>
<input type="time" class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-att-required="true" id="end_time" name="ending time"
t-model="state.end_time" t-on-change="onChangeTime"/>
<span style="position: absolute; left: 76px; top: 245px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="Floor">Floor</label>
<select name="floor"
class="form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-model="state.floor" required="true"
id="floor" t-on-change="onSelectFloor">
<option t-att-value="state.floor">Select Floor</option>
<t t-foreach="state.floors"
t-as="floor"
t-key="floor.id">
<option t-att-value="floor.id">
<t t-out="floor.name"/>
</option>
</t>
</select>
<span style="position: absolute; left: 51px; top: 320px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="table-details-header pe-2 gap-2" t-attf-style="{{ this.state.table_details_header ? 'display: block;' : 'display: none;' }}">
<div id="table_list">
<div class="row" id="table_container_row" style="margin-left: 1%;">
<t t-foreach="state.tables" t-as="table" t-key="table.id">
<div class="card card_table col-sm-2 text-truncate" t-att-data-id="table.id" t-on-click="onSelectTable"
style="background-color:#96ccd5; padding-right:0; padding-left: 0; margin:3px; width:30%; height: 50px;">
<div class="card-body" style="height: 40px;">
<span class="select_table_id text-truncate"><t t-esc="table.name"/></span>
<br/><br/>
</div>
</div>
</t>
</div>
</div>
</div>
<br/>
<br/>
<t t-if="env.services.pos.config.has_reservation_charge">
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="amount">Amount</label>
<input class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
name="amount" readonly="1" t-model="state.amount"/>
</div>
</t>
<br/>
<br/>
<t t-if="env.services.pos.config.has_lead_time">
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="Lead Time">Lead Time</label>
<input type="time" class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-model="state.lead_time" name="Lead Time"/>
</div>
</t>
</main>
<footer class="footer modal-footer">
<t t-if="env.services.pos.config.has_reservation_charge">
<div class="button confirm btn btn-lg btn-primary"
t-on-click="createReservationPayment">
Pay
</div>
</t>
<t t-else="">
<div class="button confirm btn btn-lg btn-primary"
t-on-click="createReservation">
Confirm
</div>
</t>
<div class="button cancel btn btn-lg btn-secondary"
t-on-click="cancel">
Cancel
</div>
</footer>
</div>
</t>
</templates>

227
table_reservation_on_website/static/src/app/booking_popup/editBookingPopup.js

@ -0,0 +1,227 @@
/**@odoo-module **/
import { AbstractAwaitablePopup } from "@point_of_sale/app/popup/abstract_awaitable_popup";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { usePos } from "@point_of_sale/app/store/pos_hook";
import { useService } from "@web/core/utils/hooks";
import { useState } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
export class EditBookingPopup extends AbstractAwaitablePopup {
async setup() {
super.setup();
this.orm = useService('orm')
this.popup = useService("popup")
this.pos = usePos()
const floors = this.env.services.pos.floors
const tables = floors.find(floor => floor.id === this.props.data?.floor_id[0])?.tables || [];
const bookedTableIds = this.props.data?.booked_tables_ids || [];
const parsedBookedTableIds = typeof bookedTableIds === 'string'
? bookedTableIds.split(',').map(Number).filter(id => !isNaN(id))
: [...bookedTableIds];
this.state = useState({
customerId: this.props.data?.customer_id[0],
Date: this.props.data?.date,
StartingTime: this.props.data?.starting_at,
EndTime: this.props.data?.ending_at,
Floor: this.props.data?.floor_id[0],
TableList: parsedBookedTableIds,
Table: parsedBookedTableIds.join(','),
BookingAmount: this.props.data?.booking_amount,
OrderType: this.props.data?.type,
LeadTime: this.props.data?.lead_time,
Partners: this.env.services.pos.partners,
floors: this.env.services.pos.floors,
tables: [],
time:'',
table_details_header: false,
});
if ((this.state.StartingTime && this.state.EndTime) && (this.state.StartingTime === this.state.EndTime)){
this.popup.add(ConfirmPopup, {
title: _t("Error"),
body: _t("Start time and end time can't be same."),
});
}
this.convertDecimalToTime(this.state.LeadTime)
var table_data = await this.orm.call('table.reservation', 'get_table_details', [
this.state.Floor, this.state.Date, this.state.StartingTime, this.state.EndTime, this.state.TableList])
this.state.tables = table_data
}
// Convert lead time number to string
convertDecimalToTime(decimalHours) {
const [hours, decimalMinutes] = decimalHours.toString().split('.');
const minutes = decimalMinutes ? decimalMinutes.padEnd(2, '0') : '00';
const formattedHours = String(hours).padStart(2, '0');
const formattedMinutes = String(minutes).padStart(2, '0');
this.state.time = `${formattedHours}:${formattedMinutes}`;
}
// Partner details
selectPartner(ev) {
this.state.customerId = parseInt(ev.target.value)
}
// Filter tables according to selected floor
async onSelectFloor(ev) {
this.state.BookingAmount = ''
this.state.TableList = [];
if (ev.target.options[ev.target.selectedIndex].text != 'Select Floor'){
this.state.table_details_header = true
this.state.Floor = parseInt(ev.target.value)
var table_data = []
var date = this.state.Date
var start_time = this.state.StartingTime
var end_time = this.state.EndTime
var floor_id = this.state.Floor
this.state.Table = ''
if (start_time > end_time){
this.popup.add(ErrorPopup, {
title: _t("Error"),
body: _t("Start time can't be greater than end time."),
});
}
if (floor_id && date && start_time && end_time){
var table_data = await this.orm.call('table.reservation', 'get_table_details', [
floor_id, date, start_time, end_time, this.props.data.booked_tables_ids])
this.state.tables = table_data
}
}
}
// To Check selected date is valid one
async onChangeDate() {
var selectedDate = new Date($("#date").val());
const currentDate = new Date();
if (selectedDate < currentDate.setHours(0, 0, 0, 0)){
this.popup.add(ErrorPopup, {
title: _t("Invalid Date"),
body: _t("Please select a valid date."),
}).then(() => {
$("#date").val('')
});
}
this.onChangeTime()
}
// To check selected start time is not past one
onChangeTime() {
let now = new Date();
let currentHours = now.getHours().toString().padStart(2, '0');
let currentMinutes = now.getMinutes().toString().padStart(2, '0');
let currentTime = `${currentHours}:${currentMinutes}`;
// Get the current date
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Months are zero-based
const day = String(currentDate.getDate()).padStart(2, '0');
// Format the date as YYYY-MM-DD
const formattedDate = `${year}-${month}-${day}`;
if (this.state.Date == formattedDate){
if (this.state.StartingTime && this.state.StartingTime < currentTime) {
this.popup.add(ErrorPopup, {
title: _t("Invalid Time"),
body: _t("You can't select past time."),
}).then(() => {
this.state.StartingTime = null;
});
}
if (this.state.EndTime && this.state.EndTime < currentTime) {
this.popup.add(ErrorPopup, {
title: _t("Invalid Time"),
body: _t("You can't select past time."),
}).then(() => {
this.state.EndTime = null;
});
}
}
if ((this.state.StartingTime && this.state.EndTime) && (this.state.StartingTime === this.state.EndTime)){
this.popup.add(ErrorPopup, {
title: _t("Error"),
body: _t("Start time and end time can't be same."),
}).then(() => {
this.state.StartingTime = null;
this.state.EndTime = null;
});
}
if ((this.state.StartingTime && this.state.EndTime) && (this.state.StartingTime > this.state.EndTime)){
this.popup.add(ErrorPopup, {
title: _t("Error"),
body: _t("Start time can't be greater than end time."),
}).then(() => {
this.state.StartingTime = null;
this.state.EndTime = null;
});
}
}
// To Check selected lead time is valid
async onChangeLeadTime(ev) {
if (isNaN(this.state.LeadTime)) {
this.popup.add(ErrorPopup, {
title: _t("Invalid Lead Time"),
body: _t("Please select a valid lead time."),
}).then(() => {
this.state.LeadTime = null;
});
}
this.state.LeadTime = ev.target.value
this.convertDecimalToTime(this.state.LeadTime)
}
// Save the edited reservation details
async saveData() {
var partners = this.env.services.pos.partners
var booking_id = this.props.data['id']
var date = this.state.Date
var customer = this.state.customerId
var start_time = this.state.StartingTime
var end_time = this.state.EndTime
var floor = this.state.Floor
var table_ids = this.state.Table
var lead_time = this.state.LeadTime
this.onChangeTime()
if (partners && booking_id && date && customer && start_time && end_time && floor && table_ids.length>0){
var data = await this.orm.call('table.reservation', 'edit_reservations', [
booking_id, date, customer, start_time, end_time, floor, table_ids, lead_time, this.pos.get_order().name
]);
var order = this.pos.orders.find(order => order.name === this.props.data.order_name);
if (order){
this.pos.removeOrder(order);
var product = this.pos.db.product_by_id[data]
if (product){
product['lst_price'] = this.state.BookingAmount
}
this.pos.get_order().set_partner(this.pos.db.partner_by_id[parseInt(this.state.customerId)])
this.pos.get_order().add_product(product, {
quantity: 1,
});
}
location.reload();
}
else {
this.popup.add(ErrorPopup, {
title: _t("Alert"),
body: _t("Please fill all the required details."),
});
}
}
// Select tables for booking
async onSelectTable(event) {
const tableDiv = event.target.closest('.card_table');
const tableId = parseInt(tableDiv.getAttribute('data-id'), 10);
let currentTableList = [...this.state.TableList];
let currentTable = this.state.Table ? this.state.Table.split(',').map(Number) : [];
if (tableDiv.style.backgroundColor == 'green') {
tableDiv.style.backgroundColor = '#2980b9';
currentTableList = currentTableList.filter(id => id !== tableId);
currentTable = currentTable.filter(id => id !== tableId);
}
else {
currentTableList.push(tableId);
currentTable.push(tableId);
tableDiv.style.backgroundColor = 'green';
}
// Update state with the new values
this.state.TableList = currentTableList;
this.state.Table = currentTable.join(',');
if(this.state.Floor){
var reservation_amount = await this.orm.call('table.reservation', 'get_reservation_amount', [this.state.Table])
this.state.BookingAmount = reservation_amount
}
}
}
EditBookingPopup.template = "EditBookingPopup";

148
table_reservation_on_website/static/src/app/booking_popup/editBookingPopup.xml

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="EditBookingPopup" owl="1">
<div class="popup booking-line-popup" style="display: block;">
<div class="modal-header">
<h4 class="modal-title title">
<span>Edit Booking</span>
</h4>
</div>
<main class="modal-body" style="height: 70vh; overflow-y: scroll;">
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="name">Customer</label>
<br/>
<select name="partners"
class="form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
id="partner" t-on-change="selectPartner">
<option>Select Partner</option>
<t t-foreach="state.Partners" t-as="partner"
t-key="partner.id">
<option t-att-value="partner.id"
t-att-selected="partner.id == state.customerId ? 'selected' : null">
<t t-esc="partner.name"/>
</option>
</t>
</select>
<span style="position: absolute; left: 79px; top: 20px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="Date">Date</label>
<input type="date" id="date"
class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-model="state.Date" name="Date" t-on-change="onChangeDate"/>
<span style="position: absolute; left: 48px; top: 96px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label"
for="starting time">Start Time</label>
<input type="time" class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-model="state.StartingTime" name="starting time"
t-on-change="onChangeTime"/>
<span style="position: absolute; left: 83px; top: 171px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label"
for="ending time">End Time</label>
<input type="time" class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-model="state.EndTime" name="ending time"
t-on-change="onChangeTime"/>
<span style="position: absolute; left: 76px; top: 245px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="Floor">Floor</label>
<select name="floor"
class="form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
required="true"
id="floor" t-on-change="onSelectFloor">
<option>Select Floor</option>
<t t-foreach="state.floors"
t-as="floor"
t-key="floor.id" >
<option t-att-value="floor.id"
t-att-selected="floor.id == state.Floor ? 'selected' : null">
<t t-esc="floor.name"/>
</option>
</t>
</select>
<span style="position: absolute; left: 51px; top: 320px; color: red;">*</span>
</div>
<br/>
<br/>
<div class="table-details-header pe-2 gap-2">
<div id="table_list" >
<div class="row" id="table_container_row" style="margin-left: 1%;">
<t t-foreach="state.tables" t-as="table" t-key="table.id">
<t t-set="isSelected" t-value="state.TableList.includes(table.id)"/>
<div class="card card_table col-sm-2 text-truncate" t-on-click="onSelectTable" t-att-data-id="table.id"
t-attf-style="background-color: #{isSelected ? 'green' : '#96ccd5'}; padding-right:0; padding-left: 0; margin:3px; width:30%; height: 70px;">
<div class="card-body" style="height: 40px;">
<span class="select_table_id text-truncate"><t t-esc="table.name"/></span>
<br/><br/>
</div>
</div>
</t>
</div>
</div>
</div>
<br/>
<br/>
<t t-if="env.services.pos.config.has_reservation_charge">
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="amount">Amount</label>
<input class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-model="state.BookingAmount" name="amount"
readonly="1"/>
</div>
</t>
<br/>
<br/>
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label"
for="order type">Order Type</label>
<input class="detail partner-name form-control form-control-lg"
style="position: absolute; left:100px; width:75%;"
t-model="state.OrderType" name="order type"
readonly="1"/>
</div>
<br/>
<br/>
<t t-if="env.services.pos.config.has_lead_time">
<div class="partner-details-header d-flex pe-2 gap-2">
<label class="col-form-label" for="Lead Time">Lead Time</label>
<input type='time'
class="detail lead-time form-control form-control-lg"
style="position: absolute; left: 100px; width: 75%;"
t-model="state.time" name="Lead Time"
t-on-change="onChangeLeadTime"/>
</div>
</t>
<br/>
<br/>
</main>
<footer class="footer modal-footer">
<div class="button confirm btn btn-lg btn-primary"
t-on-click="saveData">
Confirm
</div>
<div class="button cancel btn btn-lg btn-secondary"
t-on-click="cancel">
Cancel
</div>
</footer>
</div>
</t>
</templates>

37
table_reservation_on_website/static/src/app/screens/floor_screen/floor_screen.js

@ -0,0 +1,37 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { FloorScreen } from "@pos_restaurant/app/floor_screen/floor_screen";
import { jsonrpc } from "@web/core/network/rpc_service";
patch(FloorScreen.prototype, {
async setup() {
super.setup(...arguments);
await this.fetchActiveTables();
},
/**
For showing reserved tables in pos floor screen
**/
async fetchActiveTables() {
try {
var self = this
const data = await jsonrpc('/active/floor/tables', { 'floor_id': this.activeFloor.id});
this.tables = data;
let reserved_tables = [];
for (let rec in this.tables) {
let new_table = this.activeFloor.tables.find(item => item['id'] == this.tables[rec]);
if (new_table) {
reserved_tables.push(new_table);
}
}
reserved_tables.forEach(function(record) {
record.reserved = true;
});
} catch (error) {
console.error('Error fetching active tables:', error);
}
},
get activeTables() {
this.fetchActiveTables();
return this.activeFloor ? this.activeFloor.tables : null;
}
});

0
table_reservation_on_website/static/src/xml/FloorScreen.xml → table_reservation_on_website/static/src/app/screens/floor_screen/floor_screen.xml

10
table_reservation_on_website/static/src/app/screens/product_screen/product_screen.js

@ -0,0 +1,10 @@
/** @odoo-module */
import { patch } from "@web/core/utils/patch";
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
patch(ProductScreen.prototype, {
// Override the bookTable function for displaying and booking of tables
bookTable() {
this.pos.showScreen("ReservationsScreen");
},
});

63
table_reservation_on_website/static/src/app/screens/reservation_screen/reservation_screen.js

@ -0,0 +1,63 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
import { Component, onWillStart } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { useState } from "@odoo/owl";
import { usePos } from "@point_of_sale/app/store/pos_hook";
import { _t } from "@web/core/l10n/translation";
import { EditBookingPopup } from "@table_reservation_on_website/app/booking_popup/editBookingPopup";
import { createBookingPopup } from "@table_reservation_on_website/app/booking_popup/createBookingPopup";
export class ReservationsScreen extends Component {
static template = "table_reservation_on_website.ReservationsScreen";
setup() {
super.setup(...arguments);
this.orm = useService("orm");
this.pos = usePos();
this.popup = useService("popup");
this.state = useState({
bookings: [],
booking_id: [],
});
onWillStart(async () => {
await this.getReservationList()
})
}
// Displays reservations in reservation screen
async getReservationList() {
var data = await this.orm.call('table.reservation', 'table_reservations', [[]])
this.state.bookings = data
const posTables = this.env.services.pos.tables_by_id
}
// Get reservation details
get bookingList() {
return this.state.bookings || []
}
// Popup for editing reservations
async onClickEdit(data) {
const { confirmed, payload } = await this.popup.add(EditBookingPopup, {
title: _t("Edit Reservation"),
data
});
}
// For cancelling Reservations
async onClickCancel(data){
var res_id = data['id']
await this.orm.call('table.reservation', 'cancel_reservations', [data['id']])
if (data.order_name){
var order = this.pos.orders.find(order => order.name === data.order_name);
if(order){
this.pos.removeOrder(order);
}
}
location.reload()
}
// Popup for creating reservations
async createBooking() {
const { confirmed, payload } = await this.popup.add(createBookingPopup, {
title: _t("Reserve Table"),
});
}
}
registry.category("pos_screens").add("ReservationsScreen", ReservationsScreen);

89
table_reservation_on_website/static/src/app/screens/reservation_screen/reservation_screen.xml

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="table_reservation_on_website.ReservationsScreen" owl="1">
<div class="reservation-screen screen h-100 d-flex flex-column bg-100">
<div class="top-content d-flex align-items-center p-2 border-bottom text-center">
<button class="button new-booking highlight btn btn-lg btn-primary" role="img"
aria-label="Reserve table"
t-on-click="createBooking"
title="Reserve table">
<span> Create</span>
</button>
<div class="button back btn btn-lg btn-secondary mx-2"
t-on-click="() => this.pos.showScreen('ProductScreen')">
<span> Discard</span>
</div>
</div>
<div class="orders overflow-y-auto flex-grow-1">
<div class="header-row d-flex text-bg-700 fw-bolder">
<div class="col wide p-2">Customer</div>
<div class="col wide p-2">Date</div>
<div class="col wide p-2">Starts at</div>
<div class="col wide p-2">Ends at</div>
<div class="col wide p-2">Floor</div>
<div class="col wide p-2">Table ID</div>
<div class="col wide p-2">Booking Amount</div>
<div class="col wide p-2">Order Type</div>
<div class="col wide p-2">Lead Time</div>
<div class="col wide p-2">Details</div>
<div class="col wide p-2">Cancel</div>
</div>
<t t-foreach="bookingList" t-as="res" t-key="res.id">
<div class="order-row">
<div class="col wide p-2 ">
<div>
<t t-esc="res.customer_id[1]"/>
</div>
</div>
<div class="col wide p-2">
<div>
<t t-esc="res.date"/>
</div>
</div>
<div class="col wide p-2">
<div>
<t t-esc="res.starting_at"/>
</div>
</div>
<div class="col wide p-2">
<div>
<t t-esc="res.ending_at"/>
</div>
</div>
<div class="col wide p-2">
<div>
<t t-esc="res.floor_id[1]"/>
</div>
</div>
<div class="col wide p-2">
<div>
<t t-esc="res.booked_tables_ids"/>
</div>
</div>
<div class="col wide p-2">
<div>
<t t-esc="res.booking_amount"/>
</div>
</div>
<div class="col wide p-2">
<div>
<t t-esc="res.type"/>
</div>
</div>
<div class="col wide p-2">
<div>
<t t-esc="res.lead_time"/>
</div>
</div>
<div class="col wide p-2">
<button class="edit-partner-button btn btn-light border" t-att-data-id="res.id" t-on-click="() => this.onClickEdit(res)">Edit</button>
</div>
<div class="col wide p-2">
<button class="delete-partner-button btn btn-light border" t-att-data-id="res.id" t-on-click="() => this.onClickCancel(res)">Cancel</button>
</div>
</div>
</t>
</div>
</div>
</t>
</templates>

30
table_reservation_on_website/static/src/js/FloorScreen.js

@ -1,30 +0,0 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { FloorScreen } from "@pos_restaurant/app/floor_screen/floor_screen";
import { jsonrpc } from "@web/core/network/rpc_service";
patch(FloorScreen.prototype, {
setup() {
super.setup(...arguments);
},
/**
For payment validation in pos
**/
get activeTables() {
var self = this
jsonrpc('/active/floor/tables', {'floor_id' : self.activeFloor.id,
}).then( function(data){
self.tables = data
});
let reserved_tables = []
for(let rec in self.tables){
let new_tables = self.activeFloor.tables.find(item => item['id'] == self.tables[rec])
if (new_tables){
reserved_tables.push(new_tables)
}
}
reserved_tables.forEach(function(record){
record.reserved = true;
});
return self.activeFloor ? self.activeFloor.tables : null;
}
});

18
table_reservation_on_website/static/src/js/PaymentScreen.js

@ -1,18 +0,0 @@
/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen";
import { jsonrpc } from "@web/core/network/rpc_service";
import { patch } from "@web/core/utils/patch";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
patch(PaymentScreen.prototype, {
/**
For payment validation in pos
**/
async _finalizeValidation() {
jsonrpc('/table/reservation/pos',{
'table_id': this.currentOrder.tableId
}).then( function(data){})
return super._finalizeValidation()
}
});

72
table_reservation_on_website/static/src/js/reservation.js

@ -0,0 +1,72 @@
/** @odoo-module */
import publicWidget from "@web/legacy/js/public/public_widget";
publicWidget.registry.reservation = publicWidget.Widget.extend({
selector: '.container',
events: {
'change #date': '_onChangeDate',
'change #start_time': '_onChangeTime',
'change #end_time': '_onChangeTime',
'click .close_btn_alert_modal': '_onClickCloseBtn',
'click .close_btn_time_alert_modal': '_onClickCloseAlertBtn',
},
// To ensure booking date is a valid one.
_onChangeDate: function (ev) {
var selectedDate = new Date(this.$el.find("#date").val())
const currentDate = new Date();
if (selectedDate.setHours(0, 0, 0, 0) < currentDate.setHours(0, 0, 0, 0)) {
this.$el.find("#alert_modal").show();
this.$el.find("#date").val('')
}
this._onChangeTime()
},
// To close the alert modal if invalid date is chosen.
_onClickCloseBtn: function() {
this.$el.find("#alert_modal").hide();
},
// Display a modal if invalid start time and end is chosen.
_onChangeTime: function() {
var start_time = this.$el.find("#start_time")
var end_time = this.$el.find("#end_time")
let now = new Date();
// Get the current time
let currentHours = now.getHours().toString().padStart(2, '0');
let currentMinutes = now.getMinutes().toString().padStart(2, '0');
let currentTime = `${currentHours}:${currentMinutes}`;
// Get the current date
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Months are zero-based
const day = String(currentDate.getDate()).padStart(2, '0');
// Format the date as YYYY-MM-DD
const formattedDate = `${year}-${month}-${day}`;
if (start_time.val() && end_time.val()) {
if (start_time.val() > end_time.val()) {
this.$el.find("#time_alert_modal").show()
start_time.val('')
end_time.val('')
}
}
if (start_time.val() && end_time.val() && (start_time.val() == end_time.val())) {
this.$el.find("#time_alert_modal").show()
start_time.val('')
end_time.val('')
}
if (formattedDate == this.$el.find("#date").val()){
if (start_time.val() && start_time.val() < currentTime) {
this.$el.find("#time_alert_modal").show()
start_time.val('')
end_time.val('')
}
if (end_time.val() && end_time.val() < currentTime) {
this.$el.find("#time_alert_modal").show()
start_time.val('')
end_time.val('')
}
}
},
// To close the alert modal if invalid booking start and end time is chosen.
_onClickCloseAlertBtn: function() {
this.$el.find("#time_alert_modal").hide()
}
});

45
table_reservation_on_website/static/src/js/reservation_floor.js

@ -10,28 +10,51 @@ publicWidget.registry.table_reservation_floor = publicWidget.Widget.extend({
Select table for reservation Select table for reservation
**/ **/
_onTableClick: function () { _onTableClick: function () {
this.$el.find('.submit_button').prop('disabled', false);
var current_div_id = event.target.closest('.card_table') var current_div_id = event.target.closest('.card_table')
var rate = current_div_id.querySelector('#rate').innerText var rateElement = current_div_id.querySelector('#rate');
var count = this.$el.find('#count_table')[0]; var countElement = this.$el.find('#count_table')[0];
var amount = this.$el.find('#total_amount')[0]; var amountElement = this.$el.find('#total_amount')[0];
var booked = this.$el.find('#tables_input')[0]; var bookedElement = this.$el.find('#tables_input')[0];
var rate = rateElement ? rateElement.innerText : 0;
if (current_div_id.style.backgroundColor == 'green'){ if (current_div_id.style.backgroundColor == 'green'){
booked_table.splice(booked_table.indexOf(Number(current_div_id.id)), 1); booked_table.splice(booked_table.indexOf(Number(current_div_id.id)), 1);
current_div_id.style.backgroundColor = '#96ccd5'; current_div_id.style.backgroundColor = '#96ccd5';
count.innerText = Number(count.innerText) - 1; if (countElement) {
amount.innerText = Number(amount.innerText) - Number(rate) var countText = countElement.innerText.trim();
var count = countText !== '' ? Number(countText) : 0;
countElement.innerText = count > 0 ? count - 1 : 0;
}
if (amountElement) {
amountElement.innerText = Number(amountElement.innerText) - Number(rate);
}
} }
else{ else{
current_div_id.style.backgroundColor = 'green' current_div_id.style.backgroundColor = 'green'
count.innerText = Number(count.innerText) + 1; if (countElement) {
var countText = countElement.innerText.trim();
var count = countText !== '' ? Number(countText) : 0;
countElement.innerText = count + 1;
}
booked_table.push(Number(current_div_id.id)) booked_table.push(Number(current_div_id.id))
if (amount.innerText){ if (amountElement) {
amount.innerText = Number(rate) + Number(amount.innerText) if (amountElement.innerText) {
amountElement.innerText = Number(rate) + Number(amountElement.innerText);
} else {
amountElement.innerText = Number(rate);
}
}
}
if (bookedElement) {
bookedElement.value = booked_table;
}
if (this.$el.find('#count_table')[0]) {
if (Number(this.$el.find('#count_table')[0].innerText.trim()) == 0){
this.$el.find('.submit_button').prop('disabled', true);
} }
else{ else{
amount.innerText = Number(rate) this.$el.find('.submit_button').prop('disabled', false);
} }
} }
booked.value = booked_table
}, },
}); });

70
table_reservation_on_website/static/src/js/table_reservation.js

@ -2,39 +2,59 @@
import publicWidget from "@web/legacy/js/public/public_widget"; import publicWidget from "@web/legacy/js/public/public_widget";
import { jsonrpc } from "@web/core/network/rpc_service"; import { jsonrpc } from "@web/core/network/rpc_service";
publicWidget.registry.table_reservation = publicWidget.Widget.extend({ publicWidget.registry.table_reservation = publicWidget.Widget.extend({
selector: '#restaurant_floors', selector: '.swa_container',
events: { events: {
'change #floors_rest': '_onFloorChange', 'change #floors_rest': '_onFloorChange',
'click .card_table': '_onTableClick',
}, },
/** /**
To get all tables belongs to the floor To get all tables belongs to the floor
**/ **/
_onFloorChange: function (ev) { _onFloorChange: function (ev) {
var floors = this.$el.find("#floors_rest")[0].value; var floors = this.$el.find("#floors_rest")[0].value;
var date = $("#date_booking").text().trim() var date = this.$el.find("#date_booking").text().trim()
var start = $("#booking_start").text() var start = this.$el.find("#booking_start").text()
document.getElementById('count_table').innerText = 0; if (document.getElementById('count_table')){
document.getElementById('total_amount').innerText = 0; document.getElementById('count_table').innerText = 0;
jsonrpc("/restaurant/floors/tables", {'floors_id' : floors, }
'date': date, 'start':start,}) if (document.getElementById('total_amount')){
.then(function (data) { document.getElementById('total_amount').innerText = 0;
if(floors == 0){ }
$('#table_container_row').empty(); var self = this
$('#info').hide(); if (floors && date && start) {
} jsonrpc("/restaurant/floors/tables", {'floors_id' : floors,
else{ 'date': date, 'start':start,})
$('#table_container_row').empty(); .then(function (data) {
$('#info').show(); if(floors == 0){
for (let i in data){ self.$el.find('#table_container_row').empty();
$('#table_container_row').append('<div id="'+data[i]['id'] + self.$el.find('#info').hide();
'" class="card card_table col-sm-2" style="background-color:#96ccd5;padding:0;margin:5px;width:250px;"><div class="card-body"><b>' +data[i]['name'] +
'</b><br/><br/><br/><span><i class="fa fa-user-o" aria-hidden="true"></i> '+ data[i]['seats']+
'</span><br/><span><i class="fa fa-money"></i></span><span id="rate">'
+ data[i]['rate'] +
'</span>/Slot</div></div><br/>');
} }
} else{
}); self.$el.find('#table_container_row').empty();
self.$el.find('#info').show();
for (let i in data){
if (Object.keys(data).length > 1) {
let amount = '';
if (data[i]['rate'] != 0) {
amount = '<br/><span><i class="fa fa-money"></i></span><span id="rate">' + data[i]['rate'] + '</span>/Slot';
}
self.$el.find('#table_container_row').append('<div id="'+data[i]['id'] +
'" class="card card_table col-sm-2" style="background-color:#96ccd5;padding:0;margin:5px;width:250px;"><div class="card-body"><b>' +data[i]['name'] +
'</b><br/><br/><br/><span><i class="fa fa-user-o" aria-hidden="true"></i> '+ data[i]['seats']+
'</span>' + amount + '</div></div><br/>');
}
else {
let amount = '';
if (data[i]['rate'] != 0) {
amount = '<br/><span><i class="fa fa-money"></i></span><span id="rate">' + data[i]['rate'] + '</span>/Slot';
}
self.$el.find('#table_container_row').append('<div id="'+data[i]['id'] +
'" class="card card_table col-sm-2" style="background-color:#96ccd5;padding:0;margin:15px;width:250px;"><div class="card-body"><b>' +data[i]['name'] +
'</b><br/><br/><br/><span><i class="fa fa-user-o" aria-hidden="true"></i> '+ data[i]['seats']+
'</span>' + amount + '</div></div><br/>');
}
}
}
});
}
}, },
}); });

18
table_reservation_on_website/views/res_config_settings_views.xml

@ -14,7 +14,7 @@
</div> </div>
<div class="o_setting_right_pane"> <div class="o_setting_right_pane">
<label for="reservation_charge"/> <label for="reservation_charge"/>
<div class="content-group" <div class="text-muted"
id="warning_text_pos_restaurant"> id="warning_text_pos_restaurant">
Enable to show payment option to pre-booking Enable to show payment option to pre-booking
tables. tables.
@ -25,6 +25,22 @@
<field name="refund"/> <field name="refund"/>
</div> </div>
</div> </div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="is_lead_time"/>
</div>
<div class="o_setting_right_pane">
<label for="is_lead_time"/>
<div class="text-muted">
Enable to set a specific amount of time to reserve tables before the
reservation start time.(HH.MM)
</div>
</div>
<div class="o_setting_right_pane" style="display:flex;"
invisible="not is_lead_time">
<field name="reservation_lead_time"/>
</div>
</div>
</xpath> </xpath>
</field> </field>
</record> </record>

5
table_reservation_on_website/views/restaurant_floor_views.xml

@ -7,9 +7,12 @@
<field name="inherit_id" <field name="inherit_id"
ref="pos_restaurant.view_restaurant_floor_form"/> ref="pos_restaurant.view_restaurant_floor_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//form" position="inside">
<field name="is_show_field" invisible="1"/>
</xpath>
<xpath expr="//field[@name='table_ids']/tree[1]/field[@name='shape']" <xpath expr="//field[@name='table_ids']/tree[1]/field[@name='shape']"
position="after"> position="after">
<field name="rate"/> <field name="rate" column_invisible="parent.is_show_field == False"/>
</xpath> </xpath>
</field> </field>
</record> </record>

3
table_reservation_on_website/views/restaurant_table_views.xml

@ -7,7 +7,8 @@
<field name="inherit_id" ref="pos_restaurant.view_restaurant_table_form"/> <field name="inherit_id" ref="pos_restaurant.view_restaurant_table_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='seats']" position="after"> <xpath expr="//field[@name='seats']" position="after">
<field name="rate"/> <field name="is_show_field" invisible="1"/>
<field name="rate" invisible="is_show_field==False"/>
</xpath> </xpath>
</field> </field>
</record> </record>

200
table_reservation_on_website/views/table_reservation_templates.xml

@ -1,34 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<!-- Template for time and slots --> <!-- Template for time and slots -->
<template id="table_reservation" name="Table Reservation"> <template id="table_reservation" name="Table Reservation">
<t t-call="website.layout"> <t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty"> <div id="wrap" class="oe_structure oe_empty">
<div class="container"> <div class="container">
<form action="/restaurant/floors" method="post" <form action="/restaurant/floors" method="post"
enctype="multipart/form-data" enctype="multipart/form-data" class="o_mark_required"
class="o_mark_required bg-helpdesk-ticket_form"
data-mark="*"> data-mark="*">
<input type="hidden" name="csrf_token" <input type="hidden" name="csrf_token"
t-att-value="request.csrf_token()"/> t-att-value="request.csrf_token()"/>
<center> <center>
<br/><br/> <br/>
<br/>
<h1> <h1>
<b>Table Reservation</b> <b>Table Reservation</b>
</h1> </h1>
<br/><br/> <br/>
<br/>
<div> <div>
<div class="form-group row" <div class="form-group row"
style="width:70%;padding-left:15%;"> style="width:70%;padding-left:15%;">
<label for="date" <label for="date"
class="col-2 col-form-label"> class="col-2 col-form-label">Date
Date
</label> </label>
<div class="col-4"> <div class="col-4">
<input type="date" <input type="date" name="date"
name="date" class="form-control" id="date"
class="form-control"
id="date"
required="1"/> required="1"/>
</div> </div>
</div> </div>
@ -43,30 +41,24 @@
<strong>Slots:</strong> <strong>Slots:</strong>
</div> </div>
<div class="col-2"> <div class="col-2">
<label for="start_time"> <label for="start_time">Start Time
Start Time
</label> </label>
</div> </div>
<div class="col-3"> <div class="col-3">
<input id="start_time" <input id="start_time" name="start_time"
name="start_time"
type="time" type="time"
class="form-control s_website_form_input" class="form-control s_website_form_input"
required="1"/> required="1"/>
</div> </div>
</div> </div>
<br/> <br/>
<br/>
<div class="row" <div class="row"
style="padding-left:40%;width:95%;"> style="padding-left:40%;width:95%; padding-top: 10px;">
<div class="col-2"> <div class="col-2">
<label for="end_time"> <label for="end_time">End Time</label>
End Time
</label>
</div> </div>
<div class="col-3"> <div class="col-3">
<input id="end_time" <input id="end_time" name="end_time"
name="end_time"
type="time" type="time"
class="form-control s_website_form_input" class="form-control s_website_form_input"
required="1"/> required="1"/>
@ -74,11 +66,63 @@
</div> </div>
</div> </div>
</center> </center>
<center>
<div class="modal" tabindex="-1" id="alert_modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Invalid
Date
</h5>
</div>
<hr class="m-0"/>
<div class="modal-body">
<p>Please select a valid date.</p>
</div>
<hr class="m-0"/>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary close_btn_alert_modal"
data-bs-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
</center>
<center>
<div class="modal" tabindex="-1"
id="time_alert_modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Invalid
Time
</h5>
</div>
<hr class="m-0"/>
<div class="modal-body">
<p>Please select a valid booking
start and end time.
</p>
</div>
<hr class="m-0"/>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary close_btn_time_alert_modal"
data-bs-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
</center>
<br/> <br/>
<div class="row" data-name="Submit Button"> <div class="row" data-name="Submit Button">
<div class="col-sm-2" style="padding-left:45%;"> <div class="col-sm-2" style="padding-left:45%;">
<button type="submit" <button type="submit" class="btn btn-primary">
class="btn btn-primary">
Submit Submit
</button> </button>
</div> </div>
@ -88,23 +132,21 @@
</div> </div>
</t> </t>
</template> </template>
<!-- Template for floors -->
<!-- Template for floors -->
<template id="restaurant_floors" name="Admission Submit"> <template id="restaurant_floors" name="Admission Submit">
<t t-call="website.layout"> <t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty"> <div id="wrap" class="oe_structure oe_empty">
<div class="container"> <div class="container swa_container">
<br/><br/> <br/>
<br/>
<form action="/booking/confirm" method="POST" <form action="/booking/confirm" method="POST"
enctype="multipart/form-data" enctype="multipart/form-data" class="oe_import">
class="oe_import">
<div class="row"> <div class="row">
<div class="col-2"> <div class="col-2">
<span>Select Your Floor</span> <span>Select Your Floor</span>
</div> </div>
<div class="col-2" id="restaurant_floors"> <div class="col-2" id="restaurant_floors">
<select name="floors" <select name="floors" id="floors_rest"
id="floors_rest"
class="form-control"> class="form-control">
<option value="0">Select a Floor</option> <option value="0">Select a Floor</option>
<t t-foreach="floors" t-as="floor"> <t t-foreach="floors" t-as="floor">
@ -115,57 +157,76 @@
</select> </select>
</div> </div>
</div> </div>
<br/><br/><br/> <br/>
<br/>
<br/>
<div id="info" style="display: none;"> <div id="info" style="display: none;">
<div id="tableContainer" <div id="tableContainer"
style="width:100%;display:flex;"> style="width:100%;display:flex;">
<div class="row" id="table_container_row"> <div class="row" id="table_container_row"/>
</div>
<div class="card" <div class="card"
style="background-color:#c8e0e0;width:1000px;height:370px;border:0;"> style="background-color:#c8e0e0;width:1000px;height:370px;border:0;">
<div class="card-body" <div class="card-body" style="border:1px;">
style="border:1px;">
<h5 class="card-title" <h5 class="card-title"
style="Font-size:45px;">Booking Info style="Font-size:45px;">Booking Info
</h5> </h5>
<table style="border:0;"> <table style="border:0;">
<tr> <tr>
<td> <td>Date:</td>
Date: <td style="text-align:right;"
</td> id="date_booking">
<td style="text-align:right;" id="date_booking">
<t t-esc="date"/> <t t-esc="date"/>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>Start Time:</td> <td>Start Time:</td>
<td style="text-align:right;" id="booking_start"> <td style="text-align:right;"
id="booking_start">
<t t-esc="start_time"/> <t t-esc="start_time"/>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>End Time:</td> <td>End Time:</td>
<td style="text-align:right;" id="booking_end"> <td style="text-align:right;"
id="booking_end">
<t t-esc="end_time"/> <t t-esc="end_time"/>
</td> </td>
</tr> </tr>
<tr> <t t-if="payment == 'True'">
<td> <tr>
<b>Booking Amount <td>
For <span id="count_table"> <b>
0 Booking Amount For
</span>Tables <span id="count_table">
</b> 0
</td> </span>
<td style="text-align:right;"> Tables
<span id="total_amount"> </b>
</span> </td>
</td> <td style="text-align:right;">
</tr> <span id="total_amount"/>
</td>
</tr>
</t>
<t t-else="">
<tr style="display:none;">
<td>
<b>
Booking Amount For
<span id="count_table">
0
</span>
Tables
</b>
</td>
<td style="text-align:right;">
<span id="total_amount"/>
</td>
</tr>
</t>
</table> </table>
<span hidden="hidden"> <span hidden="hidden">
<input name="date" <input name="date" id="date_id"
id="date_id"
class="form-control border-0 p-0" class="form-control border-0 p-0"
type="text" type="text"
data-allow-hotkeys="true" data-allow-hotkeys="true"
@ -189,20 +250,19 @@
class="form-control border-0 p-0" class="form-control border-0 p-0"
type="text" type="text"
data-allow-hotkeys="true" data-allow-hotkeys="true"
t-ref="autofocus"> t-ref="autofocus"/>
</input>
</span> </span>
<button type="submit" <button type="submit" disabled="True"
class="btn btn-primary"> class="btn btn-primary submit_button">
Booking Confirm Booking
Confirm
</button> </button>
</div> </div>
<div style="line-height:1px;background: #ffffff;border:0;"> <div style="line-height:1px;background: #ffffff;border:0;"/>
</div>
<t t-if="payment"> <t t-if="payment">
<div style="background: #ffffff;border:0;color:#FF0000;"> <div style="background: #ffffff;border:0;color:#FF0000;">
<t t-esc="refund"/> <t t-esc="refund"/>
</div> </div>
</t> </t>
</div> </div>
</div> </div>

5
table_reservation_on_website/views/table_reservation_views.xml

@ -8,10 +8,12 @@
<tree> <tree>
<field name="sequence"/> <field name="sequence"/>
<field name="customer_id"/> <field name="customer_id"/>
<field name="date"/>
<field name="booked_tables_ids" widget="many2many_tags"/> <field name="booked_tables_ids" widget="many2many_tags"/>
<field name="floor_id"/> <field name="floor_id"/>
<field name="starting_at"/> <field name="starting_at"/>
<field name="ending_at"/> <field name="ending_at"/>
<field name="type"/>
</tree> </tree>
</field> </field>
</record> </record>
@ -50,6 +52,9 @@
<field name="booked_tables_ids" <field name="booked_tables_ids"
widget="many2many_tags"/> widget="many2many_tags"/>
<field name="booking_amount"/> <field name="booking_amount"/>
<field name="type"/>
<field name="lead_time"/>
<field name="lead_time_computed" invisible="1"/>
</group> </group>
</group> </group>
</sheet> </sheet>

Loading…
Cancel
Save