From e49e5059c199bf40a49a14b2a6260132bc61f0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Lodeiros?= Date: Sat, 4 May 2024 15:24:35 +0200 Subject: [PATCH] [ADD] nominatim address calls --- pms_ocr_klippa/README.rst | 2 +- pms_ocr_klippa/__manifest__.py | 1 + pms_ocr_klippa/models/pms_property.py | 180 ++++++++++++++----- pms_ocr_klippa/static/description/index.html | 13 +- requirements.txt | 2 + 5 files changed, 151 insertions(+), 47 deletions(-) diff --git a/pms_ocr_klippa/README.rst b/pms_ocr_klippa/README.rst index 91e4fd4f3..aa976009d 100644 --- a/pms_ocr_klippa/README.rst +++ b/pms_ocr_klippa/README.rst @@ -7,7 +7,7 @@ OCR Klippa !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:0de4876412fe017db56d0ae207c4aa1f4f01394f57851ae281b5d94f5fc20c5f + !! source digest: sha256:2b9fc0252a9368c795df1d59126287bd5538d1a23498d99bfb72658e7a2a6eff !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/pms_ocr_klippa/__manifest__.py b/pms_ocr_klippa/__manifest__.py index 58d22ddd1..f28b501b7 100644 --- a/pms_ocr_klippa/__manifest__.py +++ b/pms_ocr_klippa/__manifest__.py @@ -12,6 +12,7 @@ "depends": [ "pms_api_rest", ], + "external_dependencies": {"python": ["thefuzz", "geopy"]}, "data": [ "data/pms_ocr_klippa_data.xml", "views/res_partner_id_category_views.xml", diff --git a/pms_ocr_klippa/models/pms_property.py b/pms_ocr_klippa/models/pms_property.py index c1b9503db..5a16788ee 100644 --- a/pms_ocr_klippa/models/pms_property.py +++ b/pms_ocr_klippa/models/pms_property.py @@ -1,11 +1,16 @@ +import logging from datetime import date, datetime import requests from dateutil.relativedelta import relativedelta +from geopy.geocoders import Nominatim +from thefuzz import process from odoo import _, fields, models from odoo.exceptions import ValidationError +_logger = logging.getLogger(__name__) + class PmsProperty(models.Model): _inherit = "pms.property" @@ -21,7 +26,7 @@ class PmsProperty(models.Model): self.env["ir.config_parameter"].sudo().get_param("ocr_klippa_api_key") ) document = [] - if image_base_64_back: + if image_base_64_front: document.append(image_base_64_front) if image_base_64_back: document.append(image_base_64_back) @@ -54,30 +59,7 @@ class PmsProperty(models.Model): continue # Residence Address -------------------------------------------------- if key == "address" and value: - if "street_name" in value: - mapped_data["residence_street"] = value["street_name"] + ( - " " + value["house_number"] if "house_number" in value else "" - ) - if "city" in value: - mapped_data["residence_city"] = value["city"] - if "postcode" in value: - mapped_data["zip"] = value["postcode"] - if "province" in value: - mapped_data["residence_state_id"] = ( - self.env["res.country.state"] - .search( - [ - ("name", "ilike", value["province"]), - ( - "country_id", - "=", - self._get_country_id(value.get("country", False)), - ), - ] - ) - .id - or False - ) + mapped_data = self._complete_residence_address(value, mapped_data) # Document Data -------------------------------------------------- elif key == "issuing_country" and value: @@ -87,7 +69,9 @@ class PmsProperty(models.Model): elif key == "document_type" and value: mapped_data["document_type"] = self._get_document_type( klippa_type=value, - klippa_subtype=document_data.get("document_subtype", False), + country_id=self._get_country_id( + document_data.get("document_country_id", False) + ), ) elif key == "personal_number" and value: mapped_data["document_number"] = value @@ -100,10 +84,12 @@ class PmsProperty(models.Model): and value and not document_data.get("date_of_issue", False) ): - mapped_data["document_expiration_date"] = self._calc_expedition_date( + mapped_data["document_expedition_date"] = self._calc_expedition_date( document_class_code=self._get_document_type( klippa_type=document_data.get("document_class_code", False), - klippa_subtype=document_data.get("document_subtype", False), + country_id=self._get_country_id( + document_data.get("document_country_id", False) + ), ), date_of_expiry=value, age=False, @@ -135,29 +121,36 @@ class PmsProperty(models.Model): mapped_data["nationality"] = self._get_country_id(value) return mapped_data - def _calc_expedition_date(self, document_type, date_of_expiry, age, date_of_birth): + def _calc_expedition_date( + self, document_class_code, date_of_expiry, age, date_of_birth + ): result = False person_age = False - if age and age.value != "": - person_age = int(age.value) - elif date_of_birth and date_of_birth.value != "": + if age: + person_age = age + elif date_of_birth and date_of_birth.get("value") != "": date_of_birth = datetime.strptime( - date_of_birth.value.replace("-", "/"), "%Y-%m-%dT%H:%M:%S" + date_of_birth.get("value"), "%Y-%m-%dT%H:%M:%S" ).date() person_age = relativedelta(date.today(), date_of_birth).years - if date_of_expiry and date_of_expiry.value != "" and person_age: + if date_of_expiry and date_of_expiry != "" and person_age: date_of_expiry = datetime.strptime( - date_of_expiry.value.replace("-", "/"), "%Y-%m-%dT%H:%M:%S" + date_of_expiry, "%Y-%m-%dT%H:%M:%S" ).date() if person_age < 30: result = date_of_expiry - relativedelta(years=5) - elif person_age >= 30 and document_type and document_type.code == "P": + elif ( + person_age >= 30 + and document_class_code + and document_class_code.code == "P" + ): result = date_of_expiry - relativedelta(years=10) elif 30 <= person_age < 70: result = date_of_expiry - relativedelta(years=10) - return result.isoformat() if result else False + return result if result else False - def _get_document_type(self, klippa_type, klippa_subtype): + def _get_document_type(self, klippa_type, country_id): + document_type_id = False document_type_ids = self.env["res.partner.id_category"].search( [ ("klippa_code", "=", klippa_type), @@ -165,11 +158,15 @@ class PmsProperty(models.Model): ) if not document_type_ids: raise ValidationError(_(f"Document type not found: {klippa_type}")) - document_type_id = document_type_ids[0] + if len(document_type_ids) > 1: document_type_id = document_type_ids.filtered( - lambda r: r.klippa_subtype_code == klippa_subtype - ).id + lambda r: country_id in r.country_ids.ids + ) + if not document_type_id: + document_type_id = document_type_ids.filtered(lambda r: not r.country_ids)[ + 0 + ] return document_type_id def _get_country_id(self, country_code): @@ -185,3 +182,104 @@ class PmsProperty(models.Model): return origin_surname.split(" ") else: return [origin_surname, ""] + + def _complete_residence_address(self, value, mapped_data): + """ + This method tries to complete the residence address with the given data, + first we use the thefuzz library looking for acceptable matches + in the province and/or country name. + Once these data are completed, if the residence address has not been completed + we try to use the geopy library to complete the address with the data + """ + street_name = False + if "street_name" in value: + mapped_data["residence_street"] = value["street_name"] + ( + " " + value["house_number"] if "house_number" in value else "" + ) + street_name = value["street_name"] + if "city" in value: + mapped_data["residence_city"] = value["city"] + if "province" in value: + country_record = self._get_country_id(value.get("country", False)) + domain = [] + if country_record: + domain.append(("country_id", "=", country_record)) + candidates = process.extractOne( + value["province"], + self.env["res.country.state"].search(domain).mapped("name"), + ) + if candidates[1] >= 90: + country_state = self.env["res.country.state"].search( + domain + [("name", "=", candidates[0])] + ) + mapped_data["residence_state_id"] = country_state.id + if not country_record: + mapped_data["country_id"] = country_state.country_id.id + else: + mapped_data["residence_state_id"] = None + if "country" in value and not mapped_data.get("country_id", False): + country_record = self._get_country_id(value["country"]) + mapped_data["country_id"] = country_record + if "postcode" in value: + mapped_data["zip"] = value["postcode"] + + address_data_dict = { + "zip": mapped_data.get("zip") or None, + "country_id": mapped_data.get("country_id") or None, + "countryState": mapped_data.get("country_state") or None, + "residence_city": mapped_data.get("residence_city") or None, + "residence_street": mapped_data.get("residence_street") or None, + } + # If we have one ore more values in address_data_dict, but not all, + # we try to complete the address + if any(address_data_dict.values()) and not all(address_data_dict.values()): + geolocator = Nominatim(user_agent="roomdoo_pms") + search_address_str = f"{street_name}, {mapped_data.get('residence_city', '')}, {mapped_data.get('zip', '')}, {mapped_data.get('country_id', '')}" + location = geolocator.geocode( + search_address_str, + addressdetails=True, + timeout=5, + language="en", + ) + if not location: + search_address_str = f"{mapped_data.get('residence_city', '')}, {mapped_data.get('zip', '')}, {mapped_data.get('country_id', '')}" + location = geolocator.geocode( + search_address_str, + addressdetails=True, + timeout=5, + language="en", + ) + if location: + if not mapped_data.get("zip", False): + mapped_data["zip"] = location.raw.get("address", {}).get( + "postcode", False + ) + if not mapped_data.get("country_id", False): + country_match_name = process.extractOne( + location.raw.get("address", {}).get("country", False), + self.env["res.country"].search([]).mapped("name"), + ) + if country_match_name[1] >= 90: + country_record = self.env["res.country"].search( + [("name", "=", country_match_name[0])] + ) + mapped_data["country_id"] = country_record.id + if not mapped_data.get("country_state", False): + country_state_record = process.extractOne( + location.raw.get("address", {}).get("province", False), + self.env["res.country.state"].search([]).mapped("name"), + ) + if country_state_record[1] >= 90: + country_state = self.env["res.country.state"].search( + [("name", "=", country_state_record[0])] + ) + mapped_data["country_state"] = country_state.id + if not mapped_data.get("residence_city", False): + mapped_data["residence_city"] = location.raw.get("address", {}).get( + "city", False + ) + if not mapped_data.get("residence_street", False): + mapped_data["residence_street"] = location.raw.get( + "address", {} + ).get("road", False) + return mapped_data diff --git a/pms_ocr_klippa/static/description/index.html b/pms_ocr_klippa/static/description/index.html index 959844b8b..aa53b2c3c 100644 --- a/pms_ocr_klippa/static/description/index.html +++ b/pms_ocr_klippa/static/description/index.html @@ -9,10 +9,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -275,7 +276,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code { margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -301,7 +302,7 @@ span.option { span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -367,7 +368,7 @@ ul.auto-toc { !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:0de4876412fe017db56d0ae207c4aa1f4f01394f57851ae281b5d94f5fc20c5f +!! source digest: sha256:2b9fc0252a9368c795df1d59126287bd5538d1a23498d99bfb72658e7a2a6eff !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/pms Translate me on Weblate Try me on Runboat

Module to connect the OCR Klippa with the pms

@@ -413,7 +414,9 @@ If you spotted it first, help us to smash it by providing a detailed and welcome

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

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.

diff --git a/requirements.txt b/requirements.txt index aa2e5d60a..fb1fad675 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,11 @@ # generated from manifests external_dependencies bs4 +geopy jose jwt marshmallow pycountry regula.documentreader.webclient simplejson +thefuzz xlrd