diff --git a/pos_partner_location_abstract/README.rst b/pos_partner_location_abstract/README.rst new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pos_partner_location_abstract/__init__.py b/pos_partner_location_abstract/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/pos_partner_location_abstract/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_partner_location_abstract/__manifest__.py b/pos_partner_location_abstract/__manifest__.py new file mode 100644 index 0000000000..2f160e0bec --- /dev/null +++ b/pos_partner_location_abstract/__manifest__.py @@ -0,0 +1,20 @@ +{ + "name": "POS Partner Location Abstract", + "version": "16.0.1.0.0", + "category": "Point Of Sale", + "summary": "POS Partner Location Abstract", + "author": "Cetmix, Odoo Community Association (OCA)", + "maintainers": ["geomer198", "CetmixGitDrone"], + "website": "https://github.com/OCA/pos", + "license": "AGPL-3", + "depends": ["base_geolocalize", "point_of_sale"], + "data": [], + "assets": { + "point_of_sale.assets": [ + "pos_partner_location_abstract/static/src/css/*.css", + "pos_partner_location_abstract/static/src/js/*.js", + "pos_partner_location_abstract/static/src/xml/*.xml", + ], + }, + "installable": True, +} diff --git a/pos_partner_location_abstract/models/__init__.py b/pos_partner_location_abstract/models/__init__.py new file mode 100644 index 0000000000..3c11d3737f --- /dev/null +++ b/pos_partner_location_abstract/models/__init__.py @@ -0,0 +1,4 @@ +from . import address_struct +from . import pos_config +from . import pos_session +from . import res_config_settings diff --git a/pos_partner_location_abstract/models/address_struct.py b/pos_partner_location_abstract/models/address_struct.py new file mode 100644 index 0000000000..3c7f4b3e2a --- /dev/null +++ b/pos_partner_location_abstract/models/address_struct.py @@ -0,0 +1,79 @@ +import requests + + +class AddressStruct(object): + ADDR_FIELDS = {} + ODOO_ADDR = [ + "street", + "city", + "state_id", + "country_id", + "zip", + ] + SERVICE_URL = None + + def __init__(self, odoo_env): + self._result = {} + self.env = odoo_env + self.status = None + + @property + def street(self): + """ + Street name + :rtype str | bool + """ + return False + + @property + def city(self): + """ + City name + :rtype: str | bool + """ + return False + + @property + def state_id(self): + """ + Fed. State + :rtype: record (res.country.state) | bool + """ + return False + + @property + def country_id(self): + """ + Country + :rtype: record (res.country) | bool + """ + return False + + @property + def zip(self): + """ + Zip code + :rtype: str | bool + """ + return False + + def get_result(self): + """ + Get result for updating contact address + :return:fields values + :rtype: dict + """ + return {item: getattr(self, item) for item in self.ODOO_ADDR} + + def query_addr(self, params, timeout=5): + """ + Query to service for get address result + :param dict params: query params + :param int timeout: request timeout + :return: json object + :rtype: dict | bool + """ + response = requests.get(self.SERVICE_URL, params, timeout=timeout) + if response.status_code == 200: + return response.json() + return False diff --git a/pos_partner_location_abstract/models/pos_config.py b/pos_partner_location_abstract/models/pos_config.py new file mode 100644 index 0000000000..efcf837013 --- /dev/null +++ b/pos_partner_location_abstract/models/pos_config.py @@ -0,0 +1,25 @@ +from odoo import api, fields, models + + +class PosConfig(models.Model): + _inherit = "pos.config" + + geolocalize_tech_name = fields.Char(compute="_compute_geolocalize") + + @api.model + def _set_extended_data(self): + return {} + + def _set_pos_config_parameter(self, tech_name, ext_vals=None): + _ = ext_vals or {} + for config in self: + config.geolocalize_tech_name = tech_name + + def _compute_geolocalize(self): + 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)) + tech_name = provider.tech_name + ext_vals = self._set_extended_data() + self._set_pos_config_parameter(tech_name, ext_vals) diff --git a/pos_partner_location_abstract/models/pos_session.py b/pos_partner_location_abstract/models/pos_session.py new file mode 100644 index 0000000000..e99987dbc7 --- /dev/null +++ b/pos_partner_location_abstract/models/pos_session.py @@ -0,0 +1,15 @@ +from odoo import models + + +class PosSession(models.Model): + _inherit = "pos.session" + + def _loader_params_res_partner(self): + res = super(PosSession, self)._loader_params_res_partner() + # Add addresses fields + res["search_params"]["fields"] += [ + "contact_address", + "partner_latitude", + "partner_longitude", + ] + return res diff --git a/pos_partner_location_abstract/models/res_config_settings.py b/pos_partner_location_abstract/models/res_config_settings.py new file mode 100644 index 0000000000..fcb173e2a0 --- /dev/null +++ b/pos_partner_location_abstract/models/res_config_settings.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + auto_create_map_data = fields.Boolean( + config_parameter="pos_partner_location_abstract.auto_create_map_data", + string="Auto Create Regions", + ) diff --git a/pos_partner_location_abstract/readme/CONTRIBUTORS.rst b/pos_partner_location_abstract/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..9d697ca0a3 --- /dev/null +++ b/pos_partner_location_abstract/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Cetmix diff --git a/pos_partner_location_abstract/readme/DESCRIPTION.rst b/pos_partner_location_abstract/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..6746ae44a7 --- /dev/null +++ b/pos_partner_location_abstract/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module is base for module pos_partner_location_google_map (Google Map). diff --git a/pos_partner_location_abstract/readme/ROADMAP.rst b/pos_partner_location_abstract/readme/ROADMAP.rst new file mode 100644 index 0000000000..a9490b44ab --- /dev/null +++ b/pos_partner_location_abstract/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +As for the release date module `pos_partner_location_google_map` supports only Google Maps API. + +Contribute to this module by adding other provider such as OpenStreetMap. diff --git a/pos_partner_location_abstract/static/src/css/map_popup.css b/pos_partner_location_abstract/static/src/css/map_popup.css new file mode 100644 index 0000000000..0fc13163f4 --- /dev/null +++ b/pos_partner_location_abstract/static/src/css/map_popup.css @@ -0,0 +1,17 @@ +.partner-map-popup { + max-width: 90% !important; + height: 90% !important; +} + +.partner-map-body { + height: 80% !important; + padding: 0.5rem; +} + +.map-addr-input { + margin-top: 10px; + margin-left: 10px; + margin-right: 10px; + height: 40px !important; + width: 100% !important; +} diff --git a/pos_partner_location_abstract/static/src/js/PartnerDetailsMapEdit.esm.js b/pos_partner_location_abstract/static/src/js/PartnerDetailsMapEdit.esm.js new file mode 100644 index 0000000000..82aac70694 --- /dev/null +++ b/pos_partner_location_abstract/static/src/js/PartnerDetailsMapEdit.esm.js @@ -0,0 +1,69 @@ +/** @odoo-module alias=pos_partner_location_abstract.PartnerDetailsMapEdit **/ + +import { + ConnectionAbortedError, + ConnectionLostError, +} from "@web/core/network/rpc_service"; +import PartnerDetailsEdit from "point_of_sale.PartnerDetailsEdit"; +import Registries from "point_of_sale.Registries"; +import {identifyError} from "point_of_sale.utils"; + +const PartnerDetailsMapEdit = (PartnerDetailsEdit) => + class PartnerDetailsMapEdit extends PartnerDetailsEdit { + get accessToMap() { + return false; + } + + async partnerMap() { + const {confirmed, payload} = await this.showPopup("PartnerMapEdit", { + partner: this.props.partner, + }); + if (confirmed) { + for (const [key, value] of Object.entries(payload)) { + this.props.partner[key] = value; + if (Array.isArray(value)) { + this.changes[key] = value[0]; + } else { + this.changes[key] = value; + } + } + this.render(this); + } + } + + async openMap() { + try { + if (this.accessToMap) { + await this.partnerMap(); + } else { + this.showPopup("ErrorPopup", { + title: this.env._t("Map Error"), + body: this.env._t("Cannot access map functions!"), + }); + } + } catch (e) { + if ( + identifyError(e) instanceof ConnectionLostError || + ConnectionAbortedError + ) { + this.showPopup("OfflineErrorPopup", { + title: this.env._t("Network Error"), + body: this.env._t( + "Cannot access product information screen if offline." + ), + }); + } else { + this.showPopup("ErrorPopup", { + title: this.env._t("Unknown error"), + body: this.env._t( + "An unknown error prevents us from loading product information." + ), + }); + } + } + } + }; + +Registries.Component.extend(PartnerDetailsEdit, PartnerDetailsMapEdit); + +export default PartnerDetailsMapEdit; diff --git a/pos_partner_location_abstract/static/src/js/PartnerMapEdit.esm.js b/pos_partner_location_abstract/static/src/js/PartnerMapEdit.esm.js new file mode 100644 index 0000000000..18fd983538 --- /dev/null +++ b/pos_partner_location_abstract/static/src/js/PartnerMapEdit.esm.js @@ -0,0 +1,50 @@ +/** @odoo-module alias=pos_partner_location_abstract.PartnerMapEdit **/ + +import AbstractAwaitablePopup from "point_of_sale.AbstractAwaitablePopup"; +import Registries from "point_of_sale.Registries"; +import {useRef} from "@odoo/owl"; + +class PartnerMapEdit extends AbstractAwaitablePopup { + setup() { + super.setup(); + this.partner = this.props.partner; + this.lat = parseFloat(this.partner.partner_latitude) || 0; + this.lng = parseFloat(this.partner.partner_longitude) || 0; + this.address = {}; + this.mapContainerRef = useRef("map-container"); + this.addrInput = useRef("addr-input"); + this.config = this.env.pos.config; + this.provider = ""; + this.onHandleMap(); + } + + onHandleMap() { + return false; + } + + async getPayload() { + return { + partner_latitude: this.lat, + partner_longitude: this.lng, + ...this.address, + }; + } + + update_marker(lat, lng) { + this.lat = lat; + this.lng = lng; + } + + /* eslint no-empty-function: "warn"*/ + /* eslint no-unused-vars: "warn"*/ + setAddressByLocation(addres) {} + + inputChange(event) { + this.setAddressByLocation(event.target.value); + } +} +PartnerMapEdit.template = "PartnerMapEdit"; + +Registries.Component.add(PartnerMapEdit); + +export default PartnerMapEdit; diff --git a/pos_partner_location_abstract/static/src/xml/PartnerDetailsEdit.xml b/pos_partner_location_abstract/static/src/xml/PartnerDetailsEdit.xml new file mode 100644 index 0000000000..6b3a5829b6 --- /dev/null +++ b/pos_partner_location_abstract/static/src/xml/PartnerDetailsEdit.xml @@ -0,0 +1,44 @@ + + + + + + + + + +
+ Lat/Long + + +
+
+
+ +
diff --git a/pos_partner_location_abstract/static/src/xml/PartnerMapEdit.xml b/pos_partner_location_abstract/static/src/xml/PartnerMapEdit.xml new file mode 100644 index 0000000000..5b36f5d766 --- /dev/null +++ b/pos_partner_location_abstract/static/src/xml/PartnerMapEdit.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/pos_partner_location_abstract/tests/__init__.py b/pos_partner_location_abstract/tests/__init__.py new file mode 100644 index 0000000000..4f8ed8b72c --- /dev/null +++ b/pos_partner_location_abstract/tests/__init__.py @@ -0,0 +1,3 @@ +from . import test_address_struct +from . import test_pos_config +from . import test_pos_session diff --git a/pos_partner_location_abstract/tests/test_address_struct.py b/pos_partner_location_abstract/tests/test_address_struct.py new file mode 100644 index 0000000000..bd6ac9d51f --- /dev/null +++ b/pos_partner_location_abstract/tests/test_address_struct.py @@ -0,0 +1,62 @@ +from unittest import mock + +from odoo.tests import TransactionCase + +from odoo.addons.pos_partner_location_abstract.models.address_struct import ( + AddressStruct, +) + + +class TestAddressStruct(TransactionCase): + def setUp(self): + super(TestAddressStruct, self).setUp() + self.addr_struct = AddressStruct(self.env) + + def test_init(self): + self.assertIsNone(self.addr_struct.status, msg="Status must be is None") + self.assertEqual(self.addr_struct._result, {}, msg="Result must be empty dict") + self.assertEqual(self.addr_struct.env, self.env, msg="Env must be the same") + + def test_street(self): + self.assertFalse(self.addr_struct.street, msg="Street must be False") + + def test_city(self): + self.assertFalse(self.addr_struct.city, msg="City must be False") + + def test_state_id(self): + self.assertFalse(self.addr_struct.state_id, msg="Stage must be False") + + def test_country_id(self): + self.assertFalse(self.addr_struct.country_id, msg="Country must be False") + + def test_zip(self): + self.assertFalse(self.addr_struct.zip, msg="Zip must be 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 = {"status": "OK"} + mock_get.return_value = mock_response + response = self.addr_struct.query_addr({}) + self.assertEqual( + response.get("status"), "OK", msg="Status must be equal to 'OK'" + ) + + def test_get_result(self): + obj = { + "street": False, + "city": False, + "state_id": False, + "country_id": False, + "zip": False, + } + self.assertDictEqual( + self.addr_struct.get_result(), obj, msg="Dicts must be the same" + ) + + @mock.patch("requests.get") + def test_query_addr_invalid(self, mock_get): + mock_response = mock.Mock(status_code=404) + mock_get.return_value = mock_response + response = self.addr_struct.query_addr({}) + self.assertFalse(response, msg="Response must be the same") diff --git a/pos_partner_location_abstract/tests/test_pos_config.py b/pos_partner_location_abstract/tests/test_pos_config.py new file mode 100644 index 0000000000..abf9e054c2 --- /dev/null +++ b/pos_partner_location_abstract/tests/test_pos_config.py @@ -0,0 +1,55 @@ +from odoo.tests import tagged + +from odoo.addons.point_of_sale.tests.common import TestPointOfSaleCommon + + +@tagged("post_install", "-at_install") +class TestPosConfig(TestPointOfSaleCommon): + def setUp(self): + super(TestPosConfig, self).setUp() + self.provider_open_street = self.env.ref( + "base_geolocalize.geoprovider_open_street" + ) + self.provider_google_map = self.env.ref( + "base_geolocalize.geoprovider_google_map" + ) + + def test_provider_not_set(self): + self.env["ir.config_parameter"].set_param( + "base_geolocalize.geo_provider", False + ) + self.env["ir.config_parameter"].set_param( + "base_geolocalize.google_map_api_key", "" + ) + self.assertFalse( + self.pos_config.geolocalize_tech_name, msg="Geo Provider must be False" + ) + self.assertFalse( + self.pos_config.googlemap_api_key, msg="GoogleMap API Key must be False" + ) + + def test_provider_google_map(self): + self.env["ir.config_parameter"].set_param( + "base_geolocalize.geo_provider", self.provider_google_map.id + ) + self.env["ir.config_parameter"].set_param( + "base_geolocalize.google_map_api_key", "" + ) + self.assertEqual( + self.pos_config.geolocalize_tech_name, + "googlemap", + msg="Geo Provider Tech Name must be equal 'googlemap'", + ) + self.assertFalse( + self.pos_config.googlemap_api_key, msg="GoogleMap API Key must be False" + ) + + def test_provider_open_street_map(self): + self.env["ir.config_parameter"].set_param( + "base_geolocalize.geo_provider", self.provider_open_street.id + ) + self.assertEqual( + self.pos_config.geolocalize_tech_name, + "openstreetmap", + msg="Geo Provider Tech Name must be equal 'openstreetmap'", + ) diff --git a/pos_partner_location_abstract/tests/test_pos_session.py b/pos_partner_location_abstract/tests/test_pos_session.py new file mode 100644 index 0000000000..ed49d27da3 --- /dev/null +++ b/pos_partner_location_abstract/tests/test_pos_session.py @@ -0,0 +1,26 @@ +from odoo.tests import tagged + +from odoo.addons.point_of_sale.tests.common import TestPointOfSaleCommon + + +@tagged("post_install", "-at_install") +class PosSession(TestPointOfSaleCommon): + def setUp(self): + super(TestPointOfSaleCommon, self).setUp() + + def test_loader_params_res_partner(self): + params = self.PosSession._loader_params_res_partner() + fields = params["search_params"]["fields"] + self.assertIn( + "contact_address", fields, msg="'contact_address' must be contain in fields" + ) + self.assertIn( + "partner_latitude", + fields, + msg="'partner_latitude' must be contain in fields", + ) + self.assertIn( + "partner_longitude", + fields, + msg="'partner_longitude' must be contain in fields", + ) diff --git a/setup/pos_partner_location_abstract/odoo/addons/pos_partner_location_abstract b/setup/pos_partner_location_abstract/odoo/addons/pos_partner_location_abstract new file mode 120000 index 0000000000..c0403457da --- /dev/null +++ b/setup/pos_partner_location_abstract/odoo/addons/pos_partner_location_abstract @@ -0,0 +1 @@ +../../../../pos_partner_location_abstract \ No newline at end of file diff --git a/setup/pos_partner_location_abstract/setup.py b/setup/pos_partner_location_abstract/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/pos_partner_location_abstract/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)