Browse Source

Nov 18 [ADD] Initial Commit 'odoo_jira_connector'

pull/254/merge
AjmalCybro 5 months ago
parent
commit
484f2bdb8d
  1. 47
      odoo_jira_connector/README.rst
  2. 36
      odoo_jira_connector/__init__.py
  3. 55
      odoo_jira_connector/__manifest__.py
  4. 22
      odoo_jira_connector/controllers/__init__.py
  5. 44
      odoo_jira_connector/controllers/odoo_jira_connector.py
  6. 6
      odoo_jira_connector/doc/RELEASE_NOTES.md
  7. 28
      odoo_jira_connector/models/__init__.py
  8. 102
      odoo_jira_connector/models/ir_attachment.py
  9. 80
      odoo_jira_connector/models/jira_sprint.py
  10. 90
      odoo_jira_connector/models/mail_message.py
  11. 670
      odoo_jira_connector/models/project.py
  12. 112
      odoo_jira_connector/models/project_task_type.py
  13. 585
      odoo_jira_connector/models/res_config_settings.py
  14. 80
      odoo_jira_connector/models/res_users.py
  15. 2
      odoo_jira_connector/security/ir.model.access.csv
  16. BIN
      odoo_jira_connector/static/description/assets/icons/check.png
  17. BIN
      odoo_jira_connector/static/description/assets/icons/chevron.png
  18. BIN
      odoo_jira_connector/static/description/assets/icons/cogs.png
  19. BIN
      odoo_jira_connector/static/description/assets/icons/consultation.png
  20. BIN
      odoo_jira_connector/static/description/assets/icons/ecom-black.png
  21. BIN
      odoo_jira_connector/static/description/assets/icons/education-black.png
  22. BIN
      odoo_jira_connector/static/description/assets/icons/hotel-black.png
  23. BIN
      odoo_jira_connector/static/description/assets/icons/license.png
  24. BIN
      odoo_jira_connector/static/description/assets/icons/lifebuoy.png
  25. BIN
      odoo_jira_connector/static/description/assets/icons/manufacturing-black.png
  26. BIN
      odoo_jira_connector/static/description/assets/icons/pos-black.png
  27. BIN
      odoo_jira_connector/static/description/assets/icons/puzzle.png
  28. BIN
      odoo_jira_connector/static/description/assets/icons/restaurant-black.png
  29. BIN
      odoo_jira_connector/static/description/assets/icons/service-black.png
  30. BIN
      odoo_jira_connector/static/description/assets/icons/trading-black.png
  31. BIN
      odoo_jira_connector/static/description/assets/icons/training.png
  32. BIN
      odoo_jira_connector/static/description/assets/icons/update.png
  33. BIN
      odoo_jira_connector/static/description/assets/icons/user.png
  34. BIN
      odoo_jira_connector/static/description/assets/icons/wrench.png
  35. BIN
      odoo_jira_connector/static/description/assets/misc/Cybrosys R.png
  36. BIN
      odoo_jira_connector/static/description/assets/misc/categories.png
  37. BIN
      odoo_jira_connector/static/description/assets/misc/check-box.png
  38. BIN
      odoo_jira_connector/static/description/assets/misc/compass.png
  39. BIN
      odoo_jira_connector/static/description/assets/misc/corporate.png
  40. BIN
      odoo_jira_connector/static/description/assets/misc/customer-support.png
  41. BIN
      odoo_jira_connector/static/description/assets/misc/cybrosys-logo.png
  42. BIN
      odoo_jira_connector/static/description/assets/misc/features.png
  43. BIN
      odoo_jira_connector/static/description/assets/misc/logo.png
  44. BIN
      odoo_jira_connector/static/description/assets/misc/pictures.png
  45. BIN
      odoo_jira_connector/static/description/assets/misc/pie-chart.png
  46. BIN
      odoo_jira_connector/static/description/assets/misc/right-arrow.png
  47. BIN
      odoo_jira_connector/static/description/assets/misc/star.png
  48. BIN
      odoo_jira_connector/static/description/assets/misc/support.png
  49. BIN
      odoo_jira_connector/static/description/assets/misc/whatsapp.png
  50. BIN
      odoo_jira_connector/static/description/assets/modules/1.jpg
  51. BIN
      odoo_jira_connector/static/description/assets/modules/2.png
  52. BIN
      odoo_jira_connector/static/description/assets/modules/3.png
  53. BIN
      odoo_jira_connector/static/description/assets/modules/5.jpg
  54. BIN
      odoo_jira_connector/static/description/assets/modules/6.jpg
  55. BIN
      odoo_jira_connector/static/description/assets/modules/banner.jpg
  56. BIN
      odoo_jira_connector/static/description/assets/screenshots/Projects_in_jira.png
  57. BIN
      odoo_jira_connector/static/description/assets/screenshots/api_token2.png
  58. BIN
      odoo_jira_connector/static/description/assets/screenshots/creat_api_token.png
  59. BIN
      odoo_jira_connector/static/description/assets/screenshots/e_exported_stage.png
  60. BIN
      odoo_jira_connector/static/description/assets/screenshots/e_included_automatic.png
  61. BIN
      odoo_jira_connector/static/description/assets/screenshots/e_scrum_with_sprint.png
  62. BIN
      odoo_jira_connector/static/description/assets/screenshots/e_sprint_odoo.png
  63. BIN
      odoo_jira_connector/static/description/assets/screenshots/e_sprint_odoo_form.png
  64. BIN
      odoo_jira_connector/static/description/assets/screenshots/e_sprint_odoo_li.png
  65. BIN
      odoo_jira_connector/static/description/assets/screenshots/e_stage_odoo.png
  66. BIN
      odoo_jira_connector/static/description/assets/screenshots/e_test_connection.png
  67. BIN
      odoo_jira_connector/static/description/assets/screenshots/hero.gif
  68. BIN
      odoo_jira_connector/static/description/assets/screenshots/heros.gif
  69. BIN
      odoo_jira_connector/static/description/assets/screenshots/project_admin_user.png
  70. BIN
      odoo_jira_connector/static/description/assets/screenshots/queue_job.png
  71. BIN
      odoo_jira_connector/static/description/assets/screenshots/v16-hero.gif
  72. BIN
      odoo_jira_connector/static/description/assets/screenshots/webhook.png
  73. BIN
      odoo_jira_connector/static/description/assets/screenshots/webhook_setup_1.png
  74. BIN
      odoo_jira_connector/static/description/assets/screenshots/webhook_setup_2.png
  75. BIN
      odoo_jira_connector/static/description/assets/screenshots/webhook_setup_3.png
  76. BIN
      odoo_jira_connector/static/description/banner.png
  77. BIN
      odoo_jira_connector/static/description/icon.png
  78. 635
      odoo_jira_connector/static/description/index.html
  79. 53
      odoo_jira_connector/views/jira_sprint_views.xml
  80. 16
      odoo_jira_connector/views/project_task_type_views.xml
  81. 33
      odoo_jira_connector/views/project_views.xml
  82. 82
      odoo_jira_connector/views/res_config_settings_views.xml
  83. 16
      odoo_jira_connector/views/res_users_views.xml

47
odoo_jira_connector/README.rst

@ -0,0 +1,47 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-green.svg
:target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
Odoo Jira Connector
===================
* Connect Odoo to Jira
Configuration:
--------------
After installing this module, you need to set the URL for Jira, user name and API token.
Test the connection. Once the connection is established, you can export/ import the projects, tasks, and users.
Company
-------
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
License
-------
General Public License, Version 3 (LGPL v3).
(https://www.gnu.org/licenses/lgpl-3.0-standalone.html)
Credits
-------
Developer: (V15) Rosmy John, Contact: odoo@cybrosys.com
Contacts
--------
* Mail Contact : odoo@cybrosys.com
* Website : https://cybrosys.com
Bug Tracker
-----------
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported.
Maintainer
==========
.. image:: https://cybrosys.com/images/logo.png
:target: https://cybrosys.com
This module is maintained by Cybrosys Technologies.
For support and more information, please visit `Our Website <https://cybrosys.com/>`__
Further information
===================
HTML Description: `<static/description/index.html>`__

36
odoo_jira_connector/__init__.py

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from . import models
from . import controllers
from odoo.exceptions import UserError
from odoo import api, SUPERUSER_ID
def pre_init_hook(cr):
env = api.Environment(cr, SUPERUSER_ID, {})
queue_job = env['ir.model.data'].search([('module', '=', 'queue_job')])
queue_job_cron_jobrunner = env['ir.model.data'].search(
[('module', '=', 'queue_job_cron_jobrunner')])
if not queue_job_cron_jobrunner or not queue_job:
raise UserError("Please make sure you have added and installed Queue "
"Job and Queue Job Cron Jobrunner in your system")

55
odoo_jira_connector/__manifest__.py

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
{
'name': 'Odoo Jira Connector',
'version': '15.0.1.0.0',
'category': 'Project',
'summary': 'Odoo Jira Connector is a valuable integration tool for '
'businesses that use both Odoo and Jira. By connecting these '
'two systems, businesses can streamline their project '
'management processes and improve their overall efficiency.',
'description': 'The Odoo Jira Connector offers a range of features, '
'including bi-directional synchronization of data, '
'automatic creation of Jira issues from Odoo records, and '
'real-time updates of Jira issues in Odoo. To meet the '
'specific needs of any business users can leverage, they '
'can use Odoo to handle their business.',
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': 'https://www.cybrosys.com',
'depends': ['project'],
'data': [
'security/ir.model.access.csv',
'views/res_config_settings_views.xml',
'views/res_users_views.xml',
'views/project_views.xml',
'views/project_task_type_views.xml',
'views/jira_sprint_views.xml',
],
'images': ['static/description/banner.png'],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,
'pre_init_hook': 'pre_init_hook'
}

22
odoo_jira_connector/controllers/__init__.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from . import odoo_jira_connector

44
odoo_jira_connector/controllers/odoo_jira_connector.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
import json
from odoo import http
from odoo.http import request
class JiraWebhook(http.Controller):
"""Class to fetch Jira data using webhook"""
@http.route('/jira_webhook', type="json", auth='public',
methods=['POST'], csrf=False)
def import_jira_data(self, *args, **kwargs):
"""function to import data from Jira based on webhook events"""
automated_import_export = request.env['ir.config_parameter'] \
.sudo().get_param('odoo_jira_connector.automatic')
if automated_import_export:
data = json.loads(request.httprequest.data)
jira = json.dumps(data, sort_keys=True,
indent=4, separators=(',', ': '))
jira_data = json.loads(jira)
webhook_event = jira_data['webhookEvent']
delay = request.env['project.task'].sudo(). \
with_delay(priority=1, eta=60)
delay.webhook_data_handle(jira_data, webhook_event)

6
odoo_jira_connector/doc/RELEASE_NOTES.md

@ -0,0 +1,6 @@
## Module <odoo_jira_connector>
#### 01.08.2024
#### Version 15.0.1.0.0
#### ADD
- Initial commit for Odoo Jira Connector

28
odoo_jira_connector/models/__init__.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from . import ir_attachment
from . import jira_sprint
from . import mail_message
from . import project
from . import project_task_type
from . import res_config_settings
from . import res_users

102
odoo_jira_connector/models/ir_attachment.py

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
import base64
import os
import requests
from odoo import models, fields, api
# The Header parameters
HEADERS = {'Accept': 'application/json', 'Content-Type': 'application/json'}
class IrAttachment(models.Model):
_inherit = 'ir.attachment'
attachment_id_jira = fields.Integer(string="Jira ID",
help="Jira id of attachment.")
@api.model_create_multi
def create(self, values_list):
""" For creating attachment in Jira and attachment in the chatter """
attachment = super(IrAttachment, self).create(values_list)
if not values_list[0].get('attachment_id_jira'):
ir_config_parameter = self.env['ir.config_parameter'].sudo()
if ir_config_parameter.get_param('odoo_jira_connector.connection'):
url = ir_config_parameter.get_param('odoo_jira_connector.url')
user = ir_config_parameter.get_param(
'odoo_jira_connector.user_id_jira')
password = ir_config_parameter.get_param(
'odoo_jira_connector.api_token')
if attachment.res_model == 'project.task':
task = self.env['project.task'].browse(attachment.res_id)
attachment_url = url + 'rest/api/3/issue/%s/' \
'attachments' % task.task_id_jira
attachment_type = (self.env['res.config.settings'].
find_attachment_type(attachment))
if attachment.datas and attachment_type in (
'pdf', 'xlsx', 'jpg'):
temp_file_path = f'/tmp/temp.{attachment_type}'
binary_data = base64.b64decode(attachment.datas)
# Save the binary data to a file
with open(temp_file_path, 'wb') as file:
file.write(binary_data)
if attachment_type == 'jpg' and os.path.splitext(
temp_file_path)[1].lower() != '.jpg':
# Rename the saved file to its corresponding JPG
# file format
file_path = os.path.splitext(temp_file_path)[
0] + '.jpg'
os.rename(temp_file_path, file_path)
temp_file_path = file_path
attachment_file = {
'file': (
attachment.name, open(temp_file_path, 'rb'))
}
response = requests.post(attachment_url,
headers={
'X-Atlassian-Token':
'no-check'},
files=attachment_file,
auth=(user, password))
data = response.json()
attachment.write(
{'attachment_id_jira': data[0].get('id')})
return attachment
def unlink(self):
""" Overrides the unlink method of attachment to delete an attachment
in Jira when we delete the attachment in Odoo"""
for attachment in self:
jira_connection = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.connection')
if jira_connection:
jira_url = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.url', '')
user = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.user_id_jira')
password = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.api_token')
if attachment.attachment_id_jira:
requests.delete(
jira_url + '/rest/api/3/attachment/' +
str(attachment.attachment_id_jira),
headers=HEADERS, auth=(user, password))
return super(IrAttachment, self).unlink()

80
odoo_jira_connector/models/jira_sprint.py

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from odoo import fields, models
class JiraSprint(models.Model):
"""class for Sprint"""
_name = "jira.sprint"
_description = "jira sprint"
sprint_id_jira = fields.Integer(string="Sprint id", readonly=True,
help="sprint id in jira.")
name = fields.Char(string="Sprint Name", help="Name of the sprint.")
sprint_goal = fields.Text(string="Goal", help="Goal of the sprint.")
start_date = fields.Datetime(string="Start Date", help="Sprint start date.")
end_date = fields.Datetime(string="End Date", help="Sprint end date.")
project_id = fields.Many2one('project.project', readonly=True,
help="Respective Project ID.")
state = fields.Selection(string="State",
selection=[('to_start', 'To start'),
('ongoing', 'Ongoing'),
('completed', 'Completed')],
default='to_start', help="State of the sprint.")
def action_get_tasks(self):
"""Sprint added tasks"""
return {
'type': 'ir.actions.act_window',
'name': 'Tasks',
'view_mode': 'kanban',
'res_model': 'project.task',
'views': [[False, 'kanban'], [False, 'tree'], [False, 'form']],
'domain': [('project_id', '=', self.project_id.id),
('sprint_id.state', '=', 'ongoing')],
'context': "{'create': False}"
}
def action_get_backlogs(self):
"""Tasks in backlogs"""
return {
'type': 'ir.actions.act_window',
'name': 'Backlogs',
'view_mode': 'kanban',
'res_model': 'project.task',
'views': [[False, 'kanban'], [False, 'tree'], [False, 'form']],
'domain': [('project_id', '=', self.project_id.id),
('sprint_id.state', '=', 'to_start')],
'context': "{'create': False}"
}
def action_get_all_tasks(self):
"""All tasks in the project"""
return {
'type': 'ir.actions.act_window',
'name': 'All Tasks',
'view_mode': 'kanban',
'res_model': 'project.task',
'views': [[False, 'kanban'], [False, 'tree'], [False, 'form']],
'domain': [('project_id', '=', self.project_id.id)],
'context': "{'create': False}"
}

90
odoo_jira_connector/models/mail_message.py

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
import json
import requests
from odoo import api, fields, models
from odoo.tools import html2plaintext
class MailMessage(models.Model):
"""
This class is inherited for adding an extra field and
override the create function
Methods:
create(values_list):
extends create() to create comment in Jira
"""
_inherit = 'mail.message'
message_id_jira = fields.Integer(string='Message ID',
help='ID for the comments in Jira.')
@api.model_create_multi
def create(self, values_list):
""" For creating comment in Jira and comments in the chatter """
message = super(MailMessage, self).create(values_list)
if message.message_id_jira == 0:
ir_config_parameter = self.env['ir.config_parameter'].sudo()
if ir_config_parameter.get_param('odoo_jira_connector.connection'):
url = ir_config_parameter.get_param('odoo_jira_connector.url')
user = ir_config_parameter.get_param(
'odoo_jira_connector.user_id_jira')
password = ir_config_parameter.get_param(
'odoo_jira_connector.api_token')
if message.model == 'project.task':
task = self.env['project.task'].browse(message.res_id)
current_message = str(html2plaintext(message.body))
response = requests.get(
f'{url}rest/api/3/issue/{task.task_id_jira}/comment',
headers={
'Accept': 'application/json',
'Content-Type': 'application/json'},
auth=(user, password))
data = response.json()
if response.status_code == 200:
list_of_comments_jira = [
str(comments['body']['content'][0]['content'][0][
'text']) for comments in data['comments']]
if current_message not in list(
filter(None, list_of_comments_jira)):
data = json.dumps({
'body': {
'type': 'doc',
'version': 1,
'content': [{
'type': 'paragraph',
'content': [{
'text': current_message,
'type': 'text'
}]
}]
}
})
response = requests.post(
url + 'rest/api/3/issue/%s/comment' % (
task.task_id_jira), headers={
'Accept': 'application/json',
'Content-Type': 'application/json'},
data=data, auth=(user, password))
data = response.json()
message.write({'message_id_jira': data.get('id')})
return message

670
odoo_jira_connector/models/project.py

@ -0,0 +1,670 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
import base64
import json
from datetime import datetime
import requests
from requests.auth import HTTPBasicAuth
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools import html2plaintext
# The Header parameters
HEADERS = {'Accept': 'application/json', 'Content-Type': 'application/json'}
class ProjectProject(models.Model):
"""
This class is inherited for adding some extra field and override the
create and write function also to add function to show sprint
Methods:
create(vals_list):
extends create() to export project to Jira
write(vals):
extends write() to update corresponding project in Jira
"""
_inherit = 'project.project'
project_id_jira = fields.Integer(string='Jira Project ID',
help='Corresponding project id of Jira.',
readonly=True)
jira_project_key = fields.Char(string='Jira Project Key',
help='Corresponding project key of Jira.',
readonly=True)
sprint_active = fields.Boolean(string='Sprint active',
help='To show sprint smart button.',default=True)
board_id_jira = fields.Integer(string='Jira Board ID',
help='Corresponding Board id of Jira.',
readonly=True)
def action_get_sprint(self):
"""Getting sprint inside the project"""
return {
'type': 'ir.actions.act_window',
'name': 'Sprints',
'view_mode': 'tree,form',
'res_model': 'jira.sprint',
'context': {'default_project_id': self.id},
'domain': [('project_id', '=', self.id)],
}
@api.model_create_multi
def create(self, vals_list):
""" Overrides create method of project to export project to Jira """
self = self.with_context(mail_create_nosubscribe=True)
projects = super().create(vals_list)
jira_connection = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.connection')
if jira_connection:
jira_url = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.url', False)
user = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.user_id_jira', False)
password = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.api_token', False)
auth = HTTPBasicAuth(user, password)
project_headers = {'Accept': 'application/json'}
response = requests.request(
'GET', jira_url + '/rest/api/3/project/',
headers=project_headers, auth=auth)
projects_json = json.dumps(
json.loads(response.text), sort_keys=True, indent=4,
separators=(',', ': '))
project_json = json.loads(projects_json)
name_list = [project['name'] for project in project_json]
key = projects.name.upper()
project_key = key[:3] + '1' + key[-3:]
project_keys = project_key.replace(' ', '')
auth = HTTPBasicAuth(user, password)
project_payload = {
'name': projects.name, 'key': project_keys,
'templateKey': 'com.pyxis.greenhopper.jira:gh-simplified'
'-kanban-classic'
}
if projects.name not in name_list:
response = requests.request(
'POST', jira_url + 'rest/simplified/latest/project',
data=json.dumps(project_payload),
headers=HEADERS, auth=auth)
data = response.json()
if 'projectId' in data:
projects.write({'project_id_jira': data['projectId'],
'jira_project_key': data['projectKey']})
self.env['ir.config_parameter'].sudo().set_param(
'import_project_count', int(
self.env['ir.config_parameter'].sudo().get_param(
'import_project_count')) + 1)
elif 'errors' in data and 'projectName' in data['errors']:
raise ValidationError(
"A project with this name already exists. Please "
"rename the project.")
elif 'errors' in data and 'projectKey' in data['errors']:
raise ValidationError(data['errors']['projectKey'])
return projects
def write(self, vals):
""" Overrides the write method of project.project to update project
name in Jira when we update the project in Odoo"""
jira_connection = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.connection')
if jira_connection:
for project in self:
jira_url = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.url')
user = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.user_id_jira')
password = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.api_token')
auth = (user, password)
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}
url = (f"{jira_url}/rest/api/3/project/"
f"{project.jira_project_key}")
payload = json.dumps({
"name": vals.get('name'),
})
payload_json = json.loads(payload)
response = requests.get(
url,
headers=headers,
auth=auth)
data = response.json()
if 'name' in data:
if data['name'] != payload_json['name']:
requests.request(
"PUT",
url, data=payload, headers=headers, auth=auth)
else:
requests.request(
"PUT",
url, data=payload, headers=headers, auth=auth)
return super(ProjectProject, self).write(vals)
class ProjectTask(models.Model):
"""
This class is inherited for adding some extra field and override the
create function
Methods:
create(vals_list):
extends create() to export tasks to Jira
unlink():
extends unlink() to delete a task in Jira when we delete the
task in Odoo
write(vals):
extends write() to update a task in Jira when we update the
task in Odoo
"""
_inherit = 'project.task'
task_id_jira = fields.Char(string='Jira Task ID', help='Task id of Jira.',
readonly=True)
sprint_id = fields.Many2one('jira.sprint',
help="Sprint of this task.", readonly=True)
task_sprint_active = fields.Boolean(string="Active Sprint",
compute="_compute_task_sprint_active",
store=True,
help="Boolean field to check whether "
"the sprint is active or not.")
@api.depends('project_id.sprint_active')
def _compute_task_sprint_active(self):
"""compute function to make sprint_id invisible by changing
'task_sprint_active' field to true"""
for rec in self:
if rec.project_id.sprint_active:
rec.task_sprint_active = True
@api.model
def create(self, vals_list):
""" Override the create method of tasks to export tasks to Jira """
res = super(ProjectTask, self).create(vals_list)
jira_connection = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.connection')
if jira_connection:
jira_url = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.url')
user = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.user_id_jira')
password = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.api_token')
query = {'jql': 'project = %s' % res.project_id.jira_project_key}
requests.get(jira_url + 'rest/api/3/search', headers=HEADERS,
params=query, auth=(user, password))
if not res.task_id_jira:
payload = json.dumps({
'fields': {
'project': {'key': res.project_id.jira_project_key},
'summary': res.name,
'description': html2plaintext(res.description),
'issuetype': {'name': 'Task'}
}
})
response = requests.post(
jira_url + '/rest/api/2/issue', headers=HEADERS,
data=payload, auth=(user, password))
data = response.json()
res.task_id_jira = str(data.get('key'))
self.env['ir.config_parameter'].sudo().set_param(
'export_task_count', int(
self.env['ir.config_parameter'].sudo().get_param(
'export_task_count')) + 1)
return res
def unlink(self):
""" Overrides the unlink method of task to delete a task in Jira when
we delete the task in Odoo """
for task in self:
if task.stage_id and task.stage_id.fold:
raise Warning(_('You cannot delete a task in a folded stage.'))
jira_connection = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.connection')
if jira_connection:
jira_url = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.url', '')
user = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.user_id_jira')
password = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.api_token')
if task.task_id_jira:
requests.delete(
jira_url + '/rest/api/3/issue/' + task.task_id_jira,
headers=HEADERS, auth=(user, password))
return super(ProjectTask, self).unlink()
def write(self, vals):
""" Overrides the write method of task to update a task's name in
Jira when we update the task in Odoo"""
jira_connection = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.connection')
if jira_connection:
jira_url = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.url', '')
user = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.user_id_jira')
password = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.api_token')
for task in self:
if task.task_id_jira and 'name' in vals:
new_task_name = vals['name']
payload = {
"fields": {
"summary": new_task_name
}
}
requests.put(
jira_url + '/rest/api/3/issue/' + task.task_id_jira,
json=payload, headers=HEADERS, auth=(user, password))
return super(ProjectTask, self).write(vals)
def webhook_data_handle(self, jira_data, webhook_event):
"""Function to Handle Jira Data Received from Webhook"""
if webhook_event == 'project_created':
self.create_project(jira_data)
elif webhook_event == 'project_updated':
self.update_project(jira_data)
elif webhook_event == 'project_soft_deleted':
self.delete_project(jira_data)
elif webhook_event == 'jira:issue_created':
self.create_task(jira_data)
elif webhook_event == 'jira:issue_deleted':
self.delete_task(jira_data)
elif webhook_event == 'comment_created':
self.create_comment(jira_data)
elif webhook_event == 'comment_deleted':
self.delete_comment(jira_data)
elif webhook_event == 'user_created':
self.create_user(jira_data)
elif webhook_event == 'user_deleted':
self.delete_user(jira_data)
elif webhook_event == 'board_configuration_changed':
self.board_configuration_change(jira_data)
elif webhook_event == 'jira:issue_updated':
self.update_task(jira_data)
elif webhook_event == 'attachment_deleted':
self.delete_attachment(jira_data)
elif webhook_event == 'sprint_started':
self.sprint_started(jira_data)
elif webhook_event == 'sprint_closed':
self.sprint_closed(jira_data)
def create_project(self, jira_data):
"""function to create project based on webhook response"""
jira_project = jira_data['project']
existing_project = self.env['project.project'].sudo().search(
[('project_id_jira', '=', jira_project['id'])])
values = {
'name': jira_project['name'],
'project_id_jira': jira_project['id'],
'jira_project_key': jira_project['key']
}
if not existing_project:
imported_project = self.env['project.project'].sudo().create(
values)
url = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.url')
user = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.user_id_jira')
password = self.env['ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.api_token')
auth = HTTPBasicAuth(user, password)
headers = {
"Accept": "application/json"
}
response = requests.request(
"GET",
url + "/rest/api/3/project/" + jira_project['key'],
headers=headers,
auth=auth
)
data = response.json()
style_value = data.get('style')
if style_value == 'classic':
imported_project.write({'sprint_active': False})
else:
imported_project.write({'sprint_active': True})
def update_project(self, jira_data):
"""function to update project based on webhook response"""
project_id = jira_data['project']['id']
existing_project = self.env['project.project'].sudo().search(
[('project_id_jira', '=', project_id)])
if existing_project.name != jira_data['project']['name']:
existing_project.write({'name': jira_data['project']['name']})
def delete_project(self, jira_data):
"""function to delete project based on webhook response"""
project_id = (jira_data['project']['id'])
self.env['project.project'].sudo().search(
[('project_id_jira', '=', project_id)]).unlink()
def create_task(self, jira_data):
"""function to create task based on webhook response"""
task_name = jira_data['issue']['fields']['summary']
task_key = jira_data['issue']['key']
jira_project_id = jira_data['issue']['fields']['project']['id']
project = self.env['project.project'].sudo().search(
[('project_id_jira', '=', int(jira_project_id))])
existing_task = self.env['project.task'].sudo().search(
[('task_id_jira', '=', jira_data['issue']['key'])])
if not existing_task:
self.env['project.task'].sudo().create({
'project_id': project.id,
'name': task_name,
'task_id_jira': task_key
})
def delete_task(self, jira_data):
"""function to delete task based on webhook response"""
task_key = jira_data['issue']['key']
self.env['project.task'].sudo().search(
[('task_id_jira', '=', task_key)]).unlink()
def create_comment(self, jira_data):
"""function to create comment based on webhook response"""
text = jira_data['comment']['body']
task_key = jira_data['issue']['key']
task = self.env['project.task'].sudo().search(
[('task_id_jira', '=', task_key)])
existing_message = self.env['mail.message'].sudo().search(
['&', ('res_id', '=', task.id),
('model', '=', 'project.task'),
('message_id_jira', '=', jira_data['comment']['id'])])
if not existing_message:
input_string = str(text)
parts = input_string.split(".")
if len(parts) > 1:
body = parts[1]
else:
body = parts[0]
self.env['mail.message'].sudo().create(
{"body": html2plaintext(body),
'model': 'project.task',
'res_id': task.id,
'message_id_jira': jira_data['comment']['id']
})
def delete_comment(self, jira_data):
"""function to delete comment based on webhook response"""
self.env['mail.message'].sudo().search(
[('message_id_jira', '=',
jira_data['comment']['id'])]).unlink()
def create_user(self, jira_data):
"""function to create user based on webhook response"""
existing_user = self.env['res.user']. \
search([('jira_user_key', '=', jira_data['user']['accountId'])])
if not existing_user:
self.env['res.users'].sudo().create({
'login': jira_data['user']['displayName'],
'name': jira_data['user']['displayName'],
'jira_user_key': jira_data['user']['accountId']
})
def delete_user(self, jira_data):
"""function to delete user based on webhook response"""
self.env['res.users'].sudo().search(
[('jira_user_key', '=', jira_data['accountId'])]).unlink()
def board_configuration_change(self, jira_data):
"""function to create stages or write project into stages based on
webhook response"""
columns = jira_data['configuration']['columnConfig']['columns']
if jira_data['configuration'].get('location'):
project_key = jira_data['configuration']['location']['key']
project = self.env['project.project'].sudo().search(
[('jira_project_key', '=', project_key)])
sequence_value = 1
for column in columns:
if column['name'] != 'Backlog':
stages_jira_id = column['statuses'][0]['id']
existing_stage = self.env[
'project.task.type'].sudo().search(
[('stages_jira_id', '=', stages_jira_id)])
existing_stage.write({'project_ids': project,
'sequence': sequence_value})
if not existing_stage:
values = {
'name': column['name'],
'stages_jira_id': stages_jira_id,
'jira_project_key': project_key,
'project_ids': project,
'sequence': sequence_value,
}
self.env['project.task.type'].sudo().create(
values)
sequence_value += 1
project.write({'board_id_jira': jira_data['configuration']['id']})
else:
board_id_jira = jira_data['configuration']['id']
project = self.env['project.project'].search(
[('board_id_jira', '=', board_id_jira)])
existing_stages = self.env[
'project.task.type'].sudo().search(
[('project_ids', 'in', project.id),
('stages_jira_id', '!=', '0')])
jira_status_ids = []
for column in columns:
for status in column['statuses']:
jira_status_ids.append(status['id'])
if len(jira_status_ids) < len(existing_stages.ids):
removed_stage = self.env[
'project.task.type'].sudo().search(
[('project_ids', 'in', project.id),
('stages_jira_id', 'not in', jira_status_ids)])
removed_stage.unlink()
elif len(jira_status_ids) > len(existing_stages.ids):
columns = jira_data['configuration']['columnConfig'][
'columns']
num_stages = len(columns)
stage_id = columns[num_stages - 1]['statuses'][0]['id']
values = {
'name': columns[num_stages - 1]['name'],
'stages_jira_id': stage_id,
'project_ids': project,
'sequence': num_stages,
}
self.env['project.task.type'].sudo().create(values)
def update_task(self, jira_data):
"""function to update a task, which includes changing the task stage,
adding attachments, adding a description to the task,
changing the task's name,
and adding a sprint based on webhook response"""
task_key = jira_data['issue']['key']
imported_task = self.env['project.task'].sudo().search(
[('task_id_jira', '=', task_key)])
to_value = jira_data['changelog']['items'][0]['to']
if jira_data['changelog']['items'][0]['field'] == 'resolution':
second_to_value = jira_data['changelog']['items'][1]['to']
task_stage = self.env['project.task.type'].sudo().search(
[('stages_jira_id', '=', second_to_value)])
imported_task.write({'stage_id': task_stage.id})
elif jira_data['changelog']['items'][0]['field'] == 'status':
task_stage = self.env['project.task.type'].sudo().search(
[('stages_jira_id', '=', to_value)])
imported_task.write({'stage_id': task_stage.id})
elif jira_data['changelog']['items'][0]['field'] == 'Attachment':
if jira_data['changelog']['items'][0]['to'] != 'None':
attachments = jira_data["issue"]['fields']['attachment']
jira_attachment_id = [attachment['id'] for attachment in
attachments]
num_attachments = len(jira_attachment_id)
user_name = self.env[
'ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.user_id_jira')
api_token = self.env[
'ir.config_parameter'].sudo().get_param(
'odoo_jira_connector.api_token')
auth = HTTPBasicAuth(user_name, api_token)
if num_attachments > 0:
name = attachments[num_attachments - 1].get('filename')
mime_type = attachments[num_attachments - 1].get(
'mimeType')
src = attachments[num_attachments - 1].get('content')
jira_id = attachments[num_attachments - 1].get('id')
image = base64.b64encode(
requests.get(src, auth=auth).content)
existing_attachments = self.env[
'ir.attachment'].sudo().search(
[('res_id', '=', imported_task.id),
('res_model', '=', 'project.task'),
('attachment_id_jira', '=', jira_id)]
)
values = {
'name': name,
'type': 'binary',
'datas': image,
'res_model': 'project.task',
'res_id': imported_task.id,
'mimetype': mime_type,
'attachment_id_jira': jira_id
}
if not existing_attachments:
self.env['ir.attachment'].sudo().create(values)
else:
pass
elif jira_data['changelog']['items'][0]['field'] == 'description':
imported_task.update({'description': jira_data['changelog']
['items'][0]['toString']})
elif jira_data['changelog']['items'][0]['field'] == 'summary':
if imported_task.name != jira_data['changelog']['items'][0] \
['toString']:
imported_task.write(
{'name': jira_data['changelog']['items'][0]
['toString']})
elif jira_data['changelog']['items'][0]['field'] == 'Sprint':
project_key = jira_data['issue']['fields']['project']['key']
project = self.env['project.project'].sudo().search(
[('jira_project_key', '=', project_key)])
custom_field = jira_data['issue']['fields']['customfield_10020']
if len(custom_field) > 1:
jira_sprint = self.env['jira.sprint'].sudo().search(
[('sprint_id_jira', '=',
custom_field[len(custom_field) - 1]['id'])])
if not jira_sprint:
vals = {
'name': custom_field[len(custom_field) - 1]['name'],
'sprint_id_jira':
custom_field[len(custom_field) - 1]['id'],
'project_id': project.id
}
sprint = self.env['jira.sprint'].sudo().create(vals)
if project.task_ids:
for rec in project.task_ids:
rec.write({'sprint_id': sprint.id})
else:
jira_sprint = self.env['jira.sprint'].sudo().search([(
'sprint_id_jira', '=', custom_field[0]['id'])])
if not jira_sprint:
vals = {
'name': custom_field[0]['name'],
'sprint_id_jira': custom_field[0]['id'],
'project_id': project.id
}
sprint = self.env['jira.sprint'].sudo().create(vals)
if project.task_ids:
for rec in project.task_ids:
rec.write({'sprint_id': sprint.id})
if rec.task_id_jira != task_key:
self.create({
'project_id': project.id,
'name': jira_data['issue']['fields'][
'summary'],
'task_id_jira': task_key,
'sprint_id': jira_sprint.id
})
break
else:
task_name = jira_data['issue']['fields']['summary']
self.create({
'project_id': project.id,
'name': task_name,
'task_id_jira': task_key,
'sprint_id': sprint.id
})
else:
if project.task_ids:
for rec in project.task_ids:
rec.write({'sprint_id': jira_sprint.id})
if rec.task_id_jira != task_key:
self.create({
'project_id': project.id,
'name': jira_data['issue']['fields'][
'summary'],
'task_id_jira': task_key,
'sprint_id': jira_sprint.id
})
break
else:
task_name = jira_data['issue']['fields']['summary']
self.create({
'project_id': project.id,
'name': task_name,
'task_id_jira': task_key,
'sprint_id': jira_sprint.id
})
def delete_attachment(self, jira_data):
"""function to delete attachment based on the response received from
webhook"""
jira_id = jira_data['attachment']['id']
self.env['ir.attachment'].sudo().search(
[('attachment_id_jira', '=', jira_id)]).unlink()
def sprint_started(self, jira_data):
"""function to start sprint which is created using webhook response"""
sprint_in_odoo = self.env['jira.sprint'].sudo().search(
[('sprint_id_jira', '=', jira_data['sprint']['id'])])
if sprint_in_odoo:
start_date = jira_data['sprint']['startDate']
input_start_date = datetime. \
strptime(start_date, '%Y-%m-%dT%H:%M:%S.%fZ')
jira_start_date = input_start_date.strftime(
'%Y-%m-%d %H:%M:%S')
end_date = jira_data['sprint']['endDate']
input_end_date = datetime. \
strptime(end_date, '%Y-%m-%dT%H:%M:%S.%fZ')
jira_end_date = input_end_date.strftime(
'%Y-%m-%d %H:%M:%S')
sprint_in_odoo.write({
'start_date': jira_start_date,
'end_date': jira_end_date,
'sprint_goal': jira_data['sprint']['goal'],
'state': 'ongoing'
})
def sprint_closed(self, jira_data):
"""function to close sprint which is created using webhook response"""
sprint_in_odoo = self.env['jira.sprint'].sudo().search(
[('sprint_id_jira', '=', jira_data['sprint']['id'])])
if sprint_in_odoo:
sprint_in_odoo.write({'state': 'completed'})
self.env['project.task'].sudo().search(
[('stage_id.name', '=', 'Done'),
('sprint_id', '=', sprint_in_odoo.id)]).unlink()

112
odoo_jira_connector/models/project_task_type.py

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
import json
import requests
from requests.auth import HTTPBasicAuth
from odoo import models, fields, api
class ProjectTaskType(models.Model):
"""This class is inherited for adding some extra field and override the
create function
Methods:
create(vals):
extends create() to export tasks stages to Jira"""
_inherit = 'project.task.type'
stages_jira_id = fields.Integer(string="Jira ID",
help="Jira id for task stages.",
readonly=True)
jira_project_key = fields.Char(string='Jira Project Key',
help='Corresponding project key of Jira.',
readonly=True)
jira_stages_category = fields.Selection([
('TO_DO', 'TO_DO'),
('IN_PROGRESS', 'IN_PROGRESS'),
('DONE', 'DONE')],
default='IN_PROGRESS',
string="Jira Status Category", help="Here we can choose the category "
"and the Stage will create in "
"jira under the chosen category.")
@api.model_create_multi
def create(self, vals):
""" Override the create method of tasks stages to export
tasks stages to Jira """
stages = super(ProjectTaskType, self).create(vals)
for stage in stages:
if stage.stages_jira_id == 0 and len(stage.project_ids) == 1:
ir_config_parameter = self.env['ir.config_parameter'].sudo()
if ir_config_parameter.get_param('odoo_jira_connector'
'.connection'):
url = ir_config_parameter.get_param('odoo_jira_connector'
'.url',
False)
user = ir_config_parameter.get_param(
'odoo_jira_connector.user_id_jira', False)
password = ir_config_parameter.get_param(
'odoo_jira_connector.api_token', False)
auth = HTTPBasicAuth(user, password)
if stage.project_ids[0].sprint_active:
payload = json.dumps({
"scope": {
"project": {
"id": str(stage.project_ids[0].
project_id_jira)
},
"type": "PROJECT"
},
"statuses": [
{
"description": "The issue is resolved",
"name": stages.name,
"statusCategory": str(
stage.jira_stages_category),
}
]
})
else:
payload = json.dumps({
"scope": {
"type": "GLOBAL"
},
"statuses": [
{
"description": "The issue is resolved",
"name": stage.name,
"statusCategory": str(
stage.jira_stages_category),
}
]
})
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}
requests.request(
"POST",
url + "rest/api/3/statuses",
data=payload,
headers=headers,
auth=auth
)
return stages

585
odoo_jira_connector/models/res_config_settings.py

@ -0,0 +1,585 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
import base64
import json
import os
import re
import requests
from requests.auth import HTTPBasicAuth
from odoo import fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools import html2plaintext
# The Header parameters
HEADERS = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
JIRA_HEADERS = {
'Accept': 'application/json'
}
ATTACHMENT_HEADERS = {
'X-Atlassian-Token': 'no-check'
}
class ResConfigSettings(models.TransientModel):
""" This class is inheriting the model res.config.settings It contains
fields and functions for the model.
Methods:
get_values():
extends get_values() to include new config parameters
set_values():
extends set_values() to include new config parameters
action_test_connection():
action to perform when clicking on the 'Test Connection'
button.
action_export_to_jira():
action to perform when clicking on the 'Export/Sync Project'
button.
action_import_from_jira():
action to perform when clicking on the 'Export Users' button.
action_export_users():
action to perform when clicking on the 'Reset to Draft' button.
action_import_users():
action to perform when clicking on the 'Import Users' button.
_export_attachments(attachments, attachment_url):
it is used to export the given attachments to Jira.
find_attachment_type(attachment):
it is used to find the attachment type for the given attachment.
"""
_inherit = 'res.config.settings'
url = fields.Char(
string='URL', config_parameter='odoo_jira_connector.url',
help='Your Jira URL: E.g. https://yourname.atlassian.net/')
user_id_jira = fields.Char(
string='User Name', help='E.g. yourmail@gmail.com ',
config_parameter='odoo_jira_connector.user_id_jira')
api_token = fields.Char(string='API Token', help='API token in your Jira.',
config_parameter='odoo_jira_connector.api_token')
connection = fields.Boolean(
string='Connection', default=False, help='To identify the connection.',
config_parameter='odoo_jira_connector.connection')
export_project_count = fields.Integer(
string='Export Project Count', default=0, readonly=True,
help='Number of export projects.',
config_parameter='odoo_jira_connector.export_project_count')
export_task_count = fields.Integer(
string='Export Task Count', default=0, readonly=True,
help='Number of export tasks.',
config_parameter='odoo_jira_connector.export_task_count')
import_project_count = fields.Integer(
string='Import Project Count', default=0, readonly=True,
help='Number of import project.',
config_parameter='odoo_jira_connector.import_project_count')
import_task_count = fields.Integer(
string='Import Task Count', default=0, readonly=True,
help='Number of import tasks.',
config_parameter='odoo_jira_connector.import_task_count')
automatic = fields.Boolean(string='Automatic',
help='to make export/import data automated '
'while creating it on configured Jira '
'account.',
config_parameter='odoo_jira_connector.automatic')
def action_test_connection(self):
""" Test the connection to Jira
Raises: ValidationError: If the credentials are invalid.
Returns:
dict: client action for displaying notification
"""
try:
# Create an authentication object, using registered email-ID, and
# token received.
auth = HTTPBasicAuth(self.user_id_jira, self.api_token)
response = requests.request('GET',
self.url + 'rest/api/2/project',
headers=JIRA_HEADERS, auth=auth)
if response.status_code == 200 and 'expand' in response.text:
self.env['ir.config_parameter'].sudo().set_param(
'odoo_jira_connector.connection', True)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': _(
'Test connection to Jira successful.'),
'next': {
'type': 'ir.actions.act_window_close'
}
}
}
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'danger',
'message': _('Please Enter Valid Credentials.'),
'next': {
'type': 'ir.actions.act_window_close'
}
}
}
except Exception:
raise ValidationError(_('Please Enter Valid Credentials.'))
def action_export_to_jira(self):
""" Exporting All The Projects And Corresponding Tasks to Jira,
and updating the project or task on Jira if it is updated in Odoo.
"""
ir_config_parameter = self.env['ir.config_parameter'].sudo()
auth = HTTPBasicAuth(self.user_id_jira, self.api_token)
response = requests.request('GET', self.url + 'rest/api/2/project',
headers=JIRA_HEADERS, auth=auth)
projects = json.dumps(json.loads(response.text), sort_keys=True,
indent=4, separators=(',', ': '))
project_json = json.loads(projects)
name_list = [project['name'] for project in project_json]
id_list = [project['id'] for project in project_json]
odoo_projects = self.env['project.project'].search(
[('project_id_jira', 'in', id_list)])
for project in odoo_projects:
if project.jira_project_key:
project_keys = project.jira_project_key
else:
key = project.name.upper()
project_key = key[:3] + '1' + key[-3:]
project_keys = project_key.replace(' ', '')
response = requests.get(
self.url + 'rest/api/3/search', headers=HEADERS,
params={'jql': 'project = %s' % project_keys},
auth=(self.user_id_jira, self.api_token))
data = response.json()
issue_keys = [issue.get('key') for issue in data.get('issues', {})]
tasks = self.env['project.task'].search(
[('project_id', '=', project.id)])
for task in tasks:
attachment_url = self.url + 'rest/api/3/issue/%s/' \
'attachments' % task.task_id_jira
comment_url = self.url + 'rest/api/3/issue/%s/comment' % (
task.task_id_jira)
if str(task.task_id_jira) in issue_keys:
messages = self.env['mail.message'].search(
['&', ('res_id', '=', task.id),
('model', '=', 'project.task')])
attachments = self.env['ir.attachment'].search(
[('res_id', '=', task.id)])
self._export_attachments(attachments, attachment_url)
response = requests.get(
comment_url, headers=HEADERS, auth=(
self.user_id_jira, self.api_token))
data = response.json()
jira_comment_list = []
for comments in data['comments']:
content = comments.get('body', {}).get('content', [])
if content and isinstance(content, list) and content[
0].get('type') == 'paragraph':
text = content[0]['content'][0].get('text')
if text:
jira_comment_list.append(str(text))
odoo_comment_list = [str(
html2plaintext(chat.body)) for chat in messages if
str(html2plaintext(
chat.body)) not in jira_comment_list]
comment_list = list(filter(None, odoo_comment_list))
if len(comment_list) > 0:
for comment in comment_list:
data = json.dumps({
'body': {
'type': 'doc',
'version': 1,
'content': [{
'type': 'paragraph',
'content': [{
'text': comment,
'type': 'text'
}]}
]}
})
requests.post(
comment_url, headers=HEADERS, data=data,
auth=(self.user_id_jira, self.api_token))
else:
payload = json.dumps({
'fields': {
'project':
{
'key': project_keys
},
'summary': task.name,
'description': task.description,
'issuetype': {
'name': 'Task'
}
}
})
response = requests.post(
self.url + '/rest/api/2/issue', headers=HEADERS,
data=payload, auth=(self.user_id_jira, self.api_token))
data = response.json()
task.task_id_jira = data['key']
ir_config_parameter.set_param('odoo_jira_connector.export_task_count', int(
ir_config_parameter.get_param(
'odoo_jira_connector.export_task_count')) + 1)
messages = self.env['mail.message'].search(
['&', ('res_id', '=', task.id),
('model', '=', 'project.task')])
attachments = self.env['ir.attachment'].search(
[('res_id', '=', task.id)])
self._export_attachments(attachments, attachment_url)
for chat in messages:
data = json.dumps({
'body': {
'type': 'doc',
'version': 1,
'content': [{
'type': 'paragraph',
'content': [{
'text': str(html2plaintext(chat.body)),
'type': 'text'}]
}]
}
})
requests.post(
comment_url, headers=HEADERS, data=data,
auth=(self.user_id_jira, self.api_token))
odoo_projects = self.env['project.project'].search(
[('project_id_jira', 'not in', id_list),
('name', 'not in', name_list)])
for project in odoo_projects:
key = project.name.upper()
project_key = key[:3] + '1' + key[-3:]
project_keys = project_key.replace(' ', "")
auth = HTTPBasicAuth(self.user_id_jira, self.api_token)
project_payload = {
'name': project.name,
'key': project_keys,
'templateKey': 'com.pyxis.greenhopper.jira:gh-simplified'
'-kanban-classic'
}
response = requests.request(
'POST', self.url + 'rest/simplified/latest/project',
data=json.dumps(project_payload), headers=HEADERS, auth=auth)
data = response.json()
if 'projectId' in data:
project.write({
'project_id_jira': data['projectId'],
'jira_project_key': data['projectKey']
})
ir_config_parameter.set_param(
'odoo_jira_connector.export_project_count', int(ir_config_parameter.get_param(
'odoo_jira_connector.export_project_count')) + 1)
# for creating a new task inside the project
tasks = self.env['project.task'].search(
[('project_id', '=', project.id)])
for task in tasks:
payload = json.dumps({
'fields': {
'project': {
'key': project_keys
},
'summary': task.name,
'description': task.description,
'issuetype': {
'name': 'Task'
}
}
})
response2 = requests.post(
self.url + '/rest/api/2/issue', headers=HEADERS,
data=payload, auth=(self.user_id_jira, self.api_token))
data = response2.json()
task.task_id_jira = data['key']
attachment_url = self.url + 'rest/api/3/issue/%s/' \
'attachments' % task.task_id_jira
comment_url = self.url + 'rest/api/3/issue/%s/comment' % (
task.task_id_jira)
ir_config_parameter.set_param('odoo_jira_connector.export_task_count', int(
ir_config_parameter.get_param('odoo_jira_connector.export_task_count')) + 1)
messages = self.env['mail.message'].search(
['&', ('res_id', '=', task.id),
('model', '=', 'project.task')])
attachments = self.env['ir.attachment'].search(
[('res_id', '=', task.id)])
self._export_attachments(attachments, attachment_url)
for message in messages:
data = json.dumps({
'body': {
'type': 'doc',
'version': 1,
'content': [{
'type': 'paragraph',
'content': [{
'text': str(
html2plaintext(message.body)),
'type': 'text'}]
}]
}
})
requests.post(comment_url, headers=HEADERS, data=data,
auth=(self.user_id_jira, self.api_token))
elif 'errors' in data and 'projectName' in data['errors']:
raise ValidationError(
"A project with the names already exists in Jira. Please "
"rename the project to export as a new project.")
elif 'errors' in data and 'projectKey' in data['errors']:
raise ValidationError(data['errors']['projectKey'])
def action_import_from_jira(self):
""" Import all the projects and corresponding tasks
from Odoo to Jira. If a project or task is modified in Odoo,
it will also be updated in Jira.
"""
ir_config_parameter = self.env['ir.config_parameter'].sudo()
auth = HTTPBasicAuth(self.user_id_jira, self.api_token)
response = requests.request(
'GET', self.url + 'rest/api/2/project', headers=JIRA_HEADERS,
auth=auth)
projects = json.dumps(json.loads(response.text), sort_keys=True,
indent=4, separators=(',', ': '))
project_json = json.loads(projects)
value_list = [a_dict['key'] for a_dict in project_json]
name_list = [a_dict['name'] for a_dict in project_json]
id_list = [a_dict['id'] for a_dict in project_json]
odoo_projects = self.env['project.project'].search([])
for (project, key, jira_id) in zip(name_list, value_list, id_list):
jira_project_ids = [
project.project_id_jira for project in odoo_projects]
if int(jira_id) in jira_project_ids:
response = requests.get(
self.url + 'rest/api/3/search', headers=HEADERS,
params={'jql': 'project = %s' % key},
auth=(self.user_id_jira, self.api_token))
data = response.json()
project = self.env['project.project'].search(
[('project_id_jira', '=', int(jira_id))])
tasks = self.env['project.task'].search(
[('project_id', '=', project.id)])
task_jira_ids = [task.task_id_jira for task in tasks]
for issue in data['issues']:
comment_url = self.url + 'rest/api/3/issue/%s/' \
'comment' % issue['key']
if issue['key'] in task_jira_ids:
task_id = self.env['project.task'].search(
[('task_id_jira', '=', issue['key'])])
response = requests.get(
comment_url, headers=HEADERS,
auth=(self.user_id_jira, self.api_token))
data = response.json()
messages = self.env['mail.message'].search(
['&', ('res_id', '=', task_id.id),
('model', '=', 'project.task')])
odoo_comment_list = [str(html2plaintext(
chat.body)) for chat in messages]
jira_comment_list = [str(
comments['body']['content'][0]['content'][0][
'text']) for comments in
data['comments'] if str(
comments['body']['content'][0]['content'][0][
'text']) not in odoo_comment_list]
comment_list = list(filter(None, jira_comment_list))
if len(comment_list) > 0:
for comment in comment_list:
task_id.message_post(body=str(comment))
else:
task_id = self.env['project.task'].create({
'project_id': project.id,
'name': issue['fields']['summary'],
'task_id_jira': issue['key']
})
ir_config_parameter.set_param(
'odoo_jira_connector.import_task_count', int(
ir_config_parameter.get_param(
'odoo_jira_connector.import_task_count')) + 1)
messages = self.env['mail.message'].search(
['&', ('res_id', '=', task_id.id),
('model', '=', 'project.task')])
response = requests.get(
comment_url, headers=HEADERS,
auth=(self.user_id_jira, self.api_token))
data = response.json()
odoo_comment_list = [str(
html2plaintext(chat.body)) for chat in messages]
jira_comment_list = [str(
comments['body']['content'][0]['content'][0][
'text']) for comments in
data['comments'] if str(
comments['body']['content'][0]['content'][0][
'text']) not in odoo_comment_list]
comment_list = list(filter(None, jira_comment_list))
if len(comment_list) > 0:
for comment in comment_list:
task_id.message_post(body=str(comment))
else:
project = self.env['project.project'].create({
'name': project,
'project_id_jira': jira_id,
'jira_project_key': key
})
ir_config_parameter.set_param(
'odoo_jira_connector.import_project_count', int(ir_config_parameter.get_param(
'odoo_jira_connector.import_project_count')) + 1)
response = requests.get(
self.url + 'rest/api/3/search', headers=HEADERS,
params={'jql': 'project = %s' % key},
auth=(self.user_id_jira, self.api_token))
data = response.json()
for issue in data['issues']:
task_id = self.env['project.task'].create({
'project_id': project.id,
'name': issue['fields']['summary'],
'task_id_jira': issue['key']
})
rec = ir_config_parameter.set_param(
'odoo_jira_connector.import_task_count', int(ir_config_parameter.get_param(
'odoo_jira_connector.import_task_count')) + 1)
messages = self.env['mail.message'].search(
['&', ('res_id', '=', task_id.id),
('model', '=', 'project.task')])
comment_url = self.url + 'rest/api/3/issue/%s/' \
'comment' % issue['key']
response = requests.get(
comment_url, headers=HEADERS,
auth=(self.user_id_jira, self.api_token))
data = response.json()
odoo_comment_list = [str(html2plaintext(
chat.body)) for chat in messages]
jira_comment_list = [str(
comments['body']['content'][0]['content'][0][
'text']) for comments in
data['comments'] if str(
comments['body']['content'][0]['content'][0][
'text']) not in odoo_comment_list]
comment_list = list(filter(None, jira_comment_list))
if len(comment_list) > 0:
for comment in comment_list:
task_id.message_post(body=str(comment))
def action_export_users(self):
""" Exporting all the users from Odoo to Jira, and updating the user's
information on Jira if it has been updated in Odoo
Raises: ValidationError: If the credentials are not valid.
"""
response = requests.get(
self.url + 'rest/api/2/users/search', headers=HEADERS,
auth=(self.user_id_jira, self.api_token))
data = response.json()
issue_keys = [issue['accountId'] for issue in data]
users = self.env['res.users'].search(
[('jira_user_key', 'in', issue_keys)])
non_jira_users = self.env['res.users'].search(
[('jira_user_key', 'not in', issue_keys)])
if users:
for user_data in data:
for user in users:
if user_data['accountId'] == user.jira_user_key:
user_data.update({
'displayName': user.name
})
if non_jira_users:
regex = '^\S+@\S+\.\S+$'
for user in non_jira_users:
objs = re.search(regex, user.login)
if objs:
if objs.string == str(user.login):
payload = json.dumps({
'emailAddress': user.login,
'displayName': user.name,
'name': user.name
})
response = requests.post(
self.url + 'rest/api/3/user', headers=HEADERS,
data=payload,
auth=(self.user_id_jira, self.api_token))
data = response.json()
user.write({
'jira_user_key': data['accountId']
})
else:
raise ValidationError('Invalid E-mail address.')
def action_import_users(self):
""" Importing all the users from Jira to Odoo, and updating the user's
information on Odoo if it has been updated in Jira.
"""
response = requests.get(
self.url + 'rest/api/2/users/search', headers=HEADERS,
auth=(self.user_id_jira, self.api_token))
data = response.json()
for user_data in data:
users = self.env['res.users'].sudo().search(
[('login', '=', user_data['displayName'])])
if users:
users.write({
'jira_user_key': user_data['accountId']
})
else:
self.env['res.users'].create({
'login': user_data['displayName'],
'name': user_data['displayName'],
'jira_user_key': user_data['accountId'],
})
def _export_attachments(self, attachments, attachment_url):
""" To find the corresponding attachment type in the attachment model
Args:
attachments (model.Model): values for creating new records.
attachment_url (str): URL for the attachment.
"""
for attachment in attachments:
attachment_type = self.find_attachment_type(attachment)
if attachment.datas and attachment_type in ('pdf', 'xlsx', 'jpg'):
temp_file_path = f'/tmp/temp.{attachment_type}'
binary_data = base64.b64decode(attachment.datas)
# Save the binary data to a file
with open(temp_file_path, 'wb') as file:
file.write(binary_data)
if attachment_type == 'jpg' and os.path.splitext(
temp_file_path)[1].lower() != '.jpg':
# Rename the saved file to its corresponding JPG file format
file_path = os.path.splitext(temp_file_path)[0] + '.jpg'
os.rename(temp_file_path, file_path)
temp_file_path = file_path
attachment_file = {
'file': (attachment.name, open(temp_file_path, 'rb'))
}
requests.post(attachment_url, headers=ATTACHMENT_HEADERS,
files=attachment_file,
auth=(self.user_id_jira, self.api_token))
def find_attachment_type(self, attachment):
""" To find the corresponding attachment type in the attachment model
Args:
attachment (model.Model): attachment to fetch the type.
Returns:
str: the attachment type
"""
if attachment.mimetype == 'application/pdf':
return 'pdf'
if attachment.mimetype == 'image/png':
return 'jpg'
if attachment.mimetype == 'application/vnd.openxmlformats-' \
'officedocument.spreadsheetml.sheet':
return 'xlsx'
return ''

80
odoo_jira_connector/models/res_users.py

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Rosmy John (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
import json
import re
import requests
from odoo import api, fields, models
class ResUsers(models.Model):
"""
This class is inherited for adding an extra field and
override the create function.
Methods:
create():
extends create(vals_list) for exporting the new users to Jira
"""
_inherit = 'res.users'
jira_user_key = fields.Char(string='Jira User Key',
help='The user key of Jira.', readonly=True)
@api.model_create_multi
def create(self, vals_list):
""" Overrides the create method of users for exporting the new users
to Jira """
ir_config_parameter = self.env['ir.config_parameter'].sudo()
jira_connection = ir_config_parameter.get_param(
'odoo_jira_connector.connection')
if jira_connection:
user_auth = ir_config_parameter.get_param(
'odoo_jira_connector.user_id_jira')
password = ir_config_parameter.get_param(
'odoo_jira_connector.api_token')
users = super(ResUsers, self).create(vals_list)
odoo_user_url = ir_config_parameter.get_param(
'odoo_jira_connector.url') + 'rest/api/3/user'
odoo_user_headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
payload = json.dumps({
'emailAddress': users.login,
'displayName': users.name,
'name': users.name
})
match = re.search('^\S+@\S+\.\S+$', users.login)
if match and match.string == str(users.login):
response = requests.post(
odoo_user_url, headers=odoo_user_headers, data=payload,
auth=(user_auth, password))
data = response.json()
users.write({'jira_user_key': data['accountId']})
return users
else:
users = super(ResUsers, self).create(vals_list)
for user in users:
# if partner is global we keep it that way
if user.partner_id.company_id:
user.partner_id.company_id = user.company_id
user.partner_id.active = user.active
return users

2
odoo_jira_connector/security/ir.model.access.csv

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_jira_sprint,jira.sprint.access,model_jira_sprint,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_jira_sprint jira.sprint.access model_jira_sprint base.group_user 1 1 1 1

BIN
odoo_jira_connector/static/description/assets/icons/check.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
odoo_jira_connector/static/description/assets/icons/chevron.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

BIN
odoo_jira_connector/static/description/assets/icons/cogs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
odoo_jira_connector/static/description/assets/icons/consultation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
odoo_jira_connector/static/description/assets/icons/ecom-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
odoo_jira_connector/static/description/assets/icons/education-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
odoo_jira_connector/static/description/assets/icons/hotel-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

BIN
odoo_jira_connector/static/description/assets/icons/license.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
odoo_jira_connector/static/description/assets/icons/lifebuoy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
odoo_jira_connector/static/description/assets/icons/manufacturing-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

BIN
odoo_jira_connector/static/description/assets/icons/pos-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

BIN
odoo_jira_connector/static/description/assets/icons/puzzle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

BIN
odoo_jira_connector/static/description/assets/icons/restaurant-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
odoo_jira_connector/static/description/assets/icons/service-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

BIN
odoo_jira_connector/static/description/assets/icons/trading-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

BIN
odoo_jira_connector/static/description/assets/icons/training.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

BIN
odoo_jira_connector/static/description/assets/icons/update.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
odoo_jira_connector/static/description/assets/icons/user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

BIN
odoo_jira_connector/static/description/assets/icons/wrench.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
odoo_jira_connector/static/description/assets/misc/Cybrosys R.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
odoo_jira_connector/static/description/assets/misc/categories.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
odoo_jira_connector/static/description/assets/misc/check-box.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
odoo_jira_connector/static/description/assets/misc/compass.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
odoo_jira_connector/static/description/assets/misc/corporate.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
odoo_jira_connector/static/description/assets/misc/customer-support.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
odoo_jira_connector/static/description/assets/misc/cybrosys-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
odoo_jira_connector/static/description/assets/misc/features.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

BIN
odoo_jira_connector/static/description/assets/misc/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
odoo_jira_connector/static/description/assets/misc/pictures.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
odoo_jira_connector/static/description/assets/misc/pie-chart.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
odoo_jira_connector/static/description/assets/misc/right-arrow.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

BIN
odoo_jira_connector/static/description/assets/misc/star.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
odoo_jira_connector/static/description/assets/misc/support.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
odoo_jira_connector/static/description/assets/misc/whatsapp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
odoo_jira_connector/static/description/assets/modules/1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
odoo_jira_connector/static/description/assets/modules/2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
odoo_jira_connector/static/description/assets/modules/3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
odoo_jira_connector/static/description/assets/modules/5.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
odoo_jira_connector/static/description/assets/modules/6.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
odoo_jira_connector/static/description/assets/modules/banner.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/Projects_in_jira.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/api_token2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/creat_api_token.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/e_exported_stage.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/e_included_automatic.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/e_scrum_with_sprint.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/e_sprint_odoo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/e_sprint_odoo_form.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/e_sprint_odoo_li.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/e_stage_odoo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/e_test_connection.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/hero.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/heros.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/project_admin_user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/queue_job.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/v16-hero.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/webhook.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/webhook_setup_1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/webhook_setup_2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

BIN
odoo_jira_connector/static/description/assets/screenshots/webhook_setup_3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
odoo_jira_connector/static/description/banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
odoo_jira_connector/static/description/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

635
odoo_jira_connector/static/description/index.html

@ -0,0 +1,635 @@
<div class="container" style="padding: 1rem !important; margin-bottom: 1rem !important;">
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 d-flex justify-content-between" style="border-bottom: 1px solid #d5d5d5;">
<div class="my-3">
<img src="./assets/icons/logo.png" style="width: auto !important; height: 40px !important;">
</div>
<div class="my-3 d-flex align-items-center">
<div
style="background-color: #7C7BAD !important; color: #fff !important; font-weight: 600 !important; padding: 5px 15px 8px !important; margin: 0 5px !important;">
<i class="fa fa-check mr-1"></i>Community
</div>
<div
style="background-color: #875A7B !important; color: #fff !important; font-weight: 600 !important; padding: 5px 15px 8px !important; margin: 0 5px !important;">
<i class="fa fa-check mr-1"></i>Enterprise
</div>
</div>
</div>
</div>
</div>
<div class="container" style="padding: 0rem 1.5rem 4rem !important">
<div class="row" style="height: 900px !important;">
<div class="col-sm-12 col-md-12 col-lg-12"
style="padding: 4rem 1rem !important; background-color: #714B67 !important; height: 600px !important; border-radius: 20px !important;">
<h1
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #FFFFFF !important; font-size: 3.5rem !important; text-align: center !important;">
Odoo Jira Connector</h1>
<p
style="font-family: 'Montserrat', sans-serif !important; font-weight: 300 !important; color: #FFFFFF !important; font-size: 1.4rem !important; text-align: center !important;">
Odoo Jira Connector is a valuable integration tool for businesses that use both Odoo and Jira.
</p>
<img src="assets/screenshots/hero.gif" class="img-responsive" width="100%" height="auto" />
</div>
</div>
<div class="row">
<div class="col-md-12" style="border-bottom: 1px solid #d5d5d5 !important; margin-bottom: 2rem !important">
<h2
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;">
<i class="fa fa-compass mr-2"></i>Explore this module
</h2>
<div class="row">
<div class="col-md-6">
<a href="#overview" style="text-decoration: none !important;">
<div class="row"
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;">
<div class="col-8">
<h3
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;">
Overview</h3>
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;">
Learn more about this module</p>
</div>
<div class="col-4 text-right d-flex justify-content-end align-items-center">
<i class="fa fa-chevron-right" style="color: #714B67 !important;"></i>
</div>
</div>
</a>
</div>
<div class="col-md-6">
<a href="#features" style="text-decoration: none !important;">
<div class="row"
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;">
<div class="col-8">
<h3
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;">
Features</h3>
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;">
View features of this module</p>
</div>
<div class="col-4 text-right d-flex justify-content-end align-items-center">
<i class="fa fa-chevron-right" style="color: #714B67 !important;"></i>
</div>
</div>
</a>
</div>
<div class="col-md-6">
<a href="#screenshots" style="text-decoration: none !important;">
<div class="row"
style="background-color: #f5f2f5 !important; border-radius: 10px !important; margin: 1rem !important; padding: 1.5em !important; height: 100px !important;">
<div class="col-8">
<h3
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.2rem !important;">
Screenshots</h3>
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #714B67 !important; font-size: 0.9rem !important;">
See key screenshots of this module</p>
</div>
<div class="col-4 text-right d-flex justify-content-end align-items-center">
<i class="fa fa-chevron-right" style="color: #714B67 !important;"></i>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<div class="row" id="overview">
<div class="col-md-12" style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important">
<h2
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;">
<i class="fa fa-pie-chart mr-2"></i>Overview
</h2>
</div>
<div class="col-mg-12 pl-3">
<p
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important; line-height: 30px !important;">
The Odoo Jira Connector offers a range of features, including bidirectional synchronization of data, automatic creation of Jira issues from
Odoo records, and real-time updates of Jira issues in Odoo. To meet the specific needs of any business users can leverage, they can use Odoo
to handle their business. By connecting these two systems, businesses can streamline their project management processes and improve their
overall efficiency.</p>
</div>
</p>
</div>
<div class="row" id="features">
<div class="col-md-12" style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important">
<h2
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;">
<i class="fa fa-star mr-2"></i>Features
</h2>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Export/import all the information of Project and Task.</span>
</div>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Projects, Tasks, Attachment, Status, Users are created automatically in Jira when they are created in Odoo,
and these are also created automatically in Odoo when they are created in Jira.</span>
</div>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">If we delete Task and Attachments in Odoo, it will be automatically removed from Jira, Also if we delete Task and Attachments in Jira, it will be automatically removed from Odoo.</span>
</div>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Available in Odoo 15.0 Community and Enterprise.</span>
</div>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">All the Messages and log notes in the chatter of the Task are automatically added to the comments of corresponding Task in Jira.</span>
</div>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Available Sprint feature.</span>
</div>
</div>
<div class="col-md-6 pl-3 py-3 d-flex">
<div>
<img src="assets/icons/check.png">
</div>
<div>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Exporting Attachment from Odoo to Jira.</span>
</div>
</div>
</div>
<div class="row" id="screenshots">
<div class="col-md-12" style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important">
<h2
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;">
<i class="fa fa-image mr-2"></i>Screenshots
</h2>
</div>
<div class="col-lg-12 my-2">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Jira Api Key Generation</h3>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
Go to Jira --> Security --> Create API token.</p>
<img src="./assets/screenshots/creat_api_token.png" class="img-thumbnail">
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">Provide a label and click on the Create button.</p>
<img src="./assets/screenshots/api_token2.png" class="img-thumbnail">
</div>
<div class="col-lg-12 my-3">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Settings View</h3>
<h2 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Project Admin can only access Jira connector in settings.</h2>
<img src="./assets/screenshots/project_admin_user.png" class="img-thumbnail">
<p>First set the URL of Jira, Username and API Token. Then, you can test the connection.</p>
<img src="assets/screenshots/e_test_connection.png" class="img-thumbnail">
<p>After that you can export/import or sync all the project, task and users. It also syncs the comments and attachments.</p>
<p>You can import projects while creating them in your Jira account using the "Automatic" feature .</p>
<img src="assets/screenshots/e_included_automatic.png" class="img-thumbnail">
<p>For that you need to create a webhook in your Jira account.</p>
<h3>WebHook setup</h3>
<p>Go to Jira --> Settings --> System settings --> WebHooks</p>
<img src="assets/screenshots/webhook.png" class="img-thumbnail">
<p>click on create.</p>
<img src="assets/screenshots/webhook_setup_1.png" class="img-thumbnail">
<p>After adding details like 'name' and 'status',Add url "/jira_webhook" with your app's url.</p>
<h4>Only allowed protocol is HTTPS.</h4>
<img src="assets/screenshots/webhook_setup_2.png" class="img-thumbnail">
<p>Then enable events to trigger that webhook.</p>
<img src="assets/screenshots/webhook_setup_3.png" class="img-thumbnail">
<p>After that save the webhook.</p>
</div>
<div class="col-lg-12 my-3">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Jira View</h3>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
If the Export/Sync is clicked, all the project and the corresponding tasks, attachments, and the messages in the chatter are exported
to Jira. If the Import Project & Task is clicked, all the projects, corresponding issues and comments are imported to Odoo. We can
also import & export the users from Odoo to Jira. After that, when we create a project or task or add an attachment in the task or add
messages in Odoo it will automatically create in Jira.</p>
<img src="./assets/screenshots/Projects_in_jira.png" class="img-thumbnail">
</div>
<div class="col-lg-12 my-3">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Sprint In Odoo</h3>
<p>Sprint feature is Added in Odoo For Team Managed project Which is imported from Jira.</p>
<img src="./assets/screenshots/e_sprint_odoo.png" class="img-thumbnail">
<p>That project Which created on Jira will be imported in Odoo.</p>
<p>you can add a sprint in that project from Jira.</p>
<img src="./assets/screenshots/e_scrum_with_sprint.png" class="img-thumbnail">
<p>after start the sprint you can see that sprint in odoo.</p>
<img src="assets/screenshots/e_sprint_odoo_li.png" class="img-thumbnail">
<img src="assets/screenshots/e_sprint_odoo_form.png" class="img-thumbnail">
<h4>Sprint feature is only available for imported team managed project which is having scrum template.</h4>
</div>
<div class="col-lg-12 my-3">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Stages Exporting</h3>
<p>Stages can be exported while creating stage for imported project,you can choose the 'Jira status category' from Odoo.</p>
<img src="assets/screenshots/e_stage_odoo.png" class="img-thumbnail">
<p>exported stages can be seen in the workflow of that project,so from there you can add it to the project.</p>
<img src="assets/screenshots/e_exported_stage.png" class="img-thumbnail">
</div>
<div class="col-lg-12 my-3">
<h3>Queue Job</h3>
<p>These OCA Modules are used to makes the webhook response data into separate queues and performs each queue one after another manner
, So It is necessary to install these modules.</p>
<img src="assets/screenshots/queue_job.png" class="img-thumbnail">
</div>
</div>
<!-- SUGGESTED PRODUCTS -->
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center"
style="text-align: center; padding: 2.5rem 1rem !important;">
<h2 style="color: #212529 !important;">Suggested Products</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;" />
<div id="demo1" class="row carousel slide" data-ride="carousel">
<!-- The slideshow -->
<div class="carousel-inner">
<div class="carousel-item active" style="min-height:0px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/export_stockinfo_xls/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/export_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/custom_gantt_view/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/gantt_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/sales_credit_limit/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/credit_image.png">
</div>
</a>
</div>
</div>
<div class="carousel-item" style="min-height:0px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/base_account_budget/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/budget_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/product_to_quotation/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/quotation_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/employee_documents_expiry/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/employee_image.png">
</div>
</a>
</div>
</div>
</div>
<!-- Left and right controls -->
<a class="carousel-control-prev" href="#demo1" data-slide="prev" style="left:-25px;width: 35px;color: #000;">
<span class="carousel-control-prev-icon"><i class="fa fa-chevron-left" style="font-size:24px"></i></span> </a>
<a class="carousel-control-next" href="#demo1" data-slide="next" style="right:-25px;width: 35px;color: #000;">
<span class="carousel-control-next-icon"><i class="fa fa-chevron-right" style="font-size:24px"></i></span>
</a>
</div>
</div>
</div>
<!-- END OF SUGGESTED PRODUCTS -->
<!-- OUR SERVICES -->
<section class="container" style="margin-top: 6rem !important;">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center">
<h2 style="color: #212529 !important;">Our Services</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;" />
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/cogs.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Customization</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/wrench.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/lifebuoy.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Support</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/user.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Hire
Odoo
Developer</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/puzzle.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Integration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/update.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Migration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/consultation.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Consultancy</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/training.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/license.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Licensing Consultancy</h6>
</div>
</div>
</section>
<!-- END OF END OF OUR SERVICES -->
<!-- OUR INDUSTRIES -->
<section class="container" style="margin-top: 6rem !important;">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center">
<h2 style="color: #212529 !important;">Our Industries</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;" />
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/trading-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Trading
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easily procure
and
sell your products</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/pos-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
POS
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easy
configuration
and convivial experience</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/education-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Education
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
A platform for
educational management</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/manufacturing-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Manufacturing
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Plan, track and
schedule your operations</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/ecom-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
E-commerce &amp; Website
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Mobile
friendly,
awe-inspiring product pages</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/service-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Service Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Keep track of
services and invoice</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/restaurant-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Restaurant
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Run your bar or
restaurant methodically</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/hotel-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Hotel Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
An
all-inclusive
hotel management application</p>
</div>
</div>
</div>
</section>
<!-- END OF END OF OUR INDUSTRIES -->
<!-- FOOTER -->
<!-- Footer Section -->
<section class="container" style="margin: 5rem auto 2rem;">
<div class="row" style="max-width:1540px;">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center">
<h2 style="color: #212529 !important;">Need Help?</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;" />
</div>
</div>
<!-- Contact Cards -->
<div class="row d-flex justify-content-center align-items-center"
style="max-width:1540px; margin: 0 auto 2rem auto;">
<div class="col-lg-12" style="padding: 0rem 3rem 2rem; border-radius: 10px; margin-right: 3rem; ">
<div class="row mt-4">
<div class="col-lg-6">
<a href="mailto:odoo@cybrosys.com" target="_blank" class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #4d4d4d; color: #FFF; border-radius: 4px;"><i
class="fa fa-envelope mr-2"></i>odoo@cybrosys.com</a>
</div>
<div class="col-lg-6">
<a href="https://api.whatsapp.com/send?phone=918606827707" target="_blank"
class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #25D366; color: #FFF; border-radius: 4px;"><i
class="fa fa-whatsapp mr-2"></i>+91 86068 27707</a>
</div>
</div>
</div>
</div>
<!-- End of Contact Cards -->
</section>
<!-- Footer -->
<section class="oe_container" style="padding: 2rem 3rem 1rem;">
<div class="row" style="max-width:1540px; margin: 0 auto; margin-right: 3rem; ">
<!-- Logo -->
<div class="col-lg-12 d-flex justify-content-center align-items-center" style="margin-top: 3rem;">
<img src="https://www.cybrosys.com/images/logo.png" width="200px" height="auto" />
</div>
<!-- End of Logo -->
<div class="col-lg-12">
<hr
style="margin-top: 3rem;background: linear-gradient(90deg, rgba(2,0,36,0) 0%, rgba(229,229,229,1) 33%, rgba(229,229,229,1) 58%, rgba(0,212,255,0) 100%); height: 2px; border-style: none;">
<!-- End of Footer Section -->
</div>
</div>
</section>
<!-- END OF FOOTER -->
</div>

53
odoo_jira_connector/views/jira_sprint_views.xml

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- SPRINT TREE VIEW-->
<record id="jira_sprint_view_tree" model="ir.ui.view">
<field name="name">jira.sprint.view.tree</field>
<field name="model">jira.sprint</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="sprint_goal"/>
<field name="start_date"/>
<field name="end_date"/>
</tree>
</field>
</record>
<!-- SPRINT FORM VIEW-->
<record id="jira_sprint_view_form" model="ir.ui.view">
<field name="name">jira.sprint.view.form</field>
<field name="model">jira.sprint</field>
<field name="arch" type="xml">
<form>
<header>
<button string="Tasks" class="oe_stat_button" type="object" name="action_get_tasks"/>
<button string="Backlogs" class="oe_stat_button" type="object" name="action_get_backlogs"/>
<button string="All tasks" class="oe_stat_button" type="object" name="action_get_all_tasks"/>
</header>
<sheet>
<group>
<h3>
<field name="name" placeholder="Sprint name...." help="Enter name of the sprint."/>
</h3>
<group>
<field name="start_date" help="Start date of sprint."/>
<field name="end_date" help="End date of sprint."/>
<field name="project_id" help="Related project."/>
<field name="sprint_id_jira" help="Sprint id of Jira."/>
</group>
<notebook>
<page string="Goals">
<field name="sprint_goal" widget="html" help="Goals of the sprint."/>
</page>
</notebook>
</group>
</sheet>
</form>
</field>
</record>
<record id="jira_sprint_view_action" model="ir.actions.act_window">
<field name="name">Sprint</field>
<field name="res_model">jira.sprint</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

16
odoo_jira_connector/views/project_task_type_views.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Inheriting project.task.type form view and adding new field to project_id_jira -->
<record id="task_type_edit" model="ir.ui.view">
<field name="name">project.task.type.view.form.inherit.odoo.jira.connector</field>
<field name="model">project.task.type</field>
<field name="type">form</field>
<field name="inherit_id" ref="project.task_type_edit"/>
<field name="arch" type="xml">
<field name="project_ids" position="after">
<field name="stages_jira_id" help="stage id of Jira."/>
<field name="jira_stages_category" help="Category of stages in jira."/>
</field>
</field>
</record>
</odoo>

33
odoo_jira_connector/views/project_views.xml

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Inheriting project.project form view and adding new field to project_id_jira -->
<record id="edit_project" model="ir.ui.view">
<field name="name">project.project.view.form.inherit.odoo.jira.connector</field>
<field name="model">project.project</field>
<field name="type">form</field>
<field name="inherit_id" ref="project.edit_project"/>
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="project_id_jira"/>
</field>
<xpath expr="//button[@name='%(project.project_milestone_all)d']" position="before">
<field name="sprint_active" invisible="1"/>
<button string="Sprint" class="oe_stat_button" type="object" name="action_get_sprint" icon="fa-clock-o" attrs="{'invisible':[('sprint_active', '!=' ,True)]}"/>
</xpath>
</field>
</record>
<!-- Inheriting the project.task form view and adding new field task_id_jira -->
<record id="view_task_form2" model="ir.ui.view">
<field name="name">project.task.view.form.inherit.odoo.jira.connector</field>
<field name="model">project.task</field>
<field name="type">form</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<field name="tag_ids" position="after">
<field name="task_id_jira" help="id of task in jira."/>
<field name="sprint_id" attrs="{'invisible':[('task_sprint_active', '!=' ,True)]}" help="related sprint."/>
<field name="task_sprint_active" invisible="1"/>
</field>
</field>
</record>
</odoo>

82
odoo_jira_connector/views/res_config_settings_views.xml

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Inheriting the config setting and add the fields and buttons for connecting to Jira -->
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.odoo.jira.connector</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="20"/>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div class="app_settings_block" data-string="Jira Connector" string="Jira Connector" data-key="odoo_jira_connector" groups="project.group_project_manager">
<h2>Jira Connector</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box" id="print_node_settings">
<div class="o_setting_right_pane">
<div class="content-group">
<div class="row mt8">
<label class="col-lg-3" string="URL" for="url"/>
<field name="url" help="Url of Jira."/>
</div>
<div class="row mt8">
<label class="col-lg-3" string="User Name" for="user_id_jira"/>
<field name="user_id_jira" help="Enter User name in Jira."/>
</div>
<div class="row mt8">
<label class="col-lg-3" string="API Token" for="api_token"/>
<field name="api_token" help="Enter api token."/>
</div>
<div class="row mt8">
<label class="col-lg-3" string="Automatic" for="automatic" attrs="{'invisible':[('connection', '!=' ,True)]}"/>
<field name="automatic" attrs="{'invisible':[('connection', '!=' ,True)]}" help="Using this field we can automate the project management in Jira ,Eg:we can create project in odoo while creating it on Jira."/>
</div>
<div class="row mt8">
<label class="col-lg-3" string="Export Project Count" for="export_project_count"/>
<field name="export_project_count" help="count of exported project."/>
</div>
<div class="row mt8">
<label class="col-lg-3" string="Export Task Count" for="export_task_count"/>
<field name="export_task_count" help="count of exported tasks."/>
</div>
<div class="row mt8">
<label class="col-lg-3" string="Import Project Count" for="import_project_count"/>
<field name="import_project_count" help="count of imported project."/>
</div>
<div class="row mt8">
<label class="col-lg-3" string="Import Task Count" for="import_task_count"/>
<field name="import_task_count" help="count of imported task."/>
</div>
<div class="row mt8">
<field name="connection" invisible="1"/>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_right_pane" style="display: flex; flex-wrap: wrap;">
<button name="action_test_connection" string="Test Connection"
attrs="{'invisible':[('connection', '=' ,True)]}" type="object" class="btn-primary"/>
<div style="margin:10px !important">
<button name="action_export_to_jira" string="Export/Sync Project"
attrs="{'invisible':['|',('connection', '!=' ,True),('automatic', '=' ,True)]}" type="object" class="btn-primary"/>
</div>
<div style="margin:10px !important">
<button name="action_import_from_jira" string="Import Project &amp; Tasks"
attrs="{'invisible':['|',('connection', '!=' ,True),('automatic', '=' ,True)]}" type="object" class="btn-primary"/>
</div>
<div style="margin:10px !important">
<button name="action_export_users" string="Export Users"
attrs="{'invisible':['|',('connection', '!=' ,True),('automatic', '=' ,True)]}" type="object" class="btn-primary"/>
</div>
<div style="margin:10px !important">
<button name="action_import_users" string="Import Users"
attrs="{'invisible':['|',('connection', '!=' ,True),('automatic', '=' ,True)]}" type="object" class="btn-primary"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

16
odoo_jira_connector/views/res_users_views.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- inheriting res.user form view and adding new field -->
<record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.view.form.inherit.odoo.jira.connector</field>
<field name="model">res.users</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<field name="login" position="after">
<label for="jira_user_key">Jira Key</label>
<field name="jira_user_key" string="Jira Key" help="Jira user Key." readonly="1"/>
</field>
</field>
</record>
</odoo>
Loading…
Cancel
Save