Alaguraj0361 b9d5617051 Add SaaS multi-tenant models and views for restaurant management
- Introduced `saas.plan` model to define subscription plans with limits and pricing.
- Created `saas.restaurant` model to manage restaurant tenants, including database provisioning and subscription management.
- Implemented views for managing SaaS plans and restaurant tenants, including tree and form views.
- Added security access rights for the new models.
- Developed a backup management view for database backups.
- Updated menu structure to include new SaaS management options.
- Added Docker and deployment configurations for PostgreSQL, Redis, and Odoo services.
- Included scaling guide and backup scripts for production environments.
- Enhanced theme with new images and layout adjustments.
2026-06-19 15:03:51 +05:30

174 lines
7.5 KiB
Python

# -*- coding: utf-8 -*-
import json
import logging
from odoo import http, fields
from odoo.http import request
from odoo.service import db
from odoo.tools import config
_logger = logging.getLogger(__name__)
class SaasApiController(http.Controller):
def _authenticate(self):
# Authenticate using a SaaS Token defined in odoo.conf (e.g. saas_api_token = abcxyz123)
expected_token = config.get('saas_api_token', 'METATRON_DINE360_SECRET_SaaS_2026')
auth_header = request.httprequest.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return False
token = auth_header.split(' ')[1]
return token == expected_token
def _json_response(self, data, status=200):
return request.make_response(
json.dumps(data),
headers=[('Content-Type', 'application/json')],
status=status
)
@http.route('/api/v1/saas/create_restaurant', type='json', auth='none', methods=['POST'], csrf=False)
def api_create_restaurant(self):
if not self._authenticate():
return self._json_response({'error': 'Unauthorized'}, status=401)
try:
data = request.get_json_data()
name = data.get('name')
owner_name = data.get('owner_name')
email = data.get('email')
plan_name = data.get('plan_name', 'Starter')
billing_cycle = data.get('billing_cycle', 'monthly')
expiry_date_str = data.get('expiry_date') # Format YYYY-MM-DD
if not all([name, owner_name, email, expiry_date_str]):
return self._json_response({'error': 'Missing required fields'}, status=400)
# Find subscription plan
plan = request.env['saas.plan'].sudo().search([('name', '=', plan_name)], limit=1)
if not plan:
return self._json_response({'error': f"Plan '{plan_name}' not found"}, status=404)
expiry_date = fields.Date.from_string(expiry_date_str)
# Create Restaurant record inside the master DB env
restaurant = request.env['saas.restaurant'].sudo().create({
'name': name,
'owner_name': owner_name,
'email': email,
'phone': data.get('phone', ''),
'street': data.get('street', ''),
'city': data.get('city', ''),
'plan_id': plan.id,
'billing_cycle': billing_cycle,
'expiry_date': expiry_date,
'currency_id': request.env['res.currency'].sudo().search([('name', '=', data.get('currency', 'USD'))], limit=1).id,
'timezone': data.get('timezone', 'America/New_York')
})
# Programmatically trigger database creation in Odoo
restaurant.action_create_database()
return self._json_response({
'success': True,
'restaurant_id': restaurant.id,
'database_name': restaurant.database_name,
'subdomain': restaurant.subdomain,
'status': restaurant.status
})
except Exception as e:
_logger.error(f"SaaS API Create Restaurant Error: {str(e)}")
return self._json_response({'error': str(e)}, status=500)
@http.route('/api/v1/saas/suspend_restaurant', type='json', auth='none', methods=['POST'], csrf=False)
def api_suspend_restaurant(self):
if not self._authenticate():
return self._json_response({'error': 'Unauthorized'}, status=401)
data = request.get_json_data()
db_name = data.get('database_name')
restaurant = request.env['saas.restaurant'].sudo().search([('database_name', '=', db_name)], limit=1)
if not restaurant:
return self._json_response({'error': 'Restaurant database not found'}, status=404)
try:
restaurant.action_suspend()
return self._json_response({'success': True, 'status': restaurant.status})
except Exception as e:
return self._json_response({'error': str(e)}, status=500)
@http.route('/api/v1/saas/renew_subscription', type='json', auth='none', methods=['POST'], csrf=False)
def api_renew_subscription(self):
if not self._authenticate():
return self._json_response({'error': 'Unauthorized'}, status=401)
data = request.get_json_data()
db_name = data.get('database_name')
new_expiry_str = data.get('expiry_date')
restaurant = request.env['saas.restaurant'].sudo().search([('database_name', '=', db_name)], limit=1)
if not restaurant:
return self._json_response({'error': 'Restaurant database not found'}, status=404)
try:
new_expiry = fields.Date.from_string(new_expiry_str)
restaurant.write({'expiry_date': new_expiry})
if restaurant.status in ['suspended', 'expired']:
restaurant.action_activate()
return self._json_response({
'success': True,
'status': restaurant.status,
'expiry_date': fields.Date.to_string(restaurant.expiry_date)
})
except Exception as e:
return self._json_response({'error': str(e)}, status=500)
@http.route('/api/v1/saas/delete_restaurant', type='json', auth='none', methods=['POST'], csrf=False)
def api_delete_restaurant(self):
if not self._authenticate():
return self._json_response({'error': 'Unauthorized'}, status=401)
data = request.get_json_data()
db_name = data.get('database_name')
restaurant = request.env['saas.restaurant'].sudo().search([('database_name', '=', db_name)], limit=1)
if not restaurant:
return self._json_response({'error': 'Restaurant database not found'}, status=404)
try:
master_pwd = config.get('admin_passwd', 'admin')
# 1. Drop database in PostgreSQL via Odoo service API
db.exp_drop(master_pwd, db_name)
# 2. Delete database registry entry in master
restaurant.unlink()
return self._json_response({'success': True})
except Exception as e:
return self._json_response({'error': str(e)}, status=500)
@http.route('/api/v1/saas/backup_restaurant', type='json', auth='none', methods=['POST'], csrf=False)
def api_backup_restaurant(self):
if not self._authenticate():
return self._json_response({'error': 'Unauthorized'}, status=401)
data = request.get_json_data()
db_name = data.get('database_name')
storage_type = data.get('storage_type', 'local')
restaurant = request.env['saas.restaurant'].sudo().search([('database_name', '=', db_name)], limit=1)
if not restaurant:
return self._json_response({'error': 'Restaurant database not found'}, status=404)
try:
backup = request.env['saas.backup'].sudo().create({
'restaurant_id': restaurant.id,
'storage_type': storage_type
})
backup.action_backup_database()
return self._json_response({
'success': True,
'backup_file': backup.backup_file,
's3_url': backup.s3_url
})
except Exception as e:
return self._json_response({'error': str(e)}, status=500)