| @ -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>`__ | ||||
| @ -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") | ||||
| @ -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' | ||||
|  | } | ||||
| @ -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 | ||||
| @ -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) | ||||
| @ -0,0 +1,6 @@ | |||||
|  | ## Module <odoo_jira_connector> | ||||
|  | 
 | ||||
|  | #### 01.08.2024 | ||||
|  | #### Version 15.0.1.0.0 | ||||
|  | #### ADD | ||||
|  | - Initial commit for Odoo Jira Connector | ||||
| @ -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 | ||||
| @ -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() | ||||
| @ -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}" | ||||
|  |         } | ||||
| @ -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 | ||||
| @ -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() | ||||
| @ -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 | ||||
| @ -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 '' | ||||
| @ -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 | ||||
| 
 | 
| After Width: | Height: | Size: 3.6 KiB | 
| After Width: | Height: | Size: 310 B | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 1.4 KiB | 
| After Width: | Height: | Size: 576 B | 
| After Width: | Height: | Size: 733 B | 
| After Width: | Height: | Size: 911 B | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 673 B | 
| After Width: | Height: | Size: 878 B | 
| After Width: | Height: | Size: 653 B | 
| After Width: | Height: | Size: 905 B | 
| After Width: | Height: | Size: 839 B | 
| After Width: | Height: | Size: 427 B | 
| After Width: | Height: | Size: 627 B | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 988 B | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 80 KiB | 
| After Width: | Height: | Size: 1.5 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.9 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 2.1 KiB | 
| After Width: | Height: | Size: 4.4 KiB | 
| After Width: | Height: | Size: 589 B | 
| After Width: | Height: | Size: 3.4 KiB | 
| After Width: | Height: | Size: 1.7 KiB | 
| After Width: | Height: | Size: 2.3 KiB | 
| After Width: | Height: | Size: 967 B | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 3.8 KiB | 
| After Width: | Height: | Size: 5.0 KiB | 
| After Width: | Height: | Size: 82 KiB | 
| After Width: | Height: | Size: 76 KiB | 
| After Width: | Height: | Size: 92 KiB | 
| After Width: | Height: | Size: 85 KiB | 
| After Width: | Height: | Size: 95 KiB | 
| After Width: | Height: | Size: 73 KiB | 
| After Width: | Height: | Size: 135 KiB | 
| After Width: | Height: | Size: 72 KiB | 
| After Width: | Height: | Size: 74 KiB | 
| After Width: | Height: | Size: 110 KiB | 
| After Width: | Height: | Size: 55 KiB | 
| After Width: | Height: | Size: 92 KiB | 
| After Width: | Height: | Size: 84 KiB | 
| After Width: | Height: | Size: 63 KiB | 
| After Width: | Height: | Size: 54 KiB | 
| After Width: | Height: | Size: 72 KiB | 
| After Width: | Height: | Size: 52 KiB | 
| After Width: | Height: | Size: 327 KiB | 
| After Width: | Height: | Size: 45 KiB | 
| After Width: | Height: | Size: 90 KiB | 
| After Width: | Height: | Size: 15 KiB | 
| After Width: | Height: | Size: 115 KiB | 
| After Width: | Height: | Size: 159 KiB | 
| After Width: | Height: | Size: 127 KiB | 
| After Width: | Height: | Size: 125 KiB | 
| After Width: | Height: | Size: 123 KiB | 
| After Width: | Height: | Size: 45 KiB | 
| After Width: | Height: | Size: 7.5 KiB | 
| @ -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 & 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> | ||||
| @ -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> | ||||
| @ -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> | ||||
| @ -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> | ||||
| @ -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 & 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> | ||||
| @ -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> | ||||