diff --git a/pms_api_rest/datamodels/__init__.py b/pms_api_rest/datamodels/__init__.py index df2ed820a..ce0f40d89 100644 --- a/pms_api_rest/datamodels/__init__.py +++ b/pms_api_rest/datamodels/__init__.py @@ -65,3 +65,4 @@ from . import feed_post from . import pms_wizard_state +from . import pms_ocr diff --git a/pms_api_rest/datamodels/pms_ocr.py b/pms_api_rest/datamodels/pms_ocr.py new file mode 100644 index 000000000..205e6dbb2 --- /dev/null +++ b/pms_api_rest/datamodels/pms_ocr.py @@ -0,0 +1,26 @@ +from marshmallow import fields + +from odoo.addons.datamodel.core import Datamodel + + +class PmsOcrInput(Datamodel): + _name = "pms.ocr.input" + imageBase64 = fields.String(required=True, allow_none=False) + + +class PmsOcrCheckinResult(Datamodel): + _name = "pms.ocr.checkin.result" + nationality = fields.Integer(required=False, allow_none=True) + countryId = fields.Integer(required=False, allow_none=True) + firstname = fields.String(required=False, allow_none=True) + lastname = fields.String(required=False, allow_none=True) + lastname2 = fields.String(required=False, allow_none=True) + gender = fields.String(required=False, allow_none=True) + birthdate = fields.String(required=False, allow_none=True) + documentType = fields.Integer(required=False, allow_none=True) + documentExpeditionDate = fields.String(required=False, allow_none=True) + documentSupportNumber = fields.String(required=False, allow_none=True) + documentNumber = fields.String(required=False, allow_none=True) + residenceStreet = fields.String(required=False, allow_none=True) + residenceCity = fields.String(required=False, allow_none=True) + countryState = fields.Integer(required=False, allow_none=True) diff --git a/pms_ocr_regula/README.rst b/pms_ocr_regula/README.rst new file mode 100644 index 000000000..7f2338b2e --- /dev/null +++ b/pms_ocr_regula/README.rst @@ -0,0 +1,81 @@ +========== +OCR Regula +========== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:b34369f690039d9865de6496bc7fd3d815f16fb385b83e1e7d3db0e35ebabeb7 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpms-lightgray.png?logo=github + :target: https://github.com/OCA/pms/tree/14.0/pms_ocr_regula + :alt: OCA/pms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pms-14-0/pms-14-0-pms_ocr_regula + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/pms&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Module to connect the OCR regula with the pms + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Set api key regula and url parameters of the OCR service and activate the is_used_regula field in pms_property + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Commit [Sun] + +Contributors +~~~~~~~~~~~~ + +* Brais + +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/pms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pms_ocr_regula/__init__.py b/pms_ocr_regula/__init__.py new file mode 100644 index 000000000..c82439595 --- /dev/null +++ b/pms_ocr_regula/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import services +from . import datamodels diff --git a/pms_ocr_regula/__manifest__.py b/pms_ocr_regula/__manifest__.py new file mode 100644 index 000000000..6bf93f99e --- /dev/null +++ b/pms_ocr_regula/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2020-21 Jose Luis Algara (Alda Hotels ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "OCR Regula", + "version": "14.0.1.0.1", + "author": "Commit [Sun], Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": True, + "category": "Generic Modules/Property Management System", + "website": "https://github.com/OCA/pms", + "depends": [ + "pms_api_rest", + ], + "external_dependencies": { + "python": ["regula.documentreader.webclient", "marshmallow"], + }, + "data": ["views/pms_property_views.xml", "data/pms_ocr_regula_data.xml"], + "installable": True, +} diff --git a/pms_ocr_regula/data/pms_ocr_regula_data.xml b/pms_ocr_regula/data/pms_ocr_regula_data.xml new file mode 100644 index 000000000..a6a94753b --- /dev/null +++ b/pms_ocr_regula/data/pms_ocr_regula_data.xml @@ -0,0 +1,11 @@ + + + + api_key_regula + False + + + ocr_regula_url + False + + diff --git a/pms_ocr_regula/datamodels/__init__.py b/pms_ocr_regula/datamodels/__init__.py new file mode 100644 index 000000000..9216f6ffd --- /dev/null +++ b/pms_ocr_regula/datamodels/__init__.py @@ -0,0 +1 @@ +from . import pms_property diff --git a/pms_ocr_regula/datamodels/pms_property.py b/pms_ocr_regula/datamodels/pms_property.py new file mode 100644 index 000000000..15a988cb0 --- /dev/null +++ b/pms_ocr_regula/datamodels/pms_property.py @@ -0,0 +1,8 @@ +from marshmallow import fields + +from odoo.addons.datamodel.core import Datamodel + + +class PmsPropertyInfo(Datamodel): + _inherit = "pms.property.info" + isUsedRegula = fields.Boolean(required=False, allow_none=True) diff --git a/pms_ocr_regula/models/__init__.py b/pms_ocr_regula/models/__init__.py new file mode 100644 index 000000000..9216f6ffd --- /dev/null +++ b/pms_ocr_regula/models/__init__.py @@ -0,0 +1 @@ +from . import pms_property diff --git a/pms_ocr_regula/models/pms_property.py b/pms_ocr_regula/models/pms_property.py new file mode 100644 index 000000000..c5530fcae --- /dev/null +++ b/pms_ocr_regula/models/pms_property.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class PmsProperty(models.Model): + _inherit = "pms.property" + + is_used_regula = fields.Boolean( + string="Used regula", help="True if this property uses regula's OCR" + ) diff --git a/pms_ocr_regula/readme/CONTRIBUTORS.rst b/pms_ocr_regula/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..2401152c0 --- /dev/null +++ b/pms_ocr_regula/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Brais diff --git a/pms_ocr_regula/readme/DESCRIPTION.rst b/pms_ocr_regula/readme/DESCRIPTION.rst new file mode 100644 index 000000000..70adf77ab --- /dev/null +++ b/pms_ocr_regula/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Module to connect the OCR regula with the pms diff --git a/pms_ocr_regula/readme/USAGE.rst b/pms_ocr_regula/readme/USAGE.rst new file mode 100644 index 000000000..d74b8cd5d --- /dev/null +++ b/pms_ocr_regula/readme/USAGE.rst @@ -0,0 +1 @@ +Set api key regula and url parameters of the OCR service and activate the is_used_regula field in pms_property diff --git a/pms_ocr_regula/services/__init__.py b/pms_ocr_regula/services/__init__.py new file mode 100644 index 000000000..918dbaad5 --- /dev/null +++ b/pms_ocr_regula/services/__init__.py @@ -0,0 +1,2 @@ +from . import ocr_document_service +from . import pms_property_service diff --git a/pms_ocr_regula/services/ocr_document_service.py b/pms_ocr_regula/services/ocr_document_service.py new file mode 100644 index 000000000..a11d48813 --- /dev/null +++ b/pms_ocr_regula/services/ocr_document_service.py @@ -0,0 +1,356 @@ +from datetime import date, datetime + +from dateutil.relativedelta import relativedelta +from regula.documentreader.webclient import ( + DocumentReaderApi, + ProcessParams, + RecognitionRequest, + Result, + Scenario, + TextFieldType, +) + +from odoo.addons.base_rest import restapi +from odoo.addons.base_rest_datamodel.restapi import Datamodel +from odoo.addons.component.core import Component + + +class PmsOcr(Component): + _inherit = "base.rest.service" + _name = "ocr.document.service" + _usage = "ocr-document" + _collection = "pms.services" + + @restapi.method( + [ + ( + [ + "/", + ], + "POST", + ) + ], + input_param=Datamodel("pms.ocr.input"), + output_param=Datamodel("pms.ocr.checkin.result", is_list=False), + auth="jwt_api_pms", + ) + def process_ocr_document_regula(self, input_param): + PmsOcrCheckinResult = self.env.datamodels["pms.ocr.checkin.result"] + pms_ocr_checkin_result = PmsOcrCheckinResult() + ocr_regula_url = ( + self.env["ir.config_parameter"].sudo().get_param("ocr_regula_url") + ) + with DocumentReaderApi(host=ocr_regula_url) as api: + params = ProcessParams( + scenario=Scenario.FULL_PROCESS, + result_type_output=[ + Result.TEXT, + Result.STATUS, + Result.VISUAL_TEXT, + Result.DOCUMENT_TYPE, + ], + ) + request = RecognitionRequest( + process_params=params, images=[input_param.imageBase64] + ) + response = api.process(request) + if response.text and response.text.field_list: + # for elemento in response.text.field_list: + # print("campo: ", elemento.field_name) + # print("valor: ", elemento.value) + # print('-') + id_country_spain = ( + self.env["res.country"].search([("code", "=", "ES")]).id + ) + country_id = self.process_nationality( + response.text.get_field(TextFieldType.NATIONALITY), + response.text.get_field(TextFieldType.NATIONALITY_CODE), + response.text.get_field(TextFieldType.NATIONALITY_CODE_NUMERIC), + ) + firstname, lastname, lastname2 = self.process_name( + id_country_spain, + country_id, + response.text.get_field(TextFieldType.GIVEN_NAMES), + response.text.get_field(TextFieldType.FIRST_SURNAME), + response.text.get_field(TextFieldType.SECOND_SURNAME), + response.text.get_field(TextFieldType.SURNAME), + response.text.get_field(TextFieldType.SURNAME_AND_GIVEN_NAMES), + ) + if country_id: + pms_ocr_checkin_result.nationality = country_id + if firstname: + pms_ocr_checkin_result.firstname = firstname + if lastname: + pms_ocr_checkin_result.lastname = lastname + if lastname2: + pms_ocr_checkin_result.lastname2 = lastname2 + gender = response.text.get_field(TextFieldType.SEX) + if gender and gender.value != "": + pms_ocr_checkin_result.gender = ( + "male" + if gender.value == "M" + else "female" + if gender.value == "F" + else "other" + ) + date_of_birth = response.text.get_field(TextFieldType.DATE_OF_BIRTH) + if date_of_birth and date_of_birth.value != "": + pms_ocr_checkin_result.birthdate = ( + datetime.strptime( + date_of_birth.value.replace("-", "/"), "%Y/%m/%d" + ) + .date() + .isoformat() + ) + date_of_expiry = response.text.get_field(TextFieldType.DATE_OF_EXPIRY) + age = response.text.get_field(TextFieldType.AGE) + document_class_code = response.text.get_field( + TextFieldType.DOCUMENT_CLASS_CODE + ) + if ( + document_class_code + and document_class_code.value != "" + and document_class_code.value == "P" + ): + pms_ocr_checkin_result.documentType = ( + self.env["res.partner.id_category"] + .search([("code", "=", "P")]) + .id + ) + date_of_issue = response.text.get_field(TextFieldType.DATE_OF_ISSUE) + if country_id == id_country_spain and ( + not date_of_issue or date_of_issue.value == "" + ): + date_of_issue = self.calc_expedition_date( + document_class_code, + date_of_expiry, + age, + date_of_birth, + ) + pms_ocr_checkin_result.documentExpeditionDate = date_of_issue + elif date_of_issue and date_of_issue.value != "": + pms_ocr_checkin_result.documentExpeditionDate = ( + date_of_issue.value.replace("-", "/") + ) + support_number, document_number = self.proccess_document_number( + id_country_spain, + country_id, + document_class_code, + response.text.get_field(TextFieldType.DOCUMENT_NUMBER), + response.text.get_field(TextFieldType.PERSONAL_NUMBER), + ) + if support_number: + pms_ocr_checkin_result.documentSupportNumber = support_number + if document_number: + pms_ocr_checkin_result.documentNumber = document_number + address_street, address_city, address_area = self.process_address( + id_country_spain, + country_id, + response.text.get_field(TextFieldType.ADDRESS_STREET), + response.text.get_field(TextFieldType.ADDRESS_CITY), + response.text.get_field(TextFieldType.ADDRESS_AREA), + response.text.get_field(TextFieldType.ADDRESS), + ) + if address_street: + pms_ocr_checkin_result.residenceStreet = address_street + if address_city: + pms_ocr_checkin_result.residenceCity = address_city + if address_area: + pms_ocr_checkin_result.countryState = address_area + return pms_ocr_checkin_result + + def process_nationality( + self, nationality, nationality_code, nationality_code_numeric + ): + country_id = False + country = False + if nationality_code_numeric and nationality_code_numeric.value != "": + country = self.env["res.country"].search( + [("code_numeric", "=", nationality_code_numeric.value)] + ) + elif nationality_code and nationality_code.value != "": + country = self.env["res.country"].search( + [("code_alpha3", "=", nationality_code.value)] + ) + elif nationality and nationality.value != "": + country = self.env["res.country"].search([("name", "=", nationality.value)]) + + if country: + country_id = country.id + + return country_id + + def process_address( + self, + id_country_spain, + country_id, + address_street, + address_city, + address_area, + address, + ): + res_address_street = False + res_address_city = False + res_address_area = False + state = False + if country_id == id_country_spain: + if address_street and address_street.value != "": + res_address_street = address_street.value + if address_city and address_city.value != "": + res_address_city = address_city.value + if address_area and address_area.value != "": + res_address_area = address_area.value + if ( + address + and address != "" + and not (all([address_street, address_city, address_area])) + ): + address = address.value.replace("^", " ") + address_list = address.split(" ") + if not res_address_area: + res_address_area = address_list[-1] + if not res_address_city: + res_address_city = address_list[-2] + if not res_address_street: + res_address_street = address.replace( + res_address_area, "", 1 + ).replace(res_address_city, "", 1) + if res_address_area: + state = self.env["res.country.state"].search( + [("name", "ilike", res_address_area)] + ) + if state and len(state) == 1: + state = state.id + else: + if address and address.value != "": + res_address_street = address.value.replace("^", " ") + return res_address_street, res_address_city, state + + def process_name( + self, + id_country_spain, + country_id, + given_names, + first_surname, + second_surname, + surname, + surname_and_given_names, + ): + firstname = False + lastname = False + lastname2 = False + + if surname_and_given_names.value and surname_and_given_names.value != "": + surname_and_given_names = surname_and_given_names.value.replace("^", " ") + + if given_names and given_names.value != "": + firstname = given_names.value + + if first_surname and first_surname.value != "": + lastname = first_surname.value + + if second_surname and second_surname.value != "": + lastname2 = second_surname.value + + if country_id == id_country_spain and not ( + all([firstname, lastname, lastname2]) + ): + if surname and surname.value != "": + lastname = lastname if lastname else surname.value.split(" ")[0] + lastname2 = lastname2 if lastname2 else surname.value.split(" ")[1:][0] + if ( + surname_and_given_names + and surname_and_given_names != "" + and not firstname + ): + firstname = surname_and_given_names.replace( + lastname, "", 1 + ).replace(lastname2, "", 1) + elif surname_and_given_names and surname_and_given_names != "": + lastname = ( + lastname if lastname else surname_and_given_names.split(" ")[0] + ) + lastname2 = ( + lastname2 if lastname2 else surname_and_given_names.split(" ")[1] + ) + firstname = ( + firstname + if firstname + else surname_and_given_names.replace(lastname, "", 1).replace( + lastname2, "", 1 + ) + ) + elif ( + country_id + and country_id != id_country_spain + and not (all([firstname, lastname])) + ): + if surname and surname.value != "": + lastname = lastname if lastname else surname.value + if ( + surname_and_given_names + and surname_and_given_names != "" + and not firstname + ): + firstname = surname_and_given_names.replace(lastname, "", 1) + elif surname_and_given_names and surname_and_given_names != "": + lastname = ( + lastname if lastname else surname_and_given_names.split(" ")[0] + ) + firstname = ( + firstname + if firstname + else surname_and_given_names.replace(lastname, "", 1) + ) + return firstname, lastname, lastname2 + + 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 != "": + date_of_birth = datetime.strptime( + date_of_birth.value.replace("-", "/"), "%Y/%m/%d" + ).date() + person_age = relativedelta(date.today(), date_of_birth).years + if date_of_expiry and date_of_expiry.value != "" and person_age: + date_of_expiry = datetime.strptime( + date_of_expiry.value.replace("-", "/"), "%Y/%m/%d" + ).date() + if person_age < 30: + result = date_of_expiry - relativedelta(years=5) + elif ( + person_age >= 30 + and document_class_code + and document_class_code.value == "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 + + def proccess_document_number( + self, + id_country_spain, + country_id, + document_class_code, + document_number, + personal_number, + ): + res_support_number = False + res_document_number = False + if personal_number and personal_number.value != "": + res_document_number = personal_number.value + if document_number and document_number.value != "": + res_support_number = document_number.value + if ( + country_id == id_country_spain + and document_class_code + and document_class_code.value != "P" + ): + return res_support_number, res_document_number + else: + return False, res_support_number diff --git a/pms_ocr_regula/services/pms_property_service.py b/pms_ocr_regula/services/pms_property_service.py new file mode 100644 index 000000000..1e633dce8 --- /dev/null +++ b/pms_ocr_regula/services/pms_property_service.py @@ -0,0 +1,26 @@ +from odoo.addons.base_rest import restapi +from odoo.addons.base_rest_datamodel.restapi import Datamodel +from odoo.addons.component.core import Component + + +class PmsPropertyService(Component): + _inherit = "pms.property.service" + + @restapi.method( + [ + ( + [ + "/", + ], + "GET", + ) + ], + output_param=Datamodel("pms.property.info", is_list=True), + auth="jwt_api_pms", + ) + def get_properties(self): + result_properties = super(PmsPropertyService, self).get_properties() + for prop_info in result_properties: + pms_property = self.env["pms.property"].browse(prop_info.id) + prop_info.isUsedRegula = pms_property.is_used_regula + return result_properties diff --git a/pms_ocr_regula/static/description/index.html b/pms_ocr_regula/static/description/index.html new file mode 100644 index 000000000..77909fefb --- /dev/null +++ b/pms_ocr_regula/static/description/index.html @@ -0,0 +1,426 @@ + + + + + + +OCR Regula + + + +
+

OCR Regula

+ + +

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

+

Module to connect the OCR regula with the pms

+

Table of contents

+ +
+

Usage

+

Set api key regula and url parameters of the OCR service and activate the is_used_regula field in pms_property

+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Commit [Sun]
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

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

+

This module is part of the OCA/pms project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pms_ocr_regula/views/pms_property_views.xml b/pms_ocr_regula/views/pms_property_views.xml new file mode 100644 index 000000000..b7776c60b --- /dev/null +++ b/pms_ocr_regula/views/pms_property_views.xml @@ -0,0 +1,14 @@ + + + + pms.property + + + + + + + + + + diff --git a/requirements.txt b/requirements.txt index 093f58f35..aa2e5d60a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,6 @@ jose jwt marshmallow pycountry +regula.documentreader.webclient simplejson xlrd diff --git a/setup/pms_ocr_regula/odoo/addons/pms_ocr_regula b/setup/pms_ocr_regula/odoo/addons/pms_ocr_regula new file mode 120000 index 000000000..9dd4c37a0 --- /dev/null +++ b/setup/pms_ocr_regula/odoo/addons/pms_ocr_regula @@ -0,0 +1 @@ +../../../../pms_ocr_regula \ No newline at end of file diff --git a/setup/pms_ocr_regula/setup.py b/setup/pms_ocr_regula/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/pms_ocr_regula/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)