From 980788f6b4b0e3cddfb391864139381f6552a713 Mon Sep 17 00:00:00 2001 From: Nikita Vaghela Date: Thu, 14 Nov 2019 19:17:49 +0530 Subject: [PATCH 1/4] [ADD] Added connector_aln_data module. --- README.md | 1 + connector_alndata/README.rst | 48 ++ connector_alndata/__init__.py | 5 + connector_alndata/__manifest__.py | 27 + .../data/ir_config_parameter_data.xml | 57 ++ connector_alndata/data/sync_aln_data_view.xml | 16 + connector_alndata/models/__init__.py | 8 + connector_alndata/models/crm_lead.py | 725 ++++++++++++++++++ connector_alndata/models/fsm_location.py | 17 + connector_alndata/models/res_partner.py | 33 + .../models/res_partner_industry.py | 34 + connector_alndata/readme/CONTRIBUTORS.rst | 8 + connector_alndata/readme/DESCRIPTION.rst | 1 + connector_alndata/readme/USAGE.rst | 2 + connector_alndata/static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 52 ++ connector_alndata/tests/__init__.py | 5 + .../tests/test_connector_alndata.py | 25 + .../views/res_partner_industry_view.xml | 27 + connector_alndata/views/res_partner_view.xml | 27 + oca_dependencies.txt | 1 + 21 files changed, 1119 insertions(+) create mode 100644 connector_alndata/README.rst create mode 100644 connector_alndata/__init__.py create mode 100644 connector_alndata/__manifest__.py create mode 100644 connector_alndata/data/ir_config_parameter_data.xml create mode 100644 connector_alndata/data/sync_aln_data_view.xml create mode 100644 connector_alndata/models/__init__.py create mode 100644 connector_alndata/models/crm_lead.py create mode 100644 connector_alndata/models/fsm_location.py create mode 100644 connector_alndata/models/res_partner.py create mode 100644 connector_alndata/models/res_partner_industry.py create mode 100644 connector_alndata/readme/CONTRIBUTORS.rst create mode 100644 connector_alndata/readme/DESCRIPTION.rst create mode 100644 connector_alndata/readme/USAGE.rst create mode 100644 connector_alndata/static/description/icon.png create mode 100644 connector_alndata/static/description/index.html create mode 100644 connector_alndata/tests/__init__.py create mode 100644 connector_alndata/tests/test_connector_alndata.py create mode 100644 connector_alndata/views/res_partner_industry_view.xml create mode 100644 connector_alndata/views/res_partner_view.xml diff --git a/README.md b/README.md index a9a194ef..bc2ac559 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ addon | version | summary [l10n_us_form_1099](l10n_us_form_1099/) | 12.0.1.2.0 | Manage 1099 Types and Suppliers [l10n_us_gaap](l10n_us_gaap/) | 12.0.1.0.0 | United States Sample GAAP Chart of Accounts [l10n_us_gaap_mis_report](l10n_us_gaap_mis_report/) | 12.0.1.0.0 | MIS Builder Templates for US Chart of Accounts +[connector_alndata](connector_alndata/) | 12.0.1.0.0 | ALN Data Connector [//]: # (end addons) diff --git a/connector_alndata/README.rst b/connector_alndata/README.rst new file mode 100644 index 00000000..0eadce63 --- /dev/null +++ b/connector_alndata/README.rst @@ -0,0 +1,48 @@ +===================================================== +ALN ODOO Connector +===================================================== + +Usage +===== + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Open Source Integrators +* Odoo Community Association (OCA) + +Contributors +~~~~~~~~~~~~ + +* Maxime Chambreuil +* Serpent Consulting Services Pvt. Ltd. + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/l10n-usa `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/connector_alndata/__init__.py b/connector_alndata/__init__.py new file mode 100644 index 00000000..60cdf63f --- /dev/null +++ b/connector_alndata/__init__.py @@ -0,0 +1,5 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/connector_alndata/__manifest__.py b/connector_alndata/__manifest__.py new file mode 100644 index 00000000..e6118705 --- /dev/null +++ b/connector_alndata/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +{ + 'name': 'ALN Data Connector', + 'summary': '''This module allows you to synchronize your Odoo database + with ALN Data once a month''', + 'version': '12.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'Open Source Integrators, Odoo Community Association (OCA),', + 'website': 'https://github.com/OCA/l10n-usa', + 'category': 'Tools', + 'maintainers': ['max3903'], + 'development_status': 'Beta', + 'depends': [ + 'crm', + 'fieldservice', + ], + 'data': [ + 'data/ir_config_parameter_data.xml', + 'data/sync_aln_data_view.xml', + 'views/res_partner_industry_view.xml', + 'views/res_partner_view.xml', + ], + 'installable': True, +} diff --git a/connector_alndata/data/ir_config_parameter_data.xml b/connector_alndata/data/ir_config_parameter_data.xml new file mode 100644 index 00000000..2dd10658 --- /dev/null +++ b/connector_alndata/data/ir_config_parameter_data.xml @@ -0,0 +1,57 @@ + + + + + alndata.api.url + http://api2.alndata.com/odata/ + + + + + alndata.api.key + b85f4d81-d726-42d4-a524-30f75e28a1ac + + + + + alndata.markets.rowversion + 0 + + + + + alndata.submarkets.rowversion + 0 + + + + + alndata.managementcompanies.rowversion + 0 + + + + + alndata.owners.rowversion + 0 + + + + + alndata.apartments.rowversion + 0 + + + + + alndata.contacts.rowversion + 0 + + + + + alndata.newconstructions.rowversion + 0 + + + diff --git a/connector_alndata/data/sync_aln_data_view.xml b/connector_alndata/data/sync_aln_data_view.xml new file mode 100644 index 00000000..f1e4af22 --- /dev/null +++ b/connector_alndata/data/sync_aln_data_view.xml @@ -0,0 +1,16 @@ + + + + + Sync with ALN Data + + code + 1 + months + -1 + + + model._cron_sync_with_aln() + + + diff --git a/connector_alndata/models/__init__.py b/connector_alndata/models/__init__.py new file mode 100644 index 00000000..e14dc2c9 --- /dev/null +++ b/connector_alndata/models/__init__.py @@ -0,0 +1,8 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import res_partner +from . import res_partner_industry +from . import crm_lead +from . import fsm_location diff --git a/connector_alndata/models/crm_lead.py b/connector_alndata/models/crm_lead.py new file mode 100644 index 00000000..81f0d342 --- /dev/null +++ b/connector_alndata/models/crm_lead.py @@ -0,0 +1,725 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import threading +import logging +import requests +import json +import base64 + +from odoo import api, fields, models, _, sql_db +from odoo.exceptions import RedirectWarning + +_logger = logging.getLogger(__name__) + +us_timezone = { + 'M': 'US/Mountain', + 'C': 'US/Central', + 'E': 'US/Eastern', + 'P': 'US/Pacific', + 'H': 'US/Hawaii', +} + + +class Lead(models.Model): + _inherit = "crm.lead" + + lastdate_changed = fields.Datetime('LastDateChanged') + industry_id = fields.Many2one('res.partner.industry', 'Market') + lead_type = fields.Selection( + [('owner', 'Owner'), + ('management_company', 'Management Company'), + ('contact', 'Contact'), + ('new_construction', 'New Constructions')], + 'Lead Type') + + def aln_auth_login(self, data_key='', data_params=None): + """Aln Connector. + + This method is used to connect with ALN and fetch the data. + """ + config_obj = self.env['ir.config_parameter'] + url = config_obj.get_param('alndata.api.url') + api_key = config_obj.get_param('alndata.api.key') + full_content = [] + + url = url + data_key + count = 0 + flag = True + params = { + 'apikey': api_key, + 'Accept': 'application/json', + } + _logger.info('ALN Data Connector Data Key: %s', data_key) + if not data_params: + data_params = {} + params.update(data_params) + + # use count to fetch all data related filter by multiple request + while flag: + try: + if count > 0: + params.update({'$skip': count}) + response = requests.get(url, params=params) + response.raise_for_status() + except Exception as e: + _logger.error('%s', e) + content = response.content.decode('utf8') + if content: + content = json.loads(content).get('value') + if len(content or []) > 0: + count += len(content) + full_content += content + else: + flag = False + count = 0 + _logger.info('ALN Data Connector. Number of records: %s', + len(full_content)) + return full_content or [] + + def remove_data(self, model, datas, origin=''): + """Removed Data. + + This method is used to removed data from odoo database. + which deleted from ALN Data. + """ + obj = self.env[model] + removed_ids = [] + rem = obj + domain = [] + if model == 'res.partner': + domain += [('partner_type', '=', origin)] + elif origin and model == 'crm.lead': + domain += [('lead_type', '=', origin)] + for rec in obj.search(domain): + if model == 'crm.lead' and rec.referred not in datas: + removed_ids.append(rec.id) + rem += rec + elif model in ['res.partner', 'fsm.location'] and \ + rec.ref not in datas: + removed_ids.append(rec.id) + rem += rec + rem.write({'active': False}) + return removed_ids + + def get_state(self, obj, state_code): + """State. + + This method is used to search state from state code. + """ + return obj.search([('code', '=', state_code)], limit=1) + + def get_market(self, obj, market): + """Market/Submarket. + + This method is used to search market or submarket base on name. + """ + return obj.search([('name', '=', market)], limit=1) + + @api.model + def _prepare_industry_values(self, industry=None, origin='market'): + if not industry: + return {} + values = { + 'name': industry.get('MarketId'), + 'full_name': industry.get('MarketDescription'), + } + if origin == 'submarket': + values.update({ + 'name': industry.get('SubMarketDescription'), + 'ref': industry.get('SubmarketId'), + }) + return values + + @api.model + def sync_market_data(self): + """Synchronize Market Data. + + This method is used to sync market data. + """ + industry_obj = self.env['res.partner.industry'] + market_ids = [] + updated_market_ids = [] + markets = self.aln_auth_login('Markets') + for market in markets: + available_market = self.get_market(industry_obj, + market.get('MarketId')) + market_vals = self._prepare_industry_values(market) + if available_market: + available_market.write(market_vals) + updated_market_ids.append(available_market.id) + else: + market = industry_obj.create(market_vals) + market_ids.append(market.id) + _logger.info('ALN Data Connector.Created Market Ids : %s', market_ids) + _logger.info('ALN Data Connector.Updated Market Ids : %s', + updated_market_ids) + + @api.model + def sync_submarket_data(self): + """Synchronize Submarket Data. + + This method is used to sync submarket data. + """ + industry_obj = self.env['res.partner.industry'] + submarket_ids = [] + updated_submarket_ids = [] + for submarket in self.aln_auth_login('Submarkets'): + available_submarket = self.get_market( + industry_obj, submarket.get('SubMarketDescription')) + market_id = self.get_market(industry_obj, + submarket.get('Market')) + submarket_vals = self._prepare_industry_values( + submarket, 'submarket') + if market_id: + submarket_vals.update({ + 'parent_id': market_id.id}) + if not available_submarket: + submarket = industry_obj.create(submarket_vals) + submarket_ids.append(submarket.id) + else: + available_submarket.write(submarket_vals) + updated_submarket_ids.append(available_submarket.id) + _logger.info( + 'ALN Data Connector.Created SubMarket Ids : %s', submarket_ids) + _logger.info('ALN Data Connector.Updated SubMarket Ids : %s', + updated_submarket_ids) + + @api.model + def sync_status_code_data(self): + """Synchronize Status Codes Data. + + This method is used to sync status code data. + """ + status_obj = self.env['fsm.stage'] + stage_ids = [] + for state in self.aln_auth_login('StatusCodes'): + available_state = self.get_market(status_obj, + state.get('StatusDescription')) + if not available_state: + state_vals = { + 'name': state.get('StatusDescription'), + 'stage_type': 'location', + 'sequence': state.get('Status'), + } + state = status_obj.create(state_vals) + stage_ids.append(state.id) + _logger.info( + 'ALN Data Connector.Created FSM Stage Ids : %s', stage_ids) + + @api.model + def get_title(self, obj, title): + """Get Title. + + This method is used to get title from odoo database + if it is not exist then create a title. + """ + title_id = obj.search([('name', '=', title)], limit=1) + if not title_id: + title_id = obj.create({'name': title}) + return title_id.id + + def sync_owner_contact_data(self, origin=''): + """Synchronize Owner, Contact and Management Companies Data. + + This method is used to synchronize owner, contact and + management companies data.Differentiate data by origin. + Using origin prepared values for owner, contact and + management companies data. + """ + config_obj = self.env['ir.config_parameter'] + lead_obj = self.env['crm.lead'] + partner_obj = self.env['res.partner'] + industry_obj = self.env['res.partner.industry'] + state_obj = self.env['res.country.state'] + res_partner_title_obj = self.env['res.partner.title'] + partner_ids = [] + lead_ids = [] + contact_ids = [] + owner_ids = [] + construction_ids = [] + updated_partner_ids = [] + updated_lead_ids = [] + updated_contact_ids = [] + updated_owner_ids = [] + updated_construction_ids = [] + rowversion_list = [] + last_update_date = [] + params = {} + removed_lead = [] + removed_partner = [] + + if not origin: + management_company_key = config_obj.get_param( + 'alndata.managementcompanies.rowversion') + params.update({'$expand': 'Addresses,PhoneNumbers'}) + if management_company_key != '0': + params.update( + {'$filter': 'RowVersion lt ' + management_company_key, + '$orderby': 'RowVersion'}) + old_manage_company = self.aln_auth_login( + 'ManagementCompanies', params) + company_id_ref = [dat.get('ManagementCompanyEntityId') + for dat in old_manage_company] + removed_lead = self.remove_data( + 'crm.lead', company_id_ref, 'management_company') + removed_partner = self.remove_data( + 'res.partner', company_id_ref, 'management_company') + params.update( + {'$filter': 'RowVersion gt ' + management_company_key, + '$orderby': 'RowVersion'}) + datas = self.aln_auth_login( + 'ManagementCompanies', params) + elif origin == 'owner': + datas = self.aln_auth_login('Owners') + old_owner = [owner.get('OwnerId') for owner in datas] + removed_lead = self.remove_data('crm.lead', old_owner, 'owner') + elif origin == 'new_construction': + construction_key = config_obj.get_param( + 'alndata.newconstructions.rowversion') + if construction_key != '0': + params.update( + {'$filter': "LastDateNewConstructionChanged lt datetime'" + + construction_key + "'", + '$orderby': "LastDateNewConstructionChanged"}) + old_construction = self.aln_auth_login( + 'NewConstructions', params) + constructions = [dat.get('NewConstructionId') + for dat in old_construction] + removed_lead = self.remove_data( + 'crm.lead', constructions, 'new_construction') + params.update( + {'$filter': "LastDateNewConstructionChanged gt datetime'" + + construction_key + "'", + '$orderby': "LastDateNewConstructionChanged"}) + datas = self.aln_auth_login('NewConstructions', params) + else: + contact_key = config_obj.get_param('alndata.contacts.rowversion') + params.update({'$expand': 'Addresses,PhoneNumbers,JobCategories'}) + if contact_key != '0': + params.update({'$filter': 'RowVersion lt ' + contact_key, + '$orderby': 'RowVersion'}) + old_contacts = self.aln_auth_login('Contacts', params) + contacts = [cont.get('ContactId') for cont in old_contacts] + removed_lead = self.remove_data( + 'crm.lead', contacts, 'contact') + params.update({'$filter': 'RowVersion gt ' + contact_key, + '$orderby': 'RowVersion'}) + datas = self.aln_auth_login('Contacts', params) + + for data in datas: + obj = lead_obj + name = data.get('ManagementCompanyName') + rowversion = int(data.get('RowVersion', 0)) + rowversion_list.append(rowversion) + last_changed = data.get('ManagementCompanyLastDateChanged') + referred = data.get('ManagementCompanyEntityId') + lead_type = 'management_company' + if origin == 'owner': + name = data.get('OwnerName') + lead_type = 'owner' + last_changed = False + referred = data.get('OwnerId') + elif origin == 'contact': + lead_type = 'contact' + name = data.get('ContactName') + last_changed = data.get('ContactLastDateChanged') + referred = data.get('ContactId') + elif origin == 'new_construction': + lead_type = "new_construction" + name = data.get('ProjectName') + referred = data.get('NewConstructionId') + last_changed = data.get('LastDateNewConstructionChanged') + if last_changed: + last_update_date.append(last_changed) + domain = [('name', '=', name)] + if data.get('ManagementCompanyParentId'): + obj = partner_obj + domain += [('partner_type', '=', 'management_company'), + ('ref', '=', referred)] + else: + domain += [('lead_type', '=', lead_type), + ('referred', '=', referred)] + market = self.get_market( + industry_obj, data.get('ManagementCompanyMarket')) + lead_vals = { + 'name': name, + } + if data.get('ManagementCompanyWebSite'): + lead_vals.update( + {'website': data.get('ManagementCompanyWebSite'), + 'industry_id': market.id, }) + if data.get('Addresses', False): + address = data['Addresses'][0] + state = self.get_state(state_obj, + address.get('AddressState')) + country_id = state and state.country_id.id or 0 + lead_vals.update( + { + 'street': address.get('AddressLine1'), + 'street2': address.get('AddressLine2'), + 'city': address.get('AddressCity'), + 'state_id': state.id, + 'zip': address.get('AddressZIP'), + 'country_id': country_id, + }) + if data.get('ManagementCompanyParentId'): + lead_vals.update( + { + 'address_type': address.get('AddressType'), + 'ref': data.get('ManagementCompanyEntityId'), + 'partner_type': 'management_company', + }) + else: + lead_vals.update( + { + 'lead_type': lead_type, + 'lastdate_changed': last_changed, + 'referred': referred, + }) + if data.get('PhoneNumbers', False): + numbers = data['PhoneNumbers'] + for num in numbers: + if num.get('IsPrimary') == 'Y': + lead_vals.update({ + 'phone': num.get('Number') + }) + if (num.get('PhoneNumberType') == "Fax Number" and + data.get('ManagementCompanyParentId')): + lead_vals.update({ + 'fax': num.get('Number') + }) + if origin == 'contact': + lead = lead_obj.search([ + ('referred', '=', data.get('CorporateEntityId'))], limit=1) + partner_name = lead and lead.name or '' + function = '' + for job in data.get('JobCategories'): + function += job.get('JobCategoryDescription') + ',' + lead_vals.update({ + 'email_from': data.get('ContactEMail'), + 'partner_name': partner_name, + 'contact_name': data.get('ContactName'), + 'title': self.get_title(res_partner_title_obj, + data.get('ContactTitle')), + 'function': function[:-1], + }) + if origin == 'owner': + address = data.get('OwnerAddress', '') + address_lst = address and address.split('\r\n') or [] + street = city = state_code = owner_zip = '' + if len(address_lst) > 1: + street = address_lst[0] + city_state = address_lst[1].split(',') + if len(city_state) > 1: + city = city_state[0] + state_zip = (city_state[1].strip()).split(' ') + if len(state_zip) > 1: + state_code = state_zip[0] + owner_zip = state_zip[1] + state = self.get_state(state_obj, state_code) + lead_vals.update({ + 'street': street, + 'city': city, + 'state_id': state.id, + 'zip': owner_zip, + 'country_id': state.country_id.id, + 'phone': data.get('OwnerPhone'), + 'contact_name': data.get('OwnerName') + }) + if origin == 'new_construction': + state = self.get_state(state_obj, data.get('ProjectState')) + description = '' + if data.get('StartDate'): + description += "Start Date : " + data.get('StartDate') + if data.get('LeaseDate'): + description += "\n" + "Lease Date : " + \ + data.get('LeaseDate') + if data.get('OccupancyDate'): + description += "\n" + "Occupancy Date : " + \ + data.get('OccupancyDate') + if data.get('CompletionDate'): + description += "\n" + "Completion Date : " + \ + data.get('CompletionDate') + if data.get('Progress'): + description += "\n" + "Progress : " + data.get('Progress') + if data.get('Market'): + description += "\n" + "Market : " + data.get('Market') + lead_vals.update({ + 'street': data.get('ProjectAddress'), + 'city': data.get('ProjectCity'), + 'state_id': state.id, + 'country_id': state.country_id.id, + 'zip': data.get('ProjectZIP'), + 'partner_name': data.get('Company'), + 'description': description, + }) + + lead = obj.search(domain) + if lead: + lead.write(lead_vals) + if origin == 'contact': + updated_contact_ids.append(lead.id) + elif origin == 'owner': + updated_owner_ids.append(lead.id) + elif origin == 'new_construction': + updated_construction_ids.append(lead.id) + elif data.get('ManagementCompanyParentId'): + updated_partner_ids.append(lead.id) + else: + updated_lead_ids.append(lead.id) + else: + lead = obj.create(lead_vals) + if origin == 'contact': + contact_ids.append(lead.id) + elif origin == 'owner': + owner_ids.append(lead.id) + elif origin == 'new_construction': + construction_ids.append(lead.id) + elif data.get('ManagementCompanyParentId'): + partner_ids.append(lead.id) + else: + lead_ids.append(lead.id) + row_version = rowversion_list and max(rowversion_list) or 0 + max_date = last_update_date and max(last_update_date) or 0 + + if origin == 'contact': + _logger.info( + 'ALN Data Connector.Created Contacts : %s', contact_ids) + _logger.info('ALN Data Connector.Updated Contacts : %s', + updated_contact_ids) + _logger.info( + 'ALN Data Connector.Deleted Contacts : %s', removed_lead) + if row_version: + config_obj.sudo().set_param( + 'alndata.contacts.rowversion', row_version) + elif origin == 'owner': + _logger.info('ALN Data Connector.Created Owners : %s', owner_ids) + _logger.info('ALN Data Connector.Updated Owners : %s', + updated_owner_ids) + _logger.info( + 'ALN Data Connector.Deleted Owners : %s', removed_lead) + elif origin == 'new_construction': + _logger.info('ALN Data Connector.Created New Constructions : %s', + construction_ids) + _logger.info('ALN Data Connector.Updated Constructions : %s', + updated_construction_ids) + _logger.info('ALN Data Connector.Deleted Constructions : %s', + removed_lead) + if max_date: + config_obj.sudo().set_param( + 'alndata.newconstructions.rowversion', max_date) + else: + if row_version: + config_obj.sudo().set_param( + 'alndata.managementcompanies.rowversion', row_version) + _logger.info( + 'ALN Data Connector.Created Management Companies Partners' + ' : %s', partner_ids) + _logger.info( + 'ALN Data Connector.Updated Management Companies Partners ' + ': %s', + updated_partner_ids) + _logger.info( + 'ALN Data Connector.Deleted Management Companies Partners ' + ': %s', + removed_partner) + _logger.info( + 'ALN Data Connector.Created Management Companies Leads ' + ': %s', lead_ids) + _logger.info('ALN Data Connector.Updated Management Companies ' + 'Leads : %s', + updated_lead_ids) + _logger.info( + 'ALN Data Connector.Deleted Management Companies Leads :' + ' %s', removed_lead) + + @api.model + def sync_apartment_data(self): + """Synchronize Apartments Data. + + This method is used to synchronize apartment data. + """ + create_apart_ids = [] + update_apart_ids = [] + config_obj = self.env['ir.config_parameter'] + stage_obj = self.env['fsm.stage'] + industry_obj = self.env['res.partner.industry'] + partner_obj = self.env['res.partner'] + state_obj = self.env['res.country.state'] + fsm_obj = self.env['fsm.location'] + rowversion_list = [] + removed_apart_ids = [] + + params = {'$expand': 'Addresses,PhoneNumbers'} + apartment_key = config_obj.get_param( + 'alndata.apartments.rowversion') + if apartment_key != '0': + params.update({'$filter': 'RowVersion lt ' + apartment_key, + '$orderby': 'RowVersion'}) + old_apart = self.aln_auth_login('Apartments', params) + apartments = [apart.get('ApartmentId') for apart in old_apart] + removed_apart_ids = self.remove_data('fsm.location', apartments) + params.update({'$filter': 'RowVersion gt ' + apartment_key, + '$orderby': 'RowVersion'}) + + for apart in self.aln_auth_login('Apartments', params): + contact = partner_obj + industry = industry_obj + prop = apart.get('Property') + rowversion_list.append(int(apart.get('RowVersion'))) + stage = stage_obj.search( + [('sequence', '=', prop.get('Status')), + ('stage_type', '=', 'location')], + limit=1) + if prop.get('SubmarketId'): + industry = industry_obj.search( + [('ref', '=', prop.get('SubmarketId'))], + limit=1) + if not industry: + industry = self.get_market( + industry_obj, prop.get('Market')) + + image_data = b'' + if prop.get('AptPictureURL'): + image_res = requests.get(prop.get('AptPictureURL')) + image_data = image_res.content + apartment_vals = { + 'ref': apart.get('ApartmentId'), + 'stage_id': stage.id, + 'name': prop.get('AptName'), + 'email': prop.get('EMailAddress'), + 'industry_id': industry.id, + 'num_of_unit': prop.get('NumUnits'), + 'year_built': prop.get('YearBuilt'), + 'year_remodeled': prop.get('YearRemodeled'), + 'direction': prop.get('Directions'), + 'notes': prop.get('PropertyDescription'), + 'website': prop.get('AptHomePage'), + 'image': base64.b64encode(image_data), + 'tz': us_timezone.get(prop.get('TimeZone', '').strip(), ''), + } + if prop.get('CurrManager'): + contact = partner_obj.search( + [('name', '=', prop.get('CurrManager')), + ('partner_type', '=', 'contact')], + limit=1) + apartment_vals.update({'contact_id': contact and contact.id}) + if prop.get('CorporateManagementCompanyId'): + management_company = partner_obj.search( + [('ref', '=', prop.get('CorporateManagementCompanyId')), + ('partner_type', '=', 'management_company')], + limit=1) + apartment_vals.update( + { + 'commercial_partner_id': management_company and + management_company.id}) + if prop.get('OwnerId'): + owner = partner_obj.search( + [('ref', '=', prop.get('OwnerId')), + ('partner_type', '=', 'owner')], + limit=1) + apartment_vals.update( + { + 'owner_id': owner and owner.id}) + if apart.get('Addresses', False): + address = apart['Addresses'][0] + state = self.get_state(state_obj, + address.get('AddressState')) + country_id = state and state.country_id.id or 0 + apartment_vals.update( + { + 'street': address.get('AddressLine1'), + 'street2': address.get('AddressLine2'), + 'city': address.get('AddressCity'), + 'state_id': state.id, + 'zip': address.get('AddressZIP'), + 'country_id': country_id, + }) + if apart.get('PhoneNumbers', False): + numbers = apart['PhoneNumbers'] + for num in numbers: + if num.get('IsPrimary') == 'Y': + apartment_vals.update({ + 'phone': num.get('Number') + }) + if (num.get('PhoneNumberType') == "Fax Number" and + num.get('ManagementCompanyParentId')): + apartment_vals.update({ + 'fax': num.get('Number') + }) + fsm_location = fsm_obj.search( + [('name', '=', prop.get('AptName')), + ('ref', '=', apart.get('ApartmentId'))], + limit=1) + if not fsm_location: + fsm = fsm_obj.create(apartment_vals) + create_apart_ids.append(fsm.id) + else: + fsm_location.write(apartment_vals) + update_apart_ids.append(fsm_location.id) + row_version = rowversion_list and max(rowversion_list) or 0 + if row_version: + config_obj.sudo().set_param( + 'alndata.apartments.rowversion', row_version) + _logger.info( + 'ALN Data Connector.Created Fsm Location : %s', create_apart_ids) + _logger.info( + 'ALN Data Connector.Updated Fsm Location : %s', update_apart_ids) + _logger.info( + 'ALN Data Connector.Deleted Fsm Location : %s', removed_apart_ids) + + @api.model + def sync_aln_data_with_threading(self): + """Synchronize data with ALN Data By Threading. + + This method is used to get data from ALN Data usinf threading. + """ + _logger.info('ALN Data Connector. Starting the synchronization...') + new_cr = sql_db.db_connect(self.env.cr.dbname).cursor() + uid, context = self.env.uid, self.env.context + with api.Environment.manage(): + self.env = api.Environment(new_cr, uid, context) + # Get Market Data + self.sync_market_data() + + # Get Sub-Market Data + self.sync_submarket_data() + + # Get Owner Data + self.sync_owner_contact_data(origin='owner') + + # Get Management Companies Data + self.sync_owner_contact_data() + + # Get Contact Data + self.sync_owner_contact_data('contact') + + # Get the New Constructions Data + self.sync_owner_contact_data('new_construction') + + # Get the Apartment Data + self.sync_apartment_data() + + new_cr.commit() + new_cr.close() + _logger.info('ALN Data Connector. Synchronization successful.') + + @api.model + def _cron_sync_with_aln(self): + """Synchronize data with ALN Data. + + This method is used to get data from ALN Data and create data in odoo. + """ + api_key = self.env['ir.config_parameter'].get_param('alndata.api.key') + if api_key == '0': + action = self.env.ref('base.ir_config_list_action') + msg = _('Cannot find a ALN Data URL and API key, ' + 'You should configure it. ' + '\nPlease go to System Parameters.') + raise RedirectWarning(msg, action.id, + _('Go to the configuration panel')) + thred_cal = threading.Thread( + target=self.sync_aln_data_with_threading) + thred_cal.start() diff --git a/connector_alndata/models/fsm_location.py b/connector_alndata/models/fsm_location.py new file mode 100644 index 00000000..b49cd0f1 --- /dev/null +++ b/connector_alndata/models/fsm_location.py @@ -0,0 +1,17 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class FsmLocation(models.Model): + _inherit = "fsm.location" + + industry_id = fields.Many2one('res.partner.industry', 'Market') + num_of_unit = fields.Integer('Number of Units') + year_built = fields.Char('Build Year') + year_remodeled = fields.Char('YearRemodeled') + owner_id = fields.Many2one('res.partner', string='Related Owner', + required=False, ondelete='restrict', + auto_join=True) diff --git a/connector_alndata/models/res_partner.py b/connector_alndata/models/res_partner.py new file mode 100644 index 00000000..95764111 --- /dev/null +++ b/connector_alndata/models/res_partner.py @@ -0,0 +1,33 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + @api.multi + def _compute_number_unit(self): + """Calculate Number of Units. + + This method is used to calculate the number of units + of the apartment which links with a particular corporate company. + """ + apart_obj = self.env['fsm.location'] + for rec in self: + apartments = apart_obj.search_read( + [('commercial_partner_id', '=', rec.id)], ['num_of_unit']) + rec.num_unit = sum([apart.get('num_of_unit') + for apart in apartments]) + + partner_type = fields.Selection( + [('owner', 'Owner'), + ('management_company', 'Management Company'), + ('contact', 'Contact'), + ('new_construction', 'New Constructions')], + 'Partner Type') + address_type = fields.Char('AddressType') + num_unit = fields.Integer(compute="_compute_number_unit", + string="Number of Apartments") diff --git a/connector_alndata/models/res_partner_industry.py b/connector_alndata/models/res_partner_industry.py new file mode 100644 index 00000000..e4f8399f --- /dev/null +++ b/connector_alndata/models/res_partner_industry.py @@ -0,0 +1,34 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ResPartnerIndustry(models.Model): + _inherit = "res.partner.industry" + + ref = fields.Char('Reference') + parent_id = fields.Many2one('res.partner.industry', 'Parent') + + _sql_constraints = [ + ('ref_uniq', 'unique(ref)', 'Reference must be unique!'), + ] + + @api.multi + def name_get(self): + """Compute Display Name. + + Return the display name, including their direct parent by default. + """ + res = [] + for partner in self: + display_name = '' + if partner.parent_id and not partner.name: + display_name += partner.parent_id.name + if partner.parent_id and partner.name: + display_name += partner.parent_id.name + '/' + partner.name + if partner.name and not partner.parent_id: + display_name += partner.name + res.append((partner.id, display_name)) + return res diff --git a/connector_alndata/readme/CONTRIBUTORS.rst b/connector_alndata/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..76464a46 --- /dev/null +++ b/connector_alndata/readme/CONTRIBUTORS.rst @@ -0,0 +1,8 @@ +* Open Source Integrators + + * Mayank Gosai + * Maxime Chambreuil + +* Serpent Consulting Services Pvt. Ltd. + + * Nikita Vaghela diff --git a/connector_alndata/readme/DESCRIPTION.rst b/connector_alndata/readme/DESCRIPTION.rst new file mode 100644 index 00000000..140525d5 --- /dev/null +++ b/connector_alndata/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows you to synchronize your Odoo database with ALN Data once a month. diff --git a/connector_alndata/readme/USAGE.rst b/connector_alndata/readme/USAGE.rst new file mode 100644 index 00000000..0bb49e47 --- /dev/null +++ b/connector_alndata/readme/USAGE.rst @@ -0,0 +1,2 @@ +In the menu settings->System Parameters +Set the ALN API key as value of 'alndata.api.key' key. diff --git a/connector_alndata/static/description/icon.png b/connector_alndata/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/connector_alndata/static/description/index.html b/connector_alndata/static/description/index.html new file mode 100644 index 00000000..a24248f4 --- /dev/null +++ b/connector_alndata/static/description/index.html @@ -0,0 +1,52 @@ +
+ +
+
+

+ ALN Data Connector +

+
+ + +
+
+
+
+
+

+ + This module is used to fetch data from ALN data and + synchronize that data in ODOO. +

+

+ + This module contains cron job for synchronization of data. +

+

+ + Set the system parameter for ALN API key. +

+
+
+
+ +
diff --git a/connector_alndata/tests/__init__.py b/connector_alndata/tests/__init__.py new file mode 100644 index 00000000..e62e4cd4 --- /dev/null +++ b/connector_alndata/tests/__init__.py @@ -0,0 +1,5 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_connector_alndata diff --git a/connector_alndata/tests/test_connector_alndata.py b/connector_alndata/tests/test_connector_alndata.py new file mode 100644 index 00000000..be87fd8d --- /dev/null +++ b/connector_alndata/tests/test_connector_alndata.py @@ -0,0 +1,25 @@ +# Copyright (C) 2019 Open Source Integrators +# Copyright (C) 2019 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + +import logging +_logger = logging.getLogger(__name__) + + +class TestConnectorAlndata(TransactionCase): + + def setUp(self): + super(TestConnectorAlndata, self).setUp() + self.config_model = self.env['ir.config_parameter'] + self.crm_obj = self.env['crm.lead'] + + def test_cron_sync_with_aln(self): + """ + Test cron job for synchronization data. + """ + self.config_model.set_param( + 'alndata.api.key', + 'b85f4d81-d726-42d4-a524-30f75e28a1ac') + self.crm_obj._cron_sync_with_aln() diff --git a/connector_alndata/views/res_partner_industry_view.xml b/connector_alndata/views/res_partner_industry_view.xml new file mode 100644 index 00000000..8a519583 --- /dev/null +++ b/connector_alndata/views/res_partner_industry_view.xml @@ -0,0 +1,27 @@ + + + + + Industry + res.partner.industry + + + + + + + + + + + Industry + res.partner.industry + + + + + + + + + diff --git a/connector_alndata/views/res_partner_view.xml b/connector_alndata/views/res_partner_view.xml new file mode 100644 index 00000000..0aa195f3 --- /dev/null +++ b/connector_alndata/views/res_partner_view.xml @@ -0,0 +1,27 @@ + + + + + Partner + res.partner + + + + + + + + + + + Partner + res.partner + + + + + + + + + diff --git a/oca_dependencies.txt b/oca_dependencies.txt index 198f3dfe..1dee1878 100644 --- a/oca_dependencies.txt +++ b/oca_dependencies.txt @@ -4,3 +4,4 @@ account-payment account-reconcile bank-payment mis-builder +field-service From 849dfee7ae94b2c397cf93393d64d90b9b487022 Mon Sep 17 00:00:00 2001 From: Nikita Vaghela Date: Fri, 10 Jan 2020 18:05:44 +0530 Subject: [PATCH 2/4] [12.0][IMP]Added customer_id field with required false to fixed constrain issue. --- connector_alndata/models/fsm_location.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/connector_alndata/models/fsm_location.py b/connector_alndata/models/fsm_location.py index b49cd0f1..09f67425 100644 --- a/connector_alndata/models/fsm_location.py +++ b/connector_alndata/models/fsm_location.py @@ -15,3 +15,6 @@ class FsmLocation(models.Model): owner_id = fields.Many2one('res.partner', string='Related Owner', required=False, ondelete='restrict', auto_join=True) + customer_id = fields.Many2one( + 'res.partner', string='Billed Customer', required=False, + ondelete='restrict', auto_join=True, track_visibility='onchange') From 29deb3ee5d6f2413353b572844e78ecd8141e0f2 Mon Sep 17 00:00:00 2001 From: Nikita Vaghela Date: Mon, 13 Jan 2020 17:15:42 +0530 Subject: [PATCH 3/4] [IMP] Improved the code for apartment to fix duplicate constrain issue. --- connector_alndata/models/crm_lead.py | 43 +++++++++++++++++++----- connector_alndata/models/fsm_location.py | 3 -- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/connector_alndata/models/crm_lead.py b/connector_alndata/models/crm_lead.py index 81f0d342..ecf146f0 100644 --- a/connector_alndata/models/crm_lead.py +++ b/connector_alndata/models/crm_lead.py @@ -65,6 +65,7 @@ def aln_auth_login(self, data_key='', data_params=None): response.raise_for_status() except Exception as e: _logger.error('%s', e) + break content = response.content.decode('utf8') if content: content = json.loads(content).get('value') @@ -115,7 +116,12 @@ def get_market(self, obj, market): This method is used to search market or submarket base on name. """ - return obj.search([('name', '=', market)], limit=1) + ctx = self.env.context + domain = [('name', '=', market)] + + if ctx.get('search_archived'): + domain += ['|', ('active', '=', True), ('active', '=', False)] + return obj.search(domain, limit=1) @api.model def _prepare_industry_values(self, industry=None, origin='market'): @@ -143,10 +149,13 @@ def sync_market_data(self): updated_market_ids = [] markets = self.aln_auth_login('Markets') for market in markets: - available_market = self.get_market(industry_obj, - market.get('MarketId')) + available_market = self.with_context( + search_archived=True).get_market( + industry_obj, market.get('MarketId')) market_vals = self._prepare_industry_values(market) if available_market: + if not available_market.active: + market_vals.update({'active': True}) available_market.write(market_vals) updated_market_ids.append(available_market.id) else: @@ -166,8 +175,9 @@ def sync_submarket_data(self): submarket_ids = [] updated_submarket_ids = [] for submarket in self.aln_auth_login('Submarkets'): - available_submarket = self.get_market( - industry_obj, submarket.get('SubMarketDescription')) + available_submarket = self.with_context( + search_archived=True).get_market( + industry_obj, submarket.get('SubMarketDescription')) market_id = self.get_market(industry_obj, submarket.get('Market')) submarket_vals = self._prepare_industry_values( @@ -179,6 +189,8 @@ def sync_submarket_data(self): submarket = industry_obj.create(submarket_vals) submarket_ids.append(submarket.id) else: + if not available_submarket.active: + submarket_vals.update({'active': True}) available_submarket.write(submarket_vals) updated_submarket_ids.append(available_submarket.id) _logger.info( @@ -281,7 +293,7 @@ def sync_owner_contact_data(self, origin=''): if construction_key != '0': params.update( {'$filter': "LastDateNewConstructionChanged lt datetime'" + - construction_key + "'", + construction_key + "'", '$orderby': "LastDateNewConstructionChanged"}) old_construction = self.aln_auth_login( 'NewConstructions', params) @@ -291,7 +303,7 @@ def sync_owner_contact_data(self, origin=''): 'crm.lead', constructions, 'new_construction') params.update( {'$filter': "LastDateNewConstructionChanged gt datetime'" + - construction_key + "'", + construction_key + "'", '$orderby': "LastDateNewConstructionChanged"}) datas = self.aln_auth_login('NewConstructions', params) else: @@ -456,8 +468,11 @@ def sync_owner_contact_data(self, origin=''): 'description': description, }) + domain += ['|', ('active', '=', True), ('active', '=', False)] lead = obj.search(domain) if lead: + if not lead.active: + lead_vals.update({'active': True}) lead.write(lead_vals) if origin == 'contact': updated_contact_ids.append(lead.id) @@ -651,12 +666,24 @@ def sync_apartment_data(self): }) fsm_location = fsm_obj.search( [('name', '=', prop.get('AptName')), - ('ref', '=', apart.get('ApartmentId'))], + ('ref', '=', apart.get('ApartmentId')), + # Checked the fetched record exists in the system or not. + # sometime record is exist but it's archived. + # so it will not get in the search method. + # so it will go to create a new record but there is a + # SQL unique constraint on the 'ref' field. + # so it will raise the constraint error. + # To fix this issue added this domain. + '|', + ('active', '=', True), + ('active', '=', False)], limit=1) if not fsm_location: fsm = fsm_obj.create(apartment_vals) create_apart_ids.append(fsm.id) else: + if not fsm_location.active: + apartment_vals.update({'active': True}) fsm_location.write(apartment_vals) update_apart_ids.append(fsm_location.id) row_version = rowversion_list and max(rowversion_list) or 0 diff --git a/connector_alndata/models/fsm_location.py b/connector_alndata/models/fsm_location.py index 09f67425..b49cd0f1 100644 --- a/connector_alndata/models/fsm_location.py +++ b/connector_alndata/models/fsm_location.py @@ -15,6 +15,3 @@ class FsmLocation(models.Model): owner_id = fields.Many2one('res.partner', string='Related Owner', required=False, ondelete='restrict', auto_join=True) - customer_id = fields.Many2one( - 'res.partner', string='Billed Customer', required=False, - ondelete='restrict', auto_join=True, track_visibility='onchange') From 11d777984624bd195d67a00c3e1432fdb8d5175a Mon Sep 17 00:00:00 2001 From: Nikita Vaghela Date: Thu, 16 Jan 2020 11:50:56 +0530 Subject: [PATCH 4/4] [12.0][IMP] Improved code as per @mayank's comment added undefine partner and set to customer_id. --- connector_alndata/__init__.py | 1 + connector_alndata/__manifest__.py | 3 +++ connector_alndata/data/res_partner_data.xml | 25 +++++++++++++++++++++ connector_alndata/init_hook.py | 8 +++++++ connector_alndata/models/crm_lead.py | 6 ++++- connector_alndata/models/fsm_location.py | 3 +++ 6 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 connector_alndata/data/res_partner_data.xml create mode 100644 connector_alndata/init_hook.py diff --git a/connector_alndata/__init__.py b/connector_alndata/__init__.py index 60cdf63f..a8f71022 100644 --- a/connector_alndata/__init__.py +++ b/connector_alndata/__init__.py @@ -3,3 +3,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import models +from .init_hook import pre_init_hook diff --git a/connector_alndata/__manifest__.py b/connector_alndata/__manifest__.py index e6118705..bbbb168a 100644 --- a/connector_alndata/__manifest__.py +++ b/connector_alndata/__manifest__.py @@ -16,12 +16,15 @@ 'depends': [ 'crm', 'fieldservice', + 'account', ], 'data': [ 'data/ir_config_parameter_data.xml', + 'data/res_partner_data.xml', 'data/sync_aln_data_view.xml', 'views/res_partner_industry_view.xml', 'views/res_partner_view.xml', ], 'installable': True, + 'pre_init_hook': 'pre_init_hook', } diff --git a/connector_alndata/data/res_partner_data.xml b/connector_alndata/data/res_partner_data.xml new file mode 100644 index 00000000..c392c825 --- /dev/null +++ b/connector_alndata/data/res_partner_data.xml @@ -0,0 +1,25 @@ + + + + Undefine Receivable Account + 110101 + + True + + + + + Undefine Payable Account + 110102 + + True + + + + + Undefine + + + + + diff --git a/connector_alndata/init_hook.py b/connector_alndata/init_hook.py new file mode 100644 index 00000000..730d91fa --- /dev/null +++ b/connector_alndata/init_hook.py @@ -0,0 +1,8 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +def pre_init_hook(cr): + cr.execute("""ALTER TABLE "fsm_location" ADD "customer_id" INT;""") + cr.execute("""UPDATE "fsm_location" SET customer_id = owner_id + WHERE customer_id IS NULL;""") diff --git a/connector_alndata/models/crm_lead.py b/connector_alndata/models/crm_lead.py index ecf146f0..34798a8b 100644 --- a/connector_alndata/models/crm_lead.py +++ b/connector_alndata/models/crm_lead.py @@ -630,6 +630,9 @@ def sync_apartment_data(self): { 'commercial_partner_id': management_company and management_company.id}) + customer_id = self.env.ref( + 'connector_alndata.undefined_customer') + apartment_vals.update({'customer_id': customer_id.id}) if prop.get('OwnerId'): owner = partner_obj.search( [('ref', '=', prop.get('OwnerId')), @@ -637,7 +640,8 @@ def sync_apartment_data(self): limit=1) apartment_vals.update( { - 'owner_id': owner and owner.id}) + 'owner_id': owner and owner.id, + 'customer_id': (owner and owner.id) or customer_id.id}) if apart.get('Addresses', False): address = apart['Addresses'][0] state = self.get_state(state_obj, diff --git a/connector_alndata/models/fsm_location.py b/connector_alndata/models/fsm_location.py index b49cd0f1..2bb39860 100644 --- a/connector_alndata/models/fsm_location.py +++ b/connector_alndata/models/fsm_location.py @@ -15,3 +15,6 @@ class FsmLocation(models.Model): owner_id = fields.Many2one('res.partner', string='Related Owner', required=False, ondelete='restrict', auto_join=True) + customer_id = fields.Many2one( + 'res.partner', string='Billed Customer', required=True, + ondelete='restrict', auto_join=True, track_visibility='onchange')