import secrets from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError class McsAppointmentBooking(models.Model): _name = "mcs.appointment.booking" _description = "Appointment Booking" _inherit = ["mail.thread", "mail.activity.mixin"] _order = "start desc" name = fields.Char(compute="_compute_name", store=True) appointment_type_id = fields.Many2one( "mcs.appointment.type", required=True, ondelete="restrict", tracking=True ) company_id = fields.Many2one( related="appointment_type_id.company_id", store=True, readonly=True ) user_id = fields.Many2one( related="appointment_type_id.user_id", string="Host", store=True, readonly=True ) partner_id = fields.Many2one("res.partner", string="Customer") customer_name = fields.Char(required=True, tracking=True) customer_email = fields.Char(required=True, tracking=True) customer_phone = fields.Char() start = fields.Datetime(required=True, tracking=True) stop = fields.Datetime(required=True, tracking=True) duration = fields.Float(related="appointment_type_id.duration", readonly=True) state = fields.Selection( [ ("draft", "Draft"), ("confirmed", "Confirmed"), ("cancelled", "Cancelled"), ], default="draft", required=True, tracking=True, ) notes = fields.Text() calendar_event_id = fields.Many2one("calendar.event", readonly=True, copy=False) access_token = fields.Char(default=lambda self: secrets.token_urlsafe(24), copy=False) @api.depends("appointment_type_id", "customer_name", "start") def _compute_name(self): for booking in self: parts = [ booking.appointment_type_id.name or "Appointment", booking.customer_name or "Customer", ] if booking.start: parts.append(fields.Datetime.to_string(booking.start)) booking.name = " - ".join(parts) @api.constrains("start", "stop") def _check_dates(self): for booking in self: if booking.start and booking.stop and booking.start >= booking.stop: raise ValidationError("Appointment end must be after start.") def _ensure_partner(self): self.ensure_one() if self.partner_id: return self.partner_id partner = self.env["res.partner"].sudo().search( [("email", "=", self.customer_email)], limit=1 ) if not partner: partner = self.env["res.partner"].sudo().create( { "name": self.customer_name, "email": self.customer_email, "phone": self.customer_phone, "company_id": self.company_id.id, } ) self.partner_id = partner.id return partner def action_confirm(self): for booking in self: if booking.state == "confirmed": continue busy = booking.appointment_type_id._busy_intervals(booking.start, booking.stop) busy = [ interval for interval in busy if not booking.calendar_event_id or interval != (booking.calendar_event_id.start, booking.calendar_event_id.stop) ] if not booking.appointment_type_id._is_free(booking.start, booking.stop, busy): raise UserError("This time slot is no longer available.") partner = booking._ensure_partner() event_vals = { "name": booking.name, "start": booking.start, "stop": booking.stop, "user_id": booking.user_id.id, "partner_ids": [(6, 0, [partner.id, booking.user_id.partner_id.id])], "description": booking.notes or "", } if booking.calendar_event_id: booking.calendar_event_id.write(event_vals) else: booking.calendar_event_id = self.env["calendar.event"].sudo().create(event_vals).id booking.state = "confirmed" def action_cancel(self): for booking in self: booking.state = "cancelled" if booking.calendar_event_id: booking.calendar_event_id.sudo().unlink() booking.calendar_event_id = False