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

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

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

View file

@ -0,0 +1,3 @@
from . import project_sprint
from . import project
from . import project_task

View file

@ -0,0 +1,65 @@
from odoo import _, fields, models
class ProjectProject(models.Model):
_inherit = "project.project"
sprint_ids = fields.One2many(
comodel_name="project.sprint",
inverse_name="project_id",
string="Sprints",
)
sprint_count = fields.Integer(compute="_compute_sprint_count")
backlog_count = fields.Integer(compute="_compute_backlog_count")
def _compute_backlog_count(self):
for project in self:
project.backlog_count = len(
project.task_ids.filtered(
lambda task: not task.sprint_id and task.kanban_state != "done"
)
)
def _compute_sprint_count(self):
unassigned_sprint_count = self.env["project.sprint"].search(
[("project_id", "=", False)], count=True
)
for project in self:
project.sprint_count = len(project.sprint_ids) + unassigned_sprint_count
def action_sprints(self):
self.ensure_one()
return {
"name": _("Sprints"),
"type": "ir.actions.act_window",
"res_model": "project.sprint",
"view_mode": "tree,form,timeline",
"domain": ["|", ("project_id", "=", self.id), ("project_id", "=", False)],
"context": {"default_project_id": self.id},
}
def action_backlog(self):
self.ensure_one()
return {
"name": _("Backlog"),
"type": "ir.actions.act_window",
"res_model": "project.task",
"view_mode": "tree,form",
"domain": [
("project_id", "=", self.id),
("sprint_id", "=", False),
("kanban_state", "!=", "done"),
],
"context": {"default_project_id": self.id},
}
def action_sprint_timeline(self):
self.ensure_one()
return {
"name": _("Sprint Timeline"),
"type": "ir.actions.act_window",
"res_model": "project.task",
"view_mode": "timeline",
"domain": [("project_id", "=", self.id), ("sprint_id", "!=", False)],
"context": {"default_project_id": self.id, "no_create": True},
}

View file

@ -0,0 +1,132 @@
from dateutil.relativedelta import relativedelta
from odoo import _, api, fields, models
DATE_OPTIONS = [
("1_weeks", _("1 Week")),
("2_weeks", _("2 Weeks")),
("1_months", _("1 Month")),
("2_months", _("2 Month")),
("1_years", _("1 Year")),
("2_years", _("2 Years")),
("custom", _("Custom")),
]
class ProjectSprint(models.Model):
_name = "project.sprint"
_inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Project Sprint"
_sql_constraints = [
(
"date_check",
"CHECK (date_start <= date_end)",
_("Error: End date must be greater than start date!"),
),
]
name = fields.Char(required=True, tracking=True)
user_ids = fields.Many2many(
comodel_name="res.users",
string="Members",
required=True,
domain="[('share', '=', False), ('active', '=', True)]",
tracking=True,
relation="project_sprint_user_rel",
)
description = fields.Text(tracking=True)
project_id = fields.Many2one(
comodel_name="project.project",
string="Project",
tracking=True,
)
task_ids = fields.One2many(
comodel_name="project.task",
inverse_name="sprint_id",
string="Tasks",
domain="[('project_id', '=', project_id)]",
)
date_start = fields.Date(
string="Start Date", default=fields.Date.today, required=True
)
date_option = fields.Selection(
selection=DATE_OPTIONS, default=DATE_OPTIONS[0][0], required=True
)
date_end = fields.Date(
string="End Date",
required=True,
compute="_compute_date_end",
store=True,
readonly=False,
)
state = fields.Selection(
selection=[
("draft", "Draft"),
("in_progress", "In progress"),
("done", "Done"),
],
default="draft",
)
tasks_count = fields.Integer(compute="_compute_tasks_count")
def _compute_tasks_count(self):
for sprint in self:
sprint.tasks_count = len(sprint.task_ids)
def action_start(self):
self.write({"state": "in_progress"})
def action_done(self):
self.write({"state": "done"})
self._check_task_state()
def action_tasks(self):
self.ensure_one()
return {
"name": _("Tasks"),
"type": "ir.actions.act_window",
"res_model": "project.task",
"view_mode": "tree,form",
"domain": [("sprint_id", "=", self.id)],
"context": {
"default_project_id": self.project_id.id,
"default_sprint_id": self.id,
},
}
@api.model
def cron_update_sprint_state(self):
date = fields.Date.today()
for sprint in self.search([("state", "=", "draft")]):
if date >= sprint.date_start:
sprint.write({"state": "in_progress"})
for sprint in self.search([("state", "=", "in_progress")]):
if date >= sprint.date_end:
sprint.write({"state": "done"})
sprint._check_task_state()
def _check_task_state(self):
self.ensure_one()
in_progress_sprints = self.project_id.sprint_ids.filtered(
lambda sprint: sprint.state == "in_progress"
)
self.task_ids.filtered(lambda task: task.kanban_state != "done").write(
{
"sprint_id": (
in_progress_sprints[0].id if in_progress_sprints else False
),
"user_ids": False,
}
)
@api.depends("date_start", "date_option")
def _compute_date_end(self):
for record in self:
if record.date_option != "custom":
num, interval = record.date_option.split("_")
record.date_end = record.date_start + relativedelta(
**{interval: int(num)}
)
else:
record.date_end = record.date_start + relativedelta(days=1)

View file

@ -0,0 +1,30 @@
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ProjectTask(models.Model):
_inherit = "project.task"
sprint_id = fields.Many2one(
comodel_name="project.sprint",
string="Sprint",
tracking=True,
domain="['|', ('project_id', '=', False), ('project_id', '=', project_id)]",
)
sprint_state = fields.Selection(
related="sprint_id.state", string="Sprint State", store=True
)
@api.constrains("user_ids")
def _check_user_ids(self):
for task in self:
if task.user_ids and task.sprint_id:
if not task.user_ids <= task.sprint_id.user_ids:
raise ValidationError(
_("The assignees must be part of the sprint.")
)
@api.onchange("sprint_id")
def _onchange_sprint_id(self):
self.user_ids = False