# -*- coding: utf-8 -*- import random import re import secrets import string from datetime import timedelta from odoo import api, fields, models, _ from odoo.exceptions import ValidationError class SaasTrialRequest(models.Model): """Flow 1: User Registration & App Selection Flow Visits domain-name.com/trial -> Select Apps (Website, CRM, Accounting, ...) -> Fill Form (Email, Company, Phone, Name, ...) -> Create Account & Verify Email -> 15-Day Free Trial Activated -> User Clicks "Continue" --> triggers Flow 2 (provisioning) """ _name = 'saas.trial.request' _description = 'SaaS Trial Signup Request' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'create_date desc' # ---- Step: Fill Form (Email, Company, Phone, Name) ---- name = fields.Char('Full Name', required=True, tracking=True) company_name = fields.Char('Company Name', tracking=True) email = fields.Char('Email', required=True, tracking=True) phone = fields.Char('Phone') country_id = fields.Many2one('res.country', string='Country') lang = fields.Selection(lambda self: self.env['res.lang'].get_installed(), string='Language') company_size = fields.Selection([ ('1-5', '1 - 5 employees'), ('6-20', '6 - 20 employees'), ('21-50', '21 - 50 employees'), ('51-200', '51 - 200 employees'), ('200+', '200+ employees'), ], string='Company Size') primary_interest = fields.Selection([ ('my_company', 'Use it in my company'), ('client', 'Use it for a client'), ('explore', 'Just exploring'), ], string='Primary Interest') # ---- Step: Select Apps ---- app_ids = fields.Many2many('saas.app', string='Selected Apps', required=True) # ---- Step: Create Account & Verify Email ---- state = fields.Selection([ ('draft', 'Form Submitted'), ('pending_verification', 'Pending Email Verification'), ('verified', 'Email Verified'), ('trial_active', '15-Day Trial Activated'), ('provisioning', 'Provisioning Database'), ('ready', 'Instance Ready'), ('failed', 'Provisioning Failed'), ], default='draft', tracking=True, required=True) verification_token = fields.Char(copy=False) verification_sent_date = fields.Datetime() verified_date = fields.Datetime() # ---- Step: 15-Day Free Trial Activated ---- trial_start_date = fields.Datetime() trial_end_date = fields.Datetime() # Link forward to Flow 2 database_id = fields.Many2one('saas.database', string='Provisioned Database', copy=False) subscription_id = fields.Many2one('saas.subscription', string='Subscription', copy=False) _sql_constraints = [ ('email_unique', 'unique(email)', 'A trial request with this email already exists.'), ] @api.constrains('email') def _check_email(self): pattern = r"^[^@\s]+@[^@\s]+\.[^@\s]+$" for rec in self: if not re.match(pattern, rec.email or ''): raise ValidationError(_("Please enter a valid email address.")) # ------------------------------------------------------------------ # Step: Create Account -> send verification email # ------------------------------------------------------------------ def action_submit_and_send_verification(self): """Called right after the public form is submitted.""" for rec in self: rec.verification_token = ''.join( secrets.choice(string.ascii_letters + string.digits) for _ in range(32) ) rec.verification_sent_date = fields.Datetime.now() rec.state = 'pending_verification' rec._send_verification_email() return True def _send_verification_email(self): template = self.env.ref('saas_trial_portal.mail_template_verify_email') for rec in self: template.send_mail(rec.id, force_send=True) # ------------------------------------------------------------------ # Step: Verify Email (user clicks link from email) # ------------------------------------------------------------------ def action_verify_email(self, token): """Validates token coming from the verification link.""" self.ensure_one() if token != self.verification_token: raise ValidationError(_("Invalid or expired verification link.")) self.write({ 'state': 'verified', 'verified_date': fields.Datetime.now(), }) self._activate_trial() return True # ------------------------------------------------------------------ # Step: 15-Day Free Trial Activated # ------------------------------------------------------------------ def _activate_trial(self): self.ensure_one() now = fields.Datetime.now() self.write({ 'state': 'trial_active', 'trial_start_date': now, 'trial_end_date': now + timedelta(days=15), }) # Create the subscription record in trial mode (Flow 3 baseline state) if not self.subscription_id: self.subscription_id = self.env['saas.subscription'].create({ 'trial_request_id': self.id, 'is_trial': True, 'is_premium': False, 'expiry_date': self.trial_end_date, }) # ------------------------------------------------------------------ # Step: User Clicks "Continue" -> kicks off Flow 2 provisioning # ------------------------------------------------------------------ def action_continue_to_provisioning(self): self.ensure_one() if self.state != 'trial_active': raise ValidationError(_("Trial must be active before provisioning a database.")) self.state = 'provisioning' database = self.env['saas.database'].create({ 'trial_request_id': self.id, 'company_name': self.company_name, 'app_ids': [(6, 0, self.app_ids.ids)], }) self.database_id = database.id # Run the full provisioning pipeline (Flow 2) database.action_provision() return database