diff --git a/ai_oca_bridge_project/README.rst b/ai_oca_bridge_project/README.rst
new file mode 100644
index 0000000..f8019d9
--- /dev/null
+++ b/ai_oca_bridge_project/README.rst
@@ -0,0 +1,120 @@
+=====================
+AI OCA Bridge Project
+=====================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:7db759bf0f80da243772d180303cb0e39243db662bf554ee4adbe340d61c2d92
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fai-lightgray.png?logo=github
+ :target: https://github.com/OCA/ai/tree/16.0/ai_oca_bridge_project
+ :alt: OCA/ai
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/ai-16-0/ai-16-0-ai_oca_bridge_project
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/ai&target_branch=16.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This module is intended to allow external AI systems to perform actions
+with the correct context, based on triggers related to Project
+management.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Configuration
+=============
+
+- **Bridge with ``Usage = "AI Thread Create"``** Processes new projects
+ and tasks in the external system for AI-enhanced actions like project
+ analysis, task prioritization, or automated project management.
+
+- **Bridge with ``Usage = "AI Thread Write"``** Updates project and task
+ information in the external system when they are modified in Odoo.
+
+- **Bridge with ``Usage = "AI Thread Unlink"``** Removes project and
+ task data from the external system when they are deleted from Odoo.
+
+For creating those bridges, apart from the usage of the bridge, the user
+must define:
+
+- Payload Type: it depends on the endpoint configuration, normally
+ "Record" would work.
+- Result Type: depending on your use case.
+- Model: select the "Project" or "Task" model
+- Field: add at least the fields the endpoint is expecting (e.g., name,
+ description, stage, assignees, etc.).
+- Filter: add a domain for using the bridge only with the projects/tasks
+ intended to trigger automatic actions
+
+Usage
+=====
+
+Depending on the bridges you have created, create, update or delete a
+project or task record matching the bridge domain. You should see the
+execution on the AI Bridge Execution menu.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* Escodoo
+
+Contributors
+------------
+
+- `Escodoo `__:
+
+ - Marcel Savegnago
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-marcelsavegnago| image:: https://github.com/marcelsavegnago.png?size=40px
+ :target: https://github.com/marcelsavegnago
+ :alt: marcelsavegnago
+
+Current `maintainer `__:
+
+|maintainer-marcelsavegnago|
+
+This module is part of the `OCA/ai `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/ai_oca_bridge_project/__init__.py b/ai_oca_bridge_project/__init__.py
new file mode 100644
index 0000000..0650744
--- /dev/null
+++ b/ai_oca_bridge_project/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/ai_oca_bridge_project/__manifest__.py b/ai_oca_bridge_project/__manifest__.py
new file mode 100644
index 0000000..9266cde
--- /dev/null
+++ b/ai_oca_bridge_project/__manifest__.py
@@ -0,0 +1,21 @@
+# Copyright 2025 Escodoo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+{
+ "name": "AI OCA Bridge Project",
+ "summary": """Adds Project triggers for AI Bridges""",
+ "version": "16.0.1.0.0",
+ "license": "AGPL-3",
+ "author": "Escodoo,Odoo Community Association (OCA)",
+ "maintainers": ["marcelsavegnago"],
+ "category": "AI",
+ "website": "https://github.com/OCA/ai",
+ "depends": [
+ "ai_oca_bridge",
+ "project",
+ ],
+ "data": [],
+ "demo": [
+ "demo/ai_bridge_demo.xml",
+ ],
+}
diff --git a/ai_oca_bridge_project/demo/ai_bridge_demo.xml b/ai_oca_bridge_project/demo/ai_bridge_demo.xml
new file mode 100644
index 0000000..3a1594a
--- /dev/null
+++ b/ai_oca_bridge_project/demo/ai_bridge_demo.xml
@@ -0,0 +1,114 @@
+
+
+
+ Project AI Bridge - Create
+
+ This AI bridge is triggered when a new project is created.
+ ]]>
+
+
+ ai_thread_create
+ https://api.example.com/ai/project/create
+ none
+ record
+ none
+ immediate
+
+
+
+
+ Project AI Bridge - Update
+
+ This AI bridge is triggered when a project is updated.
+ ]]>
+
+
+ ai_thread_write
+ https://api.example.com/ai/project/update
+ none
+ record
+ none
+ immediate
+
+
+
+
+ Project AI Bridge - Delete
+
+ This AI bridge is triggered when a project is deleted.
+ ]]>
+
+
+ ai_thread_unlink
+ https://api.example.com/ai/project/delete
+ none
+ none
+ none
+ immediate
+
+
+
+ Task AI Bridge - Create
+
+ This AI bridge is triggered when a new task is created.
+ ]]>
+
+
+ ai_thread_create
+ https://api.example.com/ai/task/create
+ none
+ record
+ none
+ immediate
+
+
+
+
+ Task AI Bridge - Update
+
+ This AI bridge is triggered when a task is updated.
+ ]]>
+
+
+ ai_thread_write
+ https://api.example.com/ai/task/update
+ none
+ record
+ none
+ immediate
+
+
+
+
+ Task AI Bridge - Delete
+
+ This AI bridge is triggered when a task is deleted.
+ ]]>
+
+
+ ai_thread_unlink
+ https://api.example.com/ai/task/delete
+ none
+ none
+ none
+ immediate
+
+
diff --git a/ai_oca_bridge_project/models/__init__.py b/ai_oca_bridge_project/models/__init__.py
new file mode 100644
index 0000000..2128188
--- /dev/null
+++ b/ai_oca_bridge_project/models/__init__.py
@@ -0,0 +1,2 @@
+from . import project_project
+from . import project_task
diff --git a/ai_oca_bridge_project/models/project_project.py b/ai_oca_bridge_project/models/project_project.py
new file mode 100644
index 0000000..9055216
--- /dev/null
+++ b/ai_oca_bridge_project/models/project_project.py
@@ -0,0 +1,6 @@
+from odoo import models
+
+
+class ProjectProject(models.Model):
+ _inherit = ["project.project", "ai.bridge.thread"]
+ _name = "project.project"
diff --git a/ai_oca_bridge_project/models/project_task.py b/ai_oca_bridge_project/models/project_task.py
new file mode 100644
index 0000000..2615b49
--- /dev/null
+++ b/ai_oca_bridge_project/models/project_task.py
@@ -0,0 +1,6 @@
+from odoo import models
+
+
+class ProjectTask(models.Model):
+ _inherit = ["project.task", "ai.bridge.thread"]
+ _name = "project.task"
diff --git a/ai_oca_bridge_project/readme/CONFIGURE.md b/ai_oca_bridge_project/readme/CONFIGURE.md
new file mode 100644
index 0000000..28648d4
--- /dev/null
+++ b/ai_oca_bridge_project/readme/CONFIGURE.md
@@ -0,0 +1,15 @@
+- **Bridge with `Usage = "AI Thread Create"`**
+ Processes new projects and tasks in the external system for AI-enhanced actions like project analysis, task prioritization, or automated project management.
+
+- **Bridge with `Usage = "AI Thread Write"`**
+ Updates project and task information in the external system when they are modified in Odoo.
+
+- **Bridge with `Usage = "AI Thread Unlink"`**
+ Removes project and task data from the external system when they are deleted from Odoo.
+
+For creating those bridges, apart from the usage of the bridge, the user must define:
+- Payload Type: it depends on the endpoint configuration, normally "Record" would work.
+- Result Type: depending on your use case.
+- Model: select the "Project" or "Task" model
+- Field: add at least the fields the endpoint is expecting (e.g., name, description, stage, assignees, etc.).
+- Filter: add a domain for using the bridge only with the projects/tasks intended to trigger automatic actions
diff --git a/ai_oca_bridge_project/readme/CONTRIBUTORS.md b/ai_oca_bridge_project/readme/CONTRIBUTORS.md
new file mode 100644
index 0000000..f618905
--- /dev/null
+++ b/ai_oca_bridge_project/readme/CONTRIBUTORS.md
@@ -0,0 +1,2 @@
+- [Escodoo](https://www.escodoo.com.br):
+ - Marcel Savegnago \
diff --git a/ai_oca_bridge_project/readme/DESCRIPTION.md b/ai_oca_bridge_project/readme/DESCRIPTION.md
new file mode 100644
index 0000000..eddce8a
--- /dev/null
+++ b/ai_oca_bridge_project/readme/DESCRIPTION.md
@@ -0,0 +1 @@
+This module is intended to allow external AI systems to perform actions with the correct context, based on triggers related to Project management.
diff --git a/ai_oca_bridge_project/readme/USAGE.md b/ai_oca_bridge_project/readme/USAGE.md
new file mode 100644
index 0000000..328178a
--- /dev/null
+++ b/ai_oca_bridge_project/readme/USAGE.md
@@ -0,0 +1 @@
+Depending on the bridges you have created, create, update or delete a project or task record matching the bridge domain. You should see the execution on the AI Bridge Execution menu.
diff --git a/ai_oca_bridge_project/static/description/icon.png b/ai_oca_bridge_project/static/description/icon.png
new file mode 100644
index 0000000..4adee3c
Binary files /dev/null and b/ai_oca_bridge_project/static/description/icon.png differ
diff --git a/ai_oca_bridge_project/static/description/index.html b/ai_oca_bridge_project/static/description/index.html
new file mode 100644
index 0000000..de4484c
--- /dev/null
+++ b/ai_oca_bridge_project/static/description/index.html
@@ -0,0 +1,462 @@
+
+
+
+
+
+AI OCA Bridge Project
+
+
+
+
+
AI OCA Bridge Project
+
+
+

+
This module is intended to allow external AI systems to perform actions
+with the correct context, based on triggers related to Project
+management.
+
Table of contents
+
+
+
+
+- Bridge with ``Usage = “AI Thread Create”`` Processes new projects
+and tasks in the external system for AI-enhanced actions like project
+analysis, task prioritization, or automated project management.
+- Bridge with ``Usage = “AI Thread Write”`` Updates project and task
+information in the external system when they are modified in Odoo.
+- Bridge with ``Usage = “AI Thread Unlink”`` Removes project and
+task data from the external system when they are deleted from Odoo.
+
+
For creating those bridges, apart from the usage of the bridge, the user
+must define:
+
+- Payload Type: it depends on the endpoint configuration, normally
+“Record” would work.
+- Result Type: depending on your use case.
+- Model: select the “Project” or “Task” model
+- Field: add at least the fields the endpoint is expecting (e.g., name,
+description, stage, assignees, etc.).
+- Filter: add a domain for using the bridge only with the projects/tasks
+intended to trigger automatic actions
+
+
+
+
+
Depending on the bridges you have created, create, update or delete a
+project or task record matching the bridge domain. You should see the
+execution on the AI Bridge Execution menu.
+
+
+
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
+
This module is maintained by the OCA.
+
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
Current maintainer:
+

+
This module is part of the OCA/ai project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
+
+
+
+
+
diff --git a/ai_oca_bridge_project/tests/__init__.py b/ai_oca_bridge_project/tests/__init__.py
new file mode 100644
index 0000000..d60f863
--- /dev/null
+++ b/ai_oca_bridge_project/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_project_ai_bridge
diff --git a/ai_oca_bridge_project/tests/test_project_ai_bridge.py b/ai_oca_bridge_project/tests/test_project_ai_bridge.py
new file mode 100644
index 0000000..2c29bc8
--- /dev/null
+++ b/ai_oca_bridge_project/tests/test_project_ai_bridge.py
@@ -0,0 +1,440 @@
+# Copyright 2025 Escodoo
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from unittest import mock
+
+from odoo.tests.common import TransactionCase
+
+
+class TestProjectAiBridge(TransactionCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+
+ cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
+
+ # Create necessary Project data
+ cls.project = cls.env["project.project"].create(
+ {
+ "name": "Test Project",
+ "description": "Test project description",
+ }
+ )
+
+ cls.bridge_create = cls.env["ai.bridge"].create(
+ {
+ "name": "Project AI Bridge - Create",
+ "description": "Test bridge for project creation
",
+ "model_id": cls.env.ref("project.model_project_project").id,
+ "usage": "ai_thread_create",
+ "url": "https://api.example.com/ai/project/create",
+ "auth_type": "none",
+ "payload_type": "record",
+ "result_type": "none",
+ "result_kind": "immediate",
+ "field_ids": [
+ (
+ 6,
+ 0,
+ [
+ cls.env.ref("project.field_project_project__name").id,
+ cls.env.ref("project.field_project_project__stage_id").id,
+ ],
+ )
+ ],
+ }
+ )
+
+ cls.bridge_write = cls.env["ai.bridge"].create(
+ {
+ "name": "Project AI Bridge - Update",
+ "description": "Test bridge for project updates
",
+ "model_id": cls.env.ref("project.model_project_project").id,
+ "usage": "ai_thread_write",
+ "url": "https://api.example.com/ai/project/update",
+ "auth_type": "none",
+ "payload_type": "record",
+ "result_type": "none",
+ "result_kind": "immediate",
+ "field_ids": [
+ (
+ 6,
+ 0,
+ [
+ cls.env.ref("project.field_project_project__name").id,
+ cls.env.ref("project.field_project_project__stage_id").id,
+ ],
+ )
+ ],
+ }
+ )
+
+ cls.bridge_unlink = cls.env["ai.bridge"].create(
+ {
+ "name": "Project AI Bridge - Delete",
+ "description": "Test bridge for project deletion
",
+ "model_id": cls.env.ref("project.model_project_project").id,
+ "usage": "ai_thread_unlink",
+ "url": "https://api.example.com/ai/project/delete",
+ "auth_type": "none",
+ "payload_type": "none",
+ "result_type": "none",
+ "result_kind": "immediate",
+ }
+ )
+
+ # Create bridges for tasks
+ cls.bridge_task_create = cls.env["ai.bridge"].create(
+ {
+ "name": "Task AI Bridge - Create",
+ "description": "Test bridge for task creation
",
+ "model_id": cls.env.ref("project.model_project_task").id,
+ "usage": "ai_thread_create",
+ "url": "https://api.example.com/ai/task/create",
+ "auth_type": "none",
+ "payload_type": "record",
+ "result_type": "none",
+ "result_kind": "immediate",
+ "field_ids": [
+ (
+ 6,
+ 0,
+ [
+ cls.env.ref("project.field_project_task__name").id,
+ cls.env.ref("project.field_project_task__stage_id").id,
+ ],
+ )
+ ],
+ }
+ )
+
+ cls.bridge_task_write = cls.env["ai.bridge"].create(
+ {
+ "name": "Task AI Bridge - Update",
+ "description": "Test bridge for task updates
",
+ "model_id": cls.env.ref("project.model_project_task").id,
+ "usage": "ai_thread_write",
+ "url": "https://api.example.com/ai/task/update",
+ "auth_type": "none",
+ "payload_type": "record",
+ "result_type": "none",
+ "result_kind": "immediate",
+ "field_ids": [
+ (
+ 6,
+ 0,
+ [
+ cls.env.ref("project.field_project_task__name").id,
+ cls.env.ref("project.field_project_task__stage_id").id,
+ ],
+ )
+ ],
+ }
+ )
+
+ cls.bridge_task_unlink = cls.env["ai.bridge"].create(
+ {
+ "name": "Task AI Bridge - Delete",
+ "description": "Test bridge for task deletion
",
+ "model_id": cls.env.ref("project.model_project_task").id,
+ "usage": "ai_thread_unlink",
+ "url": "https://api.example.com/ai/task/delete",
+ "auth_type": "none",
+ "payload_type": "none",
+ "result_type": "none",
+ "result_kind": "immediate",
+ }
+ )
+
+ def test_project_create_bridge(self):
+ with mock.patch("requests.post") as mock_post:
+ mock_post.return_value.status_code = 200
+ mock_post.return_value.json.return_value = {"message": "Project created"}
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_create.id)]
+ ),
+ )
+ project = self.env["project.project"].create(
+ {
+ "name": "Test Project for Bridge",
+ "description": "Test project description for bridge",
+ }
+ )
+ executions = self.env["ai.bridge.execution"].search(
+ [("ai_bridge_id", "=", self.bridge_create.id)]
+ )
+ self.assertEqual(len(executions), 1)
+ args, kwargs = mock_post.call_args
+ self.assertEqual(args[0], "https://api.example.com/ai/project/create")
+ record = kwargs["json"].get("record", {})
+ self.assertEqual(record.get("id"), project.id)
+ self.assertEqual(record.get("name"), "Test Project for Bridge")
+
+ def test_project_write_bridge(self):
+ self.bridge_create.active = False
+ project = self.env["project.project"].create(
+ {
+ "name": "Test Project for Update",
+ "description": "Test project description for update",
+ }
+ )
+ self.bridge_create.active = True
+ with mock.patch("requests.post") as mock_post:
+ mock_post.return_value.status_code = 200
+ mock_post.return_value.json.return_value = {"message": "Project updated"}
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_write.id)]
+ ),
+ )
+ project.write(
+ {
+ "name": "Updated Project",
+ "description": "Updated project description",
+ }
+ )
+ executions = self.env["ai.bridge.execution"].search(
+ [("ai_bridge_id", "=", self.bridge_write.id)]
+ )
+ self.assertEqual(len(executions), 1)
+ args, kwargs = mock_post.call_args
+ self.assertEqual(args[0], "https://api.example.com/ai/project/update")
+ record = kwargs["json"].get("record", {})
+ self.assertEqual(record.get("id"), project.id)
+ self.assertEqual(record.get("name"), "Updated Project")
+
+ def test_project_unlink_bridge(self):
+ self.bridge_create.active = False
+ project = self.env["project.project"].create(
+ {
+ "name": "Test Project for Deletion",
+ "description": "Test project description for deletion",
+ }
+ )
+ self.bridge_create.active = True
+ project_id = project.id
+ with mock.patch("requests.post") as mock_post:
+ mock_post.return_value.status_code = 200
+ mock_post.return_value.json.return_value = {"message": "Project deleted"}
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_unlink.id)]
+ ),
+ )
+ project.unlink()
+ executions = self.env["ai.bridge.execution"].search(
+ [("ai_bridge_id", "=", self.bridge_unlink.id)]
+ )
+ self.assertEqual(len(executions), 1)
+ args, kwargs = mock_post.call_args
+ self.assertEqual(args[0], "https://api.example.com/ai/project/delete")
+ self.assertEqual(kwargs["json"].get("_id", False), project_id)
+
+ def test_all_bridges_together(self):
+ with mock.patch("requests.post") as mock_post:
+ mock_post.return_value.status_code = 200
+ mock_post.return_value.json.return_value = {"message": "Success"}
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_create.id)]
+ ),
+ )
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_write.id)]
+ ),
+ )
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_unlink.id)]
+ ),
+ )
+ project = self.env["project.project"].create(
+ {
+ "name": "Complete Test Project",
+ "description": "Complete test project description",
+ }
+ )
+ project.write(
+ {
+ "name": "Updated Complete Test Project",
+ "description": "Updated complete test project description",
+ }
+ )
+ project.unlink()
+
+ self.assertEqual(
+ 1,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_create.id)]
+ ),
+ )
+ self.assertEqual(
+ 1,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_write.id)]
+ ),
+ )
+ self.assertEqual(
+ 1,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_unlink.id)]
+ ),
+ )
+
+ def test_task_create_bridge(self):
+ with mock.patch("requests.post") as mock_post:
+ mock_post.return_value.status_code = 200
+ mock_post.return_value.json.return_value = {"message": "Task created"}
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_task_create.id)]
+ ),
+ )
+ task = self.env["project.task"].create(
+ {
+ "name": "Test Task for Bridge",
+ "description": "Test task description for bridge",
+ "project_id": self.project.id,
+ }
+ )
+ executions = self.env["ai.bridge.execution"].search(
+ [("ai_bridge_id", "=", self.bridge_task_create.id)]
+ )
+ self.assertEqual(len(executions), 1)
+ args, kwargs = mock_post.call_args
+ self.assertIn(
+ args[0],
+ [
+ "https://api.example.com/ai/task/create",
+ "https://api.example.com/ai/task/update",
+ ],
+ )
+ record = kwargs["json"].get("record", {})
+ self.assertEqual(record.get("id"), task.id)
+ self.assertEqual(record.get("name"), "Test Task for Bridge")
+
+ def test_task_write_bridge(self):
+ self.bridge_task_create.active = False
+ task = self.env["project.task"].create(
+ {
+ "name": "Test Task for Update",
+ "description": "Test task description for update",
+ "project_id": self.project.id,
+ }
+ )
+ self.bridge_task_create.active = True
+ with mock.patch("requests.post") as mock_post:
+ mock_post.return_value.status_code = 200
+ mock_post.return_value.json.return_value = {"message": "Task updated"}
+ self.env["ai.bridge.execution"].search(
+ [("ai_bridge_id", "=", self.bridge_task_write.id)]
+ ).unlink()
+ task.write(
+ {
+ "name": "Updated Task",
+ "description": "Updated task description",
+ }
+ )
+ executions = self.env["ai.bridge.execution"].search(
+ [("ai_bridge_id", "=", self.bridge_task_write.id)]
+ )
+ self.assertGreaterEqual(len(executions), 1)
+ args, kwargs = mock_post.call_args
+ self.assertEqual(args[0], "https://api.example.com/ai/task/update")
+ record = kwargs["json"].get("record", {})
+ self.assertEqual(record.get("id"), task.id)
+ self.assertEqual(record.get("name"), "Updated Task")
+
+ def test_task_unlink_bridge(self):
+ self.bridge_task_create.active = False
+ task = self.env["project.task"].create(
+ {
+ "name": "Test Task for Deletion",
+ "description": "Test task description for deletion",
+ "project_id": self.project.id,
+ }
+ )
+ self.bridge_task_create.active = True
+ task_id = task.id
+ with mock.patch("requests.post") as mock_post:
+ mock_post.return_value.status_code = 200
+ mock_post.return_value.json.return_value = {"message": "Task deleted"}
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_task_unlink.id)]
+ ),
+ )
+ task.unlink()
+ executions = self.env["ai.bridge.execution"].search(
+ [("ai_bridge_id", "=", self.bridge_task_unlink.id)]
+ )
+ self.assertEqual(len(executions), 1)
+ args, kwargs = mock_post.call_args
+ self.assertEqual(args[0], "https://api.example.com/ai/task/delete")
+ self.assertEqual(kwargs["json"].get("_id", False), task_id)
+
+ def test_all_task_bridges_together(self):
+ with mock.patch("requests.post") as mock_post:
+ mock_post.return_value.status_code = 200
+ mock_post.return_value.json.return_value = {"message": "Success"}
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_task_create.id)]
+ ),
+ )
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_task_write.id)]
+ ),
+ )
+ self.assertEqual(
+ 0,
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_task_unlink.id)]
+ ),
+ )
+ task = self.env["project.task"].create(
+ {
+ "name": "Complete Test Task",
+ "description": "Complete test task description",
+ "project_id": self.project.id,
+ }
+ )
+ task.write(
+ {
+ "name": "Updated Complete Test Task",
+ "description": "Updated complete test task description",
+ }
+ )
+ task.unlink()
+
+ self.assertGreaterEqual(
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_task_create.id)]
+ ),
+ 1,
+ )
+ self.assertGreaterEqual(
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_task_write.id)]
+ ),
+ 1,
+ )
+ self.assertGreaterEqual(
+ self.env["ai.bridge.execution"].search_count(
+ [("ai_bridge_id", "=", self.bridge_task_unlink.id)]
+ ),
+ 1,
+ )
diff --git a/setup/ai_oca_bridge_project/odoo/addons/ai_oca_bridge_project b/setup/ai_oca_bridge_project/odoo/addons/ai_oca_bridge_project
new file mode 120000
index 0000000..12c8102
--- /dev/null
+++ b/setup/ai_oca_bridge_project/odoo/addons/ai_oca_bridge_project
@@ -0,0 +1 @@
+../../../../ai_oca_bridge_project
\ No newline at end of file
diff --git a/setup/ai_oca_bridge_project/setup.py b/setup/ai_oca_bridge_project/setup.py
new file mode 100644
index 0000000..28c57bb
--- /dev/null
+++ b/setup/ai_oca_bridge_project/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+ setup_requires=['setuptools-odoo'],
+ odoo_addon=True,
+)