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
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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
This module is maintained by the OCA.
-

+
+
+
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