@ -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() |
|||
} |
|||
}); |