v0.4.1: Add parent_id to create_project; link_drive_document_to_project; 4 Cybrosys checklist tools (97 total)

This commit is contained in:
2026-05-29 18:04:37 -05:00
parent f63fe2ccf1
commit f2ca479565
+130 -2
View File
@@ -714,16 +714,20 @@ def list_task_stages(project_id: int = None) -> list:
@mcp.tool() @mcp.tool()
def create_project(name: str, description: str = "", user_id: int = None, def create_project(name: str, description: str = "", user_id: int = None,
date_start: str = "", date: str = "", date_start: str = "", date: str = "",
privacy_visibility: str = "employees") -> int: privacy_visibility: str = "employees",
parent_id: int = None) -> int:
"""Create a new project. Returns the new project ID. """Create a new project. Returns the new project ID.
parent_id: ID of the parent project.project — use to create sub-projects within a programme.
privacy_visibility: 'employees' (all employees), 'portal' (employees + invited portal users), privacy_visibility: 'employees' (all employees), 'portal' (employees + invited portal users),
'followers' (invited internal users only). 'followers' (invited internal users only).
Trigger phrases: "create project", "new project", "add a project", "start a project".""" Trigger phrases: "create project", "new project", "add a project", "start a project",
"create sub-project", "create child project"."""
vals: dict = {"name": name} vals: dict = {"name": name}
if description: vals["description"] = description if description: vals["description"] = description
if user_id: vals["user_id"] = user_id if user_id: vals["user_id"] = user_id
if date_start: vals["date_start"] = date_start if date_start: vals["date_start"] = date_start
if date: vals["date"] = date if date: vals["date"] = date
if parent_id: vals["parent_id"] = parent_id
vals["privacy_visibility"] = privacy_visibility or "employees" vals["privacy_visibility"] = privacy_visibility or "employees"
return _create("project.project", vals) return _create("project.project", vals)
@@ -937,6 +941,130 @@ def update_task_stage(stage_id: int, name: str = "", sequence: int = None,
return _write("project.task.type", stage_id, vals) if vals else False return _write("project.task.type", stage_id, vals) if vals else False
# ── Link Drive document to project ───────────────────────────────────────────
@mcp.tool()
def link_drive_document_to_project(project_id: int, drive_url: str,
name: str = "Google Drive Folder") -> int:
"""Attach a Google Drive folder or document URL to a project.project record
as an ir.attachment of type 'url'. The link appears in the project's
Attachments panel in Odoo. Returns the new attachment ID.
Trigger phrases: "link drive folder to project", "attach drive to project",
"add drive link to project", "connect drive folder"."""
return _create("ir.attachment", {
"name": name,
"type": "url",
"url": drive_url,
"res_model": "project.project",
"res_id": project_id,
})
# ── Task Checklists (projects_task_checklists — Cybrosys) ─────────────────────
# Models: task.checklist (templates), checklist.item (template items),
# checklist.item.line (per-task instances). State: todo/in_progress/done/cancel.
# project.task extended with: checklist_id (Many2one), checklists_ids (One2many via projects_id).
@mcp.tool()
def list_checklist_templates(query: str = "", limit: int = 30) -> list:
"""List available task.checklist templates with their items.
Trigger phrases: "checklist templates", "list checklists", "available checklists",
"show checklist templates"."""
domain = [["name", "ilike", query]] if query else []
templates = _search_read("task.checklist", domain,
["name", "description", "checklist_ids"], limit=limit)
for t in templates:
if t.get("checklist_ids"):
t["items"] = _search_read(
"checklist.item",
[["id", "in", t["checklist_ids"]]],
["name", "sequence", "description"],
limit=200,
)
return templates
@mcp.tool()
def get_task_checklists(task_id: int) -> list:
"""Get all checklist item lines (checklist.item.line) attached to a task,
with state and item name. State values: 'todo', 'in_progress', 'done', 'cancel'.
Task progress % is recomputed automatically from these states in Odoo.
Trigger phrases: "task checklist", "checklist items", "checklist status",
"acceptance criteria", "phase checklist", "what's checked off"."""
return _search_read(
"checklist.item.line",
[["projects_id", "=", task_id]],
["check_list_item_id", "description", "checklist_id", "state"],
limit=200,
)
@mcp.tool()
def add_checklist_to_task(task_id: int, checklist_id: int) -> int:
"""Attach a task.checklist template to a task by creating checklist.item.line records.
Idempotent — items already added for this checklist+task combination are skipped.
Also sets checklist_id on the task to the given template.
Returns count of new lines created.
Use list_checklist_templates to find valid checklist_id values.
NOTE: Replicates the UI onchange since that only fires client-side.
Trigger phrases: "add checklist to task", "attach checklist", "apply checklist",
"add acceptance criteria", "apply phase checklist"."""
# Items already present for this checklist on this task
existing = _search_read(
"checklist.item.line",
[["projects_id", "=", task_id], ["checklist_id", "=", checklist_id]],
["check_list_item_id"],
limit=200,
)
existing_item_ids = {
r["check_list_item_id"][0]
for r in existing
if isinstance(r.get("check_list_item_id"), list)
}
# Template items not yet added
items = _search_read(
"checklist.item",
[["checklist_id", "=", checklist_id]],
["id"],
limit=200,
)
new_items = [it for it in items if it["id"] not in existing_item_ids]
if not new_items:
return 0
commands = [
(0, 0, {"check_list_item_id": it["id"], "checklist_id": checklist_id, "state": "todo"})
for it in new_items
]
_write("project.task", task_id, {
"checklist_id": checklist_id,
"checklists_ids": commands,
})
return len(new_items)
@mcp.tool()
def update_checklist_item(line_id: int, state: str) -> bool:
"""Update the state of a checklist.item.line.
state: 'todo' | 'in_progress' | 'done' | 'cancel'.
Uses Odoo action methods so task progress % and chatter are updated automatically.
Use get_task_checklists to find valid line_id values.
Trigger phrases: "mark checklist item done", "check off item", "mark in progress",
"cancel checklist item", "complete acceptance criteria", "mark phase complete"."""
if state == "in_progress":
_call("checklist.item.line", "action_approve_and_next", [[line_id]], {})
elif state == "done":
_call("checklist.item.line", "action_mark_completed", [[line_id]], {})
elif state == "cancel":
_call("checklist.item.line", "action_mark_canceled", [[line_id]], {})
elif state == "todo":
_write("checklist.item.line", line_id, {"state": "todo"})
else:
raise ValueError(
f"Invalid state '{state}'. Valid values: 'todo', 'in_progress', 'done', 'cancel'."
)
return True
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# HELPDESK # HELPDESK
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════