diff --git a/.gitignore b/.gitignore
index 68bc17f..016aa55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.aider*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
diff --git a/bemade_odoo_partner_scrapper/__manifest__.py b/bemade_odoo_partner_scrapper/__manifest__.py
index 39d426f..9e389e0 100644
--- a/bemade_odoo_partner_scrapper/__manifest__.py
+++ b/bemade_odoo_partner_scrapper/__manifest__.py
@@ -16,10 +16,11 @@
'license': 'GPL-3',
'author': 'Bemade',
'website': 'https://www.bemade.org',
- 'depends': [],
+ 'depends': ['base', 'contacts', 'sale_management', 'sale' , 'partner_multi_relation'],
'data': [
# No security
'views/res_partner_views.xml',
+ 'data/res_partner_relation_type.xml'
],
"assets": {
"web.assets_backend": [
diff --git a/bemade_odoo_partner_scrapper/data/demo.xml b/bemade_odoo_partner_scrapper/data/demo.xml
new file mode 100644
index 0000000..c47c023
--- /dev/null
+++ b/bemade_odoo_partner_scrapper/data/demo.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+ Is assistant of
+ Has assistant
+ p
+ p
+
+
+ Is competitor of
+ Is competitor of
+ c
+ c
+
+
+
+ Has worked for
+ Has former employee
+ p
+ c
+
+
+
+ Washing Companies
+
+
+ Washing Gold
+
+
+
+ Washing Silver
+
+
+
+ Washing Services
+
+
+
+
+ Great Washing Powder Company
+
+ 1
+ Le Bourget du Lac
+ 73377
+ +33 4 49 23 44 54
+
+ 93, Press Avenue
+ great@yourcompany.example.com
+ http://www.great.com
+
+
+ Best Washing Powder Company
+
+ 1
+ Champs sur Marne
+ 77420
+
+ best@yourcompany.example.com
+ +33 1 64 61 04 01
+ 12 rue Albert Einstein
+ http://www.best.com/
+
+
+ Super Washing Powder Company
+
+ 1
+ 3rd Floor, Room 3-C,
+ Carretera Panamericana, Km 1, Urb. Delgado Chalbaud
+ Caracas
+ 1090
+ super@yourcompany.example.com
+ +58 212 681 0538
+
+ super.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bemade_odoo_partner_scrapper/data/res_partner_relation_type.xml b/bemade_odoo_partner_scrapper/data/res_partner_relation_type.xml
new file mode 100644
index 0000000..b193a2d
--- /dev/null
+++ b/bemade_odoo_partner_scrapper/data/res_partner_relation_type.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Is Odoo Partner Of
+ Is Odoo Client Of
+ c
+ c
+
+
+
+
+ Odoo Partner
+
+
+ Learning Partner
+
+
+
+ Ready Partner
+
+
+
+ Silver Partner
+
+
+
+ Gold Partner
+
+
+
diff --git a/bemade_odoo_partner_scrapper/models/res_partner.py b/bemade_odoo_partner_scrapper/models/res_partner.py
index c341589..64983a0 100644
--- a/bemade_odoo_partner_scrapper/models/res_partner.py
+++ b/bemade_odoo_partner_scrapper/models/res_partner.py
@@ -1,16 +1,85 @@
from odoo import models, fields, api, _
import requests
from bs4 import BeautifulSoup
+import logging
+
+_logger = logging.getLogger(__name__)
class ResPartner(models.Model):
_inherit = 'res.partner'
- is_odoo_partner = fields.Boolean(string="Is Odoo Partner", default=False)
- is_odoo_user = fields.Boolean(string="Is Odoo User", default=False)
+ is_odoo_partner = fields.Boolean(string="Is Odoo Partner", default=False, tracking=True)
+ is_odoo_user = fields.Boolean(string="Is Odoo User", default=False, tracking=True)
+ odoo_name = fields.Char(string="Name on Odoo", tracking=True)
+ odoo_id = fields.Char(string="ID on Odoo", tracking=True)
+ odoo_url = fields.Char(string="Odoo Partner URL", tracking=True, index=True)
@api.model
def get_odoo_partner(self):
+
+ def extract_partner_data(url):
+ page = requests.get("https://www.odoo.com" + url)
+ soup = BeautifulSoup(page.content, 'html.parser')
+
+ partner_data = {}
+
+ partner_data['name'] = soup.find(id="partner_name").text
+
+ partner_type = soup.find('h3', class_='col-lg-12 text-center text-muted')
+ if partner_type and partner_type.span:
+ partner_data['partner_type'] = partner_type.span.text.strip()
+
+ partner_data['odoo_name'] = soup.find(id="partner_name").text
+ address_div = soup.find('span', itemprop='streetAddress')
+ email_div = soup.find('span', itemprop='email')
+ phone_div = soup.find('span', itemprop='telephone')
+ website_div = soup.find('span', itemprop='website')
+ state_data = None
+
+ if address_div is not None:
+ # Extract address components
+ address_parts = address_div.get_text(separator="\n").split("\n")
+ partner_data['street'] = address_parts[0]
+ city_state_postal = address_parts[len(address_parts) - 2].split(', ')
+
+ if city_state_postal != [',']:
+ partner_data['city'] = city_state_postal[0]
+ state_postal = city_state_postal[1].split(' ')
+ if len(state_postal) == 3 and len(state_postal[2]) == 3:
+ partner_data['zip'] = state_postal[1] + state_postal[2]
+ state_data = state_postal[0]
+ elif len(state_postal) == 2 and state_postal[1]:
+ if len(state_postal[1]) == 6:
+ partner_data['zip'] = state_postal[1]
+ state_data = state_postal[0]
+ if len(state_postal[1]) == 3:
+ if len(state_postal[0]) == 2:
+ partner_data['zip'] = state_postal[1]
+ state_data = state_postal[0]
+ else:
+ partner_data['zip'] = state_postal[0] + state_postal[1]
+ else:
+ if len(state_postal[0]) == 6:
+ partner_data['zip'] = state_postal[0]
+ else:
+ state_data = state_postal[0]
+
+ # partner_data['country'] = address_parts[len(address_parts) - 1]
+
+ partner_data['email'] = email_div.string if email_div and email_div.string else ''
+ partner_data['phone'] = phone_div.string if phone_div and phone_div.string else ''
+ partner_data['website'] = website_div.string if website_div and website_div.string else ''
+ partner_data['is_odoo_partner'] = True
+ partner_data['is_odoo_user'] = True
+
+ if state_data:
+ print(state_data)
+ country_id = self.env['res.country'].search([('code', '=', 'CA')]).id
+ partner_data['state_id'] = self.env['res.country.state'].search([('code', '=', state_data),('country_id', '=', country_id)]).id
+
+ return partner_data
+
# Load the webpage at the given url
page = requests.get("https://www.odoo.com/fr_FR/partners/country/canada-36")
# Parse the HTML content
@@ -50,37 +119,90 @@ class ResPartner(models.Model):
for partner_url in partners_url:
page = requests.get("https://www.odoo.com" + partner_url)
soup = BeautifulSoup(page.content, 'html.parser')
- partner_name = soup.find(id="partner_name")
- address_div = soup.find('span', itemprop='streetAddress')
- email_div = soup.find('span', itemprop='email')
- phone_div = soup.find('span', itemprop='telephone')
- website_div = soup.find('span', itemprop='website')
- if address_div is not None:
- # Extract address components
- address_parts = address_div.get_text(separator="\n").split("\n")
- street_address = address_parts[0]
- city_state_postal = address_parts[len(address_parts)-2].split(', ')
- city = city_state_postal[0]
- state_postal = city_state_postal[1].split(' ')
- state = state_postal[0]
- postal_code = state_postal[1] + state_postal[2]
- country = address_parts[len(address_parts)-1]
- if email_div and email_div.string:
- email = email_div.string
- if phone_div and phone_div.string:
- phone = phone_div.string
- if website_div and website_div.string:
- website = website_div.string
- # self.env['res.partner'].create({
- # 'name': partner_name,
- # 'street': street_address,
- # 'city': city,
- # 'state_id': state,
- # 'zip': postal_code,
- # 'country_id': country,
- # 'email': email,
- # 'phone': phone,
- # 'website': website,
- # 'is_odoo_partner': True,
- # })
\ No newline at end of file
+ print (partner_url)
+ odoo_partner = extract_partner_data(partner_url)
+ print (odoo_partner)
+
+ # partner_name = soup.find(id="partner_name").text
+ # address_div = soup.find('span', itemprop='streetAddress')
+ # email_div = soup.find('span', itemprop='email')
+ # phone_div = soup.find('span', itemprop='telephone')
+ # website_div = soup.find('span', itemprop='website')
+ # if address_div is not None:
+ # # Extract address components
+ # address_parts = address_div.get_text(separator="\n").split("\n")
+ # street_address = address_parts[0]
+ # city_state_postal = address_parts[len(address_parts) - 2].split(', ')
+ #
+ # if city_state_postal != [',']:
+ # city = city_state_postal[0]
+ # state_postal = city_state_postal[1].split(' ')
+ #
+ # if len(state_postal) == 3 and len(state_postal[2]) == 3:
+ # postal_code = state_postal[1] + state_postal[2]
+ # state = state_postal[0]
+ # elif len(state_postal) == 2 and state_postal[1]:
+ # if len(state_postal[1]) == 6:
+ # postal_code = state_postal[1]
+ # state = state_postal[0]
+ # if len(state_postal[1]) == 3:
+ # if len(state_postal[0]) == 2:
+ # postal_code = state_postal[1]
+ # state = state_postal[0]
+ # else:
+ # postal_code = state_postal[0] + state_postal[1]
+ # state = ''
+ # else:
+ # if len(state_postal[0]) == 6:
+ # postal_code = state_postal[0]
+ # state = ''
+ # else:
+ # postal_code = ''
+ # state = state_postal[0]
+ # else:
+ # city = ''
+ # state = ''
+ # postal_code = ''
+ # # state = state_postal[0]
+ # # postal_code = state_postal[1] + state_postal[2]
+ # country = address_parts[len(address_parts) - 1]
+ # if email_div and email_div.string:
+ # email = email_div.string
+ # else:
+ # email = ''
+ # if phone_div and phone_div.string:
+ # phone = phone_div.string
+ # else:
+ # phone = ''
+ # if website_div and website_div.string:
+ # website = website_div.string
+ # else:
+ # website = ''
+
+ _logger.info(f"Processing {odoo_partner['name']} with email {odoo_partner['email']} and phone {odoo_partner['phone']} and website {odoo_partner['website']}")
+
+ exist_odoo_name = self.env['res.partner'].search([('odoo_name', '=', odoo_partner['odoo_name'])])
+ if not exist_odoo_name:
+ exist_email = self.env['res.partner'].search([('email', '=', odoo_partner['email'])])
+ if not exist_email:
+ exist_phone = self.env['res.partner'].search([('phone', '=', odoo_partner['phone'])])
+ if not exist_phone:
+ exist_website = self.env['res.partner'].search([('website', '=', odoo_partner['website'])])
+
+ if not exist_website or odoo_partner['website'] == '':
+ if odoo_partner['website'] == '':
+ del odoo_partner['website']
+ self.env['res.partner'].create(odoo_partner)
+ # self.env['res.partner'].create({
+ # 'name': partner_name,
+ # 'street': street_address,
+ # 'city': city,
+ # 'state_id': state_id.id if state_id else False,
+ # 'zip': postal_code,
+ # 'country_id': country_id.id if country_id else False,
+ # 'email': email,
+ # 'phone': phone,
+ # 'website': website,
+ # 'is_odoo_partner': True,
+ # })
\ No newline at end of file
diff --git a/bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js b/bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js
index 6f7f7c0..f3b0ba6 100644
--- a/bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js
+++ b/bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js
@@ -2,6 +2,8 @@
import ListController from 'web.ListController';
import ListView from 'web.ListView';
+import KanbanController from 'web.KanbanController';
+import KanbanView from 'web.KanbanView';
const viewRegistry = require('web.view_registry');
const OdooScrapperListController = ListController.extend({
@@ -32,3 +34,38 @@ const OdooScrapperListView = ListView.extend({
// key must match with the js_class attribute of the tree view you want to modify
viewRegistry.add('res_partner_odoo_scrapper_tree', OdooScrapperListView);
+
+
+
+
+
+const OdooScrapperKanbanController = KanbanController.extend({
+ // buttons_template must match the t-name on the template for the button (static xml)
+ buttons_template: 'odoo_scrapper.kanban_view_buttons',
+ events: _.extend({}, KanbanController.prototype.events, {
+ 'click .o_button_get_partner': '_onGetPartnerClick',
+ }),
+ // This may need to be async function() if there needs to be an await this._rpc({ ... }); call to not reload early
+ _onGetPartnerClick: function () {
+ this._rpc({
+ model: 'res.partner',
+ method: 'get_odoo_partner',
+ args: [],
+ }).then(() => {
+ this.reload();
+ });
+ // Couldn't test this for real, but it runs the action. May need a this.reload()
+ },
+});
+
+const OdooScrapperKanbanView = KanbanView.extend({
+ config: _.extend({}, KanbanView.prototype.config, {
+ Controller: OdooScrapperKanbanController,
+ }),
+});
+
+// key must match with the js_class attribute of the tree view you want to modify
+viewRegistry.add('res_partner_odoo_scrapper_kanban', OdooScrapperKanbanView);
+
+
+
diff --git a/bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml b/bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml
index 1a33faa..942044b 100644
--- a/bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml
+++ b/bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml
@@ -1,12 +1,24 @@
-
+
+ title="Get Odoo Partner"> Get Odoo Partner
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bemade_odoo_partner_scrapper/views/res_partner_views.xml b/bemade_odoo_partner_scrapper/views/res_partner_views.xml
index 10a55ed..1dd2f6e 100644
--- a/bemade_odoo_partner_scrapper/views/res_partner_views.xml
+++ b/bemade_odoo_partner_scrapper/views/res_partner_views.xml
@@ -15,4 +15,27 @@
+
+ res.partner.kanban.inherit
+ res.partner
+
+
+
+ res_partner_odoo_scrapper_kanban
+
+
+
+
+
+ res.partner.odoo.search
+ res.partner
+
+
+
+
+
+
+
+
+
diff --git a/bemade_time_off_follower/__init__.py b/bemade_time_off_follower/__init__.py
new file mode 100644
index 0000000..9a7e03e
--- /dev/null
+++ b/bemade_time_off_follower/__init__.py
@@ -0,0 +1 @@
+from . import models
\ No newline at end of file
diff --git a/bemade_time_off_follower/__manifest__.py b/bemade_time_off_follower/__manifest__.py
new file mode 100644
index 0000000..76eea2d
--- /dev/null
+++ b/bemade_time_off_follower/__manifest__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+{
+ "name": "Time Off Alternative Follower",
+ "version": "15.0.0.0.1",
+ "category": "Extra Tools",
+ 'summary': 'Add Alternative Follower When Receiving Message While On Time Off',
+ "description": """
+ Add Alternative Follower When Receiving Message While On Time Off
+ """,
+ "author": "Bemade",
+ 'website': 'https://www.bemade.org',
+ "depends": [
+ 'hr_holidays',
+ 'mail'
+ ],
+ "data": [
+ 'views/hr_leave_views.xml',
+ ],
+ "auto_install": False,
+ "installable": True,
+ 'license': 'OPL-1'
+}
diff --git a/bemade_time_off_follower/models/__init__.py b/bemade_time_off_follower/models/__init__.py
new file mode 100644
index 0000000..fa00ea0
--- /dev/null
+++ b/bemade_time_off_follower/models/__init__.py
@@ -0,0 +1,2 @@
+from . import hr_leave
+from . import mail_thread
diff --git a/bemade_time_off_follower/models/hr_leave.py b/bemade_time_off_follower/models/hr_leave.py
new file mode 100644
index 0000000..09750a0
--- /dev/null
+++ b/bemade_time_off_follower/models/hr_leave.py
@@ -0,0 +1,10 @@
+from odoo import models, fields, api
+
+class HrLeave(models.Model):
+ _inherit = 'hr.leave'
+
+ alternate_follower_id = fields.Many2one(
+ comodel_name='res.users',
+ string='Alternate Follower',
+ help='User who will receive a copy of all communications related to this leave',
+ )
diff --git a/bemade_time_off_follower/models/mail_thread.py b/bemade_time_off_follower/models/mail_thread.py
new file mode 100644
index 0000000..661961a
--- /dev/null
+++ b/bemade_time_off_follower/models/mail_thread.py
@@ -0,0 +1,26 @@
+from odoo import models, api
+
+class MailThread(models.AbstractModel):
+ _inherit = 'mail.thread'
+
+ @api.returns('mail.message', lambda value: value.id)
+ def message_post(self, **kwargs):
+ message = super(MailThread, self).message_post(**kwargs)
+
+ leave_env = self.env['hr.leave']
+ for recipient in message.notification_ids.mapped('res_partner_id'):
+ leave = leave_env.search([
+ ('state', '=', 'validate'),
+ ('date_from', '<=', fields.Date.today()),
+ ('date_to', '>=', fields.Date.today()),
+ ('employee_id.user_id.partner_id', '=', recipient.id),
+ ], limit=1)
+
+ if leave and leave.alternate_follower_id:
+ message.write({
+ 'notification_ids': [(0, 0, {
+ 'res_partner_id': leave.alternate_follower_id.partner_id.id,
+ # 'notification_type': 'email',
+ })]
+ })
+ return message
diff --git a/bemade_time_off_follower/views/hr_leave_views.xml b/bemade_time_off_follower/views/hr_leave_views.xml
new file mode 100644
index 0000000..a992f48
--- /dev/null
+++ b/bemade_time_off_follower/views/hr_leave_views.xml
@@ -0,0 +1,13 @@
+
+
+
+ hr.leave.form.view.inherit
+ hr.leave
+
+
+
+
+
+
+
+