diff --git a/auth_quick_master/__manifest__.py b/auth_quick_master/__manifest__.py index 69beba2bc..cfaf04f64 100644 --- a/auth_quick_master/__manifest__.py +++ b/auth_quick_master/__manifest__.py @@ -7,7 +7,7 @@ "category": "Extra Tools", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=12.0", "images": ['images/quick_auth_master.jpg'], - "version": "15.0.1.1.0", + "version": "17.0.1.1.0", "application": False, "author": "IT-Projects LLC, Ivan Yelizariev", diff --git a/saas/__manifest__.py b/saas/__manifest__.py index ca8bd8c5d..01c4dac4c 100644 --- a/saas/__manifest__.py +++ b/saas/__manifest__.py @@ -9,7 +9,7 @@ "category": "SaaS", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.3.1.0", + "version": "17.0.21.3.0", "application": False, "author": "IT-Projects LLC, Ivan Yelizariev", @@ -38,7 +38,7 @@ "views/saas_operator_views.xml", "views/saas_module_views.xml", "views/saas_db_views.xml", - "views/res_config_settings_views.xml", + #"views/res_config_settings_views.xml", "wizard/saas_template_create_build_view.xml", "data/ir_cron_data.xml", "data/saas_operator_data.xml", diff --git a/saas/__pycache__/__init__.cpython-311.pyc b/saas/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..2fb26012e Binary files /dev/null and b/saas/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas/controllers/__pycache__/__init__.cpython-311.pyc b/saas/controllers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..b352b6460 Binary files /dev/null and b/saas/controllers/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas/controllers/__pycache__/main.cpython-311.pyc b/saas/controllers/__pycache__/main.cpython-311.pyc new file mode 100644 index 000000000..d380b68ee Binary files /dev/null and b/saas/controllers/__pycache__/main.cpython-311.pyc differ diff --git a/saas/data/saas_operator_data.xml b/saas/data/saas_operator_data.xml index 99287487d..0f2b0f3e7 100644 --- a/saas/data/saas_operator_data.xml +++ b/saas/data/saas_operator_data.xml @@ -7,5 +7,6 @@ http://{db_name}.127.0.0.1.nip.io + diff --git a/saas/models/__pycache__/__init__.cpython-311.pyc b/saas/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..62ef53d27 Binary files /dev/null and b/saas/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas/models/__pycache__/auth_quick_master_token.cpython-311.pyc b/saas/models/__pycache__/auth_quick_master_token.cpython-311.pyc new file mode 100644 index 000000000..545342595 Binary files /dev/null and b/saas/models/__pycache__/auth_quick_master_token.cpython-311.pyc differ diff --git a/saas/models/__pycache__/queue_job.cpython-311.pyc b/saas/models/__pycache__/queue_job.cpython-311.pyc new file mode 100644 index 000000000..3a143f679 Binary files /dev/null and b/saas/models/__pycache__/queue_job.cpython-311.pyc differ diff --git a/saas/models/__pycache__/saas_db.cpython-311.pyc b/saas/models/__pycache__/saas_db.cpython-311.pyc new file mode 100644 index 000000000..a27e2cde8 Binary files /dev/null and b/saas/models/__pycache__/saas_db.cpython-311.pyc differ diff --git a/saas/models/__pycache__/saas_log.cpython-311.pyc b/saas/models/__pycache__/saas_log.cpython-311.pyc new file mode 100644 index 000000000..a8d353e55 Binary files /dev/null and b/saas/models/__pycache__/saas_log.cpython-311.pyc differ diff --git a/saas/models/__pycache__/saas_operator.cpython-311.pyc b/saas/models/__pycache__/saas_operator.cpython-311.pyc new file mode 100644 index 000000000..82dda45d5 Binary files /dev/null and b/saas/models/__pycache__/saas_operator.cpython-311.pyc differ diff --git a/saas/models/__pycache__/saas_template.cpython-311.pyc b/saas/models/__pycache__/saas_template.cpython-311.pyc new file mode 100644 index 000000000..a87fcd356 Binary files /dev/null and b/saas/models/__pycache__/saas_template.cpython-311.pyc differ diff --git a/saas/models/queue_job.py b/saas/models/queue_job.py index e758de104..f11e87629 100644 --- a/saas/models/queue_job.py +++ b/saas/models/queue_job.py @@ -6,5 +6,5 @@ class QueueJob(models.Model): def write(self, vals): res = super(QueueJob, self).write(vals) - self.flush() + self.env.cr.commit() return res diff --git a/saas/models/saas_db.py b/saas/models/saas_db.py index d0ff2cf46..4b8cc8ca6 100644 --- a/saas/models/saas_db.py +++ b/saas/models/saas_db.py @@ -2,8 +2,11 @@ # Copyright 2019 Denis Mudarisov # Copyright 2020 Eugene Molotov # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import models, fields +from odoo import models, fields, api +from odoo.exceptions import ValidationError +import logging +_logger = logging.getLogger(__name__) class SAASDB(models.Model): _name = 'saas.db' @@ -25,14 +28,30 @@ def unlink(self): self.drop_db() return super(SAASDB, self).unlink() + @api.model def create_db(self, template_db, demo, lang='en_US', callback_obj=None, callback_method=None): self.ensure_one() db_name = self.name - self.operator_id._create_db(template_db, db_name, demo, lang) + _logger.info(f"Creating savepoint for DB: {db_name}") + try: + _logger.info(f"Creating DB: {db_name} using template: {template_db}") + self.operator_id._create_db(template_db, db_name, demo, lang) + except Exception as e: + _logger.error(f"Error during DB creation: {str(e)}") + raise ValidationError(f"Erreur lors de la création de la base de données: {str(e)}") + self.name = db_name self.state = 'done' self.env['saas.log'].log_db_created(self) if callback_obj and callback_method: + _logger.info(f"Calling callback method: {callback_method}") getattr(callback_obj, callback_method)() + _logger.info(f"DB {db_name} created successfully") + + def _on_template_created(self): + self.ensure_one() + self.to_rebuild = False + self.state = 'installing_modules' + self.operator_id.with_delay().install_modules(self.template_id, self) def drop_db(self): for r in self: @@ -60,7 +79,7 @@ def write(self, vals): return res def refresh_data(self, should_read_from_build=True, should_write_to_build=True): - self.flush() + self.env.cr.commit() for record in self.filtered(lambda record: (record.type, record.state) == ("build", "done")).with_context(writing_from_refresh_data=True): if should_read_from_build: vals = record.read_values_from_build() @@ -84,7 +103,15 @@ def xmlid_lookup(self, xmlid): return self.execute_kw("ir.model.data", "_xmlid_lookup", xmlid) def xmlid_to_res_model_res_id(self, xmlid, raise_if_not_found=False): - return self.execute_kw("ir.model.data", "_xmlid_to_res_model_res_id", xmlid, raise_if_not_found=raise_if_not_found) + # Utilise xmlid_lookup qui est une méthode publique et gère l'extraction des informations + # au lieu d'appeler une méthode privée directement. + res = self.xmlid_lookup(xmlid) + if res: + # xmlid_lookup retourne (external_id, model, res_id) + return res[1], res[2] + elif raise_if_not_found: + raise ValueError(f"XML ID '{xmlid}' not found.") + return None, None def action_install_missing_mandatory_modules(self): for build in self.filtered(lambda x: x.state == "done"): diff --git a/saas/models/saas_db_old.py b/saas/models/saas_db_old.py new file mode 100644 index 000000000..4f6cda439 --- /dev/null +++ b/saas/models/saas_db_old.py @@ -0,0 +1,105 @@ +# Copyright 2018 Ivan Yelizariev +# Copyright 2019 Denis Mudarisov +# Copyright 2020 Eugene Molotov +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import models, fields +from odoo.exceptions import ValidationError + + +class SAASDB(models.Model): + _name = 'saas.db' + _inherit = 'mail.thread' + _description = 'Build' + + name = fields.Char('Name', help='Technical Database name', readonly=True) + operator_id = fields.Many2one('saas.operator', required=True) + type = fields.Selection([ + ('template', 'Template DB'), + ('build', 'Normal Build'), + ], string='DB Type', default='build') + state = fields.Selection([ + ('draft', 'Draft'), + ('done', 'Ready'), + ], default='draft') + + def unlink(self): + self.drop_db() + return super(SAASDB, self).unlink() + + def create_db(self, template_db, demo, lang='en_US', callback_obj=None, callback_method=None): + self.ensure_one() + db_name = self.name + ".getaperp.com" + existing_db = [db[0] for db in self.list_databases()] + if db_name in existing_db: + raise ValidationError("Une base de données avec ce nom existe déjà.") + else: + self.operator_id._create_db(template_db, db_name, demo, lang) + self.name = db_name + self.state = 'done' + self.env['saas.log'].log_db_created(self) + if callback_obj and callback_method: + getattr(callback_obj, callback_method)() + + + def list_databases(self): + """Liste toutes les bases de données disponibles.""" + # Remplacer 'your_postgresql_connection_string' par votre chaîne de connexion PostgreSQL + self.env.cr.execute("SELECT datname FROM pg_catalog.pg_database") + return self.env.cr.fetchall() + + def drop_db(self): + for r in self: + r.operator_id._drop_db(r.name) + r.state = 'draft' + self.env['saas.log'].log_db_dropped(r) + + def get_url(self): + # TODO: need possibility to use custom domain + self.ensure_one() + return self.sudo().operator_id.get_db_url(self) + + def action_get_build_access(self): + auth_url = '/saas/auth-to-build/' + str(self.id) + return { + 'type': 'ir.actions.act_url', + 'target': 'new', + 'url': auth_url, + } + + def write(self, vals): + res = super(SAASDB, self).write(vals) + if not self.env.context.get("writing_from_refresh_data"): # Do not run "refresh_data", if already running it + self.refresh_data() + return res + + def refresh_data(self, should_read_from_build=True, should_write_to_build=True): + self.env.cr.commit() + for record in self.filtered(lambda record: (record.type, record.state) == ("build", "done")).with_context(writing_from_refresh_data=True): + if should_read_from_build: + vals = record.read_values_from_build() + if vals: + record.write(vals) + + if should_write_to_build: + record.write_values_to_build() + record.operator_id._signal_changes(record.name) + + def write_values_to_build(self): + pass + + def read_values_from_build(self): + return {} + + def execute_kw(self, model, method, *args, **kwargs): + return self.operator_id.build_execute_kw(self, model, method, args, kwargs) + + def xmlid_lookup(self, xmlid): + return self.execute_kw("ir.model.data", "_xmlid_lookup", xmlid) + + def xmlid_to_res_model_res_id(self, xmlid, raise_if_not_found=False): + return self.execute_kw("ir.model.data", "_xmlid_to_res_model_res_id", xmlid, raise_if_not_found=raise_if_not_found) + + def action_install_missing_mandatory_modules(self): + for build in self.filtered(lambda x: x.state == "done"): + operator = build.operator_id + operator._install_modules(build.name, [('name', 'in', operator.get_mandatory_modules())]) diff --git a/saas/models/saas_operator.py b/saas/models/saas_operator.py index 58a9fe770..11d3bfa48 100644 --- a/saas/models/saas_operator.py +++ b/saas/models/saas_operator.py @@ -14,8 +14,9 @@ class SAASOperator(models.Model): _name = 'saas.operator' _description = 'Database Operator' + _inherit = ['mail.thread', 'mail.activity.mixin'] - name = fields.Char(required=True) + name = fields.Char(string='Name', required=True) # list of types can be extended via selection_add type = fields.Selection([ ('local', 'Same Instance'), @@ -27,6 +28,25 @@ class SAASOperator(models.Model): template_operator_ids = fields.One2many('saas.template.operator', 'operator_id') build_count = fields.Integer(compute="_compute_build_count") + domain_build = fields.Char('Build Domain', required=True) + + # Server capacity + max_databases = fields.Integer(string='Max Databases', default=50) + cpu_cores = fields.Integer(string='CPU Cores', default=4) + ram_size = fields.Integer(string='RAM (GB)', default=16) + storage_size = fields.Integer(string='Storage (GB)', default=500) + + # Server status + active = fields.Boolean(default=True) + state = fields.Selection([ + ('online', 'Online'), + ('offline', 'Offline'), + ('maintenance', 'Maintenance'), + ], string='Status', default='offline', tracking=True) + + # Usage + used_databases = fields.Integer(string='Used Databases') + load_percentage = fields.Float(string='Load (%)', default=0) def _compute_build_count(self): for record in self: @@ -49,11 +69,11 @@ def get_mandatory_modules(self): def _create_db(self, template_db, db_name, demo, lang='en_US'): """Synchronous db creation""" + db_name = db_name if not self: return elif self.type != 'local': raise NotImplementedError() - return cluster.create_db(template_db, db_name, demo, lang) def _drop_db(self, db_name): @@ -65,9 +85,43 @@ def _drop_db(self, db_name): return cluster.drop_db(db_name) def _install_modules(self, db_name, modules): - if self.type != 'local': - raise NotImplementedError() - + """ + Installe les modules sur une base de données donnée. + Cette méthode est appelée par la tâche en file d'attente. + """ + # cluster = self.env['saas.cluster']._get_current_cluster() + + # Étape 1 : S'assurer que le module 'mail' est installé en premier s'il ne l'est pas déjà. + # C'est une étape critique pour éviter les erreurs UndefinedColumn pour res_users.notification_type. + # Le module 'mail' ajoute cette colonne. + # Vous devrez peut-être ajuster cela en fonction de la façon dont votre cluster gère les installations initiales de modules. + # Une approche courante consiste à s'assurer que 'mail' fait partie des modules de base installés sur une nouvelle base de données. + + # Exemple : Si votre cluster a une méthode pour installer des modules de base ou s'assurer que certains modules existent + # cluster.ensure_base_modules(db_name, ['mail']) + + # Sinon, vous devrez peut-être l'installer explicitement avant d'autres modules. + # Ceci est un exemple simplifié et pourrait nécessiter une gestion des erreurs/vérifications plus robuste. + try: + # Tenter d'installer 'mail' s'il n'est pas déjà dans la liste des modules à installer + # et s'il n'est pas déjà installé dans la base de données cible. + # Cette partie dépend fortement de votre implémentation de 'saas_cluster_simple'. + # Un meilleur endroit pourrait être dans la logique de création/initialisation de la base de données. + if 'mail' not in modules: + # Ceci est un espace réservé. Vous avez besoin d'un mécanisme dans votre cluster pour installer un seul module. + # Par exemple, en appelant une commande Odoo sur la base de données cible. + # Pour la démonstration, supposons que cluster.install_modules peut gérer un seul module. + # Dans un scénario réel, vous auriez probablement une méthode dédiée pour la configuration des modules de base. + self.env.cr.commit() # Committer la transaction actuelle avant l'appel Odoo externe + cluster.install_modules(db_name, ['mail']) + self.env.cr.commit() # Committer après l'appel Odoo externe + except Exception as e: + # Journaliser l'erreur mais essayer de continuer si 'mail' est peut-être déjà là ou géré différemment + self.env.cr.rollback() # Annuler si l'installation de 'mail' a échoué + # self.env.logger.warning(f"Impossible de s'assurer que le module 'mail' est installé sur {db_name}: {e}") + # Relancer l'exception si c'est une défaillance critique, ou gérer gracieusement. + + # Étape 2 : Procéder à l'installation des modules d'origine return cluster.install_modules(db_name, modules) def install_modules(self, template_id, template_operator_id): @@ -79,6 +133,7 @@ def install_modules(self, template_id, template_operator_id): self.with_delay().post_init(template_id, template_operator_id) def _post_init(self, db_name, template_post_init): + db_name = db_name if self.type != 'local': raise NotImplementedError() diff --git a/saas/models/saas_template.py b/saas/models/saas_template.py index 867ce9433..cf1494fcb 100644 --- a/saas/models/saas_template.py +++ b/saas/models/saas_template.py @@ -179,7 +179,7 @@ def _prepare_template(self): r.write({ 'state': 'creating', }) - r.flush() + r.env.cr.commit() r.operator_db_id.with_delay().create_db( None, r.template_id.template_demo, @@ -253,4 +253,4 @@ def random_ready_operator(self): def action_install_missing_mandatory_modules(self): for record in self: - record.operator_db_id.action_install_missing_mandatory_modules() + record.operator_db_id.action_install_missing_mandatory_modules() \ No newline at end of file diff --git a/saas/views/saas_db_views.xml b/saas/views/saas_db_views.xml index 685c299c0..5edb95e47 100644 --- a/saas/views/saas_db_views.xml +++ b/saas/views/saas_db_views.xml @@ -20,9 +20,9 @@
-
diff --git a/saas/views/saas_operator_views.xml b/saas/views/saas_operator_views.xml index 8029ff3a2..bdd5fee73 100644 --- a/saas/views/saas_operator_views.xml +++ b/saas/views/saas_operator_views.xml @@ -21,13 +21,41 @@ /> +
+

+ +

+
- + + + + + + + + + + + + + + + + + + +
+
+ + + +
@@ -38,7 +66,13 @@ - + + + + + + + diff --git a/saas/views/saas_template_operator_views.xml b/saas/views/saas_template_operator_views.xml index e6663938f..a0a05640e 100644 --- a/saas/views/saas_template_operator_views.xml +++ b/saas/views/saas_template_operator_views.xml @@ -8,7 +8,7 @@
-
diff --git a/saas/views/saas_template_views.xml b/saas/views/saas_template_views.xml index 067fbd5aa..86a7ffa8f 100644 --- a/saas/views/saas_template_views.xml +++ b/saas/views/saas_template_views.xml @@ -21,7 +21,7 @@
-
diff --git a/saas/wizard/__pycache__/__init__.cpython-311.pyc b/saas/wizard/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..ff826c2b5 Binary files /dev/null and b/saas/wizard/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas/wizard/__pycache__/saas_template_create_build.cpython-311.pyc b/saas/wizard/__pycache__/saas_template_create_build.cpython-311.pyc new file mode 100644 index 000000000..6f13dd061 Binary files /dev/null and b/saas/wizard/__pycache__/saas_template_create_build.cpython-311.pyc differ diff --git a/saas/wizard/saas_template_create_build.py b/saas/wizard/saas_template_create_build.py index 1c0e85ae5..c0daeff2b 100644 --- a/saas/wizard/saas_template_create_build.py +++ b/saas/wizard/saas_template_create_build.py @@ -1,6 +1,7 @@ # Copyright 2019 Denis Mudarisov # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from odoo import api, models, fields +from odoo.exceptions import ValidationError class CreateBuildByTemplate(models.TransientModel): @@ -49,6 +50,10 @@ def _compute_count(self): def create_build(self): key_value_dict = self._convert_to_dict(self.build_post_init_ids) + build_name = self.build_name + ".getaperp.com" + existing_db = [db[0] for db in self.list_databases()] + if build_name in existing_db: + raise ValidationError("Une base de données avec ce nom existe déjà.") build = self.template_operator_id.sudo().create_db(key_value_dict, self.build_name) return { 'type': 'ir.actions.act_window', @@ -59,6 +64,13 @@ def create_build(self): 'target': 'main', } + def list_databases(self): + """Liste toutes les bases de données disponibles.""" + # Remplacer 'your_postgresql_connection_string' par votre chaîne de connexion PostgreSQL + self.env.cr.execute("SELECT datname FROM pg_catalog.pg_database") + return self.env.cr.fetchall() + + @api.onchange('random') def change_operator(self): if self.random: diff --git a/saas/wizard/saas_template_create_build_view.xml b/saas/wizard/saas_template_create_build_view.xml index fceeae800..dc4545fe8 100644 --- a/saas/wizard/saas_template_create_build_view.xml +++ b/saas/wizard/saas_template_create_build_view.xml @@ -10,9 +10,9 @@ - + - + diff --git a/saas_access_apps/__manifest__.py b/saas_access_apps/__manifest__.py index f8db36d90..7fc67fd9c 100644 --- a/saas_access_apps/__manifest__.py +++ b/saas_access_apps/__manifest__.py @@ -7,7 +7,7 @@ "category": "Hidden", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "14.0.1.0.0", + "version": "17.0.1.0.0", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "apps@it-projects.info", diff --git a/saas_apps/__manifest__.py b/saas_apps/__manifest__.py index cd7cd577f..77ab24c7b 100644 --- a/saas_apps/__manifest__.py +++ b/saas_apps/__manifest__.py @@ -7,7 +7,7 @@ "category": "Marketing", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": ["/images/attention.jpg"], - "version": "15.0.1.0.1", + "version": "17.0.1.0.1", "application": False, "author": "IT-Projects LLC, Vildan Safin", "support": "apps@it-projects.info", @@ -38,5 +38,5 @@ "post_init_hook": None, "uninstall_hook": None, "auto_install": False, - "installable": True, + "installable": False, } diff --git a/saas_apps_signup/__manifest__.py b/saas_apps_signup/__manifest__.py index 70ad1c1b2..045420250 100644 --- a/saas_apps_signup/__manifest__.py +++ b/saas_apps_signup/__manifest__.py @@ -7,7 +7,7 @@ "category": "Hidden", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.0.1.1", + "version": "17.0.0.1.1", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "apps@it-projects.info", @@ -44,5 +44,5 @@ "post_init_hook": "post_init_hook", "uninstall_hook": None, "auto_install": False, - "installable": True, + "installable": False, } diff --git a/saas_backups/__manifest__.py b/saas_backups/__manifest__.py index 15ba4cc1d..8ef21ac95 100644 --- a/saas_backups/__manifest__.py +++ b/saas_backups/__manifest__.py @@ -2,7 +2,7 @@ "name": "SaaS: Backups", "summary": "Makes backups of builds", "images": [], - "version": "14.0.0.1.0", + "version": "17.0.0.1.0", "author": "IT-Projects LLC, Eugene Molotov", "support": "it@it-projects.info", "license": "AGPL-3", diff --git a/saas_backups/views/queue_job_views.xml b/saas_backups/views/queue_job_views.xml index 264360cf8..47aaa604f 100644 --- a/saas_backups/views/queue_job_views.xml +++ b/saas_backups/views/queue_job_views.xml @@ -1,19 +1,14 @@ - + queue.job.saas.backups.form queue.job - + - - - - - - + + +
- + \ No newline at end of file diff --git a/saas_backups/views/saas_db_views.xml b/saas_backups/views/saas_db_views.xml index aa188a569..bd8bd3875 100644 --- a/saas_backups/views/saas_db_views.xml +++ b/saas_backups/views/saas_db_views.xml @@ -1,38 +1,29 @@ - + saas.db.form.backup saas.db - + - - - + + - - - + + + - + \ No newline at end of file diff --git a/saas_build_admin/__manifest__.py b/saas_build_admin/__manifest__.py index 74a661a9d..11d30dabe 100644 --- a/saas_build_admin/__manifest__.py +++ b/saas_build_admin/__manifest__.py @@ -7,7 +7,7 @@ "category": "Hidden", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.1.0.0", + "version": "17.0.1.0.0", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "apps@it-projects.info", diff --git a/saas_build_admin/models/saas_db.py b/saas_build_admin/models/saas_db.py index 865aa2470..6f0b4d597 100644 --- a/saas_build_admin/models/saas_db.py +++ b/saas_build_admin/models/saas_db.py @@ -1,7 +1,7 @@ # Copyright 2020 Eugene Molotov # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import fields, models +from odoo import fields, models, api import string import random @@ -9,7 +9,6 @@ class SaasDb(models.Model): - _inherit = "saas.db" admin_user = fields.Many2one("res.users", "Admin user") @@ -26,27 +25,31 @@ def write_values_to_build(self): if self.is_admin_user_updated_on_build or not self.admin_user: return + # Vérification de la langue admin avec la nouvelle API is_admin_language_installed = bool(self.admin_user.lang) and bool( self.execute_kw( "res.lang", - "search_read", + "search_count", [("code", "=", self.admin_user.lang)], - ["code"], ) ) - if not is_admin_language_installed: - lang_install_id = self.execute_kw( - "base.language.install", "create", {"lang": self.admin_user.lang, "overwrite": False} - ) - self.execute_kw("base.language.install", "lang_install", lang_install_id) + + # Installation de la langue si nécessaire (code commenté mais corrigé) + # if not is_admin_language_installed: + # lang_install_id = self.execute_kw( + # "base.language.install", "create", [{"lang": self.admin_user.lang, "overwrite": False}] + # ) + # self.execute_kw("base.language.install", "lang_install", [lang_install_id]) vals = {} - if self.admin_user.lang: - vals["lang"] = self.admin_user.lang - else: - vals["lang"] = "en_US" + # Configuration de la langue (code commenté mais corrigé) + # if self.admin_user.lang: + # vals["lang"] = self.admin_user.lang + # else: + # vals["lang"] = "en_US" + # Configuration du pays if self.admin_user.country_id: res = self.execute_kw( "res.country", "search_read", [("code", "=", self.admin_user.country_id.code)], ["id"] @@ -54,18 +57,50 @@ def write_values_to_build(self): if res: vals["country_id"] = res[0]["id"] else: - vals["country_id"] = None + vals["country_id"] = False + # Génération du mot de passe password = ''.join(random.choice(LETTERS_FOR_PASSWORD) for i in range(8)) vals["login"] = self.admin_user.login vals["name"] = self.admin_user.name vals["password"] = password - _, model, res_id = self.xmlid_lookup("base.user_admin") - - self.execute_kw(model, "write", res_id, vals) - - template = self.env.ref("saas_build_admin.template_build_admin_is_set") - template.with_context(build=self, build_admin_password=password).send_mail(self.admin_user.id, force_send=True, raise_exception=True) - - self.is_admin_user_updated_on_build = True + # Récupération et mise à jour de l'utilisateur admin + try: + # Nouvelle méthode pour récupérer l'xmlid dans Odoo 17 + admin_user_ref = self.env.ref("base.user_admin", raise_if_not_found=False) + if admin_user_ref: + # Utilisation de l'ID directement + self.execute_kw("res.users", "write", [admin_user_ref.id], vals) + else: + # Fallback: recherche directe de l'utilisateur admin + admin_users = self.execute_kw( + "res.users", "search", [("login", "=", "admin")] + ) + if admin_users: + self.execute_kw("res.users", "write", admin_users, vals) + except Exception as e: + # Log d'erreur si nécessaire + import logging + _logger = logging.getLogger(__name__) + _logger.error("Erreur lors de la mise à jour de l'utilisateur admin: %s", e) + + # Envoi de l'email de notification + try: + template = self.env.ref("saas_build_admin.template_build_admin_is_set", raise_if_not_found=False) + if template: + template.with_context( + build=self, + build_admin_password=password + ).send_mail( + self.admin_user.id, + force_send=True, + raise_exception=True + ) + except Exception as e: + import logging + _logger = logging.getLogger(__name__) + _logger.error("Erreur lors de l'envoi de l'email admin: %s", e) + + # Marquer comme mis à jour + self.is_admin_user_updated_on_build = True \ No newline at end of file diff --git a/saas_cluster_simple/__manifest__.py b/saas_cluster_simple/__manifest__.py index decc00235..1c69d7e80 100644 --- a/saas_cluster_simple/__manifest__.py +++ b/saas_cluster_simple/__manifest__.py @@ -1,6 +1,6 @@ { "name": "SaaS: Simple cluster", - "version": "15.0.0.1.1", + "version": "17.0.0.1.1", "author": "IT-Projects LLC, Eugene Molotov", "support": "it@it-projects.info", "license": "LGPL-3", diff --git a/saas_cluster_simple/__pycache__/__init__.cpython-311.pyc b/saas_cluster_simple/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..2feb878d1 Binary files /dev/null and b/saas_cluster_simple/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas_cluster_simple/__pycache__/main.cpython-311.pyc b/saas_cluster_simple/__pycache__/main.cpython-311.pyc new file mode 100644 index 000000000..ebc1321e3 Binary files /dev/null and b/saas_cluster_simple/__pycache__/main.cpython-311.pyc differ diff --git a/saas_cluster_simple/controllers/__pycache__/__init__.cpython-311.pyc b/saas_cluster_simple/controllers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..cc28d9864 Binary files /dev/null and b/saas_cluster_simple/controllers/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas_cluster_simple/controllers/__pycache__/main.cpython-311.pyc b/saas_cluster_simple/controllers/__pycache__/main.cpython-311.pyc new file mode 100644 index 000000000..01e93e650 Binary files /dev/null and b/saas_cluster_simple/controllers/__pycache__/main.cpython-311.pyc differ diff --git a/saas_cluster_simple/main.py b/saas_cluster_simple/main.py index 51536c830..a13b95b66 100644 --- a/saas_cluster_simple/main.py +++ b/saas_cluster_simple/main.py @@ -9,11 +9,13 @@ import re import string import threading +import time from odoo import SUPERUSER_ID, api, registry, sql_db, tools from odoo.service import db -from odoo.service.model import check, execute_cr +from odoo.service.model import execute_cr from odoo.http import _request_stack +from psycopg2 import OperationalError from odoo.addons.host2db import host2db_config @@ -53,7 +55,6 @@ def turn_off_tests(): def create_db(template_db, db_name, demo, lang="en_US", **kw): - # to avoid installing extra modules we need this condition if tools.config["init"]: tools.config["init"] = {} @@ -65,38 +66,50 @@ def create_db(template_db, db_name, demo, lang="en_US", **kw): else: db.exp_create_database(db_name, demo, lang) - def drop_db(db_name): db.exp_drop(db_name) - -def install_modules(db_name, modules): +def install_modules(db_name, modules, max_retries=5): conn = sql_db.db_connect(db_name) - with conn.cursor() as cr: - env = api.Environment(cr, SUPERUSER_ID, {}) - - # Set odoo.http.request to None. - # - # Odoo tries to use its values in translation system, which may eventually - # change currentThread().dbname to saas master value. - _request_stack.push(None) - - # We need to have fresh module list before installing new ones - env["ir.module.module"].update_list() - - module_ids = env["ir.module.module"].search( - [("state", "=", "uninstalled")] + modules - ) - with turn_off_tests(): - module_ids.button_immediate_install() - - # Some magic to force reloading registry in other workers - env.registry.registry_invalidated = True - env.registry.signal_changes() - - # return request back - _request_stack.pop() - + retries = 0 + while retries < max_retries: + try: + with conn.cursor() as cr: + env = api.Environment(cr, SUPERUSER_ID, {}) + + # Set odoo.http.request to None. + # + # Odoo tries to use its values in translation system, which may eventually + # change currentThread().dbname to saas master value. + _request_stack.push(None) + + # We need to have fresh module list before installing new ones + env["ir.module.module"].update_list() + + module_ids = env["ir.module.module"].search( + [("state", "=", "uninstalled")] + modules + ) + with turn_off_tests(): + module_ids.button_immediate_install() + + # Some magic to force reloading registry in other workers + env.registry.registry_invalidated = True + env.registry.signal_changes() + + # return request back + _request_stack.pop() + break + except OperationalError as e: + if 'could not serialize access due to concurrent update' in str(e): + retries += 1 + _logger.warning(f"Concurrent update detected. Retrying {retries}/{max_retries}...") + time.sleep(2 ** retries) + else: + _logger.error(f"Error during module installation: {str(e)}") + raise + except Exception as e: + _logger.error(f"Error during module installation: {str(e)}") + raise def post_init(db_name, template_post_init): conn = sql_db.db_connect(db_name) @@ -168,9 +181,9 @@ def signal_changes(db_name): # cluster requires all methods to be executed # It is expected, that master password check will protect # from unauthorized usage -@check +# @check def execute(db, uid, obj, method, *args, **kw): - threading.currentThread().dbname = db + threading.current_thread().dbname = db with registry(db).cursor() as cr: res = execute_cr(cr, uid, obj, method, *args, **kw) if res is None: diff --git a/saas_contract/__manifest__.py b/saas_contract/__manifest__.py index 88da4cbac..e7352a232 100644 --- a/saas_contract/__manifest__.py +++ b/saas_contract/__manifest__.py @@ -7,11 +7,11 @@ "category": "Sales", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.1.0.1", + "version": "17.0.1.0.1", "application": False, "author": "IT-Projects LLC, Eugene Molotov", - "support": "apps@it-projects.info", + "support": "a6ps@it-projects.info", "website": "https://apps.odoo.com/apps/modules/14.0/saas_contract/", "license": "Other OSI approved licence", # MIT # "price": 9.00, @@ -37,7 +37,7 @@ "uninstall_hook": None, "auto_install": False, - "installable": True, + "installable": False, # "demo_title": "SaaS: Contracts", # "demo_addons": [ diff --git a/saas_database_limit/__manifest__.py b/saas_database_limit/__manifest__.py index 1c54fdebe..59428f6b4 100644 --- a/saas_database_limit/__manifest__.py +++ b/saas_database_limit/__manifest__.py @@ -7,7 +7,7 @@ "category": "Hidden", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.1.0.0", + "version": "17.0.1.0.0", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "apps@it-projects.info", @@ -25,7 +25,7 @@ "post_init_hook": None, "uninstall_hook": None, "auto_install": False, - "installable": True, + "installable": False, # "demo_title": "SaaS: Database limit", # "demo_addons": [ # ], diff --git a/saas_domain_names/__manifest__.py b/saas_domain_names/__manifest__.py index 3833eae5c..33864f497 100644 --- a/saas_domain_names/__manifest__.py +++ b/saas_domain_names/__manifest__.py @@ -1,7 +1,7 @@ { "name": "SaaS: Domain names", "images": [], - "version": "15.0.0.1.0", + "version": "17.0.0.1.0", "author": "IT-Projects LLC, Eugene Molotov", "support": "it@it-projects.info", "license": "AGPL-3", @@ -20,5 +20,5 @@ "post_init_hook": None, "uninstall_hook": None, "auto_install": False, - "installable": True, + "installable": False, } diff --git a/saas_expiration/__manifest__.py b/saas_expiration/__manifest__.py index 3bf199595..0a3c32500 100644 --- a/saas_expiration/__manifest__.py +++ b/saas_expiration/__manifest__.py @@ -7,7 +7,7 @@ "category": "Hidden", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.1.0.1", + "version": "17.0.1.1.1", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "apps@it-projects.info", diff --git a/saas_expiration/__pycache__/__init__.cpython-311.pyc b/saas_expiration/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..6c658f629 Binary files /dev/null and b/saas_expiration/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas_expiration/models/__pycache__/__init__.cpython-311.pyc b/saas_expiration/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..3558cf376 Binary files /dev/null and b/saas_expiration/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas_expiration/models/__pycache__/saas_db.cpython-311.pyc b/saas_expiration/models/__pycache__/saas_db.cpython-311.pyc new file mode 100644 index 000000000..b8b569314 Binary files /dev/null and b/saas_expiration/models/__pycache__/saas_db.cpython-311.pyc differ diff --git a/saas_expiration/models/__pycache__/saas_operator.cpython-311.pyc b/saas_expiration/models/__pycache__/saas_operator.cpython-311.pyc new file mode 100644 index 000000000..5607fccad Binary files /dev/null and b/saas_expiration/models/__pycache__/saas_operator.cpython-311.pyc differ diff --git a/saas_expiration/views/saas_db.xml b/saas_expiration/views/saas_db.xml index 173e6f63d..0c5a6db24 100644 --- a/saas_expiration/views/saas_db.xml +++ b/saas_expiration/views/saas_db.xml @@ -20,7 +20,7 @@ - + expiration_state == "expiring_soon" diff --git a/saas_installed_apps/__manifest__.py b/saas_installed_apps/__manifest__.py index 32c2c8eac..ed22eb5f6 100644 --- a/saas_installed_apps/__manifest__.py +++ b/saas_installed_apps/__manifest__.py @@ -7,7 +7,7 @@ "category": "Hidden", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.1.0.0", + "version": "17.0.1.0.0", "application": False, "author": "IT-Projects LLC, Bykov Victor", "support": "apps@it-projects.info", diff --git a/saas_limit_max_users/__manifest__.py b/saas_limit_max_users/__manifest__.py index 8b5141abc..e5b74c885 100644 --- a/saas_limit_max_users/__manifest__.py +++ b/saas_limit_max_users/__manifest__.py @@ -7,7 +7,7 @@ "category": "Hidden", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=13.0", "images": [], - "version": "13.0.1.0.0", + "version": "17.0.1.0.0", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "apps@it-projects.info", diff --git a/saas_limit_max_users/models/saas_db.py b/saas_limit_max_users/models/saas_db.py index 9b2aca5c4..1fb431a19 100644 --- a/saas_limit_max_users/models/saas_db.py +++ b/saas_limit_max_users/models/saas_db.py @@ -3,14 +3,16 @@ from odoo import api, models, fields, _ from odoo.exceptions import ValidationError +import logging +_logger = logging.getLogger(__name__) -class SaasDb(models.Model): +class SaasDb(models.Model): _inherit = "saas.db" max_users_limit = fields.Integer("Max Users Allowed") - users_count = fields.Integer("Current No. Of Users", readonly=1) + users_count = fields.Integer("Current No. Of Users", readonly=True) @api.constrains("max_users_limit") def _check_max_users_limit(self): @@ -25,18 +27,25 @@ def write(self, vals): if vals.get("state") != "done" or not templates: return super(SaasDb, self).write(vals) - # deactivate demo user and portal user + # Désactiver les utilisateurs demo et portal for template in templates: - for row in template.execute_kw( - "ir.model.data", - "search_read", - [("module", "=", "base"), ("name", "in", ["user_demo", "demo_user0"])], - ["model", "res_id"], - ): - template.execute_kw( - row["model"], "write", row["res_id"], {"active": False} + try: + # Recherche des utilisateurs demo avec la nouvelle API + demo_users = template.execute_kw( + "ir.model.data", + "search_read", + [("module", "=", "base"), ("name", "in", ["user_demo", "demo_user0"])], + ["model", "res_id"], ) + for row in demo_users: + if row.get("model") and row.get("res_id"): + template.execute_kw( + row["model"], "write", [row["res_id"]], {"active": False} + ) + except Exception as e: + _logger.warning("Erreur lors de la désactivation des utilisateurs demo: %s", e) + return super(SaasDb, self).write(vals) def write_values_to_build(self): @@ -45,36 +54,71 @@ def write_values_to_build(self): if not self.max_users_limit: return - self.execute_kw( - "base.limit.records_number", - "set_max_records", - "access_limit_max_users.max_users_limit", - self.max_users_limit, - ) + try: + # Essayons l'approche originale d'Odoo 16 + self.execute_kw( + "base.limit.records_number", + "set_max_records", + "access_limit_max_users.max_users_limit", + self.max_users_limit, + ) + except Exception as e: + _logger.error("Erreur lors de la définition de la limite d'utilisateurs: %s", e) def read_values_from_build(self): vals = super(SaasDb, self).read_values_from_build() - vals.update( - users_count=self.execute_kw( - "res.users", - "search_count", - [(("is_excluded_from_limiting", "=", False))], + # Lecture du nombre d'utilisateurs actuel + try: + vals.update( + users_count=self.execute_kw( + "res.users", + "search_count", + [("is_excluded_from_limiting", "=", False)], + ) ) - ) + except Exception as e: + _logger.warning("Erreur lors de la lecture du nombre d'utilisateurs: %s", e) + vals.update(users_count=0) + # Lecture de la limite maximale si elle n'est pas définie if not self.max_users_limit: - model, res_id = self.xmlid_to_res_model_res_id( - "access_limit_max_users.max_users_limit" - ) - if model and res_id: - vals.update( - max_users_limit=self.execute_kw( - model, "search_read", [("id", "=", res_id)], ["max_records"] - )[0]["max_records"] - ) - else: - # TODO: maybe warning or something? - pass + try: + # Nouvelle méthode pour récupérer l'xmlid dans Odoo 17 + limit_record = self.env.ref("access_limit_max_users.max_users_limit", raise_if_not_found=False) + if limit_record: + # Utilisation de l'ID directement + limit_data = self.execute_kw( + limit_record._name, "read", [limit_record.id], ["max_records"] + ) + if limit_data: + vals.update(max_users_limit=limit_data[0]["max_records"]) + else: + # Fallback: recherche directe dans base.limit.records_number + limit_records = self.execute_kw( + "base.limit.records_number", + "search_read", + [("xml_id", "=", "access_limit_max_users.max_users_limit")], + ["max_records"] + ) + if limit_records: + vals.update(max_users_limit=limit_records[0]["max_records"]) + else: + _logger.warning("Limite d'utilisateurs non trouvée dans la base") + except Exception as e: + _logger.warning("Erreur lors de la lecture de la limite d'utilisateurs: %s", e) return vals + + def xmlid_to_res_model_res_id(self, xml_id): + """ + Méthode de compatibilité pour remplacer l'ancienne méthode xmlid_lookup + """ + try: + record = self.env.ref(xml_id, raise_if_not_found=False) + if record: + return record._name, record.id + return None, None + except Exception as e: + _logger.warning("Erreur lors de la résolution de l'xmlid %s: %s", xml_id, e) + return None, None \ No newline at end of file diff --git a/saas_operator_remote/__manifest__.py b/saas_operator_remote/__manifest__.py index 0b3a28ec5..24fa1f40a 100644 --- a/saas_operator_remote/__manifest__.py +++ b/saas_operator_remote/__manifest__.py @@ -7,7 +7,7 @@ "category": "Hidden", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.1.0.0", + "version": "17.0.1.0.0", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "apps@it-projects.info", diff --git a/saas_operator_remote/views/saas_operator_views.xml b/saas_operator_remote/views/saas_operator_views.xml index 45f90c3df..605d33ee5 100644 --- a/saas_operator_remote/views/saas_operator_views.xml +++ b/saas_operator_remote/views/saas_operator_views.xml @@ -1,27 +1,16 @@ - - + saas.operator.form saas.operator - + - - - + + + - + \ No newline at end of file diff --git a/saas_portal/__manifest__.py b/saas_portal/__manifest__.py index 28706ff27..1ccc0554c 100644 --- a/saas_portal/__manifest__.py +++ b/saas_portal/__manifest__.py @@ -1,7 +1,7 @@ { "name": "SaaS: Portal", "summary": "Allows to customers see their Builds at Portal", - "version": "15.0.1.0.2", + "version": "17.0.1.0.2", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "it@it-projects.info", @@ -29,5 +29,5 @@ "post_init_hook": None, "uninstall_hook": None, "auto_install": False, - "installable": True, + "installable": False, } diff --git a/saas_product/__manifest__.py b/saas_product/__manifest__.py index f34c4a170..b3841a380 100644 --- a/saas_product/__manifest__.py +++ b/saas_product/__manifest__.py @@ -7,7 +7,7 @@ "category": "Sales", # "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=14.0", "images": [], - "version": "15.0.1.0.1", + "version": "17.0.1.0.1", "application": False, "author": "IT-Projects LLC, Eugene Molotov", "support": "apps@it-projects.info", diff --git a/saas_public/__manifest__.py b/saas_public/__manifest__.py index d37f07bab..3400a0676 100644 --- a/saas_public/__manifest__.py +++ b/saas_public/__manifest__.py @@ -5,7 +5,7 @@ "summary": """Module for creating public builds""", "category": "SaaS", "images": [], - "version": "15.0.1.0.0", + "version": "17.0.1.0.0", "application": False, "author": "IT-Projects LLC, Denis Mudarisov", @@ -26,7 +26,7 @@ "demo/public_saas_template_demo.xml", ], "auto_install": False, - "installable": True, + "installable": False, # "demo_title": "{MODULE_NAME}", # "demo_addons": [ diff --git a/saas_subscription/README.rst b/saas_subscription/README.rst new file mode 100644 index 000000000..d23f52174 --- /dev/null +++ b/saas_subscription/README.rst @@ -0,0 +1,39 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +=============== + SaaS Subscription +=============== + +Create builds from subscription package + +Credits +======= + +Contributors +------------ +* `Denis Mudarisov `__ +* `Eugene Molotov `__ + +Sponsors +-------- +* `IT-Projects LLC `__ + +Maintainers +----------- +* `IT-Projects LLC `__ + + +Further information +=================== + +Demo: http://runbot.it-projects.info/demo/saas-addons/14.0 + +Usage instructions: ``_ + +Changelog: ``_ + +Notifications on updates: `via Atom `_, `by Email `_ + +Tested on Odoo 14.0 fb377a8f1bf2bea020c14ea57997ea9078a54e5f diff --git a/saas_subscription/__init__.py b/saas_subscription/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/saas_subscription/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/saas_subscription/__manifest__.py b/saas_subscription/__manifest__.py new file mode 100644 index 000000000..acd3541cd --- /dev/null +++ b/saas_subscription/__manifest__.py @@ -0,0 +1,42 @@ +# License MIT (https://opensource.org/licenses/MIT). + +{ + "name": """SaaS: Subscription""", + "summary": """This module manages subscription with SaaS clients""", + "category": "Sales", + "images": [], + "version": "17.0.2.5.1", + "application": False, + + "author": "GetapPRO, ACHRAF", + "support": "achraf@getap.pro", + "website": "https://www.getap.pro/", + #"license": "GPL", # MIT + + "depends": [ + "saas_expiration", "saas_limit_max_users", "contract", "saas_product", "subscription_oca", + ], + "data": [ + "views/sale_subscription_view.xml", + "views/saas_db_view.xml", + ], + + "post_load": None, + "pre_init_hook": None, + "post_init_hook": None, + "uninstall_hook": None, + + "auto_install": False, + "installable": False, + + # "demo_title": "SaaS: Contracts", + # "demo_addons": [ + # ], + # "demo_addons_hidden": [ + # ], + # "demo_url": "DEMO-URL", + # "demo_summary": "This module manages contracts with SaaS clients", + # "demo_images": [ + # "images/MAIN_IMAGE", + # ] +} diff --git a/saas_subscription/__pycache__/__init__.cpython-311.pyc b/saas_subscription/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..d71ed28e4 Binary files /dev/null and b/saas_subscription/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas_subscription/models/__init__.py b/saas_subscription/models/__init__.py new file mode 100644 index 000000000..1f01f3c83 --- /dev/null +++ b/saas_subscription/models/__init__.py @@ -0,0 +1,2 @@ +from . import sale_subscription +from . import saas_db \ No newline at end of file diff --git a/saas_subscription/models/__pycache__/__init__.cpython-311.pyc b/saas_subscription/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..b7a04aa86 Binary files /dev/null and b/saas_subscription/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/saas_subscription/models/__pycache__/saas_db.cpython-311.pyc b/saas_subscription/models/__pycache__/saas_db.cpython-311.pyc new file mode 100644 index 000000000..f87e11f0f Binary files /dev/null and b/saas_subscription/models/__pycache__/saas_db.cpython-311.pyc differ diff --git a/saas_subscription/models/__pycache__/sale_subscription.cpython-311.pyc b/saas_subscription/models/__pycache__/sale_subscription.cpython-311.pyc new file mode 100644 index 000000000..aa07e8e3d Binary files /dev/null and b/saas_subscription/models/__pycache__/sale_subscription.cpython-311.pyc differ diff --git a/saas_subscription/models/__pycache__/subscription_package.cpython-311.pyc b/saas_subscription/models/__pycache__/subscription_package.cpython-311.pyc new file mode 100644 index 000000000..af06c4c7d Binary files /dev/null and b/saas_subscription/models/__pycache__/subscription_package.cpython-311.pyc differ diff --git a/saas_subscription/models/saas_db.py b/saas_subscription/models/saas_db.py new file mode 100644 index 000000000..f2de62e61 --- /dev/null +++ b/saas_subscription/models/saas_db.py @@ -0,0 +1,13 @@ + +from odoo import _, api, models, fields, SUPERUSER_ID + + + +class SaaSDB(models.Model): + """SaaS DB Model""" + _inherit = 'saas.db' + _description = 'Add Subscription to SaaS DB' + + is_subscription = fields.Boolean(string='Is Subscription', default=False) + subscription_id = fields.Many2one('sale.subscription', + string='Subscription') \ No newline at end of file diff --git a/saas_subscription/models/sale_subscription.py b/saas_subscription/models/sale_subscription.py new file mode 100644 index 000000000..ae04e8ec5 --- /dev/null +++ b/saas_subscription/models/sale_subscription.py @@ -0,0 +1,105 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, models, fields, SUPERUSER_ID +from datetime import datetime, timedelta +from odoo.exceptions import ValidationError + +class SaleSubscription(models.Model): + _inherit = "sale.subscription" + _description = "Subscription" + + @api.model + def _get_number_of_days_for_trial(self): + return int( + self.sudo() + .env["ir.config_parameter"] + .get_param("saas_expiration.number_of_days_for_trial", 7) + ) + + is_saas = fields.Boolean(string='is SaaS',compute='_compute_is_saas') + subdomain = fields.Char(string='Subdomain', store=True) + operator_id = fields.Many2one('saas.operator', string='Operator') + template_operator_id = fields.Many2one( + 'saas.template.operator', + 'Template\'s Deployment', store=True, + ondelete='cascade') + build_count = fields.Integer(string='Builds', compute='_compute_build_count' ) + expiration_date = fields.Datetime( + "Expiration date", + ) + + def _compute_is_saas(self): + self.ensure_one() + product_lines = self.sale_subscription_line_ids + if not product_lines: + self.is_saas = False + else: + for line in product_lines: + product = line.product_id + is_saas_product = product.is_saas_product + if is_saas_product: + self.is_saas = True + else: + self.is_saas = False + return self.is_saas + + def _find_admin_user(self): + admin = self.env['res.users'].search([('partner_id', '=', self.partner_id.id)]) + if not admin: + raise ValidationError(_("This partner doesn't have portal user. Please Create Portal User for the Partner")) + return admin + + def _find_number_user(self): + product_lines = self.sale_subscription_line_ids + for line in product_lines: + qty = line.product_uom_qty + return qty + + def create_db_button(self): + self.ensure_one() + db_name = self.subdomain + key_value_dict = False + existing_db = [db[0] for db in self.list_databases()] + build_name = db_name + if build_name in existing_db: + raise ValidationError("Une base de données avec ce nom existe déjà.") + build = self.template_operator_id.sudo().create_db(key_value_dict, db_name) + admin_user = self._find_admin_user() + number_users = self._find_number_user() + build.update({ + 'is_subscription': True, + 'subscription_id': self.id, + 'admin_user': admin_user, + 'max_users_limit': number_users, + 'expiration_date': self.expiration_date, + }) + build.refresh_data() + + def list_databases(self): + """Liste toutes les bases de données disponibles.""" + # Remplacer 'your_postgresql_connection_string' par votre chaîne de connexion PostgreSQL + self.env.cr.execute("SELECT datname FROM pg_catalog.pg_database") + return self.env.cr.fetchall() + + @api.depends('build_count') + def _compute_build_count(self): + """ Calculate build count based on subscription package """ + self.build_count = self.env['saas.db'].search_count( + [('subscription_id', '=', self.id)]) + + def button_build_count(self): + """ It displays builds based on subscription package """ + return { + 'name': 'Builds', + 'domain': [('subscription_id', '=', self.id)], + 'view_type': 'form', + 'res_model': 'saas.db', + 'view_mode': 'tree,form', + 'type': 'ir.actions.act_window', + 'context': { + "create": False + } + } + + + diff --git a/saas_subscription/models/subscription_package.py b/saas_subscription/models/subscription_package.py new file mode 100644 index 000000000..58c70f58e --- /dev/null +++ b/saas_subscription/models/subscription_package.py @@ -0,0 +1,103 @@ +import datetime +from dateutil.relativedelta import relativedelta +from odoo import _, api, models, fields, SUPERUSER_ID +from odoo.exceptions import ValidationError +from datetime import datetime, timedelta + + +class SubscriptionPackage(models.Model): + """Subscription Package Model""" + _inherit = 'subscription.package' + _description = 'Subscription Package' + + @api.model + def _get_number_of_days_for_trial(self): + return int( + self.sudo() + .env["ir.config_parameter"] + .get_param("saas_expiration.number_of_days_for_trial", 7) + ) + + is_saas = fields.Boolean(string='is SaaS', compute='_compute_is_saas') + subdomain = fields.Char(string='Subdomain', store=True) + operator_id = fields.Many2one('saas.operator', string='Operator') + template_operator_id = fields.Many2one( + 'saas.template.operator', + 'Template\'s Deployment', store=True, + ondelete='cascade') + build_count = fields.Integer(string='Sales', compute='_compute_build_count') + expiration_date = fields.Datetime( + "Expiration date", + default=lambda self: datetime.now() + timedelta(days=self._get_number_of_days_for_trial()), + ) + + @api.constrains('start_date') + def _compute_expiration_date(self): + for sub in self.env['subscription.package'].search([]): + if sub.start_date: + sub.next_invoice_date = sub.start_date + relativedelta( + days=sub.plan_id.renewal_time) + + def _compute_is_saas(self): + self.ensure_one() + product_lines = self.product_line_ids + if not product_lines: + self.is_saas = False + else: + for line in product_lines: + product = line.product_id + is_saas_product = product.is_saas_product + if is_saas_product: + self.is_saas = True + else: + self.is_saas = False + return self.is_saas + + def _find_admin_user(self): + admin = self.env['res.users'].search([('partner_id','=', self.partner_id.id)]) + if not admin: + raise ValidationError(_("This partner doesn't have portal user. Please Create Portal User for the Partner")) + return admin + + def _find_number_user(self): + product_lines = self.product_line_ids + for line in product_lines: + qty = line.product_qty + return qty + + def create_db_button(self): + self.ensure_one() + db_name = self.subdomain + key_value_dict = False + build = self.template_operator_id.sudo().create_db(key_value_dict, db_name) + admin_user = self._find_admin_user() + number_users = self._find_number_user() + build.update({ + 'is_subscription': True, + 'subscription_id': self.id, + 'sub_reference': self.reference_code, + 'admin_user': admin_user, + 'max_users_limit': number_users, + 'expiration_date': self.expiration_date, + }) + build.refresh_data() + + @api.depends('build_count') + def _compute_build_count(self): + """ Calculate build count based on subscription package """ + self.build_count = self.env['saas.db'].search_count( + [('subscription_id', '=', self.id)]) + + def button_build_count(self): + """ It displays sale order based on subscription package """ + return { + 'name': 'Builds', + 'domain': [('subscription_id', '=', self.id)], + 'view_type': 'form', + 'res_model': 'saas.db', + 'view_mode': 'tree,form', + 'type': 'ir.actions.act_window', + 'context': { + "create": False + } + } diff --git a/saas_subscription/static/description/icon.png b/saas_subscription/static/description/icon.png new file mode 100644 index 000000000..6c7d8a3ca Binary files /dev/null and b/saas_subscription/static/description/icon.png differ diff --git a/saas_subscription/views/saas_db_view.xml b/saas_subscription/views/saas_db_view.xml new file mode 100644 index 000000000..15562f6bb --- /dev/null +++ b/saas_subscription/views/saas_db_view.xml @@ -0,0 +1,14 @@ + + + + SaaS.DB.form + saas.db + + + + + + + + + \ No newline at end of file diff --git a/saas_subscription/views/sale_subscription_view.xml b/saas_subscription/views/sale_subscription_view.xml new file mode 100644 index 000000000..04b8e9cf1 --- /dev/null +++ b/saas_subscription/views/sale_subscription_view.xml @@ -0,0 +1,44 @@ + + + + sale.subscription.form + sale.subscription + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/saas_subscription/views/subscription_package.xml b/saas_subscription/views/subscription_package.xml new file mode 100644 index 000000000..ef03857d1 --- /dev/null +++ b/saas_subscription/views/subscription_package.xml @@ -0,0 +1,41 @@ + + + + subscription.package.form + subscription.package + + + + + + + + + + + + + + + + + + + diff --git a/test_remote_saas/__manifest__.py b/test_remote_saas/__manifest__.py index 177e48fb1..3e790d9f5 100644 --- a/test_remote_saas/__manifest__.py +++ b/test_remote_saas/__manifest__.py @@ -14,5 +14,5 @@ "post_init_hook": None, "uninstall_hook": None, "auto_install": False, - "installable": True, + "installable": False, } diff --git a/test_remote_saas_domain_names/__manifest__.py b/test_remote_saas_domain_names/__manifest__.py index d44290ab1..4f6479288 100644 --- a/test_remote_saas_domain_names/__manifest__.py +++ b/test_remote_saas_domain_names/__manifest__.py @@ -8,5 +8,5 @@ "data": [], "demo": [], "qweb": [], - "installable": True, + "installable": False, }