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:
+130
-2
@@ -714,16 +714,20 @@ def list_task_stages(project_id: int = None) -> list:
|
||||
@mcp.tool()
|
||||
def create_project(name: str, description: str = "", user_id: int = None,
|
||||
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.
|
||||
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),
|
||||
'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}
|
||||
if description: vals["description"] = description
|
||||
if user_id: vals["user_id"] = user_id
|
||||
if date_start: vals["date_start"] = date_start
|
||||
if date: vals["date"] = date
|
||||
if parent_id: vals["parent_id"] = parent_id
|
||||
vals["privacy_visibility"] = privacy_visibility or "employees"
|
||||
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
|
||||
|
||||
|
||||
# ── 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
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user