@ -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') |
|
@ -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') |
@ -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> |
@ -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 |
@ -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 |
@ -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 |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 145 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 923 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 868 KiB |
After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 967 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 526 KiB |
@ -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"; |
@ -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> |
@ -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"; |
@ -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> |
@ -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,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"); |
||||
|
}, |
||||
|
}); |
@ -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); |
@ -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> |
@ -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; |
|
||||
} |
|
||||
}); |
|
@ -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() |
|
||||
} |
|
||||
}); |
|
@ -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() |
||||
|
} |
||||
|
}); |