diff --git a/pos_partner_location_google_map/README.rst b/pos_partner_location_google_map/README.rst new file mode 100644 index 0000000000..ea616bc994 --- /dev/null +++ b/pos_partner_location_google_map/README.rst @@ -0,0 +1,87 @@ +=============================== +POS Partner Location Google Map +=============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:90461c6096bed16c091f202daefa26f0832cb9a0bab1d6608f1fd457aa3d3d25 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fpos-lightgray.png?logo=github + :target: https://github.com/OCA/pos/tree/16.0/pos_partner_location_google_map + :alt: OCA/pos +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pos-16-0/pos-16-0-pos_partner_location_google_map + :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/pos&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to select partner address directly on map. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +In POS open customer list, select a customer and click "Details". +On the customer form click on the "globe" icon and select a location on map. +Click "Save" to save the location. + +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 +~~~~~~~ + +* Cetmix + +Contributors +~~~~~~~~~~~~ + +* Cetmix + + * Ivan Sokolov + * Maksim Shurupov + + +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/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_partner_location_google_map/__init__.py b/pos_partner_location_google_map/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/pos_partner_location_google_map/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_partner_location_google_map/__manifest__.py b/pos_partner_location_google_map/__manifest__.py new file mode 100644 index 0000000000..c0dd5b6158 --- /dev/null +++ b/pos_partner_location_google_map/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "POS Partner Location Google Map", + "version": "16.0.1.0.0", + "category": "Point Of Sale", + "summary": "POS Partner Location Google Map", + "author": "Cetmix, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/pos", + "license": "AGPL-3", + "depends": ["pos_partner_location_abstract"], + "data": [], + "assets": { + "point_of_sale.assets": [ + "pos_partner_location_google_map/static/src/js/*.js", + ], + }, + "installable": True, +} diff --git a/pos_partner_location_google_map/models/__init__.py b/pos_partner_location_google_map/models/__init__.py new file mode 100644 index 0000000000..8df20b924a --- /dev/null +++ b/pos_partner_location_google_map/models/__init__.py @@ -0,0 +1,4 @@ +from . import address_google_struct +from . import base_geocoder +from . import pos_config +from . import res_partner diff --git a/pos_partner_location_google_map/models/address_google_struct.py b/pos_partner_location_google_map/models/address_google_struct.py new file mode 100644 index 0000000000..936aba5d7f --- /dev/null +++ b/pos_partner_location_google_map/models/address_google_struct.py @@ -0,0 +1,140 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.osv.expression import AND, OR + +from odoo.addons.pos_partner_location_abstract.models.address_struct import ( + AddressStruct, +) + + +class AddressGoogleStruct(AddressStruct): + ADDR_FIELDS = { + "number": ["street_number"], + "street": ["street_address", "route", "plus_code"], + "city": [ + "locality", + "sublocality", + "sublocality_level_1", + "sublocality_level_2", + "sublocality_level_3", + "sublocality_level_4", + ], + "state_id": [ + "administrative_area_level_1", + "administrative_area_level_2", + "administrative_area_level_3", + "administrative_area_level_4", + "administrative_area_level_5", + ], + "country_id": ["country"], + "zip": ["postal_code"], + } + SERVICE_URL = "https://maps.googleapis.com/maps/api/place/details/json" + + def __init__(self, odoo_env): + super(AddressGoogleStruct, self).__init__(odoo_env) + self.api_key = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("base_geolocalize.google_map_api_key", False) + ) + + def has_token(self): + """Checking exists Google API key in settings""" + return bool(self.api_key) + + def _get_fields_value(self, addr_key): + """ + Get fields value for preparing values address + :param str addr_key: field name + :return list: list of fields values + """ + return [self._result.get(key, False) for key in self.ADDR_FIELDS[addr_key]] + + @property + def street(self): + street_number = self._result.get("street_number") + result = [] + if street_number: + result.append(street_number) + for address in self._get_fields_value("street"): + if address: + result.append(address) + return " ".join(result) + return "" + + @property + def city(self): + for city in self._get_fields_value("city"): + if city: + return city + return False + + @property + def state_id(self): + domain = [] + codes = [] + for state in self._get_fields_value("state_id"): + if state: + domain = OR([domain, [("name", "like", state.get("name"))]]) + codes.append(state.get("code")) + if codes: + domain = OR([domain, [("code", "in", codes)]]) + if len(domain) == 0: + return False + country_id = self.country_id + if country_id: + domain = AND([domain, [("country_id", "=", country_id[0])]]) + state = self.env["res.country.state"].search(domain, limit=1) + return state.name_get()[0] if state else False + + @property + def country_id(self): + country_item = self._result.get("country") + if not country_item: + return False + country = self.env["res.country"].search( + [ + "|", + ("name", "like", country_item.get("name")), + ("code", "=", country_item.get("code")), + ] + ) + return country.name_get()[0] if country else False + + @property + def zip(self): + return self._result.get("postal_code") + + def query_addr(self, params, timeout=5): + params.update(key=self.api_key) + response = super(AddressGoogleStruct, self).query_addr(params, timeout=timeout) + if response: + if response.get("status") == "OK": + self._prepare_components_data(response["result"]["address_components"]) + return True + return False + + def _prepare_components_data(self, components): + """ + Preparing component values for class result + :param dict components: list of components + :return: None + :rtype: NoneType + """ + state_country_fields = [ + *self.ADDR_FIELDS.get("state_id", []), + *self.ADDR_FIELDS.get("country_id", []), + ] + for component in components: + for type_ in component["types"]: + if type_ in state_country_fields: + self._result[type_] = { + "code": component["short_name"], + "name": component["long_name"], + } + elif type_ in self.ADDR_FIELDS.get("street", []): + self._result[type_] = component["short_name"] + else: + self._result[type_] = component["long_name"] diff --git a/pos_partner_location_google_map/models/base_geocoder.py b/pos_partner_location_google_map/models/base_geocoder.py new file mode 100644 index 0000000000..2e42140eb5 --- /dev/null +++ b/pos_partner_location_google_map/models/base_geocoder.py @@ -0,0 +1,36 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, api, models + +from .address_google_struct import AddressGoogleStruct + +_logger = logging.getLogger(__name__) + + +class GeoProvider(models.AbstractModel): + _inherit = "base.geocoder" + + @api.model + def prepare_geo_address_googlemap(self, place_id): + """ + Prepare Address values by place id + :param str place_id: Google map place id + :return dict: address fields values + """ + google = AddressGoogleStruct(self.env) + if not google.has_token(): + raise models.UserError( + _( + "API key for GeoCoding (Places) required.\n" + "Visit https://developers.google.com/maps/documentation/geocoding/get-api-key " # noqa + "for more information." + ) + ) + status = google.query_addr({"place_id": place_id}) + if status: + return google.get_result() + _logger.warning("Google map place id is not found!") + return {} diff --git a/pos_partner_location_google_map/models/pos_config.py b/pos_partner_location_google_map/models/pos_config.py new file mode 100644 index 0000000000..922148cf64 --- /dev/null +++ b/pos_partner_location_google_map/models/pos_config.py @@ -0,0 +1,26 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class PosConfig(models.Model): + _inherit = "pos.config" + + googlemap_api_key = fields.Char(compute="_compute_geolocalize") + + @api.model + def _set_extended_data(self): + data = super(PosConfig, self)._set_extended_data() + ICPSudo = self.env["ir.config_parameter"].sudo() + data.update( + api_key=ICPSudo.get_param("base_geolocalize.google_map_api_key", False) + ) + return data + + def _set_pos_config_parameter(self, tech_name, ext_vals=None): + super(PosConfig, self)._set_pos_config_parameter(tech_name, ext_vals) + key = ext_vals.get("api_key", "") if tech_name == "googlemap" else "" + for config in self: + config.googlemap_api_key = key + return diff --git a/pos_partner_location_google_map/models/res_partner.py b/pos_partner_location_google_map/models/res_partner.py new file mode 100644 index 0000000000..6ba018c7e1 --- /dev/null +++ b/pos_partner_location_google_map/models/res_partner.py @@ -0,0 +1,28 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + @api.model + def _get_map_provider_tech_name(self): + """Get map provider technical name""" + ICPSudo = self.env["ir.config_parameter"].sudo() + geo_provider_obj = self.env["base.geo_provider"] + geo_provider_id = ICPSudo.get_param("base_geolocalize.geo_provider") + provider = geo_provider_obj.browse(int(geo_provider_id)) + return provider.tech_name + + def _compute_qr_code_url(self): + if self._get_map_provider_tech_name() != "googlemap": + return super()._compute_qr_code_url() + map_url = "https://maps.google.com/maps?q={},{}" + for rec in self: + rec.qr_code_url = ( + map_url.format(rec.partner_latitude, rec.partner_longitude) + if rec.partner_latitude and rec.partner_longitude + else "" + ) diff --git a/pos_partner_location_google_map/readme/CONFIGURATION.rst b/pos_partner_location_google_map/readme/CONFIGURATION.rst new file mode 100644 index 0000000000..85f8f88f73 --- /dev/null +++ b/pos_partner_location_google_map/readme/CONFIGURATION.rst @@ -0,0 +1,2 @@ +In General Settings -> Integrations enable the Geo Localisation checkbox. +Select provided and add API key. NB: only google maps are currently supported. diff --git a/pos_partner_location_google_map/readme/CONTRIBUTORS.rst b/pos_partner_location_google_map/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..8519ee3fa4 --- /dev/null +++ b/pos_partner_location_google_map/readme/CONTRIBUTORS.rst @@ -0,0 +1,5 @@ +* Cetmix + + * Ivan Sokolov + * Maksim Shurupov + diff --git a/pos_partner_location_google_map/readme/DESCRIPTION.rst b/pos_partner_location_google_map/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..a1a3ee0d59 --- /dev/null +++ b/pos_partner_location_google_map/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows to select partner address directly on map. diff --git a/pos_partner_location_google_map/readme/USAGE.rst b/pos_partner_location_google_map/readme/USAGE.rst new file mode 100644 index 0000000000..4e88eb8c07 --- /dev/null +++ b/pos_partner_location_google_map/readme/USAGE.rst @@ -0,0 +1,3 @@ +In POS open customer list, select a customer and click "Details". +On the customer form click on the "globe" icon and select a location on map. +Click "Save" to save the location. diff --git a/pos_partner_location_google_map/static/description/index.html b/pos_partner_location_google_map/static/description/index.html new file mode 100644 index 0000000000..3d2f70c052 --- /dev/null +++ b/pos_partner_location_google_map/static/description/index.html @@ -0,0 +1,432 @@ + + + + + + +POS Partner Location Google Map + + + +
+

POS Partner Location Google Map

+ + +

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

+

This module allows to select partner address directly on map.

+

Table of contents

+ +
+

Usage

+

In POS open customer list, select a customer and click “Details”. +On the customer form click on the “globe” icon and select a location on map. +Click “Save” to save the location.

+
+
+

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

+
    +
  • Cetmix
  • +
+
+
+

Contributors

+ +
+
+

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/pos project on GitHub.

+

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

+
+
+
+ + diff --git a/pos_partner_location_google_map/static/src/js/PartnerDetailsMapGoogleEdit.esm.js b/pos_partner_location_google_map/static/src/js/PartnerDetailsMapGoogleEdit.esm.js new file mode 100644 index 0000000000..f3dd3b9bb6 --- /dev/null +++ b/pos_partner_location_google_map/static/src/js/PartnerDetailsMapGoogleEdit.esm.js @@ -0,0 +1,22 @@ +/** @odoo-module **/ + +import PartnerDetailsEdit from "point_of_sale.PartnerDetailsEdit"; +import Registries from "point_of_sale.Registries"; + +const PartnerDetailsMapGoogleEdit = (PartnerDetailsEdit) => + class PartnerDetailsMapGoogleEdit extends PartnerDetailsEdit { + get accessToMap() { + this.config = this.env.pos.config; + if ( + this.config.geolocalize_tech_name === "googlemap" && + this.config.googlemap_api_key + ) { + return true; + } + return super.accessToMap; + } + }; + +Registries.Component.extend(PartnerDetailsEdit, PartnerDetailsMapGoogleEdit); + +export default PartnerDetailsMapGoogleEdit; diff --git a/pos_partner_location_google_map/static/src/js/PartnerMapGoogleEdit.esm.js b/pos_partner_location_google_map/static/src/js/PartnerMapGoogleEdit.esm.js new file mode 100644 index 0000000000..1ec8ced71a --- /dev/null +++ b/pos_partner_location_google_map/static/src/js/PartnerMapGoogleEdit.esm.js @@ -0,0 +1,115 @@ +/** @odoo-module **/ + +import PartnerMapEdit from "pos_partner_location_abstract.PartnerMapEdit"; +import Registries from "point_of_sale.Registries"; +import {loadJS} from "@web/core/assets"; +import {onMounted, onWillStart} from "@odoo/owl"; + +/* eslint no-undef: "warn"*/ +const PartnerMapGoogleEdit = (PartnerMapEdit) => + class PartnerMapGoogleEdit extends PartnerMapEdit { + onHandleMap() { + if ( + this.config.geolocalize_tech_name === "googlemap" && + this.config.googlemap_api_key + ) { + this.provider = "googlemap"; + onWillStart(async () => + loadJS( + `https://maps.googleapis.com/maps/api/js?key=${this.config.googlemap_api_key}&libraries=places` + ) + ); + onMounted(() => this.googleMapConfigure()); + } else { + super.onHandleMap(); + } + } + + googleMapConfigure() { + // Default latLng + // Config + this.geocoder = new google.maps.Geocoder(); + const latLng = new google.maps.LatLng(this.lat, this.lng); + const mapOptions = { + zoom: 12, + center: latLng, + }; + // Show Map + this.map = new google.maps.Map(this.mapContainerRef.el, mapOptions); + + if (this.lat && this.lng) { + this.setAddressByLatLng(this.lat, this.lng); + } else { + this.setAddressByLocation(this.partner.contact_address); + } + + this.marker = new google.maps.Marker({ + position: latLng, + map: this.map, + draggable: true, + }); + + this.addrInput.el.value = this.partner.contact_address; + + this.map.addListener("click", (event) => { + const lat = event.latLng.lat(); + const lng = event.latLng.lng(); + this.updateMarker(lat, lng); + this.setAddressByLatLng(lat, lng); + }); + } + + setAddressByLatLng(lat, lng) { + if (lat && lng) { + const latLng = new google.maps.LatLng(lat, lng); + this.geocoder.geocode({location: latLng}, (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + this.getFormattedAddress(results[0].place_id).then(() => { + this.addrInput.el.value = results[0].formatted_address; + }); + } + }); + } + } + + updateMarker(lat, lng) { + super.updateMarker(lat, lng); + if (this.provider === "googlemap") { + const latLng = new google.maps.LatLng(lat, lng); + this.map.setCenter(latLng); + this.marker.setPosition(latLng); + google.maps.event.trigger(this.map, "resize"); + } + } + + setAddressByLocation(address) { + if (address && this.provider === "googlemap") { + this.geocoder.geocode({address: address}, (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + this.lat = results[0].geometry.location.lat(); + this.lng = results[0].geometry.location.lng(); + this.getFormattedAddress(results[0].place_id).then(() => { + this.addrInput.el.value = results[0].formatted_address; + this.updateMarker(this.lat, this.lng); + }); + } + }); + } else { + super.setAddressByLocation(address); + } + } + + getFormattedAddress(place_id) { + return this.rpc({ + model: "base.geocoder", + method: "prepare_geo_address_googlemap", + args: [place_id], + }).then((resp) => { + this.address = resp; + }); + } + }; + +Registries.Component.extend(PartnerMapEdit, PartnerMapGoogleEdit); + +export default PartnerMapGoogleEdit; diff --git a/pos_partner_location_google_map/tests/__init__.py b/pos_partner_location_google_map/tests/__init__.py new file mode 100644 index 0000000000..6a61418bb1 --- /dev/null +++ b/pos_partner_location_google_map/tests/__init__.py @@ -0,0 +1,4 @@ +from . import common +from . import test_address_google_struct +from . import test_base_geocoder +from . import test_res_partner diff --git a/pos_partner_location_google_map/tests/common.py b/pos_partner_location_google_map/tests/common.py new file mode 100644 index 0000000000..ea363026de --- /dev/null +++ b/pos_partner_location_google_map/tests/common.py @@ -0,0 +1,65 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +RESPONSE_MAP = { + "result": { + "address_components": [ + {"long_name": "277", "short_name": "277", "types": ["street_number"]}, + { + "long_name": "Bedford Avenue", + "short_name": "Bedford Ave", + "types": ["route"], + }, + { + "long_name": "Williamsburg", + "short_name": "Williamsburg", + "types": ["neighborhood", "political"], + }, + { + "long_name": "Brooklyn", + "short_name": "Brooklyn", + "types": ["sublocality", "political"], + }, + { + "long_name": "Kings", + "short_name": "Kings", + "types": ["administrative_area_level_2", "political"], + }, + { + "long_name": "New York", + "short_name": "NY", + "types": ["administrative_area_level_1", "political"], + }, + { + "long_name": "United States", + "short_name": "US", + "types": ["country", "political"], + }, + {"long_name": "11211", "short_name": "11211", "types": ["postal_code"]}, + ], + "formatted_address": "277 Bedford Avenue, Brooklyn, NY 11211, USA", + "geometry": { + "location": {"lat": 40.714232, "lng": -73.9612889}, + "location_type": "ROOFTOP", + "viewport": { + "northeast": {"lat": 40.7155809802915, "lng": -73.9599399197085}, + "southwest": {"lat": 40.7128830197085, "lng": -73.96263788029151}, + }, + }, + "place_id": "ChIJd8BlQ2BZwokRAFUEcm_qrcA", + "types": ["street_address"], + }, + "status": "OK", +} + +FORMATTED_RESULT = { + "street_number": "277", + "route": "Bedford Ave", + "neighborhood": "Williamsburg", + "political": "United States", + "sublocality": "Brooklyn", + "administrative_area_level_2": {"code": "Kings", "name": "Kings"}, + "administrative_area_level_1": {"code": "NY", "name": "New York"}, + "country": {"code": "US", "name": "United States"}, + "postal_code": "11211", +} diff --git a/pos_partner_location_google_map/tests/test_address_google_struct.py b/pos_partner_location_google_map/tests/test_address_google_struct.py new file mode 100644 index 0000000000..f5c464e539 --- /dev/null +++ b/pos_partner_location_google_map/tests/test_address_google_struct.py @@ -0,0 +1,107 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from unittest import mock + +from odoo.tests import TransactionCase + +from odoo.addons.pos_partner_location_google_map.models.address_google_struct import ( + AddressGoogleStruct, +) + +from .common import FORMATTED_RESULT, RESPONSE_MAP + + +class TestAddressGoogleStruct(TransactionCase): + def setUp(self): + super(TestAddressGoogleStruct, self).setUp() + self.env["ir.config_parameter"].set_param( + "base_geolocalize.google_map_api_key", "GoogleMapKey" + ) + self.state_ny = self.env["res.country.state"].search( + [("code", "=", "NY")], limit=1 + ) + self.country_us = self.env["res.country"].search([("code", "=", "US")], limit=1) + self.google_struct = AddressGoogleStruct(self.env) + + def test_init(self): + self.assertEqual(self.google_struct.api_key, "GoogleMapKey") + + def test_has_token(self): + self.env["ir.config_parameter"].set_param( + "base_geolocalize.google_map_api_key", "" + ) + google = AddressGoogleStruct(self.env) + self.assertFalse(google.has_token()) + self.env["ir.config_parameter"].set_param( + "base_geolocalize.google_map_api_key", "GoogleMapKey" + ) + google = AddressGoogleStruct(self.env) + self.assertTrue(google.has_token()) + + def test_get_fields_value(self): + result = self.google_struct._get_fields_value("number") + self.assertListEqual(result, [False]) + result = self.google_struct._get_fields_value("street") + self.assertListEqual(result, [False, False, False]) + + @mock.patch("requests.get") + def test_query_addr_valid(self, mock_get): + mock_response = mock.Mock(status_code=200) + mock_response.json.return_value = { + "result": {"address_components": []}, + "status": "OK", + } + mock_get.return_value = mock_response + response = self.google_struct.query_addr({}) + self.assertTrue(response) + + @mock.patch("requests.get") + def test_query_addr_invalid(self, mock_get): + mock_response = mock.Mock(status_code=200) + mock_response.json.return_value = { + "result": {"address_components": []}, + "status": "ERR", + } + mock_get.return_value = mock_response + response = self.google_struct.query_addr({}) + self.assertFalse(response) + + def test_street(self): + self.assertEqual(self.google_struct.street, "") + self.google_struct._result.update( + street_number="1", street_address="Test Address" + ) + self.assertEqual(self.google_struct.street, "1 Test Address") + + def test_city(self): + self.assertFalse(self.google_struct.city) + self.google_struct._result.update(sublocality="Tallin") + self.assertEqual(self.google_struct.city, "Tallin") + + def test_zip(self): + self.assertFalse(self.google_struct.zip) + self.google_struct._result.update(postal_code="524124") + self.assertEqual(self.google_struct.zip, "524124") + + def test_prepare_components_data(self): + self.google_struct._prepare_components_data( + RESPONSE_MAP["result"]["address_components"] + ) + self.assertDictEqual(self.google_struct._result, FORMATTED_RESULT) + + def test_country_id(self): + self.assertFalse(self.google_struct.country_id) + self.google_struct._result.update( + country={"code": "US", "name": "United States"} + ) + self.assertEqual(self.google_struct.country_id, self.country_us.name_get()[0]) + + def test_state_id(self): + self.assertFalse(self.google_struct.state_id) + self.google_struct._result.update( + country={"code": "US", "name": "United States"}, + administrative_area_level_2={"code": "Kings", "name": "Kings"}, + administrative_area_level_1={"code": "NY", "name": "New York"}, + ) + self.assertEqual(self.google_struct.state_id, self.state_ny.name_get()[0]) diff --git a/pos_partner_location_google_map/tests/test_base_geocoder.py b/pos_partner_location_google_map/tests/test_base_geocoder.py new file mode 100644 index 0000000000..10189eb144 --- /dev/null +++ b/pos_partner_location_google_map/tests/test_base_geocoder.py @@ -0,0 +1,40 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from unittest import mock + +from odoo.exceptions import UserError +from odoo.tests import TransactionCase + +from .common import RESPONSE_MAP + + +class TestBaseGeocoder(TransactionCase): + def test_prepare_geo_address_googlemap_invalid(self): + self.env["ir.config_parameter"].set_param( + "base_geolocalize.google_map_api_key", "" + ) + with self.assertRaises(UserError): + self.env["base.geocoder"].prepare_geo_address_googlemap("test_place") + + @mock.patch("requests.get") + def test_prepare_geo_address_googlemap_valid(self, mock_get): + self.env["ir.config_parameter"].set_param( + "base_geolocalize.google_map_api_key", "GoogleMapKey" + ) + self.state_ny = self.env["res.country.state"].search( + [("code", "=", "NY")], limit=1 + ) + self.country_us = self.env["res.country"].search([("code", "=", "US")], limit=1) + mock_response = mock.Mock(status_code=200) + mock_response.json.return_value = RESPONSE_MAP + mock_get.return_value = mock_response + response = self.env["base.geocoder"].prepare_geo_address_googlemap("test_place") + expected_value = { + "city": "Brooklyn", + "country_id": self.country_us.name_get()[0], + "state_id": self.state_ny.name_get()[0], + "street": "277 Bedford Ave", + "zip": "11211", + } + self.assertDictEqual(response, expected_value) diff --git a/pos_partner_location_google_map/tests/test_res_partner.py b/pos_partner_location_google_map/tests/test_res_partner.py new file mode 100644 index 0000000000..9019993b9c --- /dev/null +++ b/pos_partner_location_google_map/tests/test_res_partner.py @@ -0,0 +1,44 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import TransactionCase + +from .common import RESPONSE_MAP + + +class TestResPartner(TransactionCase): + def setUp(self): + super().setUp() + self.provider_google_map = self.env.ref( + "base_geolocalize.geoprovider_google_map" + ) + lat_long_struct = RESPONSE_MAP["result"]["geometry"]["location"] + self.partner = self.env["res.partner"].create( + { + "name": "Bob", + "partner_latitude": lat_long_struct["lat"], + "partner_longitude": lat_long_struct["lng"], + } + ) + self.map_url = "https://maps.google.com/maps?q={},{}" + + def test_compute_qr_code_url_provider(self): + """Test flow that computes qr code url by provider""" + # Without provider + self.env["ir.config_parameter"].set_param( + "base_geolocalize.geo_provider", False + ) + self.assertEqual(self.partner.qr_code_url, "", "QR URL must be empty") + + # Google provider + self.env["ir.config_parameter"].set_param( + "base_geolocalize.geo_provider", self.provider_google_map.id + ) + self.partner._compute_qr_code_url() + self.assertEqual( + self.partner.qr_code_url, + self.map_url.format( + self.partner.partner_latitude, self.partner.partner_longitude + ), + "QR URL must be the same", + ) diff --git a/setup/pos_partner_location_google_map/odoo/addons/pos_partner_location_google_map b/setup/pos_partner_location_google_map/odoo/addons/pos_partner_location_google_map new file mode 120000 index 0000000000..3ce50cfe7d --- /dev/null +++ b/setup/pos_partner_location_google_map/odoo/addons/pos_partner_location_google_map @@ -0,0 +1 @@ +../../../../pos_partner_location_google_map \ No newline at end of file diff --git a/setup/pos_partner_location_google_map/setup.py b/setup/pos_partner_location_google_map/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/pos_partner_location_google_map/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)