Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14.0][ADD] project_sequence #1101

Merged
merged 5 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ include_wkhtmltopdf: false
odoo_version: 14.0
org_name: Odoo Community Association (OCA)
org_slug: OCA
rebel_module_groups: []
rebel_module_groups:
- project_sequence
repo_description: 'TODO: add repo description.'
repo_name: project
repo_slug: project
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,18 @@ jobs:
matrix:
include:
- container: ghcr.io/oca/oca-ci/py3.6-odoo14.0:latest
include: "project_sequence"
makepot: "true"
name: test with Odoo
- container: ghcr.io/oca/oca-ci/py3.6-ocb14.0:latest
include: "project_sequence"
name: test with OCB
- container: ghcr.io/oca/oca-ci/py3.6-odoo14.0:latest
exclude: "project_sequence"
makepot: "true"
name: test with Odoo
- container: ghcr.io/oca/oca-ci/py3.6-ocb14.0:latest
exclude: "project_sequence"
name: test with OCB
services:
postgres:
Expand All @@ -49,6 +58,9 @@ jobs:
POSTGRES_DB: odoo
ports:
- 5432:5432
env:
INCLUDE: "${{ matrix.include }}"
EXCLUDE: "${{ matrix.exclude }}"
steps:
- uses: actions/checkout@v2
with:
Expand Down
35 changes: 35 additions & 0 deletions project_sequence/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
**This file is going to be generated by oca-gen-addon-readme.**

*Manual changes will be overwritten.*

Please provide content in the ``readme`` directory:

* **DESCRIPTION.rst** (required)
* INSTALL.rst (optional)
* CONFIGURE.rst (optional)
* **USAGE.rst** (optional, highly recommended)
* DEVELOP.rst (optional)
* ROADMAP.rst (optional)
* HISTORY.rst (optional, recommended)
* **CONTRIBUTORS.rst** (optional, highly recommended)
* CREDITS.rst (optional)

Content of this README will also be drawn from the addon manifest,
from keys such as name, authors, maintainers, development_status,
and license.

A good, one sentence summary in the manifest is also highly recommended.


Automatic changelog generation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

`HISTORY.rst` can be auto generated using `towncrier <https://pypi.org/project/towncrier>`_.

Just put towncrier compatible changelog fragments into `readme/newsfragments`
and the changelog file will be automatically generated and updated when a new fragment is added.

Please refer to `towncrier` documentation to know more.

NOTE: the changelog will be automatically generated when using `/ocabot merge $option`.
If you need to run it manually, refer to `OCA/maintainer-tools README <https://github.com/OCA/maintainer-tools>`_.
1 change: 1 addition & 0 deletions project_sequence/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
21 changes: 21 additions & 0 deletions project_sequence/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2023 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)

{
"name": "Project Sequence",
"summary": "Add a sequence field to projects, filled automatically",
"version": "14.0.0.1.0",
"development_status": "Alpha",
"category": "Services/Project",
"website": "https://github.com/OCA/project",
"author": "Moduon, Odoo Community Association (OCA)",
"maintainers": ["yajo", "anddago78"],
"license": "LGPL-3",
"application": False,
"installable": True,
"depends": ["project"],
"data": [
"data/ir_sequence.xml",
"views/project_project.xml",
],
}
13 changes: 13 additions & 0 deletions project_sequence/data/ir_sequence.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 Moduon Team S.L.
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) -->
<data noupdate="1">
<record id="seq_project_sequence" model="ir.sequence">
<field name="name">Project sequence</field>
<field name="code">project.sequence</field>
<field name="prefix">%(range_y)s-</field>
<field name="use_date_range">True</field>
<field name="padding">5</field>
<field name="company_id" eval="False" />
</record>
</data>
48 changes: 48 additions & 0 deletions project_sequence/i18n/es.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_sequence
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-10 10:29+0000\n"
"PO-Revision-Date: 2023-04-19 11:22+0200\n"
"Last-Translator: Andrea Cattalani <[email protected]>\n"
"Language-Team: \n"
"Language: es_ES\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: Poedit 3.1.1\n"

#. module: project_sequence
#: model:ir.model.fields,field_description:project_sequence.field_project_project__code
msgid "Code"
msgstr "Código"

#. module: project_sequence
#: model:ir.model.fields,field_description:project_sequence.field_project_project__display_name
msgid "Display Name"
msgstr "Nombre"

#. module: project_sequence
#: model:ir.model.fields,field_description:project_sequence.field_project_project__id
msgid "ID"
msgstr "ID"

#. module: project_sequence
#: model:ir.model.fields,field_description:project_sequence.field_project_project____last_update
msgid "Last Modified on"
msgstr "Última modificación el"

#. module: project_sequence
#: model:ir.model.fields,field_description:project_sequence.field_project_project__name
msgid "Name"
msgstr "Nombre"

#. module: project_sequence
#: model:ir.model,name:project_sequence.model_project_project
msgid "Project"
msgstr "Proyecto"
1 change: 1 addition & 0 deletions project_sequence/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import project_project
87 changes: 87 additions & 0 deletions project_sequence/models/project_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Copyright 2023 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)


from odoo import api, fields, models


class ProjectProject(models.Model):
_inherit = "project.project"
_sql_constraints = [
# Ensure compatibility with other modules that always expect a value in name
("name_required", "CHECK(name IS NOT NULL)", "Project name is required"),
(
"sequence_code_unique",
"UNIQUE(sequence_code)",
"Sequence code must be unique",
),
]

sequence_code = fields.Char(
copy=False,
readonly=True,
)
name = fields.Char(
# We actually require it with the SQL constraint, but it is disabled
# here to let users create/write projects without name, and let this module
# add a default name if needed
required=False,
)

def _sync_analytic_account_name(self):
"""Set analytic account name equal to project's display name."""
for rec in self:
if not rec.analytic_account_id:
continue
rec.analytic_account_id.name = rec.display_name

def name_get(self):
"""Prefix name with sequence code if they are different."""
old_result = super().name_get()
result = []
for id_, name in old_result:
project = self.browse(id_)
if project.sequence_code and project.sequence_code != name:
name = "{} - {}".format(project.sequence_code, name)
result.append((id_, name))
return result

@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
"""Allow searching by sequence code by default."""
# Do not add any domain when user just clicked on search widget
if not (name == "" and operator == "ilike"):
# The dangling | is needed to combine with the domain added by super()
args = (args or []) + ["|", ("sequence_code", operator, name)]
return super().name_search(name, args, operator, limit)

@api.model_create_multi
def create(self, vals_list):
"""Apply sequence code and a default name if not set."""
# It is important to set sequence_code before calling super() because
# other modules such as hr_timesheet expect the name to always have a value
for vals in vals_list:
if "sequence_code" not in vals:
vals["sequence_code"] = self.env["ir.sequence"].next_by_code(
"project.sequence"
)
if not vals.get("name"):
vals["name"] = vals["sequence_code"]
res = super().create(vals_list)
# The analytic account is created with just the project name, but
# it is more useful to let it contain the project sequence too
res._sync_analytic_account_name()
return res

def write(self, vals):
"""Sync name and analytic account name when name is changed."""
# If name isn't changing, nothing special to do
if "name" not in vals and "sequence_name" not in vals:
return super().write(vals)
# When changing name, we need to update the analytic account name too
for one in self:
sequence_code = vals.get("sequence_code", one.sequence_code)
name = vals.get("name") or sequence_code
super().write(dict(vals, name=name))
self._sync_analytic_account_name()
return True
2 changes: 2 additions & 0 deletions project_sequence/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Andrea Cattalani (`Moduon <https://www.moduon.team/>`__)
* Jairo Llopis (`Moduon <https://www.moduon.team/>`__)
6 changes: 6 additions & 0 deletions project_sequence/readme/CREDITS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.. This file is optional and contains additional credits, other than
authors, contributors, and maintainers.

The development of this module has been financially supported by:

* Moduon
4 changes: 4 additions & 0 deletions project_sequence/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.. This file must be max 2-3 paragraphs, and is required.
It should explain *why* this module exists.

Add a sequence field to projects, filled automatically and add a code sequence filter in tree view project.
15 changes: 15 additions & 0 deletions project_sequence/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.. This file must be present. It contains the usage instructions
for end-users. As all other rst files included in the README,
it MUST NOT contain reStructuredText sections
only body text (paragraphs, lists, tables, etc). Should you need
a more elaborate structure to explain the addon, please create a
Sphinx documentation (which may include this file as a "quick start"
section).

To use this module, you need to:

#. Go to the project icon.
#. Click the button "create" to create a new project
#. Fill in the field Project name and click the "create" button
#. Now in the Kanban view see the project name when you are created
#. Repeat this operation creating another project without the name.
Binary file added project_sequence/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions project_sequence/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_project_sequence
108 changes: 108 additions & 0 deletions project_sequence/tests/test_project_sequence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright 2023 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from freezegun import freeze_time
from psycopg2 import IntegrityError

from odoo.tests.common import Form, SavepointCase, new_test_user, users
from odoo.tools import mute_logger


@freeze_time("2023-01-01 12:00:00")
class TestProjectSequence(SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
new_test_user(
cls.env,
"manager",
"project.group_project_manager,analytic.group_analytic_accounting",
)
cls.pjr_seq = cls.env.ref("project_sequence.seq_project_sequence")
cls.pjr_seq.date_range_ids.unlink()
cls.analytic_account = cls.env["account.analytic.account"].create(
{"name": "aaa"}
)

def setUp(self):
super().setUp()
self.pjr_seq._get_current_sequence().number_next = 11

@users("manager")
def test_sequence_after_creation(self):
"""Sequence is applied only after project creation."""
prj_f = Form(self.env["project.project"])
self.assertFalse(prj_f.name)
self.assertFalse(prj_f.sequence_code)
proj = prj_f.save()
self.assertTrue(proj.sequence_code)
self.assertEqual(proj.name, proj.sequence_code)
self.assertEqual(proj.sequence_code, "23-00011")
self.assertEqual(proj.display_name, "23-00011")

def test_analytic_account_after_creation_no_name(self):
"""Project's analytic account is named like project's default name."""
proj = self.env["project.project"].create(
{"analytic_account_id": self.analytic_account.id}
)
self.assertEqual(proj.sequence_code, "23-00011")
self.assertEqual(proj.name, "23-00011")
self.assertEqual(proj.display_name, "23-00011")
self.assertEqual(proj.analytic_account_id.name, "23-00011")

def test_analytic_account_after_creation_named(self):
"""Project's analytic account is named like project's display name."""
proj = self.env["project.project"].create(
{"name": "whatever", "analytic_account_id": self.analytic_account.id}
)
self.assertEqual(proj.sequence_code, "23-00011")
self.assertEqual(proj.name, "whatever")
self.assertEqual(proj.display_name, "23-00011 - whatever")
self.assertEqual(proj.analytic_account_id.name, "23-00011 - whatever")

@users("manager")
def test_sequence_copied_to_name_if_emptied(self):
"""Sequence is copied to project name if user removes it."""
proj = self.env["project.project"].create(
{"name": "whatever", "analytic_account_id": self.analytic_account.id}
)
self.assertEqual(proj.name, "whatever")
self.assertEqual(proj.sequence_code, "23-00011")
self.assertEqual(proj.display_name, "23-00011 - whatever")
self.assertEqual(proj.analytic_account_id.name, "23-00011 - whatever")
with Form(proj) as prj_f:
prj_f.name = False
self.assertEqual(proj.name, "23-00011")
self.assertEqual(proj.sequence_code, "23-00011")
self.assertEqual(proj.display_name, "23-00011")
self.assertEqual(proj.analytic_account_id.name, "23-00011")

@users("manager")
def test_sequence_not_copied_to_another_project(self):
"""Sequence is not duplicated to another project."""
proj1 = self.env["project.project"].create({"name": "whatever"})
proj2 = proj1.copy()
self.assertEqual(proj1.sequence_code, "23-00011")
self.assertEqual(proj2.sequence_code, "23-00012")

@users("manager")
@mute_logger("odoo.sql_db")
def test_sequence_unique(self):
"""Sequence cannot have duplicates."""
proj1 = self.env["project.project"].create({"name": "one"})
self.assertEqual(proj1.sequence_code, "23-00011")
self.pjr_seq._get_current_sequence().number_next = 11
with self.assertRaises(IntegrityError), self.env.cr.savepoint():
proj1 = self.env["project.project"].create({"name": "two"})

@users("manager")
def test_project_without_sequence(self):
"""Preexisting projects had no sequence, and they should display fine."""
proj1 = self.env["project.project"].create(
{"name": "one", "sequence_code": False}
)
self.assertEqual(proj1.display_name, "one")
self.assertFalse(proj1.sequence_code)
# Make sure that the sequence is not increased
proj2 = self.env["project.project"].create({"name": "two"})
self.assertEqual(proj2.sequence_code, "23-00011")
self.assertEqual(proj2.display_name, "23-00011 - two")
Loading