- Portal: /my/* routes, signup, password reset, portal user support - Email Inbound: IMAP polling (go-imap/v2), thread matching - Discuss: mail.channel, long-polling bus, DM, unread count - Cron: ir.cron runner (goroutine scheduler) - Bank Import, CSV/Excel Import - Automation (ir.actions.server) - Fetchmail service - HR Payroll model - Various fixes across account, sale, stock, purchase, crm, hr, project Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
265 lines
10 KiB
Go
265 lines
10 KiB
Go
package models
|
|
|
|
import (
|
|
"fmt"
|
|
"odoo-go/pkg/orm"
|
|
)
|
|
|
|
// initProjectProject registers the project.project model.
|
|
// Mirrors: odoo/addons/project/models/project_project.py
|
|
func initProjectProject() {
|
|
m := orm.NewModel("project.project", orm.ModelOpts{
|
|
Description: "Project",
|
|
Order: "sequence, name, id",
|
|
})
|
|
|
|
m.AddFields(
|
|
orm.Char("name", orm.FieldOpts{String: "Name", Required: true, Index: true, Translate: true}),
|
|
orm.Text("description", orm.FieldOpts{String: "Description"}),
|
|
orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}),
|
|
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}),
|
|
orm.Many2one("partner_id", "res.partner", orm.FieldOpts{String: "Customer"}),
|
|
orm.Many2one("user_id", "res.users", orm.FieldOpts{String: "Project Manager"}),
|
|
orm.Many2one("company_id", "res.company", orm.FieldOpts{
|
|
String: "Company", Required: true, Index: true,
|
|
}),
|
|
orm.Date("date_start", orm.FieldOpts{String: "Start Date"}),
|
|
orm.Date("date", orm.FieldOpts{String: "Expiration Date"}),
|
|
orm.Many2one("stage_id", "project.task.type", orm.FieldOpts{String: "Stage"}),
|
|
orm.Many2many("favorite_user_ids", "res.users", orm.FieldOpts{String: "Favorite Users"}),
|
|
orm.Integer("task_count", orm.FieldOpts{
|
|
String: "Task Count",
|
|
Compute: "_compute_task_count",
|
|
}),
|
|
orm.Integer("color", orm.FieldOpts{String: "Color Index"}),
|
|
orm.Boolean("allow_task_dependencies", orm.FieldOpts{String: "Task Dependencies"}),
|
|
orm.Boolean("allow_milestones", orm.FieldOpts{String: "Milestones"}),
|
|
)
|
|
}
|
|
|
|
// initProjectTask registers the project.task model.
|
|
// Mirrors: odoo/addons/project/models/project_task.py
|
|
func initProjectTask() {
|
|
task := orm.NewModel("project.task", orm.ModelOpts{
|
|
Description: "Task",
|
|
Order: "priority desc, sequence, id desc",
|
|
})
|
|
|
|
task.AddFields(
|
|
orm.Char("name", orm.FieldOpts{String: "Title", Required: true, Index: true}),
|
|
orm.HTML("description", orm.FieldOpts{String: "Description"}),
|
|
orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}),
|
|
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}),
|
|
orm.Many2one("project_id", "project.project", orm.FieldOpts{String: "Project", Index: true}),
|
|
orm.Many2one("parent_id", "project.task", orm.FieldOpts{String: "Parent Task", Index: true}),
|
|
orm.One2many("child_ids", "project.task", "parent_id", orm.FieldOpts{String: "Sub-tasks"}),
|
|
orm.Many2one("stage_id", "project.task.type", orm.FieldOpts{String: "Stage", Index: true}),
|
|
orm.Selection("state", []orm.SelectionItem{
|
|
{Value: "open", Label: "In Progress"},
|
|
{Value: "done", Label: "Done"},
|
|
{Value: "cancel", Label: "Cancelled"},
|
|
}, orm.FieldOpts{String: "State", Default: "open"}),
|
|
orm.Selection("priority", []orm.SelectionItem{
|
|
{Value: "0", Label: "Normal"},
|
|
{Value: "1", Label: "Important"},
|
|
}, orm.FieldOpts{String: "Priority", Default: "0"}),
|
|
orm.Selection("kanban_state", []orm.SelectionItem{
|
|
{Value: "normal", Label: "In Progress"},
|
|
{Value: "done", Label: "Ready"},
|
|
{Value: "blocked", Label: "Blocked"},
|
|
}, orm.FieldOpts{String: "Kanban State", Default: "normal"}),
|
|
orm.Integer("color", orm.FieldOpts{String: "Color Index"}),
|
|
orm.Many2many("user_ids", "res.users", orm.FieldOpts{String: "Assignees"}),
|
|
orm.Date("date_deadline", orm.FieldOpts{String: "Deadline", Index: true}),
|
|
orm.Datetime("date_assign", orm.FieldOpts{String: "Assigning Date"}),
|
|
orm.Many2many("tag_ids", "project.tags", orm.FieldOpts{String: "Tags"}),
|
|
orm.Many2one("partner_id", "res.partner", orm.FieldOpts{String: "Customer"}),
|
|
orm.Many2one("company_id", "res.company", orm.FieldOpts{
|
|
String: "Company", Required: true, Index: true,
|
|
}),
|
|
orm.Many2one("milestone_id", "project.milestone", orm.FieldOpts{String: "Milestone"}),
|
|
orm.Many2many("depend_ids", "project.task", orm.FieldOpts{String: "Depends On"}),
|
|
orm.Boolean("recurring_task", orm.FieldOpts{String: "Recurrent"}),
|
|
orm.Datetime("planned_date_start", orm.FieldOpts{String: "Planned Start Date"}),
|
|
orm.Datetime("planned_date_end", orm.FieldOpts{String: "Planned End Date"}),
|
|
orm.Selection("display_type", []orm.SelectionItem{
|
|
{Value: "", Label: ""},
|
|
{Value: "line_section", Label: "Section"},
|
|
{Value: "line_note", Label: "Note"},
|
|
}, orm.FieldOpts{String: "Display Type"}),
|
|
)
|
|
|
|
// DefaultGet: set company_id from session
|
|
task.DefaultGet = func(env *orm.Environment, fields []string) orm.Values {
|
|
vals := make(orm.Values)
|
|
if env.CompanyID() > 0 {
|
|
vals["company_id"] = env.CompanyID()
|
|
}
|
|
return vals
|
|
}
|
|
|
|
// action_done: mark task as done
|
|
task.RegisterMethod("action_done", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
|
env := rs.Env()
|
|
for _, id := range rs.IDs() {
|
|
if _, err := env.Tx().Exec(env.Ctx(),
|
|
`UPDATE project_task SET state = 'done' WHERE id = $1`, id); err != nil {
|
|
return nil, fmt.Errorf("project.task: done %d: %w", id, err)
|
|
}
|
|
}
|
|
return true, nil
|
|
})
|
|
|
|
task.RegisterMethod("action_cancel", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
|
env := rs.Env()
|
|
for _, id := range rs.IDs() {
|
|
if _, err := env.Tx().Exec(env.Ctx(),
|
|
`UPDATE project_task SET state = 'cancel' WHERE id = $1`, id); err != nil {
|
|
return nil, fmt.Errorf("project.task: cancel %d: %w", id, err)
|
|
}
|
|
}
|
|
return true, nil
|
|
})
|
|
|
|
task.RegisterMethod("action_reopen", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
|
env := rs.Env()
|
|
for _, id := range rs.IDs() {
|
|
if _, err := env.Tx().Exec(env.Ctx(),
|
|
`UPDATE project_task SET state = 'open' WHERE id = $1`, id); err != nil {
|
|
return nil, fmt.Errorf("project.task: reopen %d: %w", id, err)
|
|
}
|
|
}
|
|
return true, nil
|
|
})
|
|
|
|
task.RegisterMethod("action_blocked", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
|
env := rs.Env()
|
|
for _, id := range rs.IDs() {
|
|
if _, err := env.Tx().Exec(env.Ctx(),
|
|
`UPDATE project_task SET kanban_state = 'blocked' WHERE id = $1`, id); err != nil {
|
|
return nil, fmt.Errorf("project.task: blocked %d: %w", id, err)
|
|
}
|
|
}
|
|
return true, nil
|
|
})
|
|
|
|
task.RegisterMethod("toggle_active", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
|
env := rs.Env()
|
|
for _, id := range rs.IDs() {
|
|
if _, err := env.Tx().Exec(env.Ctx(),
|
|
`UPDATE project_task SET active = NOT active WHERE id = $1`, id); err != nil {
|
|
return nil, fmt.Errorf("project.task: toggle_active %d: %w", id, err)
|
|
}
|
|
}
|
|
return true, nil
|
|
})
|
|
|
|
// Ensure fmt is used
|
|
_ = fmt.Sprintf
|
|
}
|
|
|
|
// initProjectTaskType registers the project.task.type model (stages).
|
|
// Mirrors: odoo/addons/project/models/project_task_type.py
|
|
func initProjectTaskType() {
|
|
m := orm.NewModel("project.task.type", orm.ModelOpts{
|
|
Description: "Task Stage",
|
|
Order: "sequence, id",
|
|
})
|
|
|
|
m.AddFields(
|
|
orm.Char("name", orm.FieldOpts{String: "Name", Required: true, Translate: true}),
|
|
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 1}),
|
|
orm.Boolean("fold", orm.FieldOpts{String: "Folded in Kanban"}),
|
|
orm.Many2many("project_ids", "project.project", orm.FieldOpts{String: "Projects"}),
|
|
)
|
|
}
|
|
|
|
// initProjectMilestone registers the project.milestone model.
|
|
// Mirrors: odoo/addons/project/models/project_milestone.py
|
|
func initProjectMilestone() {
|
|
m := orm.NewModel("project.milestone", orm.ModelOpts{
|
|
Description: "Project Milestone",
|
|
Order: "deadline, is_reached desc, name",
|
|
})
|
|
|
|
m.AddFields(
|
|
orm.Char("name", orm.FieldOpts{String: "Name", Required: true}),
|
|
orm.Many2one("project_id", "project.project", orm.FieldOpts{
|
|
String: "Project", Required: true, OnDelete: orm.OnDeleteCascade, Index: true,
|
|
}),
|
|
orm.Date("deadline", orm.FieldOpts{String: "Deadline"}),
|
|
orm.Boolean("is_reached", orm.FieldOpts{String: "Reached"}),
|
|
)
|
|
}
|
|
|
|
// initProjectTags registers the project.tags model.
|
|
// Mirrors: odoo/addons/project/models/project_tags.py
|
|
func initProjectTags() {
|
|
orm.NewModel("project.tags", orm.ModelOpts{
|
|
Description: "Project Tags",
|
|
Order: "name",
|
|
}).AddFields(
|
|
orm.Char("name", orm.FieldOpts{String: "Name", Required: true, Translate: true}),
|
|
orm.Integer("color", orm.FieldOpts{String: "Color Index"}),
|
|
)
|
|
}
|
|
|
|
// initProjectTaskChecklist registers the project.task.checklist model.
|
|
// Mirrors: odoo/addons/project/models/project_task_checklist.py
|
|
func initProjectTaskChecklist() {
|
|
m := orm.NewModel("project.task.checklist", orm.ModelOpts{
|
|
Description: "Task Checklist Item",
|
|
Order: "sequence, id",
|
|
})
|
|
|
|
m.AddFields(
|
|
orm.Many2one("task_id", "project.task", orm.FieldOpts{
|
|
String: "Task", Required: true, OnDelete: orm.OnDeleteCascade, Index: true,
|
|
}),
|
|
orm.Char("name", orm.FieldOpts{String: "Name", Required: true}),
|
|
orm.Boolean("is_done", orm.FieldOpts{String: "Done", Default: false}),
|
|
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}),
|
|
)
|
|
|
|
// action_toggle_done: Toggle the checklist item done status.
|
|
m.RegisterMethod("action_toggle_done", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
|
env := rs.Env()
|
|
for _, id := range rs.IDs() {
|
|
if _, err := env.Tx().Exec(env.Ctx(),
|
|
`UPDATE project_task_checklist SET is_done = NOT is_done WHERE id = $1`, id); err != nil {
|
|
return nil, fmt.Errorf("project.task.checklist: toggle_done %d: %w", id, err)
|
|
}
|
|
}
|
|
return true, nil
|
|
})
|
|
}
|
|
|
|
// initProjectSharing registers the project.sharing model.
|
|
// Mirrors: odoo/addons/project/models/project_sharing.py
|
|
func initProjectSharing() {
|
|
m := orm.NewModel("project.sharing", orm.ModelOpts{
|
|
Description: "Project Sharing",
|
|
Order: "id",
|
|
})
|
|
|
|
m.AddFields(
|
|
orm.Many2one("partner_id", "res.partner", orm.FieldOpts{
|
|
String: "Partner", Required: true, Index: true,
|
|
}),
|
|
orm.Many2one("project_id", "project.project", orm.FieldOpts{
|
|
String: "Project", Required: true, OnDelete: orm.OnDeleteCascade, Index: true,
|
|
}),
|
|
orm.Selection("access_level", []orm.SelectionItem{
|
|
{Value: "read", Label: "Read"},
|
|
{Value: "edit", Label: "Edit"},
|
|
{Value: "admin", Label: "Admin"},
|
|
}, orm.FieldOpts{String: "Access Level", Required: true, Default: "read"}),
|
|
)
|
|
|
|
m.AddSQLConstraint(
|
|
"unique_partner_project",
|
|
"UNIQUE(partner_id, project_id)",
|
|
"A partner can only have one sharing entry per project.",
|
|
)
|
|
}
|