| @ -1,46 +0,0 @@ | |||
| .. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg | |||
|     :target: https://www.gnu.org/licenses/agpl-3.0-standalone.html | |||
|     :alt: License: AGPL-3 | |||
| 
 | |||
| Odoo Webhook | |||
| ============ | |||
| Odoo will send payload to the registered urls when an event occurs on the selected model. | |||
| 
 | |||
| Configuration | |||
| ============= | |||
| * No additional configurations needed | |||
| 
 | |||
| Company | |||
| ------- | |||
| * `Cybrosys Techno Solutions <https://cybrosys.com/>`__ | |||
| 
 | |||
| License | |||
| ------- | |||
| GNU AFFERO GENERAL PUBLIC LICENSE, Version 3 (AGPLv3) | |||
| (https://www.gnu.org/licenses/agpl-3.0-standalone.html) | |||
| 
 | |||
| Credits | |||
| ------- | |||
| Developer: (V16) Unnimaya C O, 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>`__ | |||
| @ -1,22 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ################################################################################ | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #    Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | |||
| #    Author: Unnimaya C O (odoo@cybrosys.com) | |||
| # | |||
| #    You can modify it under the terms of the GNU AFFERO | |||
| #    GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | |||
| # | |||
| #    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | |||
| #    (AGPL v3) along with this program. | |||
| #    If not, see <http://www.gnu.org/licenses/>. | |||
| # | |||
| ################################################################################ | |||
| from . import models | |||
| @ -1,44 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ################################################################################ | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #    Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | |||
| #    Author: Unnimaya C O (odoo@cybrosys.com) | |||
| # | |||
| #    You can modify it under the terms of the GNU AFFERO | |||
| #    GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | |||
| # | |||
| #    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | |||
| #    (AGPL v3) along with this program. | |||
| #    If not, see <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################### | |||
| { | |||
|     'name': "Odoo Webhook", | |||
|     'version': '16.0.1.0.0', | |||
|     'category': 'Productivity', | |||
|     'summary': """Odoo will send payload to the registered urls when an  | |||
|      event occurs on the selected model.""", | |||
|     'description': """Webhooks are a potent tool for creating integrations  | |||
|      across various services and applications. A payload will be posted to  | |||
|      the registered urls when an event occurs on the chosen model.""", | |||
|     'author': 'Cybrosys Techno Solutions', | |||
|     'company': 'Cybrosys Techno Solutions', | |||
|     'maintainer': 'Cybrosys Techno Solutions', | |||
|     'website': 'https://www.cybrosys.com', | |||
|     'data': [ | |||
|         'security/ir.model.access.csv', | |||
|         'views/webhook_webhook_views.xml', | |||
|     ], | |||
|     'images': ['static/description/banner.png'], | |||
|     'license': 'AGPL-3', | |||
|     'installable': True, | |||
|     'auto_install': False, | |||
|     'application': False, | |||
| } | |||
| @ -1,7 +0,0 @@ | |||
| ## Module <odoo_webhook> | |||
| 
 | |||
| ####  29.09.2023 | |||
| #### Version 16.0.1.0.0 | |||
| #### ADD | |||
| 
 | |||
| - Initial commit for Odoo Webhook | |||
| @ -1,23 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ################################################################################ | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #    Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | |||
| #    Author: Unnimaya C O (odoo@cybrosys.com) | |||
| # | |||
| #    You can modify it under the terms of the GNU AFFERO | |||
| #    GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | |||
| # | |||
| #    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | |||
| #    (AGPL v3) along with this program. | |||
| #    If not, see <http://www.gnu.org/licenses/>. | |||
| # | |||
| ################################################################################ | |||
| from . import models | |||
| from . import webhook_webhook | |||
| @ -1,540 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ################################################################################ | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #    Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | |||
| #    Author: Unnimaya C O (odoo@cybrosys.com) | |||
| # | |||
| #    You can modify it under the terms of the GNU AFFERO | |||
| #    GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | |||
| # | |||
| #    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | |||
| #    (AGPL v3) along with this program. | |||
| #    If not, see <http://www.gnu.org/licenses/>. | |||
| # | |||
| ################################################################################ | |||
| import json | |||
| import requests | |||
| from collections import defaultdict | |||
| from operator import attrgetter | |||
| from requests.exceptions import MissingSchema | |||
| from odoo import api, _, SUPERUSER_ID | |||
| from odoo.exceptions import UserError, ValidationError, AccessError | |||
| from odoo.models import BaseModel, _unlink, LOG_ACCESS_COLUMNS, \ | |||
|     INSERT_BATCH_SIZE, SQL_DEFAULT | |||
| from odoo.tools import OrderedSet, split_every, attrgetter, clean_context | |||
| 
 | |||
| 
 | |||
| @api.model | |||
| def _create(self, data_list): | |||
|     """ Override _create method for sending the payload to the registered | |||
|     webhook url. When a new record is added to the model, it will check | |||
|     whether the model is added to webhook_webhook model. If it is added, | |||
|     the payload will be posted to the create_url.""" | |||
|     assert data_list | |||
|     cr = self.env.cr | |||
|     # insert rows in batches of maximum INSERT_BATCH_SIZE | |||
|     ids = []  # ids of created records | |||
|     other_fields = OrderedSet()  # non-column fields | |||
|     for data_sublist in split_every(INSERT_BATCH_SIZE, data_list): | |||
|         stored_list = [data['stored'] for data in data_sublist] | |||
|         fnames = sorted({name for stored in stored_list for name in stored}) | |||
|         columns = [] | |||
|         rows = [[] for _ in stored_list] | |||
|         for fname in fnames: | |||
|             field = self._fields[fname] | |||
|             if field.column_type: | |||
|                 columns.append(fname) | |||
|                 for stored, row in zip(stored_list, rows): | |||
|                     if fname in stored: | |||
|                         colval = field.convert_to_column(stored[fname], self, | |||
|                                                          stored) | |||
|                         if field.translate is True and colval: | |||
|                             if 'en_US' not in colval.adapted: | |||
|                                 colval.adapted['en_US'] = next( | |||
|                                     iter(colval.adapted.values())) | |||
|                         row.append(colval) | |||
|                     else: | |||
|                         row.append(SQL_DEFAULT) | |||
|             else: | |||
|                 other_fields.add(field) | |||
|             if field.type == 'properties': | |||
|                 # force calling fields.create for properties field because | |||
|                 # we might want to update the parent definition | |||
|                 other_fields.add(field) | |||
|         if not columns: | |||
|             # manage the case where we create empty records | |||
|             columns = ['id'] | |||
|             for row in rows: | |||
|                 row.append(SQL_DEFAULT) | |||
|         header = ", ".join(f'"{column}"' for column in columns) | |||
|         template = ", ".join("%s" for _ in rows) | |||
|         cr.execute( | |||
|             f'INSERT INTO "{self._table}" ({header}) VALUES {template} ' | |||
|             f'RETURNING "id"', | |||
|             [tuple(row) for row in rows], | |||
|         ) | |||
|         ids.extend(id_ for id_, in cr.fetchall()) | |||
| 
 | |||
|     # put the new records in cache, and update inverse fields, for many2one | |||
|     # | |||
|     # cachetoclear is an optimization to avoid modified()'s cost until | |||
|     # other_fields are processed | |||
|     cachetoclear = [] | |||
|     records = self.browse(ids) | |||
|     inverses_update = defaultdict(list)  # {(field, value): ids} | |||
|     common_set_vals = set( | |||
|         LOG_ACCESS_COLUMNS + [self.CONCURRENCY_CHECK_FIELD, 'id', | |||
|                               'parent_path']) | |||
|     for data, record in zip(data_list, records): | |||
|         data['record'] = record | |||
|         # DLE P104: test_inherit.py, test_50_search_one2many | |||
|         vals = dict( | |||
|             {k: v for d in data['inherited'].values() for k, v in d.items()}, | |||
|             **data['stored']) | |||
|         set_vals = common_set_vals.union(vals) | |||
|         for field in self._fields.values(): | |||
|             if field.type in ('one2many', 'many2many'): | |||
|                 self.env.cache.set(record, field, ()) | |||
|             elif field.related and not field.column_type: | |||
|                 self.env.cache.set(record, field, | |||
|                                    field.convert_to_cache(None, record)) | |||
|             # DLE P123: `test_adv_activity`, `test_message_assignation_inbox`, | |||
|             # `test_message_log`, `test_create_mail_simple`, ... | |||
|             # Set `mail.message.parent_id` to False in cache, so it doesn't do | |||
|             # the useless SELECT when computing the modified of `child_ids` | |||
|             # in other words, if `parent_id` is not set, no other message | |||
|             # `child_ids` are impacted. | |||
|             # + avoid the fetch of fields which are False. e.g. if a boolean | |||
|             # field is not passed in vals and as no default set in the field | |||
|             # attributes, then we know it can be set to False in the cache in | |||
|             # the case of create. | |||
|             elif field.name not in set_vals and not field.compute: | |||
|                 self.env.cache.set(record, field, | |||
|                                    field.convert_to_cache(None, record)) | |||
|         for fname, value in vals.items(): | |||
|             field = self._fields[fname] | |||
|             if field.type in ('one2many', 'many2many'): | |||
|                 cachetoclear.append((record, field)) | |||
|             else: | |||
|                 cache_value = field.convert_to_cache(value, record) | |||
|                 self.env.cache.set(record, field, cache_value) | |||
|                 if field.type in ('many2one', 'many2one_reference') and \ | |||
|                         self.pool.field_inverses[field]: | |||
|                     inverses_update[(field, cache_value)].append(record.id) | |||
|     for (field, value), record_ids in inverses_update.items(): | |||
|         field._update_inverses(self.browse(record_ids), value) | |||
|     # update parent_path | |||
|     records._parent_store_create() | |||
|     # protect fields being written against recomputation | |||
|     protected = [(data['protected'], data['record']) for data in data_list] | |||
|     with self.env.protecting(protected): | |||
|         # mark computed fields as todo | |||
|         records.modified(self._fields, create=True) | |||
|         if other_fields: | |||
|             # discard default values from context for other fields | |||
|             others = records.with_context(clean_context(self._context)) | |||
|             for field in sorted(other_fields, key=attrgetter('_sequence')): | |||
|                 field.create([ | |||
|                     (other, data['stored'][field.name]) | |||
|                     for other, data in zip(others, data_list) | |||
|                     if field.name in data['stored'] | |||
|                 ]) | |||
|             # mark fields to recompute | |||
|             records.modified([field.name for field in other_fields], | |||
|                              create=True) | |||
|         # if value in cache has not been updated by other_fields, remove it | |||
|         for record, field in cachetoclear: | |||
|             if self.env.cache.contains(record, | |||
|                                        field) and not self.env.cache.get(record, | |||
|                                                                          field): | |||
|                 self.env.cache.remove(record, field) | |||
|     # check Python constraints for stored fields | |||
|     records._validate_fields( | |||
|         name for data in data_list for name in data['stored']) | |||
|     records.check_access_rule('create') | |||
|     webhook = self.env['webhook.webhook'].search( | |||
|         [('model_id', '=', self.env['ir.model'].sudo().search( | |||
|             [('model', '=', records._name)]).id)]).filtered( | |||
|         lambda r: r.create_url).mapped( | |||
|         'create_url') | |||
|     if webhook: | |||
|         # Create payload if the model is added to webhook | |||
|         for rec in records: | |||
|             val_list = rec.search_read([('id', '=', rec.id)]) | |||
|             for item in val_list[0].keys(): | |||
|                 field = (self.env['ir.model.fields'].sudo().search( | |||
|                     [('model', '=', rec._name), ('name', '=', item)])) | |||
|                 if field.ttype == 'binary': | |||
|                     if val_list[0][field.name]: | |||
|                         base_url = self.env[ | |||
|                             'ir.config_parameter'].sudo().get_param( | |||
|                             'web.base.url') | |||
|                         val_list[0][field.name] = ( | |||
|                             f'{base_url}/web/image/{self._name}/' | |||
|                             f'{val_list[0]["id"]}' | |||
|                             f'/{field.name}') | |||
|             for item in webhook: | |||
|                 # Post payload to the registered url | |||
|                 requests.post(item, data=json.dumps(val_list[0], | |||
|                                                     default=str), | |||
|                               headers={ | |||
|                                   'Content-Type': 'application/json'}) | |||
|                 try: | |||
|                     requests.post(item, | |||
|                                   data=json.dumps(val_list[0], default=str), | |||
|                                   headers={'Content-Type': 'application/json'}) | |||
|                 except MissingSchema: | |||
|                     raise ValidationError("Please check the Webhook Url for " | |||
|                                           "Create Event") | |||
|     return records | |||
| 
 | |||
| 
 | |||
| def unlink(self): | |||
|     """Override unlink method for sending the payload to the registered | |||
|     webhook url. When a record is deleted from a model, it will check | |||
|     whether the model is added to the webhook_webhook model. If it is added, | |||
|     the payload will be posted to the delete_url.""" | |||
|     if not self: | |||
|         return True | |||
|     self.check_access_rights('unlink') | |||
|     self.check_access_rule('unlink') | |||
|     from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG | |||
|     for func in self._ondelete_methods: | |||
|         # func._ondelete is True if it should be called during uninstallation | |||
|         if func._ondelete or not self._context.get(MODULE_UNINSTALL_FLAG): | |||
|             func(self) | |||
|     # TO FIX: this avoids an infinite loop when trying to recompute a | |||
|     # field, which triggers the recomputation of another field using the | |||
|     # same compute function, which then triggers again the computation | |||
|     # of those two fields | |||
|     for field in self._fields.values(): | |||
|         self.env.remove_to_compute(field, self) | |||
|     self.env.flush_all() | |||
|     cr = self._cr | |||
|     Data = self.env['ir.model.data'].sudo().with_context({}) | |||
|     Defaults = self.env['ir.default'].sudo() | |||
|     Property = self.env['ir.property'].sudo() | |||
|     Attachment = self.env['ir.attachment'].sudo() | |||
|     ir_property_unlink = Property | |||
|     ir_model_data_unlink = Data | |||
|     ir_attachment_unlink = Attachment | |||
|     # mark fields that depend on 'self' to recompute them after 'self' has | |||
|     # been deleted (like updating a sum of lines after deleting one line) | |||
|     with self.env.protecting(self._fields.values(), self): | |||
|         self.modified(self._fields, before=True) | |||
|     webhook = self.env['webhook.webhook'].search( | |||
|         [('model_id', '=', self.env['ir.model'].sudo().search( | |||
|             [('model', '=', self._name)]).id)]).filtered( | |||
|         lambda r: r.delete_url).mapped('delete_url') | |||
|     # Create payload of the model is added to webhook | |||
|     if webhook.delete_url: | |||
|         val_list = [] | |||
|         for rec in self: | |||
|             val_list = rec.search_read([('id', '=', rec.id)]) | |||
|             for item in val_list[0].keys(): | |||
|                 field = (self.env['ir.model.fields'].sudo().search( | |||
|                     [('model', '=', rec._name), ('name', '=', item)])) | |||
|                 if field.ttype == 'binary': | |||
|                     if val_list[0][field.name]: | |||
|                         base_url = self.env[ | |||
|                             'ir.config_parameter'].sudo().get_param( | |||
|                             'web.base.url') | |||
|                         val_list[0][field.name] = (f'{base_url}/web/image/' | |||
|                                                    f'{self._name}/{rec.id}' | |||
|                                                    f'/{field.name}') | |||
|         for rec in webhook: | |||
|             # Post payload to the registered url | |||
|             try: | |||
|                 requests.post(rec, data=json.dumps(val_list[0], default=str), | |||
|                               headers={'Content-Type': 'application/json'}) | |||
|             except MissingSchema: | |||
|                 raise ValidationError(_("Please check the Webhook Url for " | |||
|                                         "Delete " | |||
|                                         "Event")) | |||
|     for sub_ids in cr.split_for_in_conditions(self.ids): | |||
|         records = self.browse(sub_ids) | |||
|         # Check if the records are used as default properties. | |||
|         refs = [f'{self._name},{id_}' for id_ in sub_ids] | |||
|         if Property.search( | |||
|                 [('res_id', '=', False), ('value_reference', 'in', refs)], | |||
|                 limit=1): | |||
|             raise UserError( | |||
|                 _('Unable to delete this document because it is used as a ' | |||
|                   'default property')) | |||
|         # Delete the records' properties. | |||
|         ir_property_unlink |= Property.search([('res_id', 'in', refs)]) | |||
|         query = f'DELETE FROM "{self._table}" WHERE id IN %s' | |||
|         cr.execute(query, (sub_ids,)) | |||
|         # Removing the ir_model_data reference if the record being deleted | |||
|         # is a record created by xml/csv file, as these are not connected | |||
|         # with real database foreign keys, and would be dangling references. | |||
|         # | |||
|         # Note: the following steps are performed as superuser to avoid | |||
|         # access rights restrictions, and with no context to avoid possible | |||
|         # side effects during admin calls. | |||
|         data = Data.search( | |||
|             [('model', '=', self._name), ('res_id', 'in', sub_ids)]) | |||
|         ir_model_data_unlink |= data | |||
|         # For the same reason, remove the defaults having some of the | |||
|         # records as value | |||
|         Defaults.discard_records(records) | |||
|         # For the same reason, remove the relevant records in ir_attachment | |||
|         # (the search is performed with sql as the search method of | |||
|         # ir_attachment is overridden to hide attachments of deleted | |||
|         # records) | |||
|         query = ('SELECT id FROM ir_attachment WHERE res_model=%s AND' | |||
|                  ' res_id IN %s') | |||
|         cr.execute(query, (self._name, sub_ids)) | |||
|         ir_attachment_unlink |= Attachment.browse( | |||
|             row[0] for row in cr.fetchall()) | |||
|     # invalidate the *whole* cache, since the orm does not handle all | |||
|     # changes made in the database, like cascading delete! | |||
|     self.env.invalidate_all(flush=False) | |||
|     if ir_property_unlink: | |||
|         ir_property_unlink.unlink() | |||
|     if ir_model_data_unlink: | |||
|         ir_model_data_unlink.unlink() | |||
|     if ir_attachment_unlink: | |||
|         ir_attachment_unlink.unlink() | |||
|     # DLE P93: flush after unlink, for recompute fields depending on | |||
|     # the modified of unlink | |||
|     self.env.flush_all() | |||
|     # auditing: deletions are infrequent and leave no trace in the database | |||
|     _unlink.info('User #%s deleted %s records with IDs: %r', self._uid, | |||
|                  self._name, self.ids) | |||
|     return True | |||
| 
 | |||
| 
 | |||
| def write(self, vals): | |||
|     """ write(vals) | |||
| 
 | |||
|     Updates all records in ``self`` with the provided values. | |||
| 
 | |||
|     :param dict vals: fields to update and the value to set on them | |||
|     :raise AccessError: if user is not allowed to modify the specified | |||
|      records/fields | |||
|     :raise ValidationError: if invalid values are specified for selection fields | |||
|     :raise UserError: if a loop would be created in a hierarchy of objects a | |||
|     result of the operation (such as setting an object as its own parent) | |||
| 
 | |||
|     * For numeric fields (:class:`~odoo.fields.Integer`, | |||
|       :class:`~odoo.fields.Float`) the value should be of the | |||
|       corresponding type | |||
|     * For :class:`~odoo.fields.Boolean`, the value should be a | |||
|       :class:`python:bool` | |||
|     * For :class:`~odoo.fields.Selection`, the value should match the | |||
|       selection values (generally :class:`python:str`, sometimes | |||
|       :class:`python:int`) | |||
|     * For :class:`~odoo.fields.Many2one`, the value should be the | |||
|       database identifier of the record to set | |||
|     * The expected value of a :class:`~odoo.fields.One2many` or | |||
|       :class:`~odoo.fields.Many2many` relational field is a list of | |||
|       :class:`~odoo.fields.Command` that manipulate the relation the | |||
|       implement. There are a total of 7 commands: | |||
|       :meth:`~odoo.fields.Command.create`, | |||
|       :meth:`~odoo.fields.Command.update`, | |||
|       :meth:`~odoo.fields.Command.delete`, | |||
|       :meth:`~odoo.fields.Command.unlink`, | |||
|       :meth:`~odoo.fields.Command.link`, | |||
|       :meth:`~odoo.fields.Command.clear`, and | |||
|       :meth:`~odoo.fields.Command.set`. | |||
|     * For :class:`~odoo.fields.Date` and `~odoo.fields.Datetime`, | |||
|       the value should be either a date(time), or a string. | |||
| 
 | |||
|       .. warning:: | |||
| 
 | |||
|         If a string is provided for Date(time) fields, | |||
|         it must be UTC-only and formatted according to | |||
|         :const:`odoo.tools.misc.DEFAULT_SERVER_DATE_FORMAT` and | |||
|         :const:`odoo.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT` | |||
| 
 | |||
|     * Other non-relational fields use a string for value | |||
|     """ | |||
|     if not self: | |||
|         return True | |||
| 
 | |||
|     self.check_access_rights('write') | |||
|     self.check_field_access_rights('write', vals.keys()) | |||
|     self.check_access_rule('write') | |||
|     env = self.env | |||
| 
 | |||
|     bad_names = {'id', 'parent_path'} | |||
|     if self._log_access: | |||
|         # the superuser can set log_access fields while loading registry | |||
|         if not (self.env.uid == SUPERUSER_ID and not self.pool.ready): | |||
|             bad_names.update(LOG_ACCESS_COLUMNS) | |||
| 
 | |||
|     # set magic fields | |||
|     vals = {key: val for key, val in vals.items() if key not in bad_names} | |||
|     if self._log_access: | |||
|         vals.setdefault('write_uid', self.env.uid) | |||
|         vals.setdefault('write_date', self.env.cr.now()) | |||
| 
 | |||
|     field_values = []  # [(field, value)] | |||
|     determine_inverses = defaultdict(list)  # {inverse: fields} | |||
|     fnames_modifying_relations = [] | |||
|     protected = set() | |||
|     check_company = False | |||
|     for fname, value in vals.items(): | |||
|         field = self._fields.get(fname) | |||
|         if not field: | |||
|             raise ValueError( | |||
|                 "Invalid field %r on model %r" % (fname, self._name)) | |||
|         field_values.append((field, value)) | |||
|         if field.inverse: | |||
|             if field.type in ('one2many', 'many2many'): | |||
|                 # The written value is a list of commands that must applied | |||
|                 # on the field's current value. Because the field is | |||
|                 # protected while being written, the field's current value | |||
|                 # will not be computed and default to an empty recordset. So | |||
|                 # make sure the field's value is in cache before writing, in | |||
|                 # order to avoid an inconsistent update. | |||
|                 self[fname] | |||
|             determine_inverses[field.inverse].append(field) | |||
|         if self.pool.is_modifying_relations(field): | |||
|             fnames_modifying_relations.append(fname) | |||
|         if field.inverse or (field.compute and not field.readonly): | |||
|             if field.store or field.type not in ('one2many', 'many2many'): | |||
|                 # Protect the field from being recomputed while being | |||
|                 # inversed. In the case of non-stored x2many fields, the | |||
|                 # field's value may contain unexpeced new records (created | |||
|                 # by command 0). Those new records are necessary for | |||
|                 # inversing the field, but should no longer appear if the | |||
|                 # field is recomputed afterwards. Not protecting the field | |||
|                 # will automatically invalidate the field from the cache, | |||
|                 # forcing its value to be recomputed once dependencies are | |||
|                 # up-to-date. | |||
|                 protected.update(self.pool.field_computed.get(field, [field])) | |||
|         if fname == 'company_id' or (field.relational and field.check_company): | |||
|             check_company = True | |||
| 
 | |||
|     # force the computation of fields that are computed with some assigned | |||
|     # fields, but are not assigned themselves | |||
|     to_compute = [field.name | |||
|                   for field in protected | |||
|                   if field.compute and field.name not in vals] | |||
|     if to_compute: | |||
|         self._recompute_recordset(to_compute) | |||
| 
 | |||
|     # protect fields being written against recomputation | |||
|     with env.protecting(protected, self): | |||
|         # Determine records depending on values. When modifying a relational | |||
|         # field, you have to recompute what depends on the field's values | |||
|         # before and after modification.  This is because the modification | |||
|         # has an impact on the "data path" between a computed field and its | |||
|         # dependency.  Note that this double call to modified() is only | |||
|         # necessary for relational fields. | |||
|         # | |||
|         # It is best explained with a simple example: consider two sales | |||
|         # orders SO1 and SO2.  The computed total amount on sales orders | |||
|         # indirectly depends on the many2one field 'order_id' linking lines | |||
|         # to their sales order.  Now consider the following code: | |||
|         # | |||
|         #   line = so1.line_ids[0]      # pick a line from SO1 | |||
|         #   line.order_id = so2         # move the line to SO2 | |||
|         # | |||
|         # In this situation, the total amount must be recomputed on *both* | |||
|         # sales order: the line's order before the modification, and the | |||
|         # line's order after the modification. | |||
|         self.modified(fnames_modifying_relations, before=True) | |||
| 
 | |||
|         real_recs = self.filtered('id') | |||
| 
 | |||
|         # field.write_sequence determines a priority for writing on fields. | |||
|         # Monetary fields need their corresponding currency field in cache | |||
|         # for rounding values. X2many fields must be written last, because | |||
|         # they flush other fields when deleting lines. | |||
|         for field, value in sorted(field_values, | |||
|                                    key=lambda item: item[0].write_sequence): | |||
|             field.write(self, value) | |||
| 
 | |||
|         # determine records depending on new values | |||
|         # | |||
|         # Call modified after write, because the modified can trigger a | |||
|         # search which can trigger a flush which can trigger a recompute | |||
|         # which remove the field from the recompute list while all the | |||
|         # values required for the computation could not be yet in cache. | |||
|         # e.g. Write on `name` of `res.partner` trigger the recompute of | |||
|         # `display_name`, which triggers a search on child_ids to find the | |||
|         # childs to which the display_name must be recomputed, which | |||
|         # triggers the flush of `display_name` because the _order of | |||
|         # res.partner includes display_name. The computation of display_name | |||
|         # is then done too soon because the parent_id was not yet written. | |||
|         # (`test_01_website_reset_password_tour`) | |||
|         self.modified(vals) | |||
| 
 | |||
|         if self._parent_store and self._parent_name in vals: | |||
|             self.flush_model([self._parent_name]) | |||
| 
 | |||
|         # validate non-inversed fields first | |||
|         inverse_fields = [f.name for fs in determine_inverses.values() for f in | |||
|                           fs] | |||
|         real_recs._validate_fields(vals, inverse_fields) | |||
| 
 | |||
|         for fields in determine_inverses.values(): | |||
|             # write again on non-stored fields that have been invalidated | |||
|             # from cache | |||
|             for field in fields: | |||
|                 if not field.store and any( | |||
|                         self.env.cache.get_missing_ids(real_recs, field)): | |||
|                     field.write(real_recs, vals[field.name]) | |||
| 
 | |||
|             # inverse records that are not being computed | |||
|             try: | |||
|                 fields[0].determine_inverse(real_recs) | |||
|             except AccessError as e: | |||
|                 if fields[0].inherited: | |||
|                     description = self.env['ir.model']._get(self._name).name | |||
|                     raise AccessError(_( | |||
|                         "%(previous_message)s\n\nImplicitly accessed through " | |||
|                         "'%(document_kind)s' (%(document_model)s).", | |||
|                         previous_message=e.args[0], | |||
|                         document_kind=description, | |||
|                         document_model=self._name, | |||
|                     )) | |||
|                 raise | |||
| 
 | |||
|         # validate inversed fields | |||
|         real_recs._validate_fields(inverse_fields) | |||
|     if self._name != 'ir.module.module': | |||
|         webhook = self.env['webhook.webhook'].search( | |||
|             [('model_id', '=', self.env['ir.model'].sudo().search( | |||
|                 [('model', '=', self._name)]).id)]).filtered( | |||
|             lambda r: r.edit_url).mapped('edit_url') | |||
|         if webhook: | |||
|             # Create payload of the model is added to webhook | |||
|             for item in vals.keys(): | |||
|                 field = (self.env['ir.model.fields'].sudo().search( | |||
|                     [('model', '=', self._name), ('name', '=', item)])) | |||
|                 if field.ttype == 'binary': | |||
|                     if vals[field.name]: | |||
|                         base_url = self.env[ | |||
|                             'ir.config_parameter'].sudo().get_param( | |||
|                             'web.base.url') | |||
|                         vals[field.name] = ( | |||
|                             f'{base_url}/web/image/{self._name}/{self.id}' | |||
|                             f'/{field.name}') | |||
|             for item in webhook: | |||
|                 # Post payload to the registered url | |||
|                 try: | |||
|                     requests.post(item, | |||
|                                   data=json.dumps(vals, default=str), | |||
|                                   headers={'Content-Type': 'application/json'}) | |||
|                 except MissingSchema: | |||
|                     raise ValidationError(_("Please check the Webhook Url for " | |||
|                                             "Edit Event")) | |||
|     if check_company and self._check_company_auto: | |||
|         self._check_company() | |||
|     return True | |||
| 
 | |||
| 
 | |||
| BaseModel._create = _create | |||
| BaseModel.unlink = unlink | |||
| BaseModel.write = write | |||
| @ -1,41 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ################################################################################ | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #    Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | |||
| #    Author: Unnimaya C O (odoo@cybrosys.com) | |||
| # | |||
| #    You can modify it under the terms of the GNU AFFERO | |||
| #    GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | |||
| # | |||
| #    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | |||
| #    (AGPL v3) along with this program. | |||
| #    If not, see <http://www.gnu.org/licenses/>. | |||
| # | |||
| ################################################################################ | |||
| from odoo import fields, models | |||
| 
 | |||
| 
 | |||
| class WebhookWebhook(models.Model): | |||
|     """Model that holds the webhook model and the urls to receive the payload""" | |||
|     _name = 'webhook.webhook' | |||
|     _description = 'Webhook' | |||
|     _rec_name = 'model_id' | |||
| 
 | |||
|     model_id = fields.Many2one('ir.model', string='Model', | |||
|                                help='Choose the model') | |||
|     create_url = fields.Char(string="Url for Create Event", | |||
|                              help="The URL to which payload to be send when a " | |||
|                                   "new record is created") | |||
|     edit_url = fields.Char(string="Url for Edit Event", | |||
|                            help="The URL to which payload to be send when a " | |||
|                                 "record is modified") | |||
|     delete_url = fields.Char(string="Url for Delete Event", | |||
|                              help="The URL to which payload to be send when a " | |||
|                                   "record  is deleted") | |||
| 
 | 
| Before Width: | Height: | Size: 3.6 KiB | 
| Before Width: | Height: | Size: 310 B | 
| Before Width: | Height: | Size: 1.3 KiB | 
| Before Width: | Height: | Size: 1.4 KiB | 
| Before Width: | Height: | Size: 576 B | 
| Before Width: | Height: | Size: 733 B | 
| Before Width: | Height: | Size: 911 B | 
| Before Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.2 KiB | 
| Before Width: | Height: | Size: 673 B | 
| Before Width: | Height: | Size: 878 B | 
| Before Width: | Height: | Size: 653 B | 
| Before Width: | Height: | Size: 905 B | 
| Before Width: | Height: | Size: 839 B | 
| Before Width: | Height: | Size: 427 B | 
| Before Width: | Height: | Size: 627 B | 
| Before Width: | Height: | Size: 1.2 KiB | 
| Before Width: | Height: | Size: 988 B | 
| Before Width: | Height: | Size: 1.2 KiB | 
| Before Width: | Height: | Size: 1.5 KiB | 
| Before Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.9 KiB | 
| Before Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 2.1 KiB | 
| Before Width: | Height: | Size: 4.4 KiB | 
| Before Width: | Height: | Size: 589 B | 
| Before Width: | Height: | Size: 3.4 KiB | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 2.3 KiB | 
| Before Width: | Height: | Size: 967 B | 
| Before Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 3.8 KiB | 
| Before Width: | Height: | Size: 5.0 KiB | 
| Before Width: | Height: | Size: 144 KiB | 
| Before Width: | Height: | Size: 257 KiB | 
| Before Width: | Height: | Size: 88 KiB | 
| Before Width: | Height: | Size: 147 KiB | 
| Before Width: | Height: | Size: 146 KiB | 
| Before Width: | Height: | Size: 74 KiB | 
| Before Width: | Height: | Size: 132 KiB | 
| Before Width: | Height: | Size: 55 KiB | 
| Before Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 94 KiB | 
| Before Width: | Height: | Size: 333 KiB | 
| Before Width: | Height: | Size: 109 KiB | 
| Before Width: | Height: | Size: 100 KiB | 
| Before Width: | Height: | Size: 114 KiB | 
| Before Width: | Height: | Size: 291 KiB | 
| Before Width: | Height: | Size: 350 KiB | 
| Before Width: | Height: | Size: 90 KiB | 
| Before Width: | Height: | Size: 14 KiB | 
| @ -1,671 +0,0 @@ | |||
| <div style="background-color: #714B67; min-height: 600px; width: 100%; padding: 15px; position: relative;"> | |||
|     <!-- TITLE BAR --> | |||
|     <div class="d-flex align-items-center justify-content-between" | |||
|          style="border-bottom: 1px solid #875A7B; padding: 15px; display: flex; justify-content: space-between; align-items: center;"> | |||
|         <img src="assets/misc/cybrosys-logo.png" width="42" height="42" | |||
|              style="width: 42px; height: 42px;"/> | |||
|         <div> | |||
|             <div style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;" | |||
|                  class="mr-2"> | |||
|                 <i class="fa fa-check mr-1"></i>Community | |||
|             </div> | |||
|             <div style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;" | |||
|                  class="mr-2"> | |||
|                 <i class="fa fa-check mr-1"></i>Enterprise | |||
|             </div> | |||
|         </div> | |||
|     </div> | |||
|     <!-- END OF TITLE BAR --> | |||
|     <div class="container"> | |||
|         <div class="row"> | |||
|             <div class="col-sm-12 col-md-12 col-lg-12"> | |||
|                 <!-- APP HERO --> | |||
|                 <h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;"> | |||
|                     Odoo Webhook</h1> | |||
|                 <p style="color:#FFFFFF; padding: 8px 15px; text-align: center; font-size: 24px;"> | |||
|                     Odoo Will Send Payload to the Registered Urls When | |||
|                     an Event Occurs on the Selected Model. | |||
|                 </p> | |||
|                 <!-- END OF APP HERO --> | |||
|                 <img src="assets/screenshots/hero.gif" | |||
|                      style="width: 75%; height: auto; position: absolute; margin-left: auto; margin-right: auto; top: 45%; left: 12%; right: auto;"/> | |||
|             </div> | |||
|         </div> | |||
|     </div> | |||
| </div> | |||
| <!-- NAVIGATION SECTION --> | |||
| <div class="d-flex align-items-center" | |||
|      style="border-bottom: 2px solid #714B67; padding: 15px 0px; margin-top: 300px;"> | |||
|     <div class="d-flex justify-content-center align-items-center mr-2" | |||
|          style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> | |||
|         <img src="assets/misc/compass.png"/> | |||
|     </div> | |||
|     <h2 class="mt-2" | |||
|         style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> | |||
|         Explore This | |||
|         Module</h2> | |||
| </div> | |||
| <div class="row my-4" style="font-family: 'Montserrat', sans-serif;"> | |||
|     <div class="col-sm-12 col-md-6 my-3"> | |||
|         <a href="#overview"> | |||
|             <div class="d-flex justify-content-between align-items-center" | |||
|                  style="background-color: #f5f5f5; padding: 30px; width: 100%;"> | |||
|                 <div> | |||
|                     <span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Overview</span> | |||
|                     <span | |||
|                             style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33;  display: block;">Learn | |||
|                         more about this | |||
|                         module</span> | |||
|                 </div> | |||
|                 <img src="assets/misc/right-arrow.png" width="36" height="36"/> | |||
|             </div> | |||
|         </a> | |||
|     </div> | |||
|     <div class="col-sm-12 col-md-6 my-3"> | |||
|         <a href="#features"> | |||
|             <div class="d-flex justify-content-between align-items-center" | |||
|                  style="background-color: #f5f5f5; padding: 30px; width: 100%;"> | |||
|                 <div> | |||
|                     <span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Features</span> | |||
|                     <span | |||
|                             style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33;  display: block;">View | |||
|                         features of this | |||
|                         module</span> | |||
|                 </div> | |||
|                 <img src="assets/misc/right-arrow.png" width="36" height="36"/> | |||
|             </div> | |||
|         </a> | |||
|     </div> | |||
|     <div class="col-sm-12 col-md-6 my-3"> | |||
|         <a href="#screenshots"> | |||
|             <div class="d-flex justify-content-between align-items-center" | |||
|                  style="background-color: #f5f5f5; padding: 30px; width: 100%;"> | |||
|                 <div> | |||
|                     <span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Screenshots</span> | |||
|                     <span | |||
|                             style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33;  display: block;">View | |||
|                         screenshots for this | |||
|                         module</span> | |||
|                 </div> | |||
|                 <img src="assets/misc/right-arrow.png" width="36" height="36"/> | |||
|             </div> | |||
|         </a> | |||
|     </div> | |||
| </div> | |||
| <!-- END OF NAVIGATION SECTION --> | |||
| <!-- OVERVIEW SECTION --> | |||
| <div class="d-flex align-items-center" | |||
|      style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="overview"> | |||
|     <div class="d-flex justify-content-center align-items-center mr-2" | |||
|          style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> | |||
|         <img src="assets/misc/pie-chart.png"/> | |||
|     </div> | |||
|     <h2 class="mt-2" | |||
|         style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> | |||
|         Overview | |||
|     </h2> | |||
| </div> | |||
| <div class="row" | |||
|      style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;"> | |||
|     <div class="col-sm-12 py-4"> | |||
|         Webhooks are a potent tool for creating integrations across various | |||
|         services and applications. A payload will be posted to the registered | |||
|         url when an event occurs on the chosen model. | |||
|     </div> | |||
| </div> | |||
| <!-- END OF OVERVIEW SECTION --> | |||
| <!-- FEATURES SECTION --> | |||
| <div class="d-flex align-items-center" | |||
|      style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="features"> | |||
|     <div class="d-flex justify-content-center align-items-center mr-2" | |||
|          style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> | |||
|         <img src="assets/misc/features.png"/> | |||
|     </div> | |||
|     <h2 class="mt-2" | |||
|         style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> | |||
|         Features | |||
|     </h2> | |||
| </div> | |||
| <div class="row" | |||
|      style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;"> | |||
|     <div class="col-sm-12 col-md-6"> | |||
|         <div class="d-flex align-items-center" | |||
|              style="margin-top: 40px; margin-bottom: 40px"> | |||
|             <img src="assets/misc/check-box.png" class="mr-2"/> | |||
|             <span | |||
|                     style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Webhook can be generated on Create event of any model.</span> | |||
|         </div> | |||
|         <div class="d-flex align-items-center" | |||
|              style="margin-top: 40px; margin-bottom: 40px"> | |||
|             <img src="assets/misc/check-box.png" class="mr-2"/> | |||
|             <span | |||
|                     style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Webhook can be generated on Delete event of any model.</span> | |||
|         </div> | |||
|         <div class="d-flex align-items-center" | |||
|              style="margin-top: 40px; margin-bottom: 40px"> | |||
|             <img src="assets/misc/check-box.png" class="mr-2"/> | |||
|             <span | |||
|                     style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Webhook can be generated on Edit event of any model.</span> | |||
|         </div> | |||
|     </div> | |||
| </div> | |||
| <!-- END OF FEATURES SECTION --> | |||
| <!-- SCREENSHOTS SECTION --> | |||
| <div class="d-flex align-items-center" | |||
|      style="border-bottom: 2px solid #714B67; padding: 15px 0px;" | |||
|      id="screenshots"> | |||
|     <div class="d-flex justify-content-center align-items-center mr-2" | |||
|          style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> | |||
|         <img src="assets/misc/pictures.png"/> | |||
|     </div> | |||
|     <h2 class="mt-2" | |||
|         style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> | |||
|         Screenshots | |||
|     </h2> | |||
| </div> | |||
| <div class="row"> | |||
|     <div class="col-sm-12"> | |||
| 
 | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 Webhook Menu in General Settings</h3> | |||
|             <img src="assets/screenshots/Screenshot1.png" | |||
|                  class="img-thumbnail"/> | |||
|         </div> | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 Create a Webhook. | |||
|             </h3> | |||
|             <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> | |||
|                 Select the Model for which the Webhook needs to be generated. | |||
|                 Url for Create Event, Url | |||
|                 for Delete Event and Url for Edit Event can be | |||
|                 filled with your website's urls where the payload should be | |||
|                 received when Create, Delete or Edit event occurs on the | |||
|                 selected model. | |||
|             </p> | |||
|             <img src="assets/screenshots/Screenshot2.png" class="img-thumbnail"> | |||
|         </div> | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 New Webhook for the Model Contact. </h3> | |||
|             <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> | |||
|                 As an example, | |||
|                 the urls provided by Webhook.site(www.webhook.site) are given | |||
|                 for the fields Url | |||
|                 for Create | |||
|                 Event, Url for Delete Event and Url for Edit Event. | |||
|             </p> | |||
|             <img src="assets/screenshots/Screenshot3.png" class="img-thumbnail"> | |||
|         </div> | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 Create New Contact. | |||
|             </h3> | |||
|             <img src="assets/screenshots/Screenshot4.png" class="img-thumbnail"> | |||
|         </div> | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 The Payload for Create Event. | |||
|             </h3> | |||
|             <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> | |||
|                  The payload received at the Url for Create Event after adding | |||
|                 the Contact. In our example, the payload is received at the url provided by | |||
|                 Webhook.site(www.webhook.site) | |||
|             </p> | |||
|             <img src="assets/screenshots/Screenshot5.png" class="img-thumbnail"> | |||
|         </div> | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 Edit a Contact. | |||
|             </h3> | |||
|             <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> | |||
|                 Apply any modifications to the Contact. The Contact's Company | |||
|                 Name is added here. | |||
|             </p> | |||
|             <img src="assets/screenshots/Screenshot6.png" class="img-thumbnail"> | |||
|         </div> | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 Payload for Edit Event | |||
|             </h3> | |||
|             <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> | |||
|                 Payload received at the Url for Edit Event after | |||
|                 modifying the record. | |||
|                 In our example, the payload is received at the url provided by | |||
|                 Webhook.site(www.webhook.site) | |||
|             </p> | |||
|             <img src="assets/screenshots/Screenshot7.png" class="img-thumbnail"> | |||
|         </div> | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 Delete a Contact | |||
|             </h3> | |||
|             <img src="assets/screenshots/Screenshot8.png" | |||
|                  class="img-thumbnail"> | |||
|         </div> | |||
|         <div style="display: block; margin: 30px auto;"> | |||
|             <h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;"> | |||
|                 Payload for Delete Event | |||
|             </h3> | |||
|             <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> | |||
|                 Payload received at the Url for Delete Event after | |||
|                 deleting the record. | |||
|                 In our example, the payload is received at the url provided by | |||
|                 Webhook.site(www.webhook.site) | |||
|             </p> | |||
|             <img src="assets/screenshots/Screenshot9.png" | |||
|                  class="img-thumbnail"> | |||
|         </div> | |||
|     </div> | |||
| </div> | |||
| <!-- END OF SCREENSHOTS SECTION --> | |||
| <!-- RELATED PRODUCTS --> | |||
| <div class="d-flex align-items-center" | |||
|      style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> | |||
|     <div class="d-flex justify-content-center align-items-center mr-2" | |||
|          style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> | |||
|         <img src="assets/misc/categories.png"/> | |||
|     </div> | |||
|     <h2 class="mt-2" | |||
|         style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> | |||
|         Related | |||
|         Products | |||
|     </h2> | |||
| </div> | |||
| <div class="row"> | |||
|     <div class="col-sm-12"> | |||
|         <div id="demo1" class="row carousel slide" data-ride="carousel"> | |||
|             <!-- The slideshow --> | |||
|             <div class="carousel-inner" style="padding: 30px;"> | |||
|                 <div class="carousel-item" style="min-height: 198.656px;"> | |||
|                     <div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" | |||
|                          style="float:left"> | |||
|                         <a href="https://apps.odoo.com/apps/modules/16.0/upcitemdb_integration/" | |||
|                            target="_blank"> | |||
|                             <div style="border-radius:10px"> | |||
|                                 <img class="img img-responsive center-block" | |||
|                                      style="border-radius: 0px;" | |||
|                                      src="assets/modules/l1.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/16.0/education_erp_dashboard/" | |||
|                            target="_blank"> | |||
|                             <div style="border-radius:10px"> | |||
|                                 <img class="img img-responsive center-block" | |||
|                                      style="border-radius: 0px;" | |||
|                                      src="assets/modules/l2.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/16.0/agriculture_management_odoo/" | |||
|                            target="_blank"> | |||
|                             <div style="border-radius:10px"> | |||
|                                 <img class="img img-responsive center-block" | |||
|                                      style="border-radius: 0px;" | |||
|                                      src="assets/modules/l3.png"> | |||
|                             </div> | |||
|                         </a> | |||
|                     </div> | |||
|                 </div> | |||
|                 <div class="carousel-item active" | |||
|                      style="min-height: 198.656px;"> | |||
|                     <div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" | |||
|                          style="float:left"> | |||
|                         <a href="https://apps.odoo.com/apps/modules/16.0/product_image_suggestion/" | |||
|                            target="_blank"> | |||
|                             <div style="border-radius:10px"> | |||
|                                 <img class="img img-responsive center-block" | |||
|                                      style="border-radius: 0px;" | |||
|                                      src="assets/modules/l4.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/16.0/odoo_magento_2_4_5/" | |||
|                            target="_blank"> | |||
|                             <div style="border-radius:10px"> | |||
|                                 <img class="img img-responsive center-block" | |||
|                                      style="border-radius: 0px;" | |||
|                                      src="assets/modules/l5.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/16.0/advanced_dynamic_dashboard/" | |||
|                            target="_blank"> | |||
|                             <div style="border-radius:10px"> | |||
|                                 <img class="img img-responsive center-block" | |||
|                                      style="border-radius: 0px;" | |||
|                                      src="assets/modules/l6.png"> | |||
|                             </div> | |||
|                         </a> | |||
|                     </div> | |||
|                 </div> | |||
|             </div> | |||
|             <!-- Left and right controls --> | |||
|             <a class="carousel-control-prev" href="#demo1" data-slide="prev" | |||
|                style="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="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 RELATED PRODUCTS --> | |||
| <!-- OUR SERVICES --> | |||
| <div class="d-flex align-items-center" | |||
|      style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> | |||
|     <div class="d-flex justify-content-center align-items-center mr-2" | |||
|          style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> | |||
|         <img src="assets/misc/star.png"/> | |||
|     </div> | |||
|     <h2 class="mt-2" | |||
|         style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> | |||
|         Our Services | |||
|     </h2> | |||
| </div> | |||
| <div class="container my-5"> | |||
|     <div class="row"> | |||
|         <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> | |||
| </div> | |||
| <!--END OF OUR SERVICES --> | |||
| <!-- OUR INDUSTRIES --> | |||
| <div class="d-flex align-items-center" | |||
|      style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> | |||
|     <div class="d-flex justify-content-center align-items-center mr-2" | |||
|          style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> | |||
|         <img src="assets/misc/corporate.png"/> | |||
|     </div> | |||
|     <h2 class="mt-2" | |||
|         style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> | |||
|         Our | |||
|         Industries | |||
|     </h2> | |||
| </div> | |||
| <div class="container my-5"> | |||
|     <div class="row"> | |||
|         <div class="col-lg-3"> | |||
|             <div class="my-4 d-flex flex-column justify-content-center" | |||
|                  style="background-color: #f6f8f9 !important; border-radius: 0px; 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: 0px; 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: 0px; 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: 0px; 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: 0px; 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: 0px; 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: 0px; 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: 0px; 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> | |||
| </div> | |||
| <!--  END OF OUR INDUSTRIES --> | |||
| <!-- SUPPORT --> | |||
| <div class="d-flex align-items-center" | |||
|      style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> | |||
|     <div class="d-flex justify-content-center align-items-center mr-2" | |||
|          style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> | |||
|         <img src="assets/misc/customer-support.png"/> | |||
|     </div> | |||
|     <h2 class="mt-2" | |||
|         style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> | |||
|         Support | |||
|     </h2> | |||
| </div> | |||
| <div class="container mt-5"> | |||
|     <div class="row"> | |||
|         <div class="col-sm-12 col-md-6"> | |||
|             <div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;"> | |||
|                 <div class="mr-4" | |||
|                      style="background-color: #714B67; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;"> | |||
|                     <img src="assets/misc/support.png" height="48" width="48" | |||
|                          style="width: 42px; height: 42px;"/> | |||
|                 </div> | |||
|                 <div> | |||
|                     <h4>Need Help?</h4> | |||
|                     <p style="line-height: 100%;">Got questions or need help? | |||
|                         Get in touch.</p> | |||
|                     <a href="mailto:odoo@cybrosys.com"> | |||
|                         <p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;"> | |||
|                             odoo@cybrosys.com</p> | |||
|                     </a> | |||
|                 </div> | |||
|             </div> | |||
|         </div> | |||
|         <div class="col-sm-12 col-md-6"> | |||
|             <div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;"> | |||
|                 <div class="mr-4" | |||
|                      style="background-color: #2AC44D; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;"> | |||
|                     <img src="assets/misc/whatsapp.png" height="52" width="52" | |||
|                          style="width: 52px; height: 52px;"/> | |||
|                 </div> | |||
|                 <div> | |||
|                     <h4>WhatsApp</h4> | |||
|                     <p style="line-height: 100%;">Say hi to us on WhatsApp!</p> | |||
|                     <a href="https://api.whatsapp.com/send?phone=918606827707"> | |||
|                         <p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;"> | |||
|                             +91 86068 | |||
|                             27707</p> | |||
|                     </a> | |||
|                 </div> | |||
|             </div> | |||
|         </div> | |||
|     </div> | |||
|     <div class="row"> | |||
|         <div class="col-sm-12 my-5 d-flex justify-content-center align-items-center"> | |||
|             <img src="assets/misc/logo.png" width="144" height="31" | |||
|                  style="width:144px; height: 31px; margin-top: 40px;"/> | |||
|         </div> | |||
|     </div> | |||
| </div> | |||
| <!-- END OF SUPPORT --> | |||
| @ -1,45 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <odoo> | |||
|     <!--    Webhook Webhook Form View--> | |||
|     <record id="webhook_webhook_view_form" model="ir.ui.view"> | |||
|         <field name="name">webhook.webhook.view.form</field> | |||
|         <field name="model">webhook.webhook</field> | |||
|         <field name="priority" eval="20"/> | |||
|         <field name="arch" type="xml"> | |||
|             <form> | |||
|                 <sheet name="Webhook"> | |||
|                     <group> | |||
|                         <group> | |||
|                             <field name="model_id"/> | |||
|                             <field name="create_url"/> | |||
|                         </group> | |||
|                         <group> | |||
|                             <field name="delete_url"/> | |||
|                             <field name="edit_url"/> | |||
|                         </group> | |||
|                     </group> | |||
|                 </sheet> | |||
|             </form> | |||
|         </field> | |||
|     </record> | |||
|     <!--    Webhook Webhook Tree View--> | |||
|     <record id="webhook_webhook_view_tree" model="ir.ui.view"> | |||
|         <field name="name">webhook.webhook.view.tree</field> | |||
|         <field name="model">webhook.webhook</field> | |||
|         <field name="priority" eval="20"/> | |||
|         <field name="arch" type="xml"> | |||
|             <tree> | |||
|                 <field name="model_id"/> | |||
|             </tree> | |||
|         </field> | |||
|     </record> | |||
|     <!--    Webhook Menu Action--> | |||
|     <record id="webhook_webhook_action" model="ir.actions.act_window"> | |||
|         <field name="name">Webhook</field> | |||
|         <field name="res_model">webhook.webhook</field> | |||
|         <field name="view_mode">tree,form</field> | |||
|     </record> | |||
|     <!--    Webhook Menu--> | |||
|     <menuitem id="webhook_webhook_menu" name="Webhook" | |||
|               parent="base.menu_automation" action="webhook_webhook_action"/> | |||
| </odoo> | |||