feat: Portal, Email Inbound, Discuss + module improvements
- 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>
This commit is contained in:
@@ -80,6 +80,8 @@ func initProjectTask() {
|
||||
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"},
|
||||
@@ -100,38 +102,54 @@ func initProjectTask() {
|
||||
task.RegisterMethod("action_done", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE project_task SET state = 'done' WHERE id = $1`, id)
|
||||
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
|
||||
})
|
||||
|
||||
// action_cancel: mark task as cancelled
|
||||
task.RegisterMethod("action_cancel", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE project_task SET state = 'cancel' WHERE id = $1`, id)
|
||||
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
|
||||
})
|
||||
|
||||
// action_reopen: reopen a cancelled/done task
|
||||
task.RegisterMethod("action_reopen", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE project_task SET state = 'open' WHERE id = $1`, id)
|
||||
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
|
||||
})
|
||||
|
||||
// action_blocked: set kanban state to blocked
|
||||
task.RegisterMethod("action_blocked", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE project_task SET kanban_state = 'blocked' WHERE id = $1`, id)
|
||||
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
|
||||
})
|
||||
@@ -185,3 +203,62 @@ func initProjectTags() {
|
||||
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.",
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user