diff --git a/addons/dine360_reservation/__init__.py b/addons/dine360_reservation/__init__.py new file mode 100644 index 0000000..f7209b1 --- /dev/null +++ b/addons/dine360_reservation/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers diff --git a/addons/dine360_reservation/__manifest__.py b/addons/dine360_reservation/__manifest__.py new file mode 100644 index 0000000..81ef4f0 --- /dev/null +++ b/addons/dine360_reservation/__manifest__.py @@ -0,0 +1,27 @@ +{ + 'name': 'Dine360 Table Reservation', + 'version': '1.0', + 'category': 'Sales/Restaurant', + 'summary': 'Advanced Table Reservation System with Time Slots and Overlap Prevention', + 'description': """ + Advanced Table Reservation System: + - Time Slot based reservations + - Overlap prevention + - Auto-completion of past reservations + - WhatsApp/SMS notification hooks + """, + 'author': 'Dine360', + 'depends': ['base', 'website', 'pos_restaurant'], + 'data': [ + 'security/ir.model.access.csv', + 'data/reservation_sequence.xml', + 'data/reservation_cron.xml', + 'data/website_menu.xml', + 'views/reservation_views.xml', + 'views/reservation_templates.xml', + 'views/menu_items.xml', + ], + 'installable': True, + 'application': True, + 'license': 'LGPL-3', +} diff --git a/addons/dine360_reservation/__pycache__/__init__.cpython-310.pyc b/addons/dine360_reservation/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..998782e Binary files /dev/null and b/addons/dine360_reservation/__pycache__/__init__.cpython-310.pyc differ diff --git a/addons/dine360_reservation/controllers/__init__.py b/addons/dine360_reservation/controllers/__init__.py new file mode 100644 index 0000000..12a7e52 --- /dev/null +++ b/addons/dine360_reservation/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/addons/dine360_reservation/controllers/__pycache__/__init__.cpython-310.pyc b/addons/dine360_reservation/controllers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..86f1228 Binary files /dev/null and b/addons/dine360_reservation/controllers/__pycache__/__init__.cpython-310.pyc differ diff --git a/addons/dine360_reservation/controllers/__pycache__/main.cpython-310.pyc b/addons/dine360_reservation/controllers/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000..1cfab85 Binary files /dev/null and b/addons/dine360_reservation/controllers/__pycache__/main.cpython-310.pyc differ diff --git a/addons/dine360_reservation/controllers/main.py b/addons/dine360_reservation/controllers/main.py new file mode 100644 index 0000000..b6b1ee0 --- /dev/null +++ b/addons/dine360_reservation/controllers/main.py @@ -0,0 +1,54 @@ +from odoo import http, _ +from odoo.http import request +import datetime + +class TableReservationController(http.Controller): + + @http.route(['/reservation'], type='http', auth="public", website=True) + def reservation_form(self, **post): + floors = request.env['restaurant.floor'].sudo().search([]) + tables = request.env['restaurant.table'].sudo().search([]) + return request.render("dine360_reservation.reservation_page_template", { + 'floors': floors, + 'tables': tables, + }) + + @http.route(['/reservation/submit'], type='http', auth="public", website=True, methods=['POST'], csrf=True) + def reservation_submit(self, **post): + # Extract data from post + customer_name = post.get('customer_name') + phone = post.get('phone') + email = post.get('email') + floor_id = int(post.get('floor_id')) + table_id = int(post.get('table_id')) + start_time_str = post.get('start_time') # Format: 2024-05-20T18:00 + num_people = int(post.get('num_people', 1)) + + # Convert start_time to datetime object + start_time = datetime.datetime.strptime(start_time_str, '%Y-%m-%dT%H:%M') + # Standard duration of 1 hour for now + end_time = start_time + datetime.timedelta(hours=1) + + # Create reservation + try: + reservation = request.env['restaurant.reservation'].sudo().create({ + 'customer_name': customer_name, + 'phone': phone, + 'email': email, + 'floor_id': floor_id, + 'table_id': table_id, + 'start_time': start_time, + 'end_time': end_time, + 'num_people': num_people, + 'state': 'draft' + }) + return request.render("dine360_reservation.reservation_success_template", { + 'reservation': reservation, + }) + except Exception as e: + return request.render("dine360_reservation.reservation_page_template", { + 'error': str(e), + 'floors': request.env['restaurant.floor'].sudo().search([]), + 'tables': request.env['restaurant.table'].sudo().search([]), + 'post': post, + }) diff --git a/addons/dine360_reservation/data/reservation_cron.xml b/addons/dine360_reservation/data/reservation_cron.xml new file mode 100644 index 0000000..3bba62c --- /dev/null +++ b/addons/dine360_reservation/data/reservation_cron.xml @@ -0,0 +1,16 @@ + + + + + Restaurant: Auto-complete Past Reservations + + code + model._auto_complete_reservations() + 1 + hours + -1 + + + + + diff --git a/addons/dine360_reservation/data/reservation_sequence.xml b/addons/dine360_reservation/data/reservation_sequence.xml new file mode 100644 index 0000000..eb80811 --- /dev/null +++ b/addons/dine360_reservation/data/reservation_sequence.xml @@ -0,0 +1,12 @@ + + + + + Restaurant Reservation + restaurant.reservation + RES/%(year)s/ + 5 + + + + diff --git a/addons/dine360_reservation/data/website_menu.xml b/addons/dine360_reservation/data/website_menu.xml new file mode 100644 index 0000000..9a7336c --- /dev/null +++ b/addons/dine360_reservation/data/website_menu.xml @@ -0,0 +1,12 @@ + + + + + Table Reservation + /reservation + + 40 + + + + diff --git a/addons/dine360_reservation/models/__init__.py b/addons/dine360_reservation/models/__init__.py new file mode 100644 index 0000000..7c07528 --- /dev/null +++ b/addons/dine360_reservation/models/__init__.py @@ -0,0 +1 @@ +from . import restaurant_reservation diff --git a/addons/dine360_reservation/models/__pycache__/__init__.cpython-310.pyc b/addons/dine360_reservation/models/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..a7b05de Binary files /dev/null and b/addons/dine360_reservation/models/__pycache__/__init__.cpython-310.pyc differ diff --git a/addons/dine360_reservation/models/__pycache__/restaurant_reservation.cpython-310.pyc b/addons/dine360_reservation/models/__pycache__/restaurant_reservation.cpython-310.pyc new file mode 100644 index 0000000..13e366a Binary files /dev/null and b/addons/dine360_reservation/models/__pycache__/restaurant_reservation.cpython-310.pyc differ diff --git a/addons/dine360_reservation/models/restaurant_reservation.py b/addons/dine360_reservation/models/restaurant_reservation.py new file mode 100644 index 0000000..75049aa --- /dev/null +++ b/addons/dine360_reservation/models/restaurant_reservation.py @@ -0,0 +1,89 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError +from datetime import timedelta + +class RestaurantReservation(models.Model): + _name = 'restaurant.reservation' + _description = 'Restaurant Table Reservation' + _order = 'start_time desc' + + name = fields.Char(string='Reservation Reference', required=True, copy=False, readonly=True, default=lambda self: _('New')) + customer_name = fields.Char(string='Customer Name', required=True) + phone = fields.Char(string='Phone Number', required=True) + email = fields.Char(string='Email') + num_people = fields.Integer(string='Number of People', default=1) + + floor_id = fields.Many2one('restaurant.floor', string='Floor', required=True) + table_id = fields.Many2one('restaurant.table', string='Table', required=True) + + start_time = fields.Datetime(string='Start Time', required=True) + end_time = fields.Datetime(string='End Time', required=True) + + whatsapp_url = fields.Char(compute='_compute_whatsapp_url') + + def _compute_whatsapp_url(self): + for rec in self: + if rec.phone: + msg = f"Hello {rec.customer_name}, your reservation {rec.name} for {rec.start_time.strftime('%I:%M %p')} is confirmed!" + rec.whatsapp_url = f"https://wa.me/{rec.phone}?text={msg.replace(' ', '%20')}" + else: + rec.whatsapp_url = False + + state = fields.Selection([ + ('draft', 'Draft'), + ('confirmed', 'Confirmed'), + ('completed', 'Completed'), + ('cancelled', 'Cancelled') + ], string='Status', default='draft', tracking=True) + + @api.model + def create(self, vals): + if vals.get('name', _('New')) == _('New'): + vals['name'] = self.env['ir.sequence'].next_by_code('restaurant.reservation') or _('New') + return super(RestaurantReservation, self).create(vals) + + @api.constrains('table_id', 'start_time', 'end_time', 'state') + def _check_overlap(self): + for rec in self: + if rec.state in ['confirmed', 'completed']: + overlap = self.search([ + ('id', '!=', rec.id), + ('table_id', '=', rec.table_id.id), + ('state', '=', 'confirmed'), + ('start_time', '<', rec.end_time), + ('end_time', '>', rec.start_time), + ]) + if overlap: + raise ValidationError(_('This table is already reserved for the selected time slot.')) + + @api.onchange('start_time') + def _onchange_start_time(self): + if self.start_time: + self.end_time = self.start_time + timedelta(hours=1) + + def action_confirm(self): + self.write({'state': 'confirmed'}) + self._send_confirmation_notification() + + def action_complete(self): + self.write({'state': 'completed'}) + + def action_cancel(self): + self.write({'state': 'cancelled'}) + + def _send_confirmation_notification(self): + """ Placeholder for WhatsApp/SMS logic """ + for rec in self: + # Logic for WhatsApp/SMS can be added here + # e.g., self.env['sms.api']._send_sms(rec.phone, "Your table is confirmed!") + pass + + @api.model + def _auto_complete_reservations(self): + """ Scheduled action to mark past reservations as completed """ + now = fields.Datetime.now() + past_reservations = self.search([ + ('state', '=', 'confirmed'), + ('end_time', '<', now) + ]) + past_reservations.write({'state': 'completed'}) diff --git a/addons/dine360_reservation/security/ir.model.access.csv b/addons/dine360_reservation/security/ir.model.access.csv new file mode 100644 index 0000000..6191133 --- /dev/null +++ b/addons/dine360_reservation/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_restaurant_reservation_user,restaurant.reservation,model_restaurant_reservation,base.group_user,1,1,1,1 diff --git a/addons/dine360_reservation/views/menu_items.xml b/addons/dine360_reservation/views/menu_items.xml new file mode 100644 index 0000000..0106528 --- /dev/null +++ b/addons/dine360_reservation/views/menu_items.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/addons/dine360_reservation/views/reservation_templates.xml b/addons/dine360_reservation/views/reservation_templates.xml new file mode 100644 index 0000000..7c96086 --- /dev/null +++ b/addons/dine360_reservation/views/reservation_templates.xml @@ -0,0 +1,130 @@ + + + + + + diff --git a/addons/dine360_reservation/views/reservation_views.xml b/addons/dine360_reservation/views/reservation_views.xml new file mode 100644 index 0000000..179f42f --- /dev/null +++ b/addons/dine360_reservation/views/reservation_views.xml @@ -0,0 +1,98 @@ + + + + + restaurant.reservation.tree + restaurant.reservation + + + + + + + + + + + + + + + + + + restaurant.reservation.form + restaurant.reservation + +
+
+
+ +
+ +
+
+

+ +

+
+ + + + + + + + + + + + + + +
+
+
+
+ + + + restaurant.reservation.search + restaurant.reservation + + + + + + + + + + + + + + + + + + + + + Table Reservations + ir.actions.act_window + restaurant.reservation + tree,form + + +

+ Create your first table reservation! +

+
+
+