diff --git a/sync/__manifest__.py b/sync/__manifest__.py
index bd105d80..9d2a9410 100644
--- a/sync/__manifest__.py
+++ b/sync/__manifest__.py
@@ -7,7 +7,7 @@
"name": "Sync 🪬 Studio",
"summary": """Join the Amazing 😍 Community ⤵️""",
"category": "VooDoo ✨ Magic",
- "version": "17.0.13.0.1",
+ "version": "17.0.14.0.0",
"application": True,
"author": "Ivan Yelizariev",
"support": "info@odoomagic.com",
diff --git a/sync/data/sync_project_context_demo.xml b/sync/data/sync_project_context_demo.xml
deleted file mode 100644
index 0daf372e..00000000
--- a/sync/data/sync_project_context_demo.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
- odoo2odoo_demo
- Odoo2odoo (Demo)
-
-
- telegram_demo
- Telegram (Demo)
-
-
- trello
- Trello (Demo)
-
-
- github
- Github (Demo)
-
-
- math
- Math functions (Demo)
-
-
-
diff --git a/sync/data/sync_project_odoo2odoo_demo.xml b/sync/data/sync_project_odoo2odoo_demo.xml
deleted file mode 100644
index e3c1f72f..00000000
--- a/sync/data/sync_project_odoo2odoo_demo.xml
+++ /dev/null
@@ -1,153 +0,0 @@
-
-
-
-
-
- Demo Odoo2odoo Integration
-
-
-
-
-
-
- UPLOAD_ALL_PARTNER_PREFIX
- Sync Studio:
-
-
-
- URL
-
- URL to external Odoo, e.g. https://odoo.example
-
-
-
-
- DB
- Odoo database name
-
-
-
- USERNAME
- e.g. admin
-
-
-
- PASSWORD
-
-
-
-
- Sync Local Partners To Remote Odoo
-
-
-
-
-
-
- Sync Remote Partners Updates
-
-
-", links.sync_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)]],
- fields=["write_date", IMAGE_FIELD]
- )
- # Save fetched data in local Odoo
- for ep in external_partners:
- link = get_link(PARTNER_REL, ep["id"])
- p = link.odoo
- sync_date = parse_date(ep["write_date"])
- if sync_date > link.sync_date:
- p.write({IMAGE_FIELD: ep[IMAGE_FIELD]})
- link.update_links(sync_date)
-]]>
-
-
-
- LOCAL_PARTNER_CREATED
-
-
- on_create
-
-
- CHECK_EXTERNAL_ODOO
-
- 15
- minutes
-
-
- PUSH_ALL_LOCAL_PARTNERS
-
-
-
diff --git a/sync/data/sync_project_telegram_demo.xml b/sync/data/sync_project_telegram_demo.xml
deleted file mode 100644
index 135aa52e..00000000
--- a/sync/data/sync_project_telegram_demo.xml
+++ /dev/null
@@ -1,148 +0,0 @@
-
-
-
-
- Demo Telegram Integration
-
-
-
-
-
-
- TELEGRAM_WELCOME_MESSAGE
- Hello! How can I help you?
-
- Message that is sent to a user on first bot usage
-
-
-
-
- TELEGRAM_MESSAGE_SENT
- The message has sent in telegram
-
- When Odoo Bot reports on successfully sent telegram message
-
-
-
-
- PARTNER_NAME_PREFIX
- Telegram:
- Prefix for new partner name
-
-
-
- TELEGRAM_BOT_TOKEN
-
-
-
-
-
- Setup
-
-
-
-
-
-
-
- SETUP_TELEGRAM
-
-
- Process Telegram Messages
-
-
-
-
-
-
- Telegram updates
-
- TELEGRAM
-
-
- Send response via Odoo
-
-
-
-
-
-
- ON_MESSAGE_POSTED
-
-
- on_create
-
- [["model","=","res.partner"],["body","ilike","/telegram"]]
-
-
-
diff --git a/sync/data/sync_project_trello_github_demo.xml b/sync/data/sync_project_trello_github_demo.xml
deleted file mode 100644
index 8e592a27..00000000
--- a/sync/data/sync_project_trello_github_demo.xml
+++ /dev/null
@@ -1,602 +0,0 @@
-
-
-
-
- Demo Trello-Github Integration
-
-
-
-
- GITHUB_TOKEN
-
- Token with access to read issues and create webhooks, i.e. "repo" scope
-
-
-
-
-
- GITHUB_REPO
- owner/repo_name
-
-
-
-
- TRELLO_TOKEN
-
- Trello token to make API calls. Don't confuse it with API Key. Once you get
- API Key in https://trello.com/app-key page, you will see a link to generate
- token. Click one and you'll get the token
-
-
-
-
-
- TRELLO_KEY
-
-
-
-
-
- TRELLO_BOARD_ID
-
- You can get one from the board url:
- https://trello.com/b/TRELLO_BOARD_ID/BOARD-NAME
-
-
-
-
-
- ISSUE_FROM_GITHUB_PREFIX
- GITHUB:
-
-
-
-
-
- MESSAGE_PREFIX
- A Message posted on GitHub:
-
-
-
-
-
- LABELS_MERGE_STRATEGY
- UNION
-
- Possible values:
-USE_TRELLO, USE_GITHUB: use version from one side and override values from another
-UNION: add missed labels to each side
-INTERSECTION: remove labes that are not attached on another side
-
-
-
-
-
- Setup
-
-
-
-
-
- SETUP_GITHUB
- Setup Github Webhook. To delete webhooks, open github repostory, navigate to Settings >> Webhooks
-
-
-
- SETUP_TRELLO
- Setup Trello Webhook.
-
-
-
- DELETE_TRELLO_WEBHOOKS
-
-
-
- DEBUG
-
-
-
- Sync Github Issues to Trello (name, messages, labels)
-
-
-
-
-
- PUSH_ALL_ISSUES
-
- Initial syncing of github issues. It pushes only issues without linked
- trello cards. It doesn't sync further updates
-
-
-
-
- GITHUB_ISSUE_UPDATES
-
- Github Issue updates
-
-
-
-
- Sync Trello Cards to Github (labels)
-
-
-
-
-
- TRELLO_CARD_UPDATES
-
- Trello Card updates
-
-
-
- Sync labels Updating/Deleting
-
-
-
-
-
- TRELLO_LABEL_UPDATES
-
- Trello Label updates
-
-
-
- GITHUB_LABEL_UPDATES
-
- Github Label updates
-
-
-
- Labels Conflict Resolving
-
-
- issue
- issues_index = {}
- for issue in issues:
- if str(issue["id"]) in issue_ids:
- issues_index[int(issue["id"])] = issue
- log("GITHUB issues: %s" % ([issue_ids, issues, issues_index]))
-
- card_ids = elinks.get(TRELLO)
- # https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-cards-get
- # https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-get
- # card is dict {id: int, idLabels: [int], ...}
- cards = trello.get_all_cards()
- # card_id -> card
- cards_index = {}
- for card in cards:
- if str(card["id"]) in card_ids:
- cards_index[card["id"]] = card
-
- for el in elinks:
- card_id = el.get(TRELLO)[0]
- issue_id = int(el.get(GITHUB)[0])
- card = cards_index.get(card_id)
- issue = issues_index.get(issue_id)
- if not (card and issue):
- log("Linked card or issue is missed: %s" % ([card, issue]), level=LOG_WARNING)
- continue
- # compare labels
- tlabel_ids = card["idLabels"]
- glabel_ids = [lbl["id"] for lbl in issue["labels"]]
- tlinks = search_links(LABELS_REL, {GITHUB: None, TRELLO: tlabel_ids})
- glinks = search_links(LABELS_REL, {GITHUB: glabel_ids, TRELLO: None})
- if tlinks == glinks:
- # all fine
- log("card {} and issue {} are already synced".format(card_id, issue_id), LOG_DEBUG)
- continue
- log("Found labels mismatch: issue=%s, card=%s" % (issue["id"], card["id"]), LOG_DEBUG)
-
- tlinks_add = None
- tlinks_remove = None
- glinks_add = None
- glinks_remove = None
- if params.LABELS_MERGE_STRATEGY == "USE_TRELLO":
- glinks_add = tlinks - glinks
- glinks_remove = glinks - tlinks
- elif params.LABELS_MERGE_STRATEGY == "USE_GITHUB":
- tlinks_add = glinks - tlinks
- tlinks_remove = tlinks - glinks
- elif params.LABELS_MERGE_STRATEGY == "UNION":
- tlinks_add = glinks - tlinks
- glinks_add = tlinks - glinks
- elif params.LABELS_MERGE_STRATEGY == "INTERSECTION":
- tlinks_remove = tlinks - glinks
- glinks_remove = glinks - tlinks
- else:
- raise Exception("Unknown LABELS_MERGE_STRATEGY: %s" % LABELS_MERGE_STRATEGY, level=LOG_ERROR)
-
- if tlinks_add:
- trello.card_add_labels(card_id, tlinks_add.get(TRELLO))
- if glinks_add:
- github.issue_add_labels(issue_id, glinks_add.get(GITHUB))
- if tlinks_remove:
- trello.card_remove_labels(card_id, tlinks_remove.get(TRELLO))
- if glinks_remove:
- github.issue_remove_labels(issue_id, glinks_remove.get(GITHUB))
-
- ]]>
-
-
- CONFLICT_RESOLVING
-
-
- 1
- days
-
-
diff --git a/sync/data/sync_project_unittest_demo.xml b/sync/data/sync_project_unittest_demo.xml
index 3d543c88..8644cdc4 100644
--- a/sync/data/sync_project_unittest_demo.xml
+++ b/sync/data/sync_project_unittest_demo.xml
@@ -15,6 +15,7 @@
Assign ref to new partners
+ test
-from odoo import api, fields, models
+# Copyright 2024-2025 Ivan Yelizariev
+from odoo import fields, models
class SyncOrder(models.Model):
@@ -16,12 +16,17 @@ class SyncOrder(models.Model):
ondelete="cascade",
required=True,
)
+ sync_job_id = fields.Many2one("sync.job")
description = fields.Html(related="sync_task_id.sync_order_description")
+ # DEPRECATED. Use line_ids.record_id instead
record_id = fields.Reference(
string="Blackjack",
selection="_selection_record_id",
help="Optional extra information to perform this task",
)
+ line_ids = fields.One2many(
+ "sync.order.line", "sync_order_id", string="Linked Records"
+ )
partner_ids = fields.Many2many("res.partner", string="Partners")
state = fields.Selection(
@@ -34,7 +39,6 @@ class SyncOrder(models.Model):
default="draft",
)
- @api.model
def _selection_record_id(self):
mm = self.sync_task_id.sync_order_model_id
if not mm:
@@ -53,3 +57,53 @@ def action_cancel(self):
def action_refresh(self):
# Magic
pass
+
+
+class SyncOrderLine(models.Model):
+ _name = "sync.order.line"
+ _description = "Sync Order Records"
+
+ sync_order_id = fields.Many2one("sync.order")
+ record_id = fields.Reference(
+ string="Linked Record",
+ selection="_selection_record_id",
+ help="Optional extra information to perform this task",
+ )
+ state = fields.Selection(
+ [
+ ("draft", "Draft"),
+ ("open", "In Progress"),
+ ("done", "Done"),
+ ("error", "Failed"),
+ ("cancel", "Canceled"),
+ ],
+ default="draft",
+ )
+ value = fields.Char("Extra Input")
+ result = fields.Char("Result")
+
+ def _selection_record_id(self):
+ mm = self.sync_order_id.sync_task_id.sync_order_model_id
+ if not mm:
+ return []
+ return [(mm.model, mm.name)]
+
+ def action_done(self, msg=None):
+ self.write({"state": "done"})
+ if msg:
+ self.write({"result": msg})
+
+ def action_confirm(self, msg=None):
+ self.write({"state": "open"})
+ if msg:
+ self.write({"result": msg})
+
+ def action_error(self, msg=None):
+ self.write({"state": "error"})
+ if msg:
+ self.write({"result": msg})
+
+ def action_cancel(self, msg=None):
+ self.write({"state": "cancel"})
+ if msg:
+ self.write({"result": msg})
diff --git a/sync/models/sync_project.py b/sync/models/sync_project.py
index be8fe4c1..20c629b0 100644
--- a/sync/models/sync_project.py
+++ b/sync/models/sync_project.py
@@ -1,4 +1,4 @@
-# Copyright 2020,2022,2024 Ivan Yelizariev
+# Copyright 2020,2022,2024-2025 Ivan Yelizariev
# Copyright 2020-2021 Denis Mudarisov
# Copyright 2021 Ilya Ilchenko
# License MIT (https://opensource.org/licenses/MIT).
@@ -519,6 +519,12 @@ def link_src_dst(src_data, dst_ref):
"sync_external": sync_external,
}
+ def task(self, technical_name):
+ """Finds task by technical_name"""
+ return self.task_ids.filtered(
+ lambda task: task.technical_name == technical_name
+ )[:1]
+
def magic_upgrade(self):
self.ensure_one()
if not self.source_url:
@@ -645,6 +651,7 @@ def magic_upgrade(self):
task_vals = {
"name": task_name,
+ "technical_name": task_technical_name,
"code": file_content,
"magic_button": meta.get("MAGIC_BUTTON", "Magic ✨ Button")
if has_handle_button
diff --git a/sync/models/sync_task.py b/sync/models/sync_task.py
index 4f4be285..8a04b63e 100644
--- a/sync/models/sync_task.py
+++ b/sync/models/sync_task.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Ivan Yelizariev
+# Copyright 2020,2025 Ivan Yelizariev
# Copyright 2021 Denis Mudarisov
# License MIT (https://opensource.org/licenses/MIT).
@@ -23,6 +23,11 @@ class SyncTask(models.Model):
project_id = fields.Many2one("sync.project", ondelete="cascade")
name = fields.Char("Name", help="e.g. Sync Products", required=True)
+ technical_name = fields.Char(
+ "Technical Name",
+ help="Identifier equal to gist file name after removing prefix 'task.' and suffix '.py'",
+ required=True,
+ )
code = fields.Text("Code")
code_check = fields.Text("Syntax check", store=False, readonly=True)
active = fields.Boolean(default=True)
diff --git a/sync/models/sync_trigger_automation.py b/sync/models/sync_trigger_automation.py
index e211d68d..fe9ffe40 100644
--- a/sync/models/sync_trigger_automation.py
+++ b/sync/models/sync_trigger_automation.py
@@ -52,7 +52,9 @@ def create(self, vals_list):
def start(self, records):
if self.active:
- self.sync_task_id.start(self, args=(records,), with_delay=True)
+ sync_job = self.sync_task_id.start(self, args=(records,), with_delay=True)
+ if records._name == "sync.order":
+ records.write({"sync_job_id": sync_job.id})
def get_code(self):
return (
diff --git a/sync/security/ir.model.access.csv b/sync/security/ir.model.access.csv
index fa317b6b..7de65d7d 100644
--- a/sync/security/ir.model.access.csv
+++ b/sync/security/ir.model.access.csv
@@ -8,6 +8,9 @@ access_sync_task_manager,sync.task manager,model_sync_task,sync_group_manager,1,
access_sync_order_user,sync.order user,model_sync_order,sync_group_user,1,0,0,0
access_sync_order_dev,sync.order dev,model_sync_order,sync_group_dev,1,1,1,1
access_sync_order_manager,sync.order manager,model_sync_order,sync_group_manager,1,1,1,1
+access_sync_order_line_user,sync.order_line user,model_sync_order_line,sync_group_user,1,0,0,0
+access_sync_order_line_dev,sync.order_line dev,model_sync_order_line,sync_group_dev,1,1,1,1
+access_sync_order_line_manager,sync.order_line manager,model_sync_order_line,sync_group_manager,1,1,1,1
access_sync_data_user,sync.data user,model_sync_data,sync_group_user,1,0,0,0
access_sync_data_dev,sync.data dev,model_sync_data,sync_group_dev,1,1,1,1
access_sync_data_manager,sync.data manager,model_sync_data,sync_group_manager,1,1,1,1
diff --git a/sync/views/sync_order_views.xml b/sync/views/sync_order_views.xml
index d68ecf86..7b18ab2b 100644
--- a/sync/views/sync_order_views.xml
+++ b/sync/views/sync_order_views.xml
@@ -55,21 +55,35 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/sync/views/sync_project_views.xml b/sync/views/sync_project_views.xml
index d6d41c57..9022ff71 100644
--- a/sync/views/sync_project_views.xml
+++ b/sync/views/sync_project_views.xml
@@ -178,7 +178,7 @@
/>
-
+