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