-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BAH-3521 | Refactor address mapping customisation (#58)
* BAH-3521 | Add .idea to gitignore * BAH-3521 | Add new module bahmni_address_mapping * BAH-3521 | Move address models and views to bahmni_address_mapping module * BAH-3521 | Add schema for address attributes mapping * BAH-3521 | Move address mapping to separate service * BAH-3521 | Move custom address field extension to bahmni_address module * BAH-3521 | Create a sub menu for address configurations under Bahmni Masters * BAH-3521 | Add. Default address field mappings * BAH-3521 | Add. Bahmni address mapping module to Dockerfile * BAH-3521 | Add. Install bahmni address mapping module when container boots up * BAH-3521 | Add. Readme for address module * BAH-3521 | Add. Default mappings for all 8 address fields
- Loading branch information
Showing
23 changed files
with
601 additions
and
277 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
*.swo | ||
*.swp | ||
*.swn | ||
.idea/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Bahmni Address Mapping Module | ||
This module specifically deals with the custom address fields that are required for syncing address information from Bahmni Patient to Odoo 16 Customer. | ||
|
||
### Customisations introduced by this module: | ||
1. **Address Mapping Configuration**: This module introduces a new model `address.mapping.table` which is used to map the Bahmni address fields to Odoo address fields. This model is used to store the mapping configuration for each address field. | ||
2. **Address Mapping Service**: This serves as a mapper service that maps the Bahmni address fields to Odoo address fields based on the configuration stored in the `address.mapping.table` model. This is invoked from the api_event_worker class when customer creation or updation happens. | ||
3. **Views for address fields**: The customer create view is customised to include the custom address fields that are intorduced into the Customer creation page of Odoo. | ||
4. **Extending res.partner model of Odoo**: The res.partner model of Odoo is extended to include the custom address fields that are introduced by this module. | ||
5. **Hiding default City field**: The default city field of Odoo is hidden from the customer creation page as it is a text field and a new field village with references to higher address fields is introduced by this module. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
'name': 'Bahmni Address Mapping', | ||
'version': '1.0', | ||
'summary': 'Module to add additional fields to the address model', | ||
'sequence': 1, | ||
'description': """ | ||
Bahmni Address Mapping | ||
==================== | ||
""", | ||
'category': 'Services', | ||
'website': '', | ||
'images': [], | ||
'depends': [], | ||
'data': ['security/ir.model.access.csv', | ||
'data/data.xml', | ||
'views/res_partner_address_extension.xml', | ||
'views/address_mapping_table_view.xml'], | ||
'demo': [], | ||
'qweb': [], | ||
'license': 'LGPL-3', | ||
'installable': True, | ||
'application': True, | ||
'auto_install': False, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<odoo> | ||
<data noupdate="1"> | ||
<record id="country_mapping" model="address.mapping.table"> | ||
<field name="openmrs_address_field">country</field> | ||
<field name="odoo_address_field">country</field> | ||
</record> | ||
<record id="state_mapping" model="address.mapping.table"> | ||
<field name="openmrs_address_field">stateProvince</field> | ||
<field name="odoo_address_field">state</field> | ||
</record> | ||
<record id="district_mapping" model="address.mapping.table"> | ||
<field name="openmrs_address_field">countyDistrict</field> | ||
<field name="odoo_address_field">district</field> | ||
</record> | ||
<record id="subdistrict_mapping" model="address.mapping.table"> | ||
<field name="openmrs_address_field">address3</field> | ||
<field name="odoo_address_field">subdistrict</field> | ||
</record> | ||
<record id="cityVillage_mapping" model="address.mapping.table"> | ||
<field name="openmrs_address_field">cityVillage</field> | ||
<field name="odoo_address_field">village</field> | ||
</record> | ||
<record id="street2_mapping" model="address.mapping.table"> | ||
<field name="openmrs_address_field">address2</field> | ||
<field name="odoo_address_field">street2</field> | ||
</record> | ||
<record id="street_mapping" model="address.mapping.table"> | ||
<field name="openmrs_address_field">address1</field> | ||
<field name="odoo_address_field">street</field> | ||
</record> | ||
<record id="postalcode_mapping" model="address.mapping.table"> | ||
<field name="openmrs_address_field">postalCode</field> | ||
<field name="odoo_address_field">zip</field> | ||
</record> | ||
</data> | ||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from . import address_master | ||
from . import res_partner | ||
from . import address_mapping_table | ||
from . import address_mapping_service |
265 changes: 265 additions & 0 deletions
265
bahmni_address_mapping/models/address_mapping_service.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
from odoo import fields, models, api | ||
import uuid | ||
import logging | ||
|
||
STATE_CODE_PREFIX = 'UNKNOWN-' | ||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
class AddressMappingService(models.Model): | ||
_name = 'address.mapping.service' | ||
_auto = False | ||
auto_create_customer_address_levels = True | ||
|
||
@api.model | ||
def _map_address_fields(self, address): | ||
mapped_result = {} | ||
address_field_for_country = self._get_openmrs_address_field('country') | ||
address_field_for_state = self._get_openmrs_address_field('state') | ||
address_field_for_district = self._get_openmrs_address_field('district') | ||
address_field_for_subdistrict = self._get_openmrs_address_field('subdistrict') | ||
address_field_for_village = self._get_openmrs_address_field('village') | ||
address_field_for_zip = self._get_openmrs_address_field('zip') | ||
address_field_for_street = self._get_openmrs_address_field('street') | ||
address_field_for_street2 = self._get_openmrs_address_field('street2') | ||
|
||
# Mapping plain text field address fields | ||
if address_field_for_zip and address.get(address_field_for_zip): | ||
mapped_result.update({'zip': address.get(address_field_for_zip, '')}) | ||
if address_field_for_street and address.get(address_field_for_street): | ||
mapped_result.update({'street': address.get(address_field_for_street, '')}) | ||
if address_field_for_street2 and address.get(address_field_for_street2): | ||
mapped_result.update({'street2': address.get(address_field_for_street2, '')}) | ||
|
||
# Mapping hierarchical address fields | ||
country = None | ||
state = None | ||
district = None | ||
subdistrict = None | ||
village = None | ||
if address_field_for_country: | ||
country_name = address.get(address_field_for_country) | ||
if country_name: | ||
country = self._find_country(country_name) | ||
if country: | ||
mapped_result.update({'country_id': country.id}) | ||
else: | ||
_logger.warning( | ||
'Country %s not found in the Odoo database', country_name) | ||
else: | ||
_logger.warning('Country value not passed in address with field %s', address_field_for_country) | ||
|
||
if address_field_for_state: | ||
state_province_name = address.get(address_field_for_state) | ||
if state_province_name: | ||
state = self._find_or_create_state(country, state_province_name, | ||
self.auto_create_customer_address_levels) | ||
if state: | ||
mapped_result.update({'state_id': state.id}) | ||
if not country: | ||
_logger.info("Mapping country from state to customer address") | ||
country = state.country_id | ||
mapped_result.update({'country_id': state.country_id.id}) | ||
else: | ||
_logger.warning('State %s not found/created in the Odoo database', state_province_name) | ||
else: | ||
_logger.warning('State value not passed in address with field %s', address_field_for_state) | ||
|
||
if address_field_for_district: | ||
district_name = address.get(address_field_for_district) | ||
if district_name: | ||
district = self._find_or_create_district(country, state, district_name, | ||
self.auto_create_customer_address_levels) | ||
if district: | ||
mapped_result.update({'district_id': district.id}) | ||
else: | ||
_logger.warning('District %s not found/created in the Odoo database', district_name) | ||
else: | ||
_logger.warning('District value not passed in address with field %s', address_field_for_district) | ||
|
||
if address_field_for_subdistrict: | ||
subdistrict_name = address.get(address_field_for_subdistrict) | ||
if subdistrict_name: | ||
subdistrict = self._find_or_create_subdistrict(country, state, district, subdistrict_name, | ||
self.auto_create_customer_address_levels) | ||
if subdistrict: | ||
mapped_result.update({'subdistrict_id': subdistrict.id}) | ||
else: | ||
_logger.warning('Subdistrict %s not found/created in the Odoo database', subdistrict_name) | ||
else: | ||
_logger.warning('Subdistrict value not passed in address with field %s', address_field_for_subdistrict) | ||
|
||
if address_field_for_village: | ||
village_name = address.get(address_field_for_village) | ||
if village_name: | ||
village = self._find_or_create_village(country, state, district, subdistrict, village_name, | ||
self.auto_create_customer_address_levels) | ||
if village: | ||
mapped_result.update({'village_id': village.id}) | ||
else: | ||
_logger.warning('Village %s not found/created in the Odoo database', village_name) | ||
else: | ||
_logger.warning('Village value not passed in address with field %s', address_field_for_village) | ||
|
||
mapped_result = self.clean_up_address(mapped_result) | ||
_logger.info('Input address: %s', str(address)) | ||
_logger.info('Mapped address: %s', str(mapped_result)) | ||
return mapped_result | ||
|
||
def _get_openmrs_address_field(self, odoo_field_name): | ||
return self.env['address.mapping.table'].search([('odoo_address_field', '=', odoo_field_name)], | ||
limit=1).openmrs_address_field | ||
|
||
def clean_up_address(self, address): | ||
return {key: value for key, value in address.items() if value} | ||
|
||
@api.model | ||
def _find_country(self, country_name): | ||
return self.env['res.country'].search([('name', '=ilike', country_name)], limit=1) | ||
|
||
@api.model | ||
def _find_or_create_state(self, country, state_province_name, auto_create_customer_address_levels): | ||
|
||
if country: | ||
_logger.info('Finding state entry for %s for country %s', state_province_name, country.name) | ||
state = self.env['res.country.state'].search([('name', '=ilike', state_province_name), | ||
('country_id', '=', country.id)], limit=1) | ||
if state: | ||
_logger.info('State %s found in the Odoo database', state_province_name) | ||
return state | ||
|
||
_logger.info('Country not mapped. Searching with only state name %s', state_province_name) | ||
state = self.env['res.country.state'].search([('name', '=ilike', state_province_name)], limit=1) | ||
if state: | ||
_logger.info('State %s found in the Odoo database', state_province_name) | ||
return state | ||
|
||
if country and auto_create_customer_address_levels: | ||
_logger.info("Creating new state %s for country %s", state_province_name, country.name) | ||
state_code = STATE_CODE_PREFIX + str(uuid.uuid4()) | ||
state = self.env['res.country.state'].create({'name': state_province_name, | ||
'code': state_code, | ||
'country_id': country.id}) | ||
return state | ||
else: | ||
_logger.info( | ||
"Country is not defined or address auto creation is disabled. Returning None for State mapping") | ||
return None | ||
|
||
@api.model | ||
def _find_or_create_district(self, country, state, district_name, auto_create_customer_address_levels): | ||
|
||
_logger.info('Finding district entry for %s with state %s, country %s', | ||
district_name, | ||
state.name if state else 'None', | ||
country.name if country else 'None') | ||
districts = self.env['state.district'].search([('name', '=ilike', district_name), | ||
('state_id', '=', state.id if state else None), | ||
('country_id', '=', country.id if country else None)]) | ||
|
||
if districts: | ||
if len(districts) == 1: | ||
_logger.info('Unique District %s found in the Odoo database', district_name) | ||
return districts[0] | ||
else: | ||
_logger.warning( | ||
'Multiple districts found with name %s. Returning None for district mapping', district_name) | ||
return None | ||
|
||
if auto_create_customer_address_levels: | ||
_logger.info('Cresting new district entry for %s with state %s, country %s', | ||
district_name, | ||
state.name if state else 'None', | ||
country.name if country else 'None') | ||
district = self.env['state.district'].create({'name': district_name, | ||
'state_id': state.id if state else None, | ||
'country_id': country.id if country else None}) | ||
return district | ||
else: | ||
_logger.info("Address auto creation is disabled. Returning None for District mapping") | ||
return None | ||
|
||
@api.model | ||
def _find_or_create_subdistrict(self, country, state, district, subdistrict_name, | ||
auto_create_customer_address_levels): | ||
_logger.info('Finding subdistrict entry for %s with district %s, state %s, country %s', | ||
subdistrict_name, | ||
district.name if district else 'None', | ||
state.name if state else 'None', | ||
country.name if country else 'None') | ||
subdistricts = self.env['district.subdistrict'].search([('name', '=ilike', subdistrict_name), | ||
( | ||
'district_id', '=', | ||
district.id if district else False), | ||
('state_id', '=', state.id if state else False), | ||
('country_id', '=', | ||
country.id if country else False)]) | ||
if subdistricts: | ||
if len(subdistricts) == 1: | ||
_logger.info('Unique Subdistrict %s found in the Odoo database', subdistrict_name) | ||
return subdistricts[0] | ||
else: | ||
_logger.warning( | ||
'Multiple subdistricts entry for %s with district %s, state %s, country %s', subdistrict_name, | ||
district.name, state.name, country.name) | ||
return None | ||
|
||
if auto_create_customer_address_levels: | ||
_logger.info('Creating new subdistrict entry for %s with district %s, state %s, country %s', | ||
subdistrict_name, | ||
district.name if district else 'None', | ||
state.name if state else 'None', | ||
country.name if country else 'None') | ||
subdistrict = self.env['district.subdistrict'].create({'name': subdistrict_name, | ||
'district_id': district.id if district else None, | ||
'state_id': state.id if state else None, | ||
'country_id': country.id if country else None}) | ||
return subdistrict | ||
else: | ||
_logger.info("Address auto creation is disabled. Returning None for Subdistrict mapping") | ||
return None | ||
|
||
@api.model | ||
def _find_or_create_village(self, country, state, district, subdistrict, village_name, | ||
auto_create_customer_address_levels): | ||
_logger.info( | ||
'Finding village entry for %s with subdistrict %s, district %s, state %s, country %s', | ||
village_name, | ||
subdistrict.name if subdistrict else 'None', | ||
district.name if district else 'None', | ||
state.name if state else 'None', | ||
country.name if country else 'None') | ||
|
||
villages = self.env['village.village'].search( | ||
[('name', '=ilike', village_name), | ||
('subdistrict_id', '=', subdistrict.id if subdistrict else False), | ||
('district_id', '=', district.id if district else False), | ||
('state_id', '=', state.id if state else False), | ||
('country_id', '=', country.id if country else False)]) | ||
if villages: | ||
if len(villages) == 1: | ||
_logger.info('Unique Village %s found in the Odoo database', village_name) | ||
return villages[0] | ||
else: | ||
_logger.warning( | ||
'Multiple village entry for %s with subdistrict %s, district %s, state %s, country %s', | ||
village_name, | ||
subdistrict.name, district.name, state.name, country.name) | ||
return None | ||
if auto_create_customer_address_levels: | ||
_logger.info( | ||
'Creating new village entry for %s with subdistrict %s, district %s, state %s, country %s', | ||
village_name, | ||
subdistrict.name if subdistrict else 'None', | ||
district.name if district else 'None', | ||
state.name if state else 'None', | ||
country.name if country else 'None') | ||
village = self.env['village.village'].create({'name': village_name, | ||
'subdistrict_id': subdistrict.id if subdistrict else None, | ||
'district_id': district.id if district else None, | ||
'state_id': state.id if state else None, | ||
'country_id': country.id if country else None}) | ||
return village | ||
else: | ||
_logger.info("Address auto creation is disabled. Returning None for Village mapping") | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class AddressMappingTable(models.Model): | ||
_name = 'address.mapping.table' | ||
|
||
openmrs_address_field = fields.Char(string="OpenMRS Address Field") | ||
odoo_address_field = fields.Selection([('country', 'Country'), | ||
('state', 'State'), | ||
('district', 'District'), | ||
('subdistrict', 'Sub-District'), | ||
('village', 'Village'), | ||
('street', 'Street'), | ||
('street2', 'Street 2'), | ||
('zip', 'Zip') | ||
], string="Odoo Address Field") |
Oops, something went wrong.