From ddb6f78588d8fb72f9dc943751f055391effe1ed Mon Sep 17 00:00:00 2001 From: ps-tubtim Date: Wed, 11 Jan 2023 10:18:26 +0700 Subject: [PATCH 01/11] Initial 15.0 --- res_project/__init__.py | 4 + res_project/__manifest__.py | 26 ++ res_project/data/res_project_cron.xml | 20 ++ res_project/models/__init__.py | 6 + res_project/models/hr_department.py | 16 ++ res_project/models/hr_employee_base.py | 15 ++ res_project/models/res_project.py | 227 ++++++++++++++++++ res_project/models/res_project_plan.py | 23 ++ res_project/readme/CONFIGURE.rst | 6 + res_project/readme/CONTRIBUTORS.rst | 1 + res_project/readme/DESCRIPTION.rst | 1 + res_project/security/ir.model.access.csv | 7 + .../security/res_project_security_groups.xml | 26 ++ res_project/views/hr_department_views.xml | 17 ++ res_project/views/hr_employee_views.xml | 15 ++ res_project/views/res_project_menuitem.xml | 17 ++ res_project/views/res_project_split.xml | 11 + res_project/views/res_project_views.xml | 227 ++++++++++++++++++ res_project/wizard/__init__.py | 3 + res_project/wizard/split_project_wizard.py | 93 +++++++ .../wizard/split_project_wizard_view.xml | 43 ++++ 21 files changed, 804 insertions(+) create mode 100644 res_project/__init__.py create mode 100644 res_project/__manifest__.py create mode 100644 res_project/data/res_project_cron.xml create mode 100644 res_project/models/__init__.py create mode 100644 res_project/models/hr_department.py create mode 100644 res_project/models/hr_employee_base.py create mode 100644 res_project/models/res_project.py create mode 100644 res_project/models/res_project_plan.py create mode 100644 res_project/readme/CONFIGURE.rst create mode 100644 res_project/readme/CONTRIBUTORS.rst create mode 100644 res_project/readme/DESCRIPTION.rst create mode 100644 res_project/security/ir.model.access.csv create mode 100644 res_project/security/res_project_security_groups.xml create mode 100644 res_project/views/hr_department_views.xml create mode 100644 res_project/views/hr_employee_views.xml create mode 100644 res_project/views/res_project_menuitem.xml create mode 100644 res_project/views/res_project_split.xml create mode 100644 res_project/views/res_project_views.xml create mode 100644 res_project/wizard/__init__.py create mode 100644 res_project/wizard/split_project_wizard.py create mode 100644 res_project/wizard/split_project_wizard_view.xml diff --git a/res_project/__init__.py b/res_project/__init__.py new file mode 100644 index 00000000..4d7a49b5 --- /dev/null +++ b/res_project/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizard diff --git a/res_project/__manifest__.py b/res_project/__manifest__.py new file mode 100644 index 00000000..61374903 --- /dev/null +++ b/res_project/__manifest__.py @@ -0,0 +1,26 @@ +# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Project Management", + "summary": "New menu Projects management with analytic", + "version": "14.0.1.0.0", + "license": "AGPL-3", + "category": "Project", + "website": "https://github.com/OCA/account-budgeting", + "author": "Ecosoft, Odoo Community Association (OCA)", + "depends": ["hr", "mail"], + "data": [ + "security/res_project_security_groups.xml", + "security/ir.model.access.csv", + "data/res_project_cron.xml", + "views/res_project_menuitem.xml", + "views/res_project_views.xml", + "views/res_project_split.xml", + "views/hr_department_views.xml", + "views/hr_employee_views.xml", + "wizard/split_project_wizard_view.xml", + ], + "maintainers": ["Saran440"], + "development_status": "Alpha", +} diff --git a/res_project/data/res_project_cron.xml b/res_project/data/res_project_cron.xml new file mode 100644 index 00000000..bd66e678 --- /dev/null +++ b/res_project/data/res_project_cron.xml @@ -0,0 +1,20 @@ + + + + Res Project: Automatically change state expiration date + + code + model.action_auto_expired() + + 1 + days + -1 + + + + diff --git a/res_project/models/__init__.py b/res_project/models/__init__.py new file mode 100644 index 00000000..e0dcdc9f --- /dev/null +++ b/res_project/models/__init__.py @@ -0,0 +1,6 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import hr_department +from . import hr_employee_base +from . import res_project_plan +from . import res_project diff --git a/res_project/models/hr_department.py b/res_project/models/hr_department.py new file mode 100644 index 00000000..f99aef49 --- /dev/null +++ b/res_project/models/hr_department.py @@ -0,0 +1,16 @@ +# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class Department(models.Model): + _inherit = "hr.department" + + project_ids = fields.One2many( + comodel_name="res.project", + inverse_name="department_id", + copy=False, + help="Project to which this department is linked " + "for structure organization.", + ) diff --git a/res_project/models/hr_employee_base.py b/res_project/models/hr_employee_base.py new file mode 100644 index 00000000..879a357d --- /dev/null +++ b/res_project/models/hr_employee_base.py @@ -0,0 +1,15 @@ +# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class HrEmployeeBase(models.AbstractModel): + _inherit = "hr.employee.base" + + project_ids = fields.Many2many( + comodel_name="res.project", + relation="project_employee_rel", + column1="employee_id", + column2="project_id", + string="Project", + ) diff --git a/res_project/models/res_project.py b/res_project/models/res_project.py new file mode 100644 index 00000000..d0c8173a --- /dev/null +++ b/res_project/models/res_project.py @@ -0,0 +1,227 @@ +# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class ResProject(models.Model): + _name = "res.project" + _description = "Project Management" + _inherit = "mail.thread" + _check_company_auto = True + + name = fields.Char( + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + code = fields.Char( + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + parent_project_id = fields.Many2one( + comodel_name="res.project", + string="Parent", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + parent_project_name = fields.Char( + compute="_compute_parent_project_name", + string="Parent Project", + store=True, + readonly=False, + tracking=True, + ) + child_ids = fields.One2many( + comodel_name="res.project", + inverse_name="parent_project_id", + string="Child Projects", + check_company=True, + ) + active = fields.Boolean( + default=True, + tracking=True, + help="If the active field is set to False, " + "it will allow you to hide the project without removing it.", + ) + description = fields.Html( + readonly=True, copy=False, states={"draft": [("readonly", False)]} + ) + company_id = fields.Many2one( + comodel_name="res.company", + string="Company", + required=True, + readonly=True, + default=lambda self: self.env.company, + ) + currency_id = fields.Many2one( + comodel_name="res.currency", + string="Currency", + required=True, + related="company_id.currency_id", + states={"done": [("readonly", True)]}, + ) + project_manager_id = fields.Many2one( + comodel_name="hr.employee", + string="Project Manager", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + date_from = fields.Date( + required=True, + string="Project Start", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + date_to = fields.Date( + required=True, + string="Project End", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + department_id = fields.Many2one( + comodel_name="hr.department", + readonly=True, + required=True, + states={"draft": [("readonly", False)]}, + ) + member_ids = fields.Many2many( + comodel_name="hr.employee.public", + relation="project_employee_rel", + column1="project_id", + column2="employee_id", + string="Member", + readonly=True, + states={"draft": [("readonly", False)]}, + ) + plan_amount = fields.Monetary( + string="Plan Amount", + compute="_compute_plan_amount", + currency_field="currency_id", + help="Total Plan Amount for this project", + ) + project_plan_ids = fields.One2many( + comodel_name="res.project.plan", + inverse_name="project_id", + ) + + state = fields.Selection( + [ + ("draft", "Draft"), + ("confirm", "Confirmed"), + ("close", "Closed"), + ("cancel", "Cancelled"), + ], + string="Status", + required=True, + readonly=True, + copy=False, + tracking=True, + default="draft", + ) + + _sql_constraints = [("unique_name", "UNIQUE(name)", "name must be unique")] + + @api.depends("parent_project_id", "name") + def _compute_parent_project_name(self): + for rec in self: + rec.parent_project_name = ( + rec.parent_project_id and rec.parent_project_id.name or rec.name + ) + + @api.depends("project_plan_ids") + def _compute_plan_amount(self): + for rec in self: + rec.plan_amount = sum(rec.project_plan_ids.mapped("amount")) + + @api.model + def create(self, vals): + if not vals.get("parent_project_id", False): + vals["parent_project_name"] = vals["name"] + return super().create(vals) + + @api.model + def name_search(self, name, args=None, operator="ilike", limit=100): + args = args or [] + domain = [] + if name: + domain = ["|", ("code", operator, name), ("name", operator, name)] + projects = self.search(domain + args, limit=limit) + return projects.name_get() + + def name_get(self): + res = [] + for project in self: + name = project.name + if project.code: + name = "[{}] {}".format(project.code, name) + res.append((project.id, name)) + return res + + def copy(self, default=None): + self.ensure_one() + default = dict(default or {}, name=_("%s (copy)") % self.name) + return super().copy(default) + + def action_split_project(self): + project = self.browse(self.env.context["active_ids"]) + if len(project) != 1: + raise UserError(_("Please select one project.")) + wizard = self.env.ref("res_project.split_project_wizard_form") + return { + "name": _("Split Project"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "split.project.wizard", + "views": [(wizard.id, "form")], + "view_id": wizard.id, + "target": "new", + "context": { + "default_parent_project_id": project.parent_project_id + and project.parent_project_id.id + or project.id, + "default_parent_project_name": project.parent_project_name, + "default_date_from": project.date_from, + "default_date_to": project.date_to, + "default_project_manager_id": project.project_manager_id.id, + "default_department_id": project.department_id.id, + "default_member_ids": [(6, 0, project.member_ids.ids)], + }, + } + + def action_confirm(self): + return self.write({"state": "confirm"}) + + def action_close_project(self): + return self.write({"state": "close"}) + + def action_draft(self): + return self.write({"state": "draft"}) + + def action_cancel(self): + return self.write({"state": "cancel"}) + + def _get_domain_project_expired(self): + date = self._context.get("force_project_date") or fields.Date.context_today( + self + ) + domain = [("date_to", "<", date), ("state", "=", "confirm")] + return domain + + def action_auto_expired(self): + domain = self._get_domain_project_expired() + project_expired = self.search(domain) + if not project_expired: + return + return project_expired.write({"active": False}) + + @api.onchange("project_manager_id") + def _onchange_department_id(self): + for rec in self: + rec.department_id = rec.project_manager_id.department_id or False diff --git a/res_project/models/res_project_plan.py b/res_project/models/res_project_plan.py new file mode 100644 index 00000000..d94bd57b --- /dev/null +++ b/res_project/models/res_project_plan.py @@ -0,0 +1,23 @@ +# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResProjectPlan(models.Model): + _name = "res.project.plan" + _description = "Project Plan" + + project_id = fields.Many2one( + comodel_name="res.project", + required=True, + index=True, + ondelete="cascade", + ) + currency_id = fields.Many2one( + comodel_name="res.currency", + related="project_id.currency_id", + ) + date_from = fields.Date(required=True) + date_to = fields.Date(required=True) + amount = fields.Monetary() diff --git a/res_project/readme/CONFIGURE.rst b/res_project/readme/CONFIGURE.rst new file mode 100644 index 00000000..553021fb --- /dev/null +++ b/res_project/readme/CONFIGURE.rst @@ -0,0 +1,6 @@ +To configure this module, following access right must be set. + - Go to Settings > Users & Companies > Users + - Select User that you have to see Project + - Select access right on Res Project Field + - Project User + - Project Manager diff --git a/res_project/readme/CONTRIBUTORS.rst b/res_project/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..cc6b2310 --- /dev/null +++ b/res_project/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Saran Lim. diff --git a/res_project/readme/DESCRIPTION.rst b/res_project/readme/DESCRIPTION.rst new file mode 100644 index 00000000..188857c5 --- /dev/null +++ b/res_project/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module create new menu Project (res.project) diff --git a/res_project/security/ir.model.access.csv b/res_project/security/ir.model.access.csv new file mode 100644 index 00000000..88bc7d13 --- /dev/null +++ b/res_project/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_res_project_user,access_res_project_user,model_res_project,res_project.group_res_project_user,1,0,0,0 +access_res_project_manager,access_res_project_manager,model_res_project,res_project.group_res_project_manager,1,1,1,1 +access_res_project_plan_user,access_res_project_plan_user,model_res_project_plan,res_project.group_res_project_user,1,0,0,0 +access_res_project_plan_manager,access_res_project_plan_manager,model_res_project_plan,res_project.group_res_project_manager,1,1,1,1 +access_split_project_wizard,access_split_project_wizard,model_split_project_wizard,res_project.group_res_project_manager,1,1,1,1 +access_split_project_wizard_line,access_split_project_wizard_line,model_split_project_wizard_line,res_project.group_res_project_manager,1,1,1,1 diff --git a/res_project/security/res_project_security_groups.xml b/res_project/security/res_project_security_groups.xml new file mode 100644 index 00000000..21848829 --- /dev/null +++ b/res_project/security/res_project_security_groups.xml @@ -0,0 +1,26 @@ + + + + + Res Project + Helps you handle your project needs. + 10 + + + Project User + + + + Project Manager + + + + + diff --git a/res_project/views/hr_department_views.xml b/res_project/views/hr_department_views.xml new file mode 100644 index 00000000..90b86997 --- /dev/null +++ b/res_project/views/hr_department_views.xml @@ -0,0 +1,17 @@ + + + + hr.department.form + hr.department + + + + + + + + diff --git a/res_project/views/hr_employee_views.xml b/res_project/views/hr_employee_views.xml new file mode 100644 index 00000000..2d38f5b6 --- /dev/null +++ b/res_project/views/hr_employee_views.xml @@ -0,0 +1,15 @@ + + + + hr.employee.form + hr.employee + + + + + + + + + + diff --git a/res_project/views/res_project_menuitem.xml b/res_project/views/res_project_menuitem.xml new file mode 100644 index 00000000..1a6fd3ce --- /dev/null +++ b/res_project/views/res_project_menuitem.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/res_project/views/res_project_split.xml b/res_project/views/res_project_split.xml new file mode 100644 index 00000000..ccc22c7b --- /dev/null +++ b/res_project/views/res_project_split.xml @@ -0,0 +1,11 @@ + + + + Split Project + + + form + code + action = model.action_split_project() + + diff --git a/res_project/views/res_project_views.xml b/res_project/views/res_project_views.xml new file mode 100644 index 00000000..30bdbf04 --- /dev/null +++ b/res_project/views/res_project_views.xml @@ -0,0 +1,227 @@ + + + + res.project.tree + res.project + + + + + + + + + + + + + + + res.project.form + res.project + +
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+
+
+ + res.project.select + res.project + + + + + + + + + + + + + + + + + + + + + + + + + + Projects + res.project + tree,form + + +

+ No projects found. Let's create one! +

+
+
+ + Confirm + + + code + list + records.action_confirm() + + +
diff --git a/res_project/wizard/__init__.py b/res_project/wizard/__init__.py new file mode 100644 index 00000000..0955a992 --- /dev/null +++ b/res_project/wizard/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import split_project_wizard diff --git a/res_project/wizard/split_project_wizard.py b/res_project/wizard/split_project_wizard.py new file mode 100644 index 00000000..a9d440da --- /dev/null +++ b/res_project/wizard/split_project_wizard.py @@ -0,0 +1,93 @@ +# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models +from odoo.exceptions import UserError + + +class SplitProjectWizard(models.TransientModel): + _name = "split.project.wizard" + _description = "Split Project Wizard" + + parent_project_id = fields.Many2one( + comodel_name="res.project", + string="Parent", + readonly=True, + ) + parent_project_name = fields.Char( + string="Parent Project", + readonly=True, + ) + project_manager_id = fields.Many2one( + comodel_name="hr.employee", + string="Project Manager", + readonly=True, + ) + department_id = fields.Many2one( + comodel_name="hr.department", + string="Department", + readonly=True, + ) + date_from = fields.Date(string="Project Start", readonly=True) + date_to = fields.Date(string="Project End", readonly=True) + member_ids = fields.Many2many( + comodel_name="hr.employee", + string="Member", + readonly=True, + ) + line_ids = fields.One2many( + string="Lines", + comodel_name="split.project.wizard.line", + inverse_name="wizard_id", + ) + + def split_project(self): + self.ensure_one() + if not self.line_ids: + raise UserError(_("Please add a new project name")) + ResProject = self.env["res.project"] + ctx = self._context.copy() + if self.parent_project_id: + # Archive parent project record + self.parent_project_id.action_archive() + # Update context + ctx.update( + { + "split_project": True, + "parent_project_id": self.parent_project_id.id, + } + ) + # Create new project record + vals = [line._prepare_project_val() for line in self.line_ids] + new_projects = ResProject.with_context(ctx).create(vals) + return { + "name": _("Project"), + "type": "ir.actions.act_window", + "res_model": "res.project", + "view_mode": "tree,form", + "context": self.env.context, + "domain": [("id", "in", new_projects.ids)], + } + + +class SplitProjectWizardLine(models.TransientModel): + _name = "split.project.wizard.line" + _description = "Split Project Wizard Line" + + wizard_id = fields.Many2one(comodel_name="split.project.wizard") + project_name = fields.Char(string="Project Name") + + def _prepare_project_val(self): + self.ensure_one() + wizard = self.wizard_id + return { + "name": self.project_name, + "parent_project_id": wizard.parent_project_id.id, + "parent_project_name": wizard.parent_project_name, + "date_from": wizard.date_from, + "date_to": wizard.date_to, + "project_manager_id": wizard.project_manager_id.id, + "department_id": wizard.department_id.id, + "company_id": self.env.company.id, + "member_ids": [(6, 0, wizard.member_ids.ids)], + } diff --git a/res_project/wizard/split_project_wizard_view.xml b/res_project/wizard/split_project_wizard_view.xml new file mode 100644 index 00000000..2706c59a --- /dev/null +++ b/res_project/wizard/split_project_wizard_view.xml @@ -0,0 +1,43 @@ + + + + split.project.wizard.form + split.project.wizard + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
From 7f2d63c5ddba2ff5bfe018a7f1d469c98ca71df4 Mon Sep 17 00:00:00 2001 From: ps-tubtim Date: Wed, 11 Jan 2023 10:19:06 +0700 Subject: [PATCH 02/11] Initial 15.0 --- res_project/__init__.py | 4 - res_project/__manifest__.py | 26 -- res_project/data/res_project_cron.xml | 20 -- res_project/models/__init__.py | 6 - res_project/models/hr_department.py | 16 -- res_project/models/hr_employee_base.py | 15 -- res_project/models/res_project.py | 227 ------------------ res_project/models/res_project_plan.py | 23 -- res_project/readme/CONFIGURE.rst | 6 - res_project/readme/CONTRIBUTORS.rst | 1 - res_project/readme/DESCRIPTION.rst | 1 - res_project/security/ir.model.access.csv | 7 - .../security/res_project_security_groups.xml | 26 -- res_project/views/hr_department_views.xml | 17 -- res_project/views/hr_employee_views.xml | 15 -- res_project/views/res_project_menuitem.xml | 17 -- res_project/views/res_project_split.xml | 11 - res_project/views/res_project_views.xml | 227 ------------------ res_project/wizard/__init__.py | 3 - res_project/wizard/split_project_wizard.py | 93 ------- .../wizard/split_project_wizard_view.xml | 43 ---- 21 files changed, 804 deletions(-) delete mode 100644 res_project/__init__.py delete mode 100644 res_project/__manifest__.py delete mode 100644 res_project/data/res_project_cron.xml delete mode 100644 res_project/models/__init__.py delete mode 100644 res_project/models/hr_department.py delete mode 100644 res_project/models/hr_employee_base.py delete mode 100644 res_project/models/res_project.py delete mode 100644 res_project/models/res_project_plan.py delete mode 100644 res_project/readme/CONFIGURE.rst delete mode 100644 res_project/readme/CONTRIBUTORS.rst delete mode 100644 res_project/readme/DESCRIPTION.rst delete mode 100644 res_project/security/ir.model.access.csv delete mode 100644 res_project/security/res_project_security_groups.xml delete mode 100644 res_project/views/hr_department_views.xml delete mode 100644 res_project/views/hr_employee_views.xml delete mode 100644 res_project/views/res_project_menuitem.xml delete mode 100644 res_project/views/res_project_split.xml delete mode 100644 res_project/views/res_project_views.xml delete mode 100644 res_project/wizard/__init__.py delete mode 100644 res_project/wizard/split_project_wizard.py delete mode 100644 res_project/wizard/split_project_wizard_view.xml diff --git a/res_project/__init__.py b/res_project/__init__.py deleted file mode 100644 index 4d7a49b5..00000000 --- a/res_project/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import models -from . import wizard diff --git a/res_project/__manifest__.py b/res_project/__manifest__.py deleted file mode 100644 index 61374903..00000000 --- a/res_project/__manifest__.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -{ - "name": "Project Management", - "summary": "New menu Projects management with analytic", - "version": "14.0.1.0.0", - "license": "AGPL-3", - "category": "Project", - "website": "https://github.com/OCA/account-budgeting", - "author": "Ecosoft, Odoo Community Association (OCA)", - "depends": ["hr", "mail"], - "data": [ - "security/res_project_security_groups.xml", - "security/ir.model.access.csv", - "data/res_project_cron.xml", - "views/res_project_menuitem.xml", - "views/res_project_views.xml", - "views/res_project_split.xml", - "views/hr_department_views.xml", - "views/hr_employee_views.xml", - "wizard/split_project_wizard_view.xml", - ], - "maintainers": ["Saran440"], - "development_status": "Alpha", -} diff --git a/res_project/data/res_project_cron.xml b/res_project/data/res_project_cron.xml deleted file mode 100644 index bd66e678..00000000 --- a/res_project/data/res_project_cron.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - Res Project: Automatically change state expiration date - - code - model.action_auto_expired() - - 1 - days - -1 - - - - diff --git a/res_project/models/__init__.py b/res_project/models/__init__.py deleted file mode 100644 index e0dcdc9f..00000000 --- a/res_project/models/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import hr_department -from . import hr_employee_base -from . import res_project_plan -from . import res_project diff --git a/res_project/models/hr_department.py b/res_project/models/hr_department.py deleted file mode 100644 index f99aef49..00000000 --- a/res_project/models/hr_department.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import fields, models - - -class Department(models.Model): - _inherit = "hr.department" - - project_ids = fields.One2many( - comodel_name="res.project", - inverse_name="department_id", - copy=False, - help="Project to which this department is linked " - "for structure organization.", - ) diff --git a/res_project/models/hr_employee_base.py b/res_project/models/hr_employee_base.py deleted file mode 100644 index 879a357d..00000000 --- a/res_project/models/hr_employee_base.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models - - -class HrEmployeeBase(models.AbstractModel): - _inherit = "hr.employee.base" - - project_ids = fields.Many2many( - comodel_name="res.project", - relation="project_employee_rel", - column1="employee_id", - column2="project_id", - string="Project", - ) diff --git a/res_project/models/res_project.py b/res_project/models/res_project.py deleted file mode 100644 index d0c8173a..00000000 --- a/res_project/models/res_project.py +++ /dev/null @@ -1,227 +0,0 @@ -# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, api, fields, models -from odoo.exceptions import UserError - - -class ResProject(models.Model): - _name = "res.project" - _description = "Project Management" - _inherit = "mail.thread" - _check_company_auto = True - - name = fields.Char( - required=True, - readonly=True, - states={"draft": [("readonly", False)]}, - tracking=True, - ) - code = fields.Char( - readonly=True, - states={"draft": [("readonly", False)]}, - tracking=True, - ) - parent_project_id = fields.Many2one( - comodel_name="res.project", - string="Parent", - readonly=True, - states={"draft": [("readonly", False)]}, - tracking=True, - ) - parent_project_name = fields.Char( - compute="_compute_parent_project_name", - string="Parent Project", - store=True, - readonly=False, - tracking=True, - ) - child_ids = fields.One2many( - comodel_name="res.project", - inverse_name="parent_project_id", - string="Child Projects", - check_company=True, - ) - active = fields.Boolean( - default=True, - tracking=True, - help="If the active field is set to False, " - "it will allow you to hide the project without removing it.", - ) - description = fields.Html( - readonly=True, copy=False, states={"draft": [("readonly", False)]} - ) - company_id = fields.Many2one( - comodel_name="res.company", - string="Company", - required=True, - readonly=True, - default=lambda self: self.env.company, - ) - currency_id = fields.Many2one( - comodel_name="res.currency", - string="Currency", - required=True, - related="company_id.currency_id", - states={"done": [("readonly", True)]}, - ) - project_manager_id = fields.Many2one( - comodel_name="hr.employee", - string="Project Manager", - readonly=True, - states={"draft": [("readonly", False)]}, - tracking=True, - ) - date_from = fields.Date( - required=True, - string="Project Start", - readonly=True, - states={"draft": [("readonly", False)]}, - tracking=True, - ) - date_to = fields.Date( - required=True, - string="Project End", - readonly=True, - states={"draft": [("readonly", False)]}, - tracking=True, - ) - department_id = fields.Many2one( - comodel_name="hr.department", - readonly=True, - required=True, - states={"draft": [("readonly", False)]}, - ) - member_ids = fields.Many2many( - comodel_name="hr.employee.public", - relation="project_employee_rel", - column1="project_id", - column2="employee_id", - string="Member", - readonly=True, - states={"draft": [("readonly", False)]}, - ) - plan_amount = fields.Monetary( - string="Plan Amount", - compute="_compute_plan_amount", - currency_field="currency_id", - help="Total Plan Amount for this project", - ) - project_plan_ids = fields.One2many( - comodel_name="res.project.plan", - inverse_name="project_id", - ) - - state = fields.Selection( - [ - ("draft", "Draft"), - ("confirm", "Confirmed"), - ("close", "Closed"), - ("cancel", "Cancelled"), - ], - string="Status", - required=True, - readonly=True, - copy=False, - tracking=True, - default="draft", - ) - - _sql_constraints = [("unique_name", "UNIQUE(name)", "name must be unique")] - - @api.depends("parent_project_id", "name") - def _compute_parent_project_name(self): - for rec in self: - rec.parent_project_name = ( - rec.parent_project_id and rec.parent_project_id.name or rec.name - ) - - @api.depends("project_plan_ids") - def _compute_plan_amount(self): - for rec in self: - rec.plan_amount = sum(rec.project_plan_ids.mapped("amount")) - - @api.model - def create(self, vals): - if not vals.get("parent_project_id", False): - vals["parent_project_name"] = vals["name"] - return super().create(vals) - - @api.model - def name_search(self, name, args=None, operator="ilike", limit=100): - args = args or [] - domain = [] - if name: - domain = ["|", ("code", operator, name), ("name", operator, name)] - projects = self.search(domain + args, limit=limit) - return projects.name_get() - - def name_get(self): - res = [] - for project in self: - name = project.name - if project.code: - name = "[{}] {}".format(project.code, name) - res.append((project.id, name)) - return res - - def copy(self, default=None): - self.ensure_one() - default = dict(default or {}, name=_("%s (copy)") % self.name) - return super().copy(default) - - def action_split_project(self): - project = self.browse(self.env.context["active_ids"]) - if len(project) != 1: - raise UserError(_("Please select one project.")) - wizard = self.env.ref("res_project.split_project_wizard_form") - return { - "name": _("Split Project"), - "type": "ir.actions.act_window", - "view_mode": "form", - "res_model": "split.project.wizard", - "views": [(wizard.id, "form")], - "view_id": wizard.id, - "target": "new", - "context": { - "default_parent_project_id": project.parent_project_id - and project.parent_project_id.id - or project.id, - "default_parent_project_name": project.parent_project_name, - "default_date_from": project.date_from, - "default_date_to": project.date_to, - "default_project_manager_id": project.project_manager_id.id, - "default_department_id": project.department_id.id, - "default_member_ids": [(6, 0, project.member_ids.ids)], - }, - } - - def action_confirm(self): - return self.write({"state": "confirm"}) - - def action_close_project(self): - return self.write({"state": "close"}) - - def action_draft(self): - return self.write({"state": "draft"}) - - def action_cancel(self): - return self.write({"state": "cancel"}) - - def _get_domain_project_expired(self): - date = self._context.get("force_project_date") or fields.Date.context_today( - self - ) - domain = [("date_to", "<", date), ("state", "=", "confirm")] - return domain - - def action_auto_expired(self): - domain = self._get_domain_project_expired() - project_expired = self.search(domain) - if not project_expired: - return - return project_expired.write({"active": False}) - - @api.onchange("project_manager_id") - def _onchange_department_id(self): - for rec in self: - rec.department_id = rec.project_manager_id.department_id or False diff --git a/res_project/models/res_project_plan.py b/res_project/models/res_project_plan.py deleted file mode 100644 index d94bd57b..00000000 --- a/res_project/models/res_project_plan.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import fields, models - - -class ResProjectPlan(models.Model): - _name = "res.project.plan" - _description = "Project Plan" - - project_id = fields.Many2one( - comodel_name="res.project", - required=True, - index=True, - ondelete="cascade", - ) - currency_id = fields.Many2one( - comodel_name="res.currency", - related="project_id.currency_id", - ) - date_from = fields.Date(required=True) - date_to = fields.Date(required=True) - amount = fields.Monetary() diff --git a/res_project/readme/CONFIGURE.rst b/res_project/readme/CONFIGURE.rst deleted file mode 100644 index 553021fb..00000000 --- a/res_project/readme/CONFIGURE.rst +++ /dev/null @@ -1,6 +0,0 @@ -To configure this module, following access right must be set. - - Go to Settings > Users & Companies > Users - - Select User that you have to see Project - - Select access right on Res Project Field - - Project User - - Project Manager diff --git a/res_project/readme/CONTRIBUTORS.rst b/res_project/readme/CONTRIBUTORS.rst deleted file mode 100644 index cc6b2310..00000000 --- a/res_project/readme/CONTRIBUTORS.rst +++ /dev/null @@ -1 +0,0 @@ -* Saran Lim. diff --git a/res_project/readme/DESCRIPTION.rst b/res_project/readme/DESCRIPTION.rst deleted file mode 100644 index 188857c5..00000000 --- a/res_project/readme/DESCRIPTION.rst +++ /dev/null @@ -1 +0,0 @@ -This module create new menu Project (res.project) diff --git a/res_project/security/ir.model.access.csv b/res_project/security/ir.model.access.csv deleted file mode 100644 index 88bc7d13..00000000 --- a/res_project/security/ir.model.access.csv +++ /dev/null @@ -1,7 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_res_project_user,access_res_project_user,model_res_project,res_project.group_res_project_user,1,0,0,0 -access_res_project_manager,access_res_project_manager,model_res_project,res_project.group_res_project_manager,1,1,1,1 -access_res_project_plan_user,access_res_project_plan_user,model_res_project_plan,res_project.group_res_project_user,1,0,0,0 -access_res_project_plan_manager,access_res_project_plan_manager,model_res_project_plan,res_project.group_res_project_manager,1,1,1,1 -access_split_project_wizard,access_split_project_wizard,model_split_project_wizard,res_project.group_res_project_manager,1,1,1,1 -access_split_project_wizard_line,access_split_project_wizard_line,model_split_project_wizard_line,res_project.group_res_project_manager,1,1,1,1 diff --git a/res_project/security/res_project_security_groups.xml b/res_project/security/res_project_security_groups.xml deleted file mode 100644 index 21848829..00000000 --- a/res_project/security/res_project_security_groups.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Res Project - Helps you handle your project needs. - 10 - - - Project User - - - - Project Manager - - - - - diff --git a/res_project/views/hr_department_views.xml b/res_project/views/hr_department_views.xml deleted file mode 100644 index 90b86997..00000000 --- a/res_project/views/hr_department_views.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - hr.department.form - hr.department - - - - - - - - diff --git a/res_project/views/hr_employee_views.xml b/res_project/views/hr_employee_views.xml deleted file mode 100644 index 2d38f5b6..00000000 --- a/res_project/views/hr_employee_views.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - hr.employee.form - hr.employee - - - - - - - - - - diff --git a/res_project/views/res_project_menuitem.xml b/res_project/views/res_project_menuitem.xml deleted file mode 100644 index 1a6fd3ce..00000000 --- a/res_project/views/res_project_menuitem.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/res_project/views/res_project_split.xml b/res_project/views/res_project_split.xml deleted file mode 100644 index ccc22c7b..00000000 --- a/res_project/views/res_project_split.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - Split Project - - - form - code - action = model.action_split_project() - - diff --git a/res_project/views/res_project_views.xml b/res_project/views/res_project_views.xml deleted file mode 100644 index 30bdbf04..00000000 --- a/res_project/views/res_project_views.xml +++ /dev/null @@ -1,227 +0,0 @@ - - - - res.project.tree - res.project - - - - - - - - - - - - - - - res.project.form - res.project - -
-
-
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-
- - res.project.select - res.project - - - - - - - - - - - - - - - - - - - - - - - - - - Projects - res.project - tree,form - - -

- No projects found. Let's create one! -

-
-
- - Confirm - - - code - list - records.action_confirm() - - -
diff --git a/res_project/wizard/__init__.py b/res_project/wizard/__init__.py deleted file mode 100644 index 0955a992..00000000 --- a/res_project/wizard/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import split_project_wizard diff --git a/res_project/wizard/split_project_wizard.py b/res_project/wizard/split_project_wizard.py deleted file mode 100644 index a9d440da..00000000 --- a/res_project/wizard/split_project_wizard.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import _, fields, models -from odoo.exceptions import UserError - - -class SplitProjectWizard(models.TransientModel): - _name = "split.project.wizard" - _description = "Split Project Wizard" - - parent_project_id = fields.Many2one( - comodel_name="res.project", - string="Parent", - readonly=True, - ) - parent_project_name = fields.Char( - string="Parent Project", - readonly=True, - ) - project_manager_id = fields.Many2one( - comodel_name="hr.employee", - string="Project Manager", - readonly=True, - ) - department_id = fields.Many2one( - comodel_name="hr.department", - string="Department", - readonly=True, - ) - date_from = fields.Date(string="Project Start", readonly=True) - date_to = fields.Date(string="Project End", readonly=True) - member_ids = fields.Many2many( - comodel_name="hr.employee", - string="Member", - readonly=True, - ) - line_ids = fields.One2many( - string="Lines", - comodel_name="split.project.wizard.line", - inverse_name="wizard_id", - ) - - def split_project(self): - self.ensure_one() - if not self.line_ids: - raise UserError(_("Please add a new project name")) - ResProject = self.env["res.project"] - ctx = self._context.copy() - if self.parent_project_id: - # Archive parent project record - self.parent_project_id.action_archive() - # Update context - ctx.update( - { - "split_project": True, - "parent_project_id": self.parent_project_id.id, - } - ) - # Create new project record - vals = [line._prepare_project_val() for line in self.line_ids] - new_projects = ResProject.with_context(ctx).create(vals) - return { - "name": _("Project"), - "type": "ir.actions.act_window", - "res_model": "res.project", - "view_mode": "tree,form", - "context": self.env.context, - "domain": [("id", "in", new_projects.ids)], - } - - -class SplitProjectWizardLine(models.TransientModel): - _name = "split.project.wizard.line" - _description = "Split Project Wizard Line" - - wizard_id = fields.Many2one(comodel_name="split.project.wizard") - project_name = fields.Char(string="Project Name") - - def _prepare_project_val(self): - self.ensure_one() - wizard = self.wizard_id - return { - "name": self.project_name, - "parent_project_id": wizard.parent_project_id.id, - "parent_project_name": wizard.parent_project_name, - "date_from": wizard.date_from, - "date_to": wizard.date_to, - "project_manager_id": wizard.project_manager_id.id, - "department_id": wizard.department_id.id, - "company_id": self.env.company.id, - "member_ids": [(6, 0, wizard.member_ids.ids)], - } diff --git a/res_project/wizard/split_project_wizard_view.xml b/res_project/wizard/split_project_wizard_view.xml deleted file mode 100644 index 2706c59a..00000000 --- a/res_project/wizard/split_project_wizard_view.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - split.project.wizard.form - split.project.wizard - -
- - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- -
From 14470ddf13ca95361060ee882aa5625a37514a5e Mon Sep 17 00:00:00 2001 From: Saran440 Date: Wed, 11 Jan 2023 16:40:54 +0700 Subject: [PATCH 03/11] [ADD] res_project --- res_project/README.rst | 102 ++++ res_project/__init__.py | 4 + res_project/__manifest__.py | 26 + res_project/data/res_project_cron.xml | 20 + res_project/models/__init__.py | 6 + res_project/models/hr_department.py | 16 + res_project/models/hr_employee_base.py | 15 + res_project/models/res_project.py | 223 +++++++++ res_project/models/res_project_plan.py | 23 + res_project/readme/CONFIGURE.rst | 12 + res_project/readme/CONTRIBUTORS.rst | 1 + res_project/readme/DESCRIPTION.rst | 1 + res_project/security/ir.model.access.csv | 7 + .../security/res_project_security_groups.xml | 26 + res_project/static/description/index.html | 450 ++++++++++++++++++ res_project/tests/__init__.py | 3 + res_project/tests/test_res_project.py | 139 ++++++ res_project/views/hr_department_views.xml | 17 + res_project/views/hr_employee_views.xml | 15 + res_project/views/res_project_menuitem.xml | 17 + res_project/views/res_project_split.xml | 11 + res_project/views/res_project_views.xml | 231 +++++++++ res_project/wizard/__init__.py | 3 + res_project/wizard/split_project_wizard.py | 98 ++++ .../wizard/split_project_wizard_view.xml | 43 ++ 25 files changed, 1509 insertions(+) create mode 100644 res_project/README.rst create mode 100644 res_project/__init__.py create mode 100644 res_project/__manifest__.py create mode 100644 res_project/data/res_project_cron.xml create mode 100644 res_project/models/__init__.py create mode 100644 res_project/models/hr_department.py create mode 100644 res_project/models/hr_employee_base.py create mode 100644 res_project/models/res_project.py create mode 100644 res_project/models/res_project_plan.py create mode 100644 res_project/readme/CONFIGURE.rst create mode 100644 res_project/readme/CONTRIBUTORS.rst create mode 100644 res_project/readme/DESCRIPTION.rst create mode 100644 res_project/security/ir.model.access.csv create mode 100644 res_project/security/res_project_security_groups.xml create mode 100644 res_project/static/description/index.html create mode 100644 res_project/tests/__init__.py create mode 100644 res_project/tests/test_res_project.py create mode 100644 res_project/views/hr_department_views.xml create mode 100644 res_project/views/hr_employee_views.xml create mode 100644 res_project/views/res_project_menuitem.xml create mode 100644 res_project/views/res_project_split.xml create mode 100644 res_project/views/res_project_views.xml create mode 100644 res_project/wizard/__init__.py create mode 100644 res_project/wizard/split_project_wizard.py create mode 100644 res_project/wizard/split_project_wizard_view.xml diff --git a/res_project/README.rst b/res_project/README.rst new file mode 100644 index 00000000..e33004e6 --- /dev/null +++ b/res_project/README.rst @@ -0,0 +1,102 @@ +================== +Project Management +================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--budgeting-lightgray.png?logo=github + :target: https://github.com/OCA/account-budgeting/tree/15.0/res_project + :alt: OCA/account-budgeting +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-budgeting-15-0/account-budgeting-15-0-res_project + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/88/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is one of the master data used in all Project sections as information + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, following access right must be set. + +* Go to Settings > Users & Companies > Users +* Select User that you have to see Project +* Select access right on Res Project Field + * Project User + * Project Manager + +**Note:** + +* Project User: Users will be able to see all project documents but cannot create documents. +* Project Manager: Users will be able to see all project documents, create documents and configuration. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Ecosoft + +Contributors +~~~~~~~~~~~~ + +* Saran Lim. + +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. + +.. |maintainer-Saran440| image:: https://github.com/Saran440.png?size=40px + :target: https://github.com/Saran440 + :alt: Saran440 + +Current `maintainer `__: + +|maintainer-Saran440| + +This module is part of the `OCA/account-budgeting `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/res_project/__init__.py b/res_project/__init__.py new file mode 100644 index 00000000..4d7a49b5 --- /dev/null +++ b/res_project/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizard diff --git a/res_project/__manifest__.py b/res_project/__manifest__.py new file mode 100644 index 00000000..7592b09f --- /dev/null +++ b/res_project/__manifest__.py @@ -0,0 +1,26 @@ +# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Project Management", + "summary": "New menu Projects management", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "category": "Project", + "website": "https://github.com/OCA/account-analytic", + "author": "Ecosoft, Odoo Community Association (OCA)", + "depends": ["hr", "mail"], + "data": [ + "security/res_project_security_groups.xml", + "security/ir.model.access.csv", + "data/res_project_cron.xml", + "views/res_project_menuitem.xml", + "views/res_project_views.xml", + "views/res_project_split.xml", + "views/hr_department_views.xml", + "views/hr_employee_views.xml", + "wizard/split_project_wizard_view.xml", + ], + "maintainers": ["Saran440"], + "development_status": "Alpha", +} diff --git a/res_project/data/res_project_cron.xml b/res_project/data/res_project_cron.xml new file mode 100644 index 00000000..bd66e678 --- /dev/null +++ b/res_project/data/res_project_cron.xml @@ -0,0 +1,20 @@ + + + + Res Project: Automatically change state expiration date + + code + model.action_auto_expired() + + 1 + days + -1 + + + + diff --git a/res_project/models/__init__.py b/res_project/models/__init__.py new file mode 100644 index 00000000..e0dcdc9f --- /dev/null +++ b/res_project/models/__init__.py @@ -0,0 +1,6 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import hr_department +from . import hr_employee_base +from . import res_project_plan +from . import res_project diff --git a/res_project/models/hr_department.py b/res_project/models/hr_department.py new file mode 100644 index 00000000..f99aef49 --- /dev/null +++ b/res_project/models/hr_department.py @@ -0,0 +1,16 @@ +# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class Department(models.Model): + _inherit = "hr.department" + + project_ids = fields.One2many( + comodel_name="res.project", + inverse_name="department_id", + copy=False, + help="Project to which this department is linked " + "for structure organization.", + ) diff --git a/res_project/models/hr_employee_base.py b/res_project/models/hr_employee_base.py new file mode 100644 index 00000000..879a357d --- /dev/null +++ b/res_project/models/hr_employee_base.py @@ -0,0 +1,15 @@ +# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class HrEmployeeBase(models.AbstractModel): + _inherit = "hr.employee.base" + + project_ids = fields.Many2many( + comodel_name="res.project", + relation="project_employee_rel", + column1="employee_id", + column2="project_id", + string="Project", + ) diff --git a/res_project/models/res_project.py b/res_project/models/res_project.py new file mode 100644 index 00000000..618b8829 --- /dev/null +++ b/res_project/models/res_project.py @@ -0,0 +1,223 @@ +# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class ResProject(models.Model): + _name = "res.project" + _description = "Project Management" + _inherit = "mail.thread" + _check_company_auto = True + + name = fields.Char( + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + code = fields.Char( + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + parent_project_id = fields.Many2one( + comodel_name="res.project", + string="Parent", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + parent_project_name = fields.Char( + compute="_compute_parent_project_name", + string="Parent Project", + store=True, + readonly=False, + tracking=True, + ) + child_ids = fields.One2many( + comodel_name="res.project", + inverse_name="parent_project_id", + string="Child Projects", + check_company=True, + ) + active = fields.Boolean( + default=True, + tracking=True, + help="If the active field is set to False, " + "it will allow you to hide the project without removing it.", + ) + description = fields.Html( + readonly=True, copy=False, states={"draft": [("readonly", False)]} + ) + company_id = fields.Many2one( + comodel_name="res.company", + string="Company", + required=True, + readonly=True, + default=lambda self: self.env.company, + ) + currency_id = fields.Many2one( + comodel_name="res.currency", + string="Currency", + required=True, + related="company_id.currency_id", + states={"done": [("readonly", True)]}, + ) + project_manager_id = fields.Many2one( + comodel_name="hr.employee", + string="Project Manager", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + date_from = fields.Date( + required=True, + string="Project Start", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + date_to = fields.Date( + required=True, + string="Project End", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + department_id = fields.Many2one( + comodel_name="hr.department", + readonly=True, + required=True, + states={"draft": [("readonly", False)]}, + ) + member_ids = fields.Many2many( + comodel_name="hr.employee.public", + relation="project_employee_rel", + column1="project_id", + column2="employee_id", + string="Member", + readonly=True, + states={"draft": [("readonly", False)]}, + ) + plan_amount = fields.Monetary( + compute="_compute_plan_amount", + currency_field="currency_id", + help="Total Plan Amount for this project", + ) + project_plan_ids = fields.One2many( + comodel_name="res.project.plan", + inverse_name="project_id", + ) + + state = fields.Selection( + [ + ("draft", "Draft"), + ("confirm", "Confirmed"), + ("close", "Closed"), + ("cancel", "Cancelled"), + ], + string="Status", + required=True, + readonly=True, + copy=False, + tracking=True, + default="draft", + ) + + _sql_constraints = [("unique_name", "UNIQUE(name)", "name must be unique")] + + @api.depends("parent_project_id", "name") + def _compute_parent_project_name(self): + for rec in self: + rec.parent_project_name = ( + rec.parent_project_id and rec.parent_project_id.name or rec.name + ) + + @api.depends("project_plan_ids") + def _compute_plan_amount(self): + for rec in self: + rec.plan_amount = sum(rec.project_plan_ids.mapped("amount")) + + @api.model + def create(self, vals): + if not vals.get("parent_project_id", False): + vals["parent_project_name"] = vals["name"] + return super().create(vals) + + @api.model + def name_search(self, name, args=None, operator="ilike", limit=100): + args = args or [] + domain = [] + if name: + domain = ["|", ("code", operator, name), ("name", operator, name)] + projects = self.search(domain + args, limit=limit) + return projects.name_get() + + def name_get(self): + res = [] + for project in self: + name = project.name + if project.code: + name = "[{}] {}".format(project.code, name) + res.append((project.id, name)) + return res + + def copy(self, default=None): + self.ensure_one() + default = dict(default or {}, name=_("%s (copy)") % self.name) + return super().copy(default) + + def action_split_project(self): + project = self.browse(self.env.context["active_ids"]) + if len(project) != 1: + raise UserError(_("Please select one project.")) + wizard = self.env.ref("res_project.split_project_wizard_form") + return { + "name": _("Split Project"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "split.project.wizard", + "views": [(wizard.id, "form")], + "view_id": wizard.id, + "target": "new", + "context": { + "default_parent_project_id": project.parent_project_id.id or project.id, + "default_parent_project_name": project.parent_project_name, + "default_date_from": project.date_from, + "default_date_to": project.date_to, + "default_project_manager_id": project.project_manager_id.id, + "default_department_id": project.department_id.id, + "default_member_ids": [(6, 0, project.member_ids.ids)], + }, + } + + def action_confirm(self): + return self.write({"state": "confirm"}) + + def action_close_project(self): + return self.write({"state": "close"}) + + def action_draft(self): + return self.write({"state": "draft"}) + + def action_cancel(self): + return self.write({"state": "cancel"}) + + def _get_domain_project_expired(self): + date = self._context.get("force_project_date") or fields.Date.context_today( + self + ) + domain = [("date_to", "<", date), ("state", "=", "confirm")] + return domain + + def action_auto_expired(self): + """Close a project automatically when the specified conditions are met""" + domain = self._get_domain_project_expired() + project_expired = self.search(domain) + return project_expired.action_close_project() + + @api.onchange("project_manager_id") + def _onchange_department_id(self): + for rec in self: + rec.department_id = rec.project_manager_id.department_id or False diff --git a/res_project/models/res_project_plan.py b/res_project/models/res_project_plan.py new file mode 100644 index 00000000..d94bd57b --- /dev/null +++ b/res_project/models/res_project_plan.py @@ -0,0 +1,23 @@ +# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResProjectPlan(models.Model): + _name = "res.project.plan" + _description = "Project Plan" + + project_id = fields.Many2one( + comodel_name="res.project", + required=True, + index=True, + ondelete="cascade", + ) + currency_id = fields.Many2one( + comodel_name="res.currency", + related="project_id.currency_id", + ) + date_from = fields.Date(required=True) + date_to = fields.Date(required=True) + amount = fields.Monetary() diff --git a/res_project/readme/CONFIGURE.rst b/res_project/readme/CONFIGURE.rst new file mode 100644 index 00000000..9c9a8051 --- /dev/null +++ b/res_project/readme/CONFIGURE.rst @@ -0,0 +1,12 @@ +To configure this module, following access right must be set. + +* Go to Settings > Users & Companies > Users +* Select User that you have to see Project +* Select access right on Res Project Field + * Project User + * Project Manager + +**Note:** + +* Project User: Users will be able to see all project documents but cannot create documents. +* Project Manager: Users will be able to see all project documents, create documents and configuration. diff --git a/res_project/readme/CONTRIBUTORS.rst b/res_project/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..cc6b2310 --- /dev/null +++ b/res_project/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Saran Lim. diff --git a/res_project/readme/DESCRIPTION.rst b/res_project/readme/DESCRIPTION.rst new file mode 100644 index 00000000..8bc8f66c --- /dev/null +++ b/res_project/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module is one of the master data used in all Project sections as information diff --git a/res_project/security/ir.model.access.csv b/res_project/security/ir.model.access.csv new file mode 100644 index 00000000..88bc7d13 --- /dev/null +++ b/res_project/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_res_project_user,access_res_project_user,model_res_project,res_project.group_res_project_user,1,0,0,0 +access_res_project_manager,access_res_project_manager,model_res_project,res_project.group_res_project_manager,1,1,1,1 +access_res_project_plan_user,access_res_project_plan_user,model_res_project_plan,res_project.group_res_project_user,1,0,0,0 +access_res_project_plan_manager,access_res_project_plan_manager,model_res_project_plan,res_project.group_res_project_manager,1,1,1,1 +access_split_project_wizard,access_split_project_wizard,model_split_project_wizard,res_project.group_res_project_manager,1,1,1,1 +access_split_project_wizard_line,access_split_project_wizard_line,model_split_project_wizard_line,res_project.group_res_project_manager,1,1,1,1 diff --git a/res_project/security/res_project_security_groups.xml b/res_project/security/res_project_security_groups.xml new file mode 100644 index 00000000..21848829 --- /dev/null +++ b/res_project/security/res_project_security_groups.xml @@ -0,0 +1,26 @@ + + + + + Res Project + Helps you handle your project needs. + 10 + + + Project User + + + + Project Manager + + + + + diff --git a/res_project/static/description/index.html b/res_project/static/description/index.html new file mode 100644 index 00000000..df90884f --- /dev/null +++ b/res_project/static/description/index.html @@ -0,0 +1,450 @@ + + + + + + +Project Management + + + +
+

Project Management

+ + +

Alpha License: AGPL-3 OCA/account-budgeting Translate me on Weblate Try me on Runbot

+

This module is one of the master data used in all Project sections as information

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

To configure this module, following access right must be set.

+
    +
  • Go to Settings > Users & Companies > Users
  • +
  • Select User that you have to see Project
  • +
  • +
    Select access right on Res Project Field
    +
      +
    • Project User
    • +
    • Project Manager
    • +
    +
    +
    +
  • +
+

Note:

+
    +
  • Project User: Users will be able to see all project documents but cannot create documents.
  • +
  • Project Manager: Users will be able to see all project documents, create documents and configuration.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ecosoft
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

Current maintainer:

+

Saran440

+

This module is part of the OCA/account-budgeting project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/res_project/tests/__init__.py b/res_project/tests/__init__.py new file mode 100644 index 00000000..394ee0dd --- /dev/null +++ b/res_project/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_res_project diff --git a/res_project/tests/test_res_project.py b/res_project/tests/test_res_project.py new file mode 100644 index 00000000..1a0e0903 --- /dev/null +++ b/res_project/tests/test_res_project.py @@ -0,0 +1,139 @@ +# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import datetime, timedelta + +from freezegun import freeze_time + +from odoo.exceptions import UserError +from odoo.tests.common import Form, TransactionCase + + +class ResProject(TransactionCase): + @classmethod + @freeze_time("2001-02-01") + def setUpClass(cls): + super().setUpClass() + cls.ResProject = cls.env["res.project"] + cls.ProjectWizard = cls.env["split.project.wizard"] + cls.dep_admin = cls.env.ref("hr.dep_administration") + cls.dep_sales = cls.env.ref("hr.dep_sales") + cls.employee_sale = cls.env.ref("hr.employee_lur") + cls.today = datetime.today() + cls.project1 = cls._create_res_project( + cls, + "Test Project1", + cls.dep_admin.id, + cls.today, + cls.today, + ) + + def _create_res_project( + self, + name, + department_id, + date_from, + date_to, + code=False, + parent_project_id=False, + ): + return self.ResProject.create( + { + "name": name, + "code": code, + "parent_project_id": parent_project_id, + "department_id": department_id, + "date_from": date_from, + "date_to": date_to, + } + ) + + def _create_project_wizard(self, project, new_name=False): + return self.ProjectWizard.create( + { + "parent_project_id": project.parent_project_id.id or project.id, + "parent_project_name": project.parent_project_name, + "date_from": project.date_from, + "date_to": project.date_to, + "project_manager_id": project.project_manager_id.id, + "department_id": project.department_id.id, + "line_ids": [(0, 0, {"project_name": new_name})] if new_name else [], + } + ) + + @freeze_time("2001-02-01") + def test_01_project_standard(self): + """Test normally process project""" + self.assertEqual(self.project1.name, "Test Project1") + self.assertEqual(self.project1.parent_project_name, "Test Project1") + self.assertFalse(self.project1.code) + # Change name project, parent project should change it too + self.project1.name = "Test new project" + self.assertEqual(self.project1.name, "Test new project") + self.assertEqual(self.project1.parent_project_name, "Test new project") + # Duplicate project, it should have (copy) at last + project_copy = self.project1.copy() + self.assertEqual(project_copy.name, "Test new project (copy)") + self.assertEqual(project_copy.parent_project_name, "Test new project (copy)") + # Check department should be change following project manager + self.assertEqual(self.project1.department_id, self.dep_admin) + with Form(self.project1) as p: + p.project_manager_id = self.employee_sale + p.save() + self.assertEqual(self.project1.department_id, self.dep_sales) + # Check plan amount when add it in project + self.assertFalse(self.project1.plan_amount) + self.project1.project_plan_ids.create( + { + "project_id": self.project1.id, + "date_from": self.today, + "date_to": self.today, + "amount": 100.0, + } + ) + self.assertEqual(self.project1.plan_amount, 100.0) + self.project1.action_cancel() + self.assertEqual(self.project1.state, "cancel") + self.project1.action_draft() + self.assertEqual(self.project1.state, "draft") + self.project1.action_confirm() + self.assertEqual(self.project1.state, "confirm") + # Check auto close project, if today is more than date to + self.project1.action_auto_expired() + self.assertEqual(self.project1.state, "confirm") + yesterday = self.today - timedelta(days=1) + self.project1.date_to = yesterday + self.project1.action_auto_expired() + self.assertEqual(self.project1.state, "close") + # Check name search with code + res = self.project1.name_search("0001") + self.assertFalse(res) + self.project1.code = "C_TEST000001" + res = self.project1.name_search("0001") + self.assertTrue(res) + + @freeze_time("2001-02-01") + def test_02_split_project(self): + """add code project and search""" + self.project2 = self.project1.copy() + projects = self.project1 + self.project2 + # Not allow split multi project + with self.assertRaises(UserError): + self.ResProject.with_context(active_ids=projects.ids).action_split_project() + split_project_wizard = self.ResProject.with_context( + active_ids=self.project1.ids + ).action_split_project() + self.assertEqual(split_project_wizard["res_model"], "split.project.wizard") + # Create new wizard for split project + self.assertTrue(self.project1.active) + project_wizard = self._create_project_wizard(self.project1) + with self.assertRaises(UserError): + project_wizard.split_project() + project_wizard = self._create_project_wizard(self.project1, "new split1") + new_project_list = project_wizard.split_project() + new_project = self.ResProject.browse(new_project_list["domain"][0][2]) + # Parent project will archive when split project + self.assertFalse(self.project1.active) + self.assertEqual(new_project.name, "new split1") + self.assertEqual(new_project.parent_project_id, self.project1) + self.assertEqual(new_project.parent_project_name, self.project1.name) diff --git a/res_project/views/hr_department_views.xml b/res_project/views/hr_department_views.xml new file mode 100644 index 00000000..90b86997 --- /dev/null +++ b/res_project/views/hr_department_views.xml @@ -0,0 +1,17 @@ + + + + hr.department.form + hr.department + + + + + + + + diff --git a/res_project/views/hr_employee_views.xml b/res_project/views/hr_employee_views.xml new file mode 100644 index 00000000..2d38f5b6 --- /dev/null +++ b/res_project/views/hr_employee_views.xml @@ -0,0 +1,15 @@ + + + + hr.employee.form + hr.employee + + + + + + + + + + diff --git a/res_project/views/res_project_menuitem.xml b/res_project/views/res_project_menuitem.xml new file mode 100644 index 00000000..1a6fd3ce --- /dev/null +++ b/res_project/views/res_project_menuitem.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/res_project/views/res_project_split.xml b/res_project/views/res_project_split.xml new file mode 100644 index 00000000..ccc22c7b --- /dev/null +++ b/res_project/views/res_project_split.xml @@ -0,0 +1,11 @@ + + + + Split Project + + + form + code + action = model.action_split_project() + + diff --git a/res_project/views/res_project_views.xml b/res_project/views/res_project_views.xml new file mode 100644 index 00000000..dcc73127 --- /dev/null +++ b/res_project/views/res_project_views.xml @@ -0,0 +1,231 @@ + + + + res.project.tree + res.project + + + + + + + + + + + + + + + res.project.form + res.project + +
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+
+
+ + res.project.select + res.project + + + + + + + + + + + + + + + + + + + + + + + + + + + + Projects + res.project + tree,form + + +

+ No projects found. Let's create one! +

+
+
+ + Confirm + + + code + list + records.action_confirm() + + +
diff --git a/res_project/wizard/__init__.py b/res_project/wizard/__init__.py new file mode 100644 index 00000000..0955a992 --- /dev/null +++ b/res_project/wizard/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import split_project_wizard diff --git a/res_project/wizard/split_project_wizard.py b/res_project/wizard/split_project_wizard.py new file mode 100644 index 00000000..afc3d489 --- /dev/null +++ b/res_project/wizard/split_project_wizard.py @@ -0,0 +1,98 @@ +# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models +from odoo.exceptions import UserError + + +class SplitProjectWizard(models.TransientModel): + _name = "split.project.wizard" + _description = "Split Project Wizard" + + parent_project_id = fields.Many2one( + comodel_name="res.project", + string="Parent", + readonly=True, + required=True, + ) + parent_project_name = fields.Char( + string="Parent Project", + readonly=True, + required=True, + ) + project_manager_id = fields.Many2one( + comodel_name="hr.employee", + string="Project Manager", + readonly=True, + ) + department_id = fields.Many2one( + comodel_name="hr.department", + string="Department", + readonly=True, + required=True, + ) + date_from = fields.Date( + string="Project Start", + readonly=True, + required=True, + ) + date_to = fields.Date( + string="Project End", + readonly=True, + required=True, + ) + member_ids = fields.Many2many( + comodel_name="hr.employee", + string="Member", + readonly=True, + ) + line_ids = fields.One2many( + string="Lines", + comodel_name="split.project.wizard.line", + inverse_name="wizard_id", + ) + + def split_project(self): + self.ensure_one() + if not self.line_ids: + raise UserError(_("Please add a new project name")) + ResProject = self.env["res.project"] + # Archive parent project record + self.parent_project_id.action_archive() + # Create new project record + vals = [line._prepare_project_val() for line in self.line_ids] + new_projects = ResProject.with_context( + split_project=True, # for do something before create project + parent_project_id=self.parent_project_id.id, + ).create(vals) + return { + "name": _("Project"), + "type": "ir.actions.act_window", + "res_model": "res.project", + "view_mode": "tree,form", + "context": self.env.context, + "domain": [("id", "in", new_projects.ids)], + } + + +class SplitProjectWizardLine(models.TransientModel): + _name = "split.project.wizard.line" + _description = "Split Project Wizard Line" + + wizard_id = fields.Many2one(comodel_name="split.project.wizard") + project_name = fields.Char() + + def _prepare_project_val(self): + self.ensure_one() + wizard = self.wizard_id + return { + "name": self.project_name, + "parent_project_id": wizard.parent_project_id.id, + "parent_project_name": wizard.parent_project_name, + "date_from": wizard.date_from, + "date_to": wizard.date_to, + "project_manager_id": wizard.project_manager_id.id, + "department_id": wizard.department_id.id, + "company_id": self.env.company.id, + "member_ids": [(6, 0, wizard.member_ids.ids)], + } diff --git a/res_project/wizard/split_project_wizard_view.xml b/res_project/wizard/split_project_wizard_view.xml new file mode 100644 index 00000000..2706c59a --- /dev/null +++ b/res_project/wizard/split_project_wizard_view.xml @@ -0,0 +1,43 @@ + + + + split.project.wizard.form + split.project.wizard + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
From 693492ba6f554635c7edf230da2f309b1c1c89ef Mon Sep 17 00:00:00 2001 From: Saran440 Date: Thu, 12 Jan 2023 15:27:38 +0700 Subject: [PATCH 04/11] [ADD] res_project_sequence --- res_project/tests/__init__.py | 1 + res_project/tests/common.py | 48 +++++++++++++++++++++++++++ res_project/tests/test_res_project.py | 46 ++++--------------------- 3 files changed, 55 insertions(+), 40 deletions(-) create mode 100644 res_project/tests/common.py diff --git a/res_project/tests/__init__.py b/res_project/tests/__init__.py index 394ee0dd..20e9253d 100644 --- a/res_project/tests/__init__.py +++ b/res_project/tests/__init__.py @@ -1,3 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import common from . import test_res_project diff --git a/res_project/tests/common.py b/res_project/tests/common.py new file mode 100644 index 00000000..37844937 --- /dev/null +++ b/res_project/tests/common.py @@ -0,0 +1,48 @@ +# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class ResProjectCommon(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ResProject = cls.env["res.project"] + cls.ProjectWizard = cls.env["split.project.wizard"] + cls.dep_admin = cls.env.ref("hr.dep_administration") + cls.dep_sales = cls.env.ref("hr.dep_sales") + cls.employee_sale = cls.env.ref("hr.employee_lur") + + def _create_res_project( + self, + name, + department_id, + date_from, + date_to, + code=False, + parent_project_id=False, + ): + return self.ResProject.create( + { + "name": name, + "code": code, + "parent_project_id": parent_project_id, + "department_id": department_id, + "date_from": date_from, + "date_to": date_to, + } + ) + + def _create_project_wizard(self, project, new_name=False): + return self.ProjectWizard.create( + { + "parent_project_id": project.parent_project_id.id or project.id, + "parent_project_name": project.parent_project_name, + "date_from": project.date_from, + "date_to": project.date_to, + "project_manager_id": project.project_manager_id.id, + "department_id": project.department_id.id, + "line_ids": [(0, 0, {"project_name": new_name})] if new_name else [], + } + ) diff --git a/res_project/tests/test_res_project.py b/res_project/tests/test_res_project.py index 1a0e0903..b75016cd 100644 --- a/res_project/tests/test_res_project.py +++ b/res_project/tests/test_res_project.py @@ -6,19 +6,18 @@ from freezegun import freeze_time from odoo.exceptions import UserError -from odoo.tests.common import Form, TransactionCase +from odoo.tests import tagged +from odoo.tests.common import Form +from .common import ResProjectCommon -class ResProject(TransactionCase): + +@tagged("post_install", "-at_install") +class ResProject(ResProjectCommon): @classmethod @freeze_time("2001-02-01") def setUpClass(cls): super().setUpClass() - cls.ResProject = cls.env["res.project"] - cls.ProjectWizard = cls.env["split.project.wizard"] - cls.dep_admin = cls.env.ref("hr.dep_administration") - cls.dep_sales = cls.env.ref("hr.dep_sales") - cls.employee_sale = cls.env.ref("hr.employee_lur") cls.today = datetime.today() cls.project1 = cls._create_res_project( cls, @@ -28,39 +27,6 @@ def setUpClass(cls): cls.today, ) - def _create_res_project( - self, - name, - department_id, - date_from, - date_to, - code=False, - parent_project_id=False, - ): - return self.ResProject.create( - { - "name": name, - "code": code, - "parent_project_id": parent_project_id, - "department_id": department_id, - "date_from": date_from, - "date_to": date_to, - } - ) - - def _create_project_wizard(self, project, new_name=False): - return self.ProjectWizard.create( - { - "parent_project_id": project.parent_project_id.id or project.id, - "parent_project_name": project.parent_project_name, - "date_from": project.date_from, - "date_to": project.date_to, - "project_manager_id": project.project_manager_id.id, - "department_id": project.department_id.id, - "line_ids": [(0, 0, {"project_name": new_name})] if new_name else [], - } - ) - @freeze_time("2001-02-01") def test_01_project_standard(self): """Test normally process project""" From 92a54c318b7826b23f9af24977cc5114a02a4012 Mon Sep 17 00:00:00 2001 From: Saran440 Date: Fri, 13 Jan 2023 10:59:08 +0700 Subject: [PATCH 05/11] [ADD] budget_res_project_department --- res_project/views/res_project_views.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/res_project/views/res_project_views.xml b/res_project/views/res_project_views.xml index dcc73127..de44e86a 100644 --- a/res_project/views/res_project_views.xml +++ b/res_project/views/res_project_views.xml @@ -214,11 +214,15 @@ - Confirm + Confirm Project code list + records.action_confirm() Date: Tue, 14 Mar 2023 17:49:30 +0700 Subject: [PATCH 06/11] [FIX] confirmation before close/cancel project --- res_project/views/res_project_views.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res_project/views/res_project_views.xml b/res_project/views/res_project_views.xml index de44e86a..f2b17dac 100644 --- a/res_project/views/res_project_views.xml +++ b/res_project/views/res_project_views.xml @@ -42,6 +42,7 @@ string="Close" class="oe_highlight" attrs="{'invisible':[('state', '!=', 'confirm')]}" + confirm="This action will close Project. Are you sure to continue?" groups="res_project.group_res_project_manager" />