From d0ecc7345329f8b56a2e37a6a79cd5d9a9feb5b5 Mon Sep 17 00:00:00 2001 From: Alaguraj0361 Date: Thu, 5 Feb 2026 18:39:38 +0530 Subject: [PATCH] Implement a new Odoo module for restaurant table reservations with a public web form. --- addons/dine360_reservation/__init__.py | 2 + addons/dine360_reservation/__manifest__.py | 27 ++++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 207 bytes .../controllers/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 183 bytes .../__pycache__/main.cpython-310.pyc | Bin 0 -> 1907 bytes .../dine360_reservation/controllers/main.py | 54 ++++++++ .../data/reservation_cron.xml | 16 +++ .../data/reservation_sequence.xml | 12 ++ .../dine360_reservation/data/website_menu.xml | 12 ++ addons/dine360_reservation/models/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 196 bytes .../restaurant_reservation.cpython-310.pyc | Bin 0 -> 4034 bytes .../models/restaurant_reservation.py | 89 ++++++++++++ .../security/ir.model.access.csv | 2 + .../dine360_reservation/views/menu_items.xml | 15 ++ .../views/reservation_templates.xml | 130 ++++++++++++++++++ .../views/reservation_views.xml | 98 +++++++++++++ 18 files changed, 459 insertions(+) create mode 100644 addons/dine360_reservation/__init__.py create mode 100644 addons/dine360_reservation/__manifest__.py create mode 100644 addons/dine360_reservation/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/dine360_reservation/controllers/__init__.py create mode 100644 addons/dine360_reservation/controllers/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/dine360_reservation/controllers/__pycache__/main.cpython-310.pyc create mode 100644 addons/dine360_reservation/controllers/main.py create mode 100644 addons/dine360_reservation/data/reservation_cron.xml create mode 100644 addons/dine360_reservation/data/reservation_sequence.xml create mode 100644 addons/dine360_reservation/data/website_menu.xml create mode 100644 addons/dine360_reservation/models/__init__.py create mode 100644 addons/dine360_reservation/models/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/dine360_reservation/models/__pycache__/restaurant_reservation.cpython-310.pyc create mode 100644 addons/dine360_reservation/models/restaurant_reservation.py create mode 100644 addons/dine360_reservation/security/ir.model.access.csv create mode 100644 addons/dine360_reservation/views/menu_items.xml create mode 100644 addons/dine360_reservation/views/reservation_templates.xml create mode 100644 addons/dine360_reservation/views/reservation_views.xml 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 0000000000000000000000000000000000000000..998782e838f93e761f9fa5ee1ef2ed390566a436 GIT binary patch literal 207 zcmd1j<>g`kf;D|DnTA06F^Gc%z278UzxGT&k-0vT7t3?x=E6tRNXAmW#y zer{fgeriQYQKD{QN=klSv3^QsUaGN~L3~kaacWUnVo7Fxo_>6MW?p7Ve7s&kg`kf+c+|nIb^?F^Gc(44TX@8G*u@ zjJH^F6EpMtG?{KO6oGWCWGG?*Qefhjt$uD^iGFHDNl~J1VoFMWUa@{kW?rhXnL&I} zYH?~&Sz<|Mex81EeqKpYeojtmQL%n}d}dx|NqoFsLFFwDo80`A(wtN~kX6MX(*+m+ Dne!_# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1cfab85d83fb6dd8e33a8eac683a8e20b6400fa1 GIT binary patch literal 1907 zcma)7-D?|15Z~R~JDpA^MX_C{U+D)$6cKC%0)>WBLQ83BsatA8iC&Jgc5TVrhqAX9 z$Kq59b|3TDH$O;^{g?K&Px%W9rZk;dIq?}1$Q}E;nc1(|ncwW??RG%mN&Y;WgiS*J zMy396P#!=l?gI!SXhKptq$wLRiaJZ2ln=R;ox~lw-x9%vdq#vS>5+Hp3>(7PC4KK1 z%qM+j_*miTp(tQaz6KB+tIiGp+I@k!ltW)RkQ^ z*xGp0azB}j_WF+D`eZ5%k4rr^jbpiAOtc(P81&-7PL*lLZ$}#VR<~Ia@Mt)ZMx0f-^cURh;SVgCx(@HH*e$Tx^L+ChvWAH>x?l zU6Z3}d?+I=(`gcG3DNiLN`>+4{G#tF^x8Nwdu(`7irjdGjMZptJS8(BRiA@hCI^O3 z^FpgOj8+|hzleGZJ{O-4(o7HJ6RqN{Scp6;2CwjGFnSr~Vvxp@Y2(ZC6s7D#R{(rMOug}=Eqv8s)^?C{iMY*3&wBbMhw7UmK#q||ZO99h~X{1t* zb5R&RD%8QXRN^tw5u3^ol3dXRa32$BB?gjXB?ly~sg?PXp3=E{skv73Y>x+{8khEl z@Rtn6`I;TEg;O;|bIIn-stGz5KCqVFBJ*H&Tf0ZVU!Y0@=;I?jDqgvjS2e4k@KS=-}iJO6K7D>=WDH)vYQ#rFPAmmurSdNo!fFTPiBn z&pEE-k%v&@^YFKD_wsv8k4b-H$FxUfq4QL#D2r2R-03*a;3FZoiSaGTqKPoRr5Q>A za9Qa{PhfPAm1#7U`81Kwuz+Tr-ove4y44eVz3=Y#euS&R9p|MgW}CGdsIkSkLd6HV z-&F6xboD;MZG;aHK1BEk!1zF6yQTUVRTq5Fa(4F>1ii+jq8yE6QM{}h)2i*QkQWLs zP1OQ``f#OExwu7qG8Ax(_upwNevN$OOWIC8%y7bODSvQWD0i zHB_44JQ+!tk!Qw%x#}hu`)#$3qj8_tM!Y?;0fT7Y>@d`H4vQ|54 zw5lI;s`v^ZZ~_|84!i;F(vH(*%<0%N#ZtX`{eQd`r~dPf<3Sm(!OQ5zzy9y+`E2VA zi+Q#4PQPLN$WkndOb|tBF3LoL97fTjGER_uQM9_MU6q#*wOin*x`%-GQw{KV$_2ny zU;=%z#sd1b-=Wst_YKblT(o%eD+j + + + + 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 0000000000000000000000000000000000000000..a7b05deb6fd363036308e1dcf3eb3cb5adcdad32 GIT binary patch literal 196 zcmd1j<>g`k0>!?TOf?|=7{oyaOhAqU5Elyoi4=wu#vF!R#wbQch7_h?22JLdj6h*c z##>@Vsl_FUrA3K(CGkLJYEfBYNoIbYpC;2Sh9Zy|D;bJdfE1YcWuc#&SE8RqUSKpY;3w&z*;+I;BfeY7m zogLP^#HaQy!*!U2Ihfx!;YaY^u7>B+czy+D_Zhqob#*cWH{d36z6!VCHqI_Az#`7q z;0}C%^L644cj2C*p8ZlYwdNc;=Ew5kb0uDIAxE;yJJhsO)J9GvIVN968v z|3$>zp{MdOWF<0>{N@5rgPgbzo<+(Q7d#I5^W^~DcKD;Uj@Ki+QU`|&QMbcTJiQ@} z2H&hRVd3#xCe4&bt+$#O#qGiJVkgcP`4^e+Z~FjAoGwBb^LszJ;|+3telN=M(MP>BOFCS*4Zp+dx){foh%-3k)o1M_=5C|cdBKIWo^<>$LVbxKA1_Yb zq~$*2NjKtOlc@%epHn19YAv*)BtaI-H09$%H!^aRw3NT{$}0KDeU3iLohLD9r0v(c-SOt?yajKzSc2zcRO(XTQ>+(w{X%!AK*wKj%@DxcS*z z?pD$hE-H5jtK4#1iEwjg{^`>EiaX!UYi^jjK@zt@feGYVUTbGrH(gp>-1HYZd@;8_ z&JZ;Z4w#!8^LOto*Gtmsk{je`(07yx?@rGrjy5x!+f@NVP>Tvm@+%n((y zhcpBaq|r&&6jX}=v#lmB;xL6*N@NbR*&Lf;j&6RN-#i&{Cp*Th>(T<@w=!uxrV01u zLzQLu(Lnp+yo2D$5TSV9U#fE-gE5NK`Qg$=d}O^;%%gmDqd+P_O*$B7!AnGZKp`^t z(VBvjAdx=CVVmt?_+||B9k#C*^GjV^+Sb-}FfM9)2Em|vp}4cnfZf+JGqbj}9eoc$ zu}^_-HN*_^5)_g7NGKLkj4?_bf?$XKGS+yel7cjNydkSGI@Eiyg~kFzdfMx9At;qd zJHT+vDCU%3%nd(EF_Dx#FNpj!^}He9+`|Xn!z;CMn9`@&I2%rQ=$`@T77DIt6ACRa z1aXyK=I|I)_CpcSiu?ybL;9$o`7y113uSqVmVFm3*gVfawkR2!%?sUzwnn;t^0Uwa^aikk!W z;H@D(qI*B5K~a$g2kgH1nVKKwOV8UFMoK}7CqJURw$%?k4l3xfHg_>4^8mr+s*;m=vXoyLq!#ruVF)yX43z<<+e^Tf=LMtj)M>vz`yd)0Ve!RxLqtrrAYCY=wc^h?8`x`2qEAgQq?rZOE22bj@k5IR40~T6{Bse2!~zOnQR%TP7&^oG9AZ z;d_eQM}*%{-kM2$g25v(kvY!}L z^u2>q@dSC3hzgdDt{dim@()jz_WwZUnEVE^r07@j-@!$ut+PFSU&B;L<)NPfF}>+U zdZW$(tOCEq_eB1YyNU?`dRV;N!me(XxE)`-bi00<4L1mwx~LoI*F9I<$2S(S`G8g` z00=^jx+O?7={&~Nt!lgxC!6A9q9V);64eS3ouStZcarHl){~b#zn3Mdd{gl~M*ACN zp*nzyVPfXOKH$3vGymWe+n&y+J}(_l^(vkxE1rkFE}8__RnL3X^CRkCaAy_mOx2^N zT!dYURB3yOKzI~7n~&PQC~EE%(s!D4v^aI6vh+00_!@SnmAKdOx~dbE&Wayz-KCz% zQGF-XK$YQtMUSf_NQFdPr-7OtSzc9z0$*L&oU((oRCg|@^2!Qk9Bi-rFiyp%bl?nf zogkVw=R>NIY$AaS67L^gaXH}RpikFFh!!gX#1XEo_qdp?d1hCJhI8+uCq)yS1jM1G1 y{zbr*p;J}rq=O%~3RS8&NNa_@gcY|T;3%m{VEia4eEE=ylKLu|(yA!yr2b#>;TJam literal 0 HcmV?d00001 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! +

+
+
+