Move 124 sale modules to oca-sale, create oca-project with 56 project modules from oca-workflow-process

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ernad Husremovic 2025-08-30 18:04:10 +02:00
parent 9eb7ae5807
commit 6094c218b2
2332 changed files with 125826 additions and 0 deletions

View file

@ -0,0 +1,121 @@
===========
Project Key
===========
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:20ffd47dc4de9ec005e18c523ee79f5635635ea7f76640194786d92eeec1367c
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fproject-lightgray.png?logo=github
:target: https://github.com/OCA/project/tree/16.0/project_key
:alt: OCA/project
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/project-16-0/project-16-0-project_key
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/project&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module provides functionality to uniquely identify projects and tasks by simple ``key`` field.
**Table of contents**
.. contents::
:local:
Usage
=====
To use this module functionality you just need to:
On ``project.project`` level:
In Kanban View:
#. Go to Project > Dashboard
#. Create
#. Enter project name and use auto generated key or simply override value by entering your own key value.
In Tree View:
#. Go to Project > Configuration > Projects
#. Create
#. Enter project name and use auto generated key or simply override value by entering your own key value.
In form View:
#. Go to Project > Dashboard
#. Open the projects settings
#. Modify the "key" value
#. After modifying project key the key of any existing tasks related to that project will be updated automatically.
When you create a project, under the hood a ir.sequence record gets creted with prefix: ``<project-key>-``.
On ``project.task`` level:
#. Actually there is nothing to be done here
#. Task keys are auto generated based on project key value with per project auto incremented number (i.e. PA-1, PA-2, etc)
In browser address bar:
#. Navigate to your project by entering following url: http://<<your-domain>>/projects/PROJECT-KEY
#. Navigate to your task by entering following url: http://<<your-domain>>/tasks/TASK-KEY
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/project/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/project/issues/new?body=module:%20project_key%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Modoolar
Contributors
~~~~~~~~~~~~
* Petar Najman <petar.najman@modoolar.com>
* Sladjan Kantar <sladjan.kantar@modoolar.com>
* `CorporateHub <https://corporatehub.eu/>`__
* Alexey Pelykh <alexey.pelykh@corphub.eu>
* Saran Lim. <saranl@ecosoft.co.th>
* Tharathip Chaweewongphan <tharathipc@ecosoft.co.th>
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/project <https://github.com/OCA/project/tree/16.0/project_key>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,5 @@
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from . import models
from . import controllers
from .hooks import post_init_hook

View file

@ -0,0 +1,15 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
{
"name": "Project Key",
"summary": "Module decorates projects and tasks with Project Key",
"category": "Project",
"version": "16.0.1.0.3",
"license": "LGPL-3",
"author": "Modoolar, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/project",
"depends": ["project"],
"data": ["views/project_key_views.xml"],
"post_init_hook": "post_init_hook",
}

View file

@ -0,0 +1,3 @@
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from . import main

View file

@ -0,0 +1,41 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
import werkzeug
from odoo import http
# from odoo.http import request
class ProjectBrowser(http.Controller):
def get_record_url(self, model, domain, action_xml_id):
env = http.request.env()
records = env[model].search(domain)
record_id = records and records.id or -1
action_id = env.ref(action_xml_id).id
return "/web#id={}&view_type=form&model={}&action={}".format(
record_id, model, action_id
)
def get_task_url(self, key):
return self.get_record_url(
"project.task", [("key", "=ilike", key)], "project.action_view_task"
)
def get_project_url(self, key):
return self.get_record_url(
"project.project",
[("key", "=ilike", key)],
"project.open_view_project_all_config",
)
@http.route(["/projects/<string:key>"], type="http", auth="user")
def open_project(self, key, **kwargs):
return werkzeug.utils.redirect(self.get_project_url(key), 301)
@http.route(["/tasks/<string:key>"], type="http", auth="user")
def open_task(self, key, **kwargs):
return werkzeug.utils.redirect(self.get_task_url(key), 301)

View file

@ -0,0 +1,9 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
def post_init_hook(cr, registry):
from odoo import SUPERUSER_ID, api
env = api.Environment(cr, SUPERUSER_ID, {})
env["project.project"]._set_default_project_key()

View file

@ -0,0 +1,57 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_key
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__key
#: model:ir.model.fields,field_description:project_key.field_project_task__key
msgid "Key"
msgstr "Ključ"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__task_key_sequence_id
msgid "Key Sequence"
msgstr "Ključna sekvenca"
#. module: project_key
#: model:ir.model,name:project_key.model_project_project
msgid "Project"
msgstr "Projekat"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_project_project_key_unique
msgid "Project key must be unique"
msgstr "Ključ projekta mora biti jedinstven"
#. module: project_key
#. odoo-python
#: code:addons/project_key/models/project_project.py:0
#, python-format
msgid "Project task sequence for project"
msgstr "Sekvenca zadatka projekta za projekat"
#. module: project_key
#: model:ir.model,name:project_key.model_project_task
msgid "Task"
msgstr "Zadatak"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_task_task_key_unique
msgid "Task key must be unique!"
msgstr "Ključ zadatka mora biti jedinstven!"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_task__url
msgid "URL"
msgstr "URL"

View file

@ -0,0 +1,67 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_key
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2019-07-12 15:43+0000\n"
"Last-Translator: Maria Sparenberg <maria.sparenberg@gmx.net>\n"
"Language-Team: none\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.7.1\n"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__key
#: model:ir.model.fields,field_description:project_key.field_project_task__key
msgid "Key"
msgstr "Nummerierungsmuster"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__task_key_sequence_id
msgid "Key Sequence"
msgstr "Musterfolge"
#. module: project_key
#: model:ir.model,name:project_key.model_project_project
msgid "Project"
msgstr "Projekt"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_project_project_key_unique
msgid "Project key must be unique"
msgstr "Das Nummerierungsmuster für Projekte muss eindeutig sein."
#. module: project_key
#. odoo-python
#: code:addons/project_key/models/project_project.py:0
#, python-format
msgid "Project task sequence for project"
msgstr ""
#. module: project_key
#: model:ir.model,name:project_key.model_project_task
msgid "Task"
msgstr "Aufgabe"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_task_task_key_unique
msgid "Task key must be unique!"
msgstr "Aufgabennummerierung muss eindeutig sein!"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_task__url
msgid "URL"
msgstr "URL"
#~ msgid "key"
#~ msgstr "Nummer"
#, python-format
#~ msgid "Project task sequence for project "
#~ msgstr "Aufgabennummerierung für Projekt "

View file

@ -0,0 +1,75 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_key
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-02-21 00:08+0000\n"
"Last-Translator: Ignacio Buioli <ibuioli@gmail.com>\n"
"Language-Team: none\n"
"Language: es_AR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14.1\n"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__key
#: model:ir.model.fields,field_description:project_key.field_project_task__key
msgid "Key"
msgstr "Clave"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__task_key_sequence_id
msgid "Key Sequence"
msgstr "Secuencia de la Clave"
#. module: project_key
#: model:ir.model,name:project_key.model_project_project
msgid "Project"
msgstr "Proyecto"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_project_project_key_unique
msgid "Project key must be unique"
msgstr "La clave del proyecto debe ser única"
#. module: project_key
#. odoo-python
#: code:addons/project_key/models/project_project.py:0
#, python-format
msgid "Project task sequence for project"
msgstr "Secuencia de tareas del proyecto para el proyecto"
#. module: project_key
#: model:ir.model,name:project_key.model_project_task
msgid "Task"
msgstr "Tarea"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_task_task_key_unique
msgid "Task key must be unique!"
msgstr "¡La clave de la tarea debe ser única!"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_task__url
msgid "URL"
msgstr "URL"
#~ msgid "Display Name"
#~ msgstr "Mostrar Nombre"
#~ msgid "ID"
#~ msgstr "ID"
#~ msgid "Last Modified on"
#~ msgstr "Última Modificación el"
#~ msgid "WBS element"
#~ msgstr "Elemento WBS"
#~ msgid "key"
#~ msgstr "clave"

View file

@ -0,0 +1,64 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_key
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-18 15:37+0000\n"
"PO-Revision-Date: 2024-03-29 08:13+0000\n"
"Last-Translator: Vincent Hatakeyama <vincent+github@hatakeyama.fr>\n"
"Language-Team: none\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__key
#: model:ir.model.fields,field_description:project_key.field_project_task__key
msgid "Key"
msgstr "Clé"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__task_key_sequence_id
msgid "Key Sequence"
msgstr "Séquence de clé"
#. module: project_key
#: model:ir.model,name:project_key.model_project_project
msgid "Project"
msgstr "Projet"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_project_project_key_unique
msgid "Project key must be unique"
msgstr "La clé de projet doit être unique"
#. module: project_key
#. odoo-python
#: code:addons/project_key/models/project_project.py:0
#, python-format
msgid "Project task sequence for project"
msgstr "Séquence des tâches pour le projet"
#. module: project_key
#: model:ir.model,name:project_key.model_project_task
msgid "Task"
msgstr "Tâche"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_task_task_key_unique
msgid "Task key must be unique!"
msgstr "La clé de tâche doit être unique!"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_task__url
msgid "URL"
msgstr "Adresse universelle"
#~ msgid "key"
#~ msgstr "Clé"

View file

@ -0,0 +1,75 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_key
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-04-12 12:35+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14.1\n"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__key
#: model:ir.model.fields,field_description:project_key.field_project_task__key
msgid "Key"
msgstr "Chiave"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__task_key_sequence_id
msgid "Key Sequence"
msgstr "Sequenza chiave"
#. module: project_key
#: model:ir.model,name:project_key.model_project_project
msgid "Project"
msgstr "Progetto"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_project_project_key_unique
msgid "Project key must be unique"
msgstr "La chiave del progetto deve essere univoca"
#. module: project_key
#. odoo-python
#: code:addons/project_key/models/project_project.py:0
#, python-format
msgid "Project task sequence for project"
msgstr "Sequenza lavoro per il progetto"
#. module: project_key
#: model:ir.model,name:project_key.model_project_task
msgid "Task"
msgstr "Lavoro"
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_task_task_key_unique
msgid "Task key must be unique!"
msgstr "La chiave del lavoro deve essere univoca!"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_task__url
msgid "URL"
msgstr "URL"
#~ msgid "Display Name"
#~ msgstr "Nome visualizzato"
#~ msgid "ID"
#~ msgstr "ID"
#~ msgid "Last Modified on"
#~ msgstr "Ultima modifica il"
#~ msgid "WBS element"
#~ msgstr "Elemento WBS"
#~ msgid "key"
#~ msgstr "chiave"

View file

@ -0,0 +1,57 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_key
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__key
#: model:ir.model.fields,field_description:project_key.field_project_task__key
msgid "Key"
msgstr ""
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_project__task_key_sequence_id
msgid "Key Sequence"
msgstr ""
#. module: project_key
#: model:ir.model,name:project_key.model_project_project
msgid "Project"
msgstr ""
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_project_project_key_unique
msgid "Project key must be unique"
msgstr ""
#. module: project_key
#. odoo-python
#: code:addons/project_key/models/project_project.py:0
#, python-format
msgid "Project task sequence for project"
msgstr ""
#. module: project_key
#: model:ir.model,name:project_key.model_project_task
msgid "Task"
msgstr ""
#. module: project_key
#: model:ir.model.constraint,message:project_key.constraint_project_task_task_key_unique
msgid "Task key must be unique!"
msgstr ""
#. module: project_key
#: model:ir.model.fields,field_description:project_key.field_project_task__url
msgid "URL"
msgstr ""

View file

@ -0,0 +1,4 @@
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from . import project_project
from . import project_task

View file

@ -0,0 +1,208 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# 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()

View file

@ -0,0 +1,75 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from odoo import api, fields, models
TASK_URL = "/web#id=%s&view_type=form&model=project.task&action=%s"
class Task(models.Model):
_inherit = "project.task"
_rec_names_search = ["key", "name"]
key = fields.Char(size=20, required=False, index=True)
url = fields.Char(string="URL", compute="_compute_task_url")
_sql_constraints = [("task_key_unique", "UNIQUE(key)", "Task key must be unique!")]
def _compute_task_url(self):
action_id = self.env.ref("project.action_view_task").id
for task in self:
task.url = TASK_URL % (task.id, action_id)
@api.model_create_multi
def create(self, vals_list):
ctx = self.env.context.get
for vals in vals_list:
project_id = vals.get("project_id", False)
if not project_id:
project_id = ctx("default_project_id", False)
if not project_id and ctx("active_model", False) == "project.project":
project_id = ctx("active_id", False)
if project_id:
project = self.env["project.project"].browse(project_id)
vals["key"] = project.get_next_task_key()
return super(Task, self).create(vals_list)
def write(self, vals):
project_id = vals.get("project_id", False)
if not project_id:
return super(Task, self).write(vals)
project = self.env["project.project"].browse(project_id)
for task in self:
if task.key and task.project_id.id == project.id:
continue
values = self.prepare_task_for_project_switch(task, project)
super(Task, task).write(values)
return super(Task, self).write(vals)
def prepare_task_for_project_switch(self, task, project):
data = {"key": project.get_next_task_key(), "project_id": project.id}
if len(task.child_ids) > 0:
data["child_ids"] = [
(1, child.id, self.prepare_task_for_project_switch(child, project))
for child in task.child_ids
]
return data
def name_get(self):
result = []
for record in self:
task_name = []
if record.key:
task_name.append(record.key)
task_name.append(record.name)
result.append((record.id, " - ".join(task_name)))
return result

View file

@ -0,0 +1,8 @@
* Petar Najman <petar.najman@modoolar.com>
* Sladjan Kantar <sladjan.kantar@modoolar.com>
* `CorporateHub <https://corporatehub.eu/>`__
* Alexey Pelykh <alexey.pelykh@corphub.eu>
* Saran Lim. <saranl@ecosoft.co.th>
* Tharathip Chaweewongphan <tharathipc@ecosoft.co.th>

View file

@ -0,0 +1 @@
This module provides functionality to uniquely identify projects and tasks by simple ``key`` field.

View file

@ -0,0 +1,34 @@
To use this module functionality you just need to:
On ``project.project`` level:
In Kanban View:
#. Go to Project > Dashboard
#. Create
#. Enter project name and use auto generated key or simply override value by entering your own key value.
In Tree View:
#. Go to Project > Configuration > Projects
#. Create
#. Enter project name and use auto generated key or simply override value by entering your own key value.
In form View:
#. Go to Project > Dashboard
#. Open the projects settings
#. Modify the "key" value
#. After modifying project key the key of any existing tasks related to that project will be updated automatically.
When you create a project, under the hood a ir.sequence record gets creted with prefix: ``<project-key>-``.
On ``project.task`` level:
#. Actually there is nothing to be done here
#. Task keys are auto generated based on project key value with per project auto incremented number (i.e. PA-1, PA-2, etc)
In browser address bar:
#. Navigate to your project by entering following url: http://<<your-domain>>/projects/PROJECT-KEY
#. Navigate to your task by entering following url: http://<<your-domain>>/tasks/TASK-KEY

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,466 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Project Key</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="project-key">
<h1 class="title">Project Key</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:20ffd47dc4de9ec005e18c523ee79f5635635ea7f76640194786d92eeec1367c
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/project/tree/16.0/project_key"><img alt="OCA/project" src="https://img.shields.io/badge/github-OCA%2Fproject-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/project-16-0/project-16-0-project_key"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/project&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module provides functionality to uniquely identify projects and tasks by simple <tt class="docutils literal">key</tt> field.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>To use this module functionality you just need to:</p>
<p>On <tt class="docutils literal">project.project</tt> level:</p>
<p>In Kanban View:</p>
<ol class="arabic simple">
<li>Go to Project &gt; Dashboard</li>
<li>Create</li>
<li>Enter project name and use auto generated key or simply override value by entering your own key value.</li>
</ol>
<p>In Tree View:</p>
<ol class="arabic simple">
<li>Go to Project &gt; Configuration &gt; Projects</li>
<li>Create</li>
<li>Enter project name and use auto generated key or simply override value by entering your own key value.</li>
</ol>
<p>In form View:</p>
<ol class="arabic simple">
<li>Go to Project &gt; Dashboard</li>
<li>Open the projects settings</li>
<li>Modify the “key” value</li>
<li>After modifying project key the key of any existing tasks related to that project will be updated automatically.</li>
</ol>
<p>When you create a project, under the hood a ir.sequence record gets creted with prefix: <tt class="docutils literal"><span class="pre">&lt;project-key&gt;-</span></tt>.</p>
<p>On <tt class="docutils literal">project.task</tt> level:</p>
<ol class="arabic simple">
<li>Actually there is nothing to be done here</li>
<li>Task keys are auto generated based on project key value with per project auto incremented number (i.e. PA-1, PA-2, etc)</li>
</ol>
<p>In browser address bar:</p>
<ol class="arabic simple">
<li>Navigate to your project by entering following url: <a class="reference external" href="http:/">http:/</a>/&lt;&lt;your-domain&gt;&gt;/projects/PROJECT-KEY</li>
<li>Navigate to your task by entering following url: <a class="reference external" href="http:/">http:/</a>/&lt;&lt;your-domain&gt;&gt;/tasks/TASK-KEY</li>
</ol>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/project/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/project/issues/new?body=module:%20project_key%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>Modoolar</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul class="simple">
<li>Petar Najman &lt;<a class="reference external" href="mailto:petar.najman&#64;modoolar.com">petar.najman&#64;modoolar.com</a>&gt;</li>
<li>Sladjan Kantar &lt;<a class="reference external" href="mailto:sladjan.kantar&#64;modoolar.com">sladjan.kantar&#64;modoolar.com</a>&gt;</li>
<li><a class="reference external" href="https://corporatehub.eu/">CorporateHub</a><ul>
<li>Alexey Pelykh &lt;<a class="reference external" href="mailto:alexey.pelykh&#64;corphub.eu">alexey.pelykh&#64;corphub.eu</a>&gt;</li>
</ul>
</li>
<li>Saran Lim. &lt;<a class="reference external" href="mailto:saranl&#64;ecosoft.co.th">saranl&#64;ecosoft.co.th</a>&gt;</li>
<li>Tharathip Chaweewongphan &lt;<a class="reference external" href="mailto:tharathipc&#64;ecosoft.co.th">tharathipc&#64;ecosoft.co.th</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/project/tree/16.0/project_key">OCA/project</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,5 @@
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from . import test_project
from . import test_task
from . import test_controller

View file

@ -0,0 +1,55 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from odoo.tests.common import HttpCase, TransactionCase
class TestMixin(object):
@staticmethod
def _setup_records(class_or_instance):
self = class_or_instance
self.Project = self.env["project.project"].with_context(test_project_key=True)
self.Task = self.env["project.task"].with_context(test_project_key=True)
self.project_action = self.env.ref("project.open_view_project_all_config")
self.task_action = self.env.ref("project.action_view_task")
self.project_1 = self.Project.create({"name": "OCA"})
self.project_2 = self.Project.create({"name": "Odoo", "key": "ODOO"})
self.project_3 = self.Project.create({"name": "Python"})
self.task11 = self.Task.create({"name": "1", "project_id": self.project_1.id})
self.task12 = self.Task.create(
{"name": "2", "parent_id": self.task11.id, "project_id": self.project_1.id}
)
self.task21 = self.Task.create({"name": "3", "project_id": self.project_2.id})
self.task30 = self.Task.create({"name": "3"})
def get_record_url(self, record, model, action):
return "/web#id={}&view_type=form&model={}&action={}".format(
record.id, model, action
)
def get_task_url(self, task):
return self.get_record_url(task, task._name, self.task_action.id)
def get_project_url(self, project):
return self.get_record_url(project, project._name, self.project_action.id)
class TestCommon(TransactionCase, TestMixin):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls._setup_records(cls)
class HttpTestCommon(HttpCase, TestMixin):
def setUp(self):
super().setUp()
self.env = self.env(context=dict(self.env.context, tracking_disable=True))
self._setup_records(self)

View file

@ -0,0 +1,22 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from .test_common import HttpTestCommon
class TestController(HttpTestCommon):
def test_01_project_browse(self):
self.authenticate("admin", "admin")
response = self.url_open("/projects/" + self.project_1.key)
self.assertEqual(response.status_code, 200)
self.assertTrue(
response.url.endswith(self.get_project_url(self.project_1)), response.url
)
def test_02_task_browse(self):
self.authenticate("admin", "admin")
response = self.url_open("/tasks/" + self.task11.key)
self.assertEqual(response.status_code, 200)
self.assertTrue(
response.url.endswith(self.get_task_url(self.task11)), response.url
)

View file

@ -0,0 +1,76 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from odoo.tools import mute_logger
from .test_common import TestCommon
class TestProject(TestCommon):
def test_01_key(self):
self.assertEqual(self.project_1.key, "OCA")
self.assertEqual(self.project_2.key, "ODOO")
self.assertEqual(self.project_3.key, "PYT")
def test_02_change_key(self):
self.project_1.key = "XXX"
self.assertEqual(self.task11.key, "XXX-1")
self.assertEqual(self.task12.key, "XXX-2")
def test_03_name_search(self):
projects = self.Project.name_search("ODO")
self.assertEqual(len(projects), 1)
non_odoo_projects = [
x[0] for x in self.Project.name_search("ODO", operator="not ilike")
]
odoo_projects = self.Project.browse(non_odoo_projects).filtered(
lambda x: x.id == self.project_2.id
)
self.assertEqual(len(odoo_projects), 0)
def test_04_name_search_empty(self):
projects = self.Project.name_search("")
self.assertGreater(len(projects), 0)
def test_05_name_onchange(self):
project = self.Project.new({"name": "Software Development"})
project._onchange_project_name()
self.assertEqual(project.key, "SD")
def test_06_name_onchange(self):
project = self.Project.new({})
project._onchange_project_name()
self.assertEqual(project.key, "")
@mute_logger("odoo.models.unlink")
def test_07_delete(self):
self.project_1.task_ids.unlink()
self.project_1.unlink()
self.project_2.task_ids.unlink()
self.project_2.unlink()
self.project_3.unlink()
def test_08_generate_empty_project_key(self):
empty_key = self.Project.generate_project_key(False)
self.assertEqual(empty_key, "")
def test_09_name_onchange_with_key(self):
project = self.Project.new({"name": "Software Development", "key": "TEST"})
project._onchange_project_name()
self.assertEqual(project.key, "TEST")
def test_10_generate_unique_key_with_counter(self):
project = self.Project.create({"name": "OCA"})
self.assertEqual(project.key, "OCA1")
def test_11_generate_unique_key_with_counter_inactive(self):
self.project_1.active = False
project = self.Project.create({"name": "OCA"})
self.assertEqual(project.key, "OCA1")

View file

@ -0,0 +1,54 @@
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
from .test_common import TestCommon
class TestTask(TestCommon):
def test_01_key(self):
self.assertEqual(self.task11.key, "OCA-1")
self.assertEqual(self.task12.key, "OCA-2")
self.assertEqual(self.task21.key, "ODOO-1")
self.assertEqual(self.task30.key, False)
def test_02_compute_task_url(self):
task_url = self.get_task_url(self.task11)
self.task11._compute_task_url()
self.assertEqual(self.task11.url, task_url)
def test_03_create_task_project_in_context(self):
self.Task.with_context(
active_model="project.project", active_id=self.project_1.id
).create({"name": "4"})
def test_04_no_switch_project(self):
self.task11.write({"project_id": self.project_1.id})
self.assertEqual(self.task11.key, "OCA-1")
self.assertEqual(self.task12.key, "OCA-2")
def test_05_switch_project(self):
self.task11.write({"project_id": self.project_2.id})
self.assertEqual(self.task11.key, "ODOO-2")
self.assertEqual(self.task12.key, "ODOO-3")
def test_06_name_search(self):
oca_tasks = self.Task.name_search("OCA")
self.assertEqual(len(oca_tasks), 2)
non_oca_task_ids = [
x[0] for x in self.Task.name_search("OCA", operator="not ilike")
]
oca_tasks = self.Task.browse(non_oca_task_ids).filtered(
lambda x: x.project_id.id == self.project_1.id
)
self.assertEqual(len(oca_tasks), 0)
def test_07_name_search_empty(self):
tasks = self.Task.name_search("")
self.assertGreater(len(tasks), 0)
def test_08_create_new_company(self):
self.env["res.company"].create({"name": "New company"})

View file

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
# Copyright 2017 - 2018 Modoolar <info@modoolar.com>
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
-->
<odoo>
<record id="edit_project_extend_with_key" model="ir.ui.view">
<field name="name">project.edit.project.inherited</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.edit_project" />
<field name="arch" type="xml">
<field name="partner_id" position="before">
<field name="key" required="1" />
</field>
</field>
</record>
<record id="view_project_extend_with_key" model="ir.ui.view">
<field name="name">project.project.tree</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project" />
<field name="arch" type="xml">
<field name="name" position="after">
<field name="key" required="0" readonly="1" />
</field>
</field>
</record>
<record id="view_project_project_filter_extend_with_key" model="ir.ui.view">
<field name="name">project.project.select</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project_project_filter" />
<field name="arch" type="xml">
<field name="name" position="attributes">
<attribute
name="filter_domain"
>['|',('name','ilike',self),('key','ilike',self)]</attribute>
</field>
</field>
</record>
<record id="view_task_form2_extend_with_key" model="ir.ui.view">
<field name="name">project.task.form.key</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2" />
<field name="arch" type="xml">
<field name="name" position="before">
<field name="key" readonly="1" nolabel="1" class="oe_read_only" />
</field>
</field>
</record>
<record id="view_task_tree2_extend_with_key" model="ir.ui.view">
<field name="name">project.task.tree</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_tree2" />
<field eval="2" name="priority" />
<field name="arch" type="xml">
<field name="name" position="before">
<field name="key" />
</field>
</field>
</record>
<record id="view_task_search_key" model="ir.ui.view">
<field name="name">project.task.search.key</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_search_form" />
<field name="arch" type="xml">
<field name="name" position="attributes">
<attribute
name="filter_domain"
>['|',('name','ilike',self),('key','ilike',self)]</attribute>
</field>
</field>
</record>
<record id="view_task_kanban_key" model="ir.ui.view">
<field name="name">project.task.kanban.key</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_kanban" />
<field name="arch" type="xml">
<field name="color" position="after">
<field name="url" />
<field name="key" />
</field>
<xpath
expr="//t[@t-name='kanban-box']//field[@name='name']/parent::s/parent::strong"
position="before"
>
<a t-att-href="record.url.raw_value">
<field name="key" />
</a>
</xpath>
</field>
</record>
<record id="project_project_view_form_simplified" model="ir.ui.view">
<field name="name">project.project.view.form.simplified</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.project_project_view_form_simplified" />
<field name="arch" type="xml">
<div name="alias_def" position="after">
<field name="key" />
</div>
</field>
</record>
<record id="view_project_kanban_details" model="ir.ui.view">
<field name="name">project.project.kanban</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project_kanban" />
<field name="arch" type="xml">
<field name="display_name" position="after">
<field name="key" />
</field>
<xpath expr="//t[@t-esc='record.display_name.value']" position="before">
<t t-esc="record.key.value" /> -
</xpath>
</field>
</record>
</odoo>