diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/partner_autocomplete/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/partner_autocomplete/models')
5 files changed, 283 insertions, 0 deletions
diff --git a/addons/partner_autocomplete/models/__init__.py b/addons/partner_autocomplete/models/__init__.py new file mode 100644 index 00000000..5d9eecc1 --- /dev/null +++ b/addons/partner_autocomplete/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import res_partner +from . import res_company +from . import res_config_settings +from . import res_partner_autocomplete_sync diff --git a/addons/partner_autocomplete/models/res_company.py b/addons/partner_autocomplete/models/res_company.py new file mode 100644 index 00000000..e946567f --- /dev/null +++ b/addons/partner_autocomplete/models/res_company.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + +class ResCompany(models.Model): + _name = 'res.company' + _inherit = 'res.company' + + partner_gid = fields.Integer('Company database ID', related="partner_id.partner_gid", inverse="_inverse_partner_gid", store=True) + + def _inverse_partner_gid(self): + for company in self: + company.partner_id.partner_gid = company.partner_gid diff --git a/addons/partner_autocomplete/models/res_config_settings.py b/addons/partner_autocomplete/models/res_config_settings.py new file mode 100644 index 00000000..2ae3fd6d --- /dev/null +++ b/addons/partner_autocomplete/models/res_config_settings.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + partner_autocomplete_insufficient_credit = fields.Boolean('Insufficient credit', compute="_compute_partner_autocomplete_insufficient_credit") + + def _compute_partner_autocomplete_insufficient_credit(self): + for config in self: + config.partner_autocomplete_insufficient_credit = self.env['iap.account'].get_credits('partner_autocomplete') <= 0 + + def redirect_to_buy_autocomplete_credit(self): + Account = self.env['iap.account'] + return { + 'type': 'ir.actions.act_url', + 'url': Account.get_credits_url('partner_autocomplete'), + 'target': '_new', + } diff --git a/addons/partner_autocomplete/models/res_partner.py b/addons/partner_autocomplete/models/res_partner.py new file mode 100644 index 00000000..d53fcf2d --- /dev/null +++ b/addons/partner_autocomplete/models/res_partner.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +import json +from odoo import api, fields, models, exceptions, _ +from odoo.addons.iap.tools import iap_tools +# TDE FIXME: check those errors at iap level ? +from requests.exceptions import ConnectionError, HTTPError + +_logger = logging.getLogger(__name__) + +DEFAULT_ENDPOINT = 'https://partner-autocomplete.odoo.com' + +class ResPartner(models.Model): + _name = 'res.partner' + _inherit = 'res.partner' + + partner_gid = fields.Integer('Company database ID') + additional_info = fields.Char('Additional info') + + @api.model + def _replace_location_code_by_id(self, record): + record['country_id'], record['state_id'] = self._find_country_data( + state_code=record.pop('state_code', False), + state_name=record.pop('state_name', False), + country_code=record.pop('country_code', False), + country_name=record.pop('country_name', False) + ) + return record + + @api.model + def _format_data_company(self, company): + self._replace_location_code_by_id(company) + + if company.get('child_ids'): + child_ids = [] + for child in company.get('child_ids'): + child_ids.append(self._replace_location_code_by_id(child)) + company['child_ids'] = child_ids + + if company.get('additional_info'): + company['additional_info'] = json.dumps(company['additional_info']) + + return company + + @api.model + def _find_country_data(self, state_code, state_name, country_code, country_name): + country = self.env['res.country'].search([['code', '=ilike', country_code]]) + if not country: + country = self.env['res.country'].search([['name', '=ilike', country_name]]) + + state_id = False + country_id = False + if country: + country_id = { + 'id': country.id, + 'display_name': country.display_name + } + if state_name or state_code: + state = self.env['res.country.state'].search([ + ('country_id', '=', country_id.get('id')), + '|', + ('name', '=ilike', state_name), + ('code', '=ilike', state_code) + ], limit=1) + + if state: + state_id = { + 'id': state.id, + 'display_name': state.display_name + } + else: + _logger.info('Country code not found: %s', country_code) + + return country_id, state_id + + @api.model + def get_endpoint(self): + url = self.env['ir.config_parameter'].sudo().get_param('iap.partner_autocomplete.endpoint', DEFAULT_ENDPOINT) + url += '/iap/partner_autocomplete' + return url + + @api.model + def _rpc_remote_api(self, action, params, timeout=15): + if self.env.registry.in_test_mode() : + return False, 'Insufficient Credit' + url = '%s/%s' % (self.get_endpoint(), action) + account = self.env['iap.account'].get('partner_autocomplete') + if not account.account_token: + return False, 'No Account Token' + params.update({ + 'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'), + 'account_token': account.account_token, + 'country_code': self.env.company.country_id.code, + 'zip': self.env.company.zip, + }) + try: + return iap_tools.iap_jsonrpc(url=url, params=params, timeout=timeout), False + except (ConnectionError, HTTPError, exceptions.AccessError, exceptions.UserError) as exception: + _logger.error('Autocomplete API error: %s' % str(exception)) + return False, str(exception) + except iap_tools.InsufficientCreditError as exception: + _logger.warning('Insufficient Credits for Autocomplete Service: %s' % str(exception)) + return False, 'Insufficient Credit' + + @api.model + def autocomplete(self, query): + suggestions, error = self._rpc_remote_api('search', { + 'query': query, + }) + if suggestions: + results = [] + for suggestion in suggestions: + results.append(self._format_data_company(suggestion)) + return results + else: + return [] + + @api.model + def enrich_company(self, company_domain, partner_gid, vat): + response, error = self._rpc_remote_api('enrich', { + 'domain': company_domain, + 'partner_gid': partner_gid, + 'vat': vat, + }) + if response and response.get('company_data'): + result = self._format_data_company(response.get('company_data')) + else: + result = {} + + if response and response.get('credit_error'): + result.update({ + 'error': True, + 'error_message': 'Insufficient Credit' + }) + elif error: + result.update({ + 'error': True, + 'error_message': error + }) + + return result + + @api.model + def read_by_vat(self, vat): + vies_vat_data, error = self._rpc_remote_api('search_vat', { + 'vat': vat, + }) + if vies_vat_data: + return [self._format_data_company(vies_vat_data)] + else: + return [] + + @api.model + def _is_company_in_europe(self, country_code): + country = self.env['res.country'].search([('code', '=ilike', country_code)]) + if country: + country_id = country.id + europe = self.env.ref('base.europe') + if not europe: + europe = self.env["res.country.group"].search([('name', '=', 'Europe')], limit=1) + if not europe or country_id not in europe.country_ids.ids: + return False + return True + + def _is_vat_syncable(self, vat): + vat_country_code = vat[:2] + partner_country_code = self.country_id.code if self.country_id else '' + return self._is_company_in_europe(vat_country_code) and (partner_country_code == vat_country_code or not partner_country_code) + + def _is_synchable(self): + already_synched = self.env['res.partner.autocomplete.sync'].search([('partner_id', '=', self.id), ('synched', '=', True)]) + return self.is_company and self.partner_gid and not already_synched + + def _update_autocomplete_data(self, vat): + self.ensure_one() + if vat and self._is_synchable() and self._is_vat_syncable(vat): + self.env['res.partner.autocomplete.sync'].sudo().add_to_queue(self.id) + + @api.model_create_multi + def create(self, vals_list): + partners = super(ResPartner, self).create(vals_list) + if len(vals_list) == 1: + partners._update_autocomplete_data(vals_list[0].get('vat', False)) + if partners.additional_info: + template_values = json.loads(partners.additional_info) + template_values['flavor_text'] = _("Partner created by Odoo Partner Autocomplete Service") + partners.message_post_with_view( + 'iap_mail.enrich_company', + values=template_values, + subtype_id=self.env.ref('mail.mt_note').id, + ) + partners.write({'additional_info': False}) + + return partners + + def write(self, values): + res = super(ResPartner, self).write(values) + if len(self) == 1: + self._update_autocomplete_data(values.get('vat', False)) + + return res diff --git a/addons/partner_autocomplete/models/res_partner_autocomplete_sync.py b/addons/partner_autocomplete/models/res_partner_autocomplete_sync.py new file mode 100644 index 00000000..8b386e29 --- /dev/null +++ b/addons/partner_autocomplete/models/res_partner_autocomplete_sync.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + +class ResPartnerAutocompleteSync(models.Model): + _name = 'res.partner.autocomplete.sync' + _description = 'Partner Autocomplete Sync' + + partner_id = fields.Many2one('res.partner', string="Partner", ondelete='cascade') + synched = fields.Boolean('Is synched', default=False) + + @api.model + def start_sync(self): + to_sync_items = self.search([('synched', '=', False)]) + for to_sync_item in to_sync_items: + partner = to_sync_item.partner_id + + params = { + 'partner_gid': partner.partner_gid, + } + + if partner.vat and partner._is_vat_syncable(partner.vat): + params['vat'] = partner.vat + result, error = partner._rpc_remote_api('update', params) + if error: + _logger.error('Send Partner to sync failed: %s' % str(error)) + + to_sync_item.write({'synched': True}) + + def add_to_queue(self, partner_id): + to_sync = self.search([('partner_id', '=', partner_id)]) + if not to_sync: + to_sync = self.create({'partner_id': partner_id}) + return to_sync |
