# Copyright 2017 - 2018 Modoolar # License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html). from odoo import _, api, fields, models from odoo.tools import config class Project(models.Model): _inherit = "project.project" _rec_names_search = ["key", "name", "id"] task_key_sequence_id = fields.Many2one( comodel_name="ir.sequence", string="Key Sequence", ondelete="restrict" ) key = fields.Char(size=10, required=False, index=True, copy=False) _sql_constraints = [ ("project_key_unique", "UNIQUE(key)", "Project key must be unique") ] @api.onchange("name") def _onchange_project_name(self): for rec in self: if rec.key: continue if rec.name: rec.key = self.generate_project_key(rec.name) else: rec.key = "" @api.model_create_multi def create(self, vals_list): for vals in vals_list: key = vals.get("key", False) if not key: vals["key"] = self.generate_project_key(vals["name"]) # Tasks must be created after the project. if vals.get("task_ids", False): task_vals = vals.pop("task_ids") else: task_vals = [] # The key sequences to create stories and tasks with keys, created with # a project, must be linked to the project company to avoid security # issues. # Propagate the company ID, using the context key, to fill the # sequences company. company_id = vals.get("company_id") if company_id: self = self.with_context(project_sequence_company=company_id) new_project = super(Project, self).create(vals) new_project.create_sequence() # Tasks must be created after the project. if task_vals: new_project.write({"task_ids": task_vals}) return new_project def write(self, values): update_key = False if "key" in values: key = values["key"] update_key = self.key != key res = super(Project, self).write(values) if update_key: # Here we don't expect to have more than one record # because we can not have multiple projects with the same KEY. self.update_sequence() self._update_task_keys() return res def unlink(self): for project in self: sequence = project.task_key_sequence_id project.task_key_sequence_id = False sequence.sudo().unlink() return super(Project, self).unlink() def create_sequence(self): """ This method creates ir.sequence fot the current project :return: Returns create sequence """ self.ensure_one() sequence_data = self._prepare_sequence_data() sequence = self.env["ir.sequence"].sudo().create(sequence_data) self.write({"task_key_sequence_id": sequence.id}) return sequence def update_sequence(self): """ This method updates existing task sequence :return: """ sequence_data = self._prepare_sequence_data(init=False) self.task_key_sequence_id.sudo().write(sequence_data) def _prepare_sequence_data(self, init=True): """ This method prepares data for create/update_sequence methods :param init: Set to False in case you don't want to set initial values for number_increment and number_next_actual """ values = { "name": "{} {}".format(_("Project task sequence for project"), self.name), "implementation": "standard", "code": "project.task.key.{}".format(self.id), "prefix": "{}-".format(self.key), "use_date_range": False, } # The key sequences to create stories and tasks with keys, created with # a project, must be linked to the project company to avoid security # issues. company_id = self.env.context.get("project_sequence_company") if company_id: values["company_id"] = company_id if init: values.update(dict(number_increment=1, number_next_actual=1)) return values def get_next_task_key(self): test_project_key = self.env.context.get("test_project_key") if (config["test_enable"] and not test_project_key) or ( config["demo"].get("project_key") and not test_project_key ): return False return self.sudo().task_key_sequence_id.next_by_id() def generate_project_key(self, text): test_project_key = self.env.context.get("test_project_key") if (config["test_enable"] and not test_project_key) or ( config["demo"].get("project_key") and not test_project_key ): return False if not text: return "" data = text.split(" ") if len(data) == 1: return self._generate_project_unique_key(data[0][:3].upper()) key = [] for item in data: key.append(item[:1].upper()) return self._generate_project_unique_key("".join(key)) def _generate_project_unique_key(self, text): self_context = self.with_context(active_test=False) res = text unique_key = False counter = 0 while not unique_key: if counter != 0: res = "%s%s" % (text, counter) unique_key = not bool(self_context.search([("key", "=", res)])) counter += 1 return res def _update_task_keys(self): """ This method will update task keys of the current project. """ self.ensure_one() self.flush_model() reindex_query = """ UPDATE project_task SET key = x.key FROM ( SELECT t.id, p.key || '-' || split_part(t.key, '-', 2) AS key FROM project_task t INNER JOIN project_project p ON t.project_id = p.id WHERE t.project_id = %s ) AS x WHERE project_task.id = x.id; """ self.env.cr.execute(reindex_query, (self.id,)) self.task_ids.invalidate_model(["key"]) @api.model def _set_default_project_key(self): """ This method will be called from the post_init hook in order to set default values on project.project and project.task, so we leave those tables nice and clean after module installation. :return: """ for project in self.search([("key", "=", False)]): project.key = self.generate_project_key(project.name) project.create_sequence() for task in project.task_ids: task.key = project.get_next_task_key()