156 lines
6.1 KiB
Python
156 lines
6.1 KiB
Python
# -*- 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
|