new functionality for tzm.at

develop
Andreas Osim 2021-04-06 17:40:59 +02:00
parent 2b9811d1e6
commit 63254bed24
63 changed files with 5402 additions and 0 deletions

View File

@ -0,0 +1,3 @@
.DS_Store
*.pyc

View File

@ -0,0 +1,289 @@
Web Google Maps
===============
[![Demo](https://i.ytimg.com/vi/2UdG5ILDtiY/3.jpg)](https://youtu.be/5hvAubXgUnc "Demo")
This module contains three new features:
- New view type and mode `map`
- New widget `gplaces_address_autocomplete`
- New widget `gplaces_autocomplete`
# Map view `"map"`
Basically, this new view `map` will integrate Google Maps into Odoo.
Enable you to display `res.partner` geolocation on map or any model contains geolocation.
This feature will work seamlessly with Odoo means you can search your partner location using Odoo search feature.
There are five available attributes that you can customize
- `lat` : an attritube to tell the map the latitude field on the object __[mandatory]__
- `lng` : an attritute to tell the map the longitude field on the object __[mandatory]__
- `color` : an attribute to modify marker color (optional) any given color will set all markers color __[optional]__.
- `colors` : work like attribute `color` but more configurable (you can set marker color depends on it's value) this attribute works similar to `colors` of tree view on Odoo 9.0 __[optional]__
- `library` : an attribute to tell map which map that will be loaded __[mandatory]__.
This options has two values:
1. `geometry`
2. `drawing`
How to create the view?
Example
>
<!-- View -->
<record id="view_res_partner_map" model="ir.ui.view">
<field name="name">view.res.partner.map</field>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<map class="o_res_partner_map" library='geometry' string="Map" lat="partner_latitude" lng="partner_longitude" colors="blue:company_type=='person';green:company_type=='company';">
<field name="id"/>
<field name="partner_latitude"/>
<field name="partner_longitude"/>
<field name="company_type"/>
<field name="color"/>
<field name="display_name"/>
<field name="title"/>
<field name="email"/>
<field name="parent_id"/>
<field name="is_company"/>
<field name="function"/>
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="mobile"/>
<field name="state_id"/>
<field name="category_id"/>
<field name="image_small"/>
<field name="type"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click o_res_partner_kanban">
<div class="o_kanban_image">
<t t-if="record.image_small.raw_value">
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.raw_value)"/>
</t>
<t t-if="!record.image_small.raw_value">
<t t-if="record.type.raw_value === 'delivery'">
<img t-att-src='_s + "/base/static/src/img/truck.png"' class="o_kanban_image oe_kanban_avatar_smallbox"/>
</t>
<t t-if="record.type.raw_value === 'invoice'">
<img t-att-src='_s + "/base/static/src/img/money.png"' class="o_kanban_image oe_kanban_avatar_smallbox"/>
</t>
<t t-if="record.type.raw_value != 'invoice' &amp;&amp; record.type.raw_value != 'delivery'">
<t t-if="record.is_company.raw_value === true">
<img t-att-src='_s + "/base/static/src/img/company_image.png"'/>
</t>
<t t-if="record.is_company.raw_value === false">
<img t-att-src='_s + "/base/static/src/img/avatar.png"'/>
</t>
</t>
</t>
</div>
<div class="oe_kanban_details">
<strong class="o_kanban_record_title oe_partner_heading">
<field name="display_name"/>
</strong>
<div class="o_kanban_tags_section oe_kanban_partner_categories">
<span class="oe_kanban_list_many2many">
<field name="category_id" widget="many2many_tags" options="{'color_field': 'color'}"/>
</span>
</div>
<ul>
<li t-if="record.parent_id.raw_value and !record.function.raw_value">
<field name="parent_id"/>
</li>
<li t-if="!record.parent_id.raw_value and record.function.raw_value">
<field name="function"/>
</li>
<li t-if="record.parent_id.raw_value and record.function.raw_value">
<field name="function"/> at <field name="parent_id"/>
</li>
<li t-if="record.city.raw_value and !record.country_id.raw_value">
<field name="city"/>
</li>
<li t-if="!record.city.raw_value and record.country_id.raw_value">
<field name="country_id"/>
</li>
<li t-if="record.city.raw_value and record.country_id.raw_value">
<field name="city"/>
, <field name="country_id"/>
</li>
<li t-if="record.email.raw_value" class="o_text_overflow">
<field name="email"/>
</li>
</ul>
<div class="oe_kanban_partner_links"/>
</div>
</div>
</t>
</templates>
</map>
</field>
</record>
<!-- Action -->
<record id="action_partner_map" model="ir.actions.act_window">
...
<field name="view_type">form</field>
<field name="view_mode">tree,form,map</field>
...
</record>
The view looks familiar?
Yes, you're right.
The marker infowindow will use `kanban-box` kanban card style.
### How to setup color for marker on map?
There are two attributes:
- `colors`
- `color`
Example:
>
<!-- colors -->
<map string="Map" lat="partner_latitude" lng="partner_longitude" colors="green:company_type=='person';blue:company_type=='company';">
...
</map>
<!-- color -->
<map string="Map" lat="partner_latitude" lng="partner_longitude" color="orange">
...
</map>
# New widget `"gplaces_address_autocomplete"`
New widget to integrate [Place Autocomplete Address Form](https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform) in Odoo.
The widget has four options that can be modify:
- `component_form`
- `fillfields`
- `lat`
- `lng`
### Component form `component_form`
Is an option used to modify which value you want to take from an objects returned by the geocoder.
Full documentation about Google component types can be found [here](https://developers.google.com/maps/documentation/geocoding/intro#Types)
By default this option are configured like the following value
>
{
'street_number': 'long_name',
'route': 'long_name',
'intersection': 'short_name',
'political': 'short_name',
'country': 'short_name',
'administrative_area_level_1': 'short_name',
'administrative_area_level_2': 'short_name',
'administrative_area_level_3': 'short_name',
'administrative_area_level_4': 'short_name',
'administrative_area_level_5': 'short_name',
'colloquial_area': 'short_name',
'locality': 'short_name',
'ward': 'short_name',
'sublocality_level_1': 'short_name',
'sublocality_level_2': 'short_name',
'sublocality_level_3': 'short_name',
'sublocality_level_5': 'short_name',
'neighborhood': 'short_name',
'premise': 'short_name',
'postal_code': 'short_name',
'natural_feature': 'short_name',
'airport': 'short_name',
'park': 'short_name',
'point_of_interest': 'long_name'
}
This configuration can be modify into view field definition.
Example:
>
<record id="view_res_partner_form" model="ir.ui.view">
...
<field name="arch" type="xml">
...
<field name="street" widget="gplaces_address_form" options="{'component_form': {'street_number': 'short_name'}}"/>
...
</field>
</record>
### Fill fields `fillfields`
Is an option that will be influenced by `gplaces_address_autocomplete` widget.
This options should contains known `fields` that you want the widget to fulfill a value for each given field automatically.
A field can contains one or multiple elements of component form
By default this options are configured like the following
>
{
'street': ['street_number', 'route'],
'street2': ['administrative_area_level_3', 'administrative_area_level_4', 'administrative_area_level_5'],
'city': ['locality', 'administrative_area_level_2'],
'zip': 'postal_code',
'state_id': 'administrative_area_level_1',
'country_id': 'country',
}
This configuration can be modify into view field definition as well
Example:
>
<record id="view_res_partner_form" model="ir.ui.view">
...
<field name="arch" type="xml">
...
<field name="street" widget="google_places" options="{'fillfields': {'street2': ['route', 'street_number']}}"/>
...
</field>
</record>
### Latitude `lat` and Longitude `lng`
This options tell the widget the fields geolocation, in order to have this fields filled automatically.
# New widget `"gplaces_autocomplete"`
New widget to integrate [Place Autocomplete](https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete) in Odoo.
This widget have similar configuration to `gplaces_address_autocomplete`.
### Component form `component_form` ###
Same configuration of `gplaces_address_autocomplete` component form
### Fill fields `fillfields`
This configuration works similar to `gplaces_address_autocomplete`.
By default this options are configured like following value:
>
{
general: {
name: 'name',
website: 'website',
phone: ['international_phone_number', 'formatted_phone_number']
},
geolocation: {
partner_latitude: 'latitude',
partner_longitude: 'longitude'
},
address: {
street: ['street_number', 'route'],
street2: ['administrative_area_level_3', 'administrative_area_level_4', 'administrative_area_level_5'],
city: ['locality', 'administrative_area_level_2'],
zip: 'postal_code',
state_id: 'administrative_area_level_1',
country_id: 'country'
}
};
# Technical
This module will install `base_setup` and `base_geolocalize`.
*I recommend you to setup __Google Maps Key API__ and add it into Odoo `Settings > General` Settings when you installed this module*
*__List of Google APIs & services required in order to make all features works__*
- Geocoding API
- Maps JavaScript API
- Places API for Web
The goal of this module is to bring the power of Google Maps into Odoo
This module has tested on Odoo Version 11.0c
[![ko-fi](https://www.ko-fi.com/img/donate_sm.png)](https://ko-fi.com/P5P4FOM0)
*if you want to support me to keep this project maintained. Thanks :)*

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# License AGPL-3
from . import models
from . import controllers
from .hooks import uninstall_hook

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
{
'name': 'Web Google Maps',
'version': '11.0.1.0.5',
'author': 'Yopi Angi',
'license': 'AGPL-3',
'maintainer': 'Yopi Angi<yopiangi@gmail.com>',
'support': 'yopiangi@gmail.com',
'category': 'Extra Tools',
'description': """
Web Google Map and google places autocomplete address form
==========================================================
This module brings three features:
1. Allows user to view all partners addresses on google maps.
2. Enabled google places autocomplete address form into partner
form view, it provide autocomplete feature when typing address of partner
""",
'depends': [
'base_setup',
'base_geolocalize',
],
'website': '',
'data': [
'data/google_maps_libraries.xml',
'views/google_places_template.xml',
'views/res_partner.xml',
'views/res_config.xml'
],
'demo': [],
'images': ['static/description/thumbnails.png'],
'qweb': ['static/src/xml/widget_places.xml'],
'installable': True,
'uninstall_hook': 'uninstall_hook',
}

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import main

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from odoo import http
class Main(http.Controller):
@http.route('/web/map_theme', type='json', auth='user')
def map_theme(self):
ICP = http.request.env['ir.config_parameter'].sudo()
theme = ICP.get_param('google.maps_theme', default='default')
res = {'theme': theme}
return res

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<function model="ir.config_parameter" name="set_param" eval="('google.maps_libraries', 'geometry,places')"/>
</data>
</odoo>

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# License AGPL-3
def uninstall_hook(cr, registry):
cr.execute("UPDATE ir_act_window "
"SET view_mode=replace(view_mode, ',map', '')"
"WHERE view_mode LIKE '%,map%';")
cr.execute("UPDATE ir_act_window "
"SET view_mode=replace(view_mode, 'map,', '')"
"WHERE view_mode LIKE '%map,%';")
cr.execute("DELETE FROM ir_act_window "
"WHERE view_mode = 'map';")

View File

@ -0,0 +1,466 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_google_maps
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 11.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-09 16:04+0000\n"
"PO-Revision-Date: 2018-10-09 16:04+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Api key"
msgstr "Api key"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Arabic"
msgstr "Arabic"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_theme:0
msgid "Aubergine"
msgstr "Aubergine"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Basque"
msgstr "Basque"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Bengali"
msgstr "Bengali"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Bulgarian"
msgstr "Bulgarian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Catalan"
msgstr "Catalan"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Chinese (Simplified)"
msgstr "Chinese (Simplified)"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Chinese (Traditional)"
msgstr "Chinese (Traditional)"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Configure your Google Maps View"
msgstr "Configure your Google Maps View"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Croatian"
msgstr "Croatian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Czech"
msgstr "Czech"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Danish"
msgstr "Danish"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_theme:0
msgid "Dark"
msgstr "Dark"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_theme:0
msgid "Default"
msgstr "Default"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Dutch"
msgstr "Dutch"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "English"
msgstr "English"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "English (Australian)"
msgstr "English (Australian)"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "English (Great Britain)"
msgstr "English (Great Britain)"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Farsi"
msgstr "Farsi"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Filipino"
msgstr "Filipino"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Finnish"
msgstr "Finnish"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "French"
msgstr "French"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Galician"
msgstr "Galician"
#. module: web_google_maps
#: model:ir.model.fields,field_description:web_google_maps.field_res_config_settings_google_maps_geometry
msgid "Geometry"
msgstr "Geometry"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Geometry includes utility functions for calculating scalar geometric values (such as distance and area) on the surface of the earth. \n"
" Consult the"
msgstr "Geometry includes utility functions for calculating scalar geometric values (such as distance and area) on the surface of the earth. \n"
" Consult the"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Geometry library documentation"
msgstr "Geometry library documentation"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "German"
msgstr "German"
#. module: web_google_maps
#: model:ir.model.fields,field_description:web_google_maps.field_res_config_settings_google_maps_lang_localization
msgid "Google Maps Language Localization"
msgstr "Google Maps Language Localization"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Google Maps Libraries"
msgstr "Google Maps Libraries"
#. module: web_google_maps
#: model:ir.model.fields,field_description:web_google_maps.field_res_config_settings_google_maps_region_localization
msgid "Google Maps Region Localization"
msgstr "Google Maps Region Localization"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Google Maps View"
msgstr "Google Maps View"
#. module: web_google_maps
#: model:ir.model.fields,field_description:web_google_maps.field_res_config_settings_google_maps_view_api_key
msgid "Google Maps View Api Key"
msgstr "Google Maps View Api Key"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Greek"
msgstr "Greek"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Gujarati"
msgstr "Gujarati"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Hebrew"
msgstr "Hebrew"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Hindi"
msgstr "Hindi"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Hungarian"
msgstr "Hungarian"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "If you set the language of the map, it's important to consider setting the region too. This helps ensure that your application complies with local laws."
msgstr "If you set the language of the map, it's important to consider setting the region too. This helps ensure that your application complies with local laws."
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Indonesian"
msgstr "Indonesian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Italian"
msgstr "Italian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Japanese"
msgstr "Japanese"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Kannada"
msgstr "Kannada"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Korean"
msgstr "Korean"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Language"
msgstr "Language"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Latvian"
msgstr "Latvian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Lithuanian"
msgstr "Lithuanian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Malayalam"
msgstr "Malayalam"
#. module: web_google_maps
#. openerp-web
#: code:addons/web_google_maps/static/src/js/view/map/map_view.js:15
#: model:ir.ui.view,arch_db:web_google_maps.view_res_partner_map
#, python-format
msgid "Map"
msgstr "Map"
#. module: web_google_maps
#: model:ir.model.fields,field_description:web_google_maps.field_res_config_settings_google_maps_theme
msgid "Map theme"
msgstr "Map theme"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Marathi"
msgstr "Marathi"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_theme:0
msgid "Night"
msgstr "Night"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Norwegian"
msgstr "Norwegian"
#. module: web_google_maps
#: model:ir.model.fields,field_description:web_google_maps.field_res_config_settings_google_maps_places
msgid "Places"
msgstr "Places"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Places enables your application to search for places such as establishments, geographic locations, or prominent points of interest, within a defined area. \n"
" Consult the"
msgstr "Places enables your application to search for places such as establishments, geographic locations, or prominent points of interest, within a defined area. \n"
" Consult the"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Places library documentation"
msgstr "Places library documentation"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Polish"
msgstr "Polish"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Portuguese"
msgstr "Portuguese"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Portuguese (Brazil)"
msgstr "Portuguese (Brazil)"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Portuguese (Portugal)"
msgstr "Portuguese (Portugal)"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Region"
msgstr "Region"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_theme:0
msgid "Retro"
msgstr "Retro"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Romanian"
msgstr "Romanian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Russian"
msgstr "Russian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Serbian"
msgstr "Serbian"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Set API keys and map localization"
msgstr "Set API keys and map localization"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_theme:0
msgid "Silver"
msgstr "Silver"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Slovak"
msgstr "Slovak"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Slovenian"
msgstr "Slovenian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Spanish"
msgstr "Spanish"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Swedish"
msgstr "Swedish"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Tagalog"
msgstr "Tagalog"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Tamil"
msgstr "Tamil"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Telugu"
msgstr "Telugu"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Thai"
msgstr "Thai"
#. module: web_google_maps
#. openerp-web
#: code:addons/web_google_maps/static/src/js/widgets/gplaces_autocomplete.js:244
#: code:addons/web_google_maps/static/src/js/widgets/gplaces_autocomplete.js:352
#, python-format
msgid "The following fields are invalid:"
msgstr "The following fields are invalid:"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Theme"
msgstr "Theme"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Turkish"
msgstr "Turkish"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Ukrainian"
msgstr "Ukrainian"
#. module: web_google_maps
#: selection:res.config.settings,google_maps_lang_localization:0
msgid "Vietnamese"
msgstr "Vietnamese"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "Visit the"
msgstr "Visit the"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "about Localizing the Map"
msgstr "about Localizing the Map"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_res_partner_map
msgid "at"
msgstr "at"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "for more information."
msgstr "for more information."
#. module: web_google_maps
#: model:ir.model,name:web_google_maps.model_ir_actions_act_window_view
msgid "ir.actions.act_window.view"
msgstr "ir.actions.act_window.view"
#. module: web_google_maps
#: model:ir.model,name:web_google_maps.model_ir_ui_view
msgid "ir.ui.view"
msgstr "ir.ui.view"
#. module: web_google_maps
#: model:ir.ui.view,arch_db:web_google_maps.view_web_google_maps_config_settings
msgid "page"
msgstr "page"
#. module: web_google_maps
#: model:ir.model,name:web_google_maps.model_res_config_settings
msgid "res.config.settings"
msgstr "res.config.settings"

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# License AGPL-3
from . import ir_act_window_view
from . import ir_ui_view
from . import res_config

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# License AGPL-3
from odoo import fields, models
class IrActionsActWindowView(models.Model):
_inherit = 'ir.actions.act_window.view'
view_mode = fields.Selection(selection_add=[('map', 'Map')])

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# License AGPL-3
from odoo import api, fields, models
class IrUiView(models.Model):
_inherit = 'ir.ui.view'
type = fields.Selection(selection_add=[('map', 'Map')])

View File

@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
# License AGPL-3
from odoo import api, fields, models
GMAPS_LANG_LOCALIZATION = [
('ar', 'Arabic'),
('bg', 'Bulgarian'),
('bn', 'Bengali'),
('ca', 'Catalan'),
('cs', 'Czech'),
('da', 'Danish'),
('de', 'German'),
('el', 'Greek'),
('en', 'English'),
('en-AU', 'English (Australian)'),
('en-GB', 'English (Great Britain)'),
('es', 'Spanish'),
('eu', 'Basque'),
('eu', 'Basque'),
('fa', 'Farsi'),
('fi', 'Finnish'),
('fil', 'Filipino'),
('fr', 'French'),
('gl', 'Galician'),
('gu', 'Gujarati'),
('hi', 'Hindi'),
('hr', 'Croatian'),
('hu', 'Hungarian'),
('id', 'Indonesian'),
('it', 'Italian'),
('iw', 'Hebrew'),
('ja', 'Japanese'),
('kn', 'Kannada'),
('ko', 'Korean'),
('lt', 'Lithuanian'),
('lv', 'Latvian'),
('ml', 'Malayalam'),
('mr', 'Marathi'),
('nl', 'Dutch'),
('no', 'Norwegian'),
('pl', 'Polish'),
('pt', 'Portuguese'),
('pt-BR', 'Portuguese (Brazil)'),
('pt-PT', 'Portuguese (Portugal)'),
('ro', 'Romanian'),
('ru', 'Russian'),
('sk', 'Slovak'),
('sl', 'Slovenian'),
('sr', 'Serbian'),
('sv', 'Swedish'),
('ta', 'Tamil'),
('te', 'Telugu'),
('th', 'Thai'),
('tl', 'Tagalog'),
('tr', 'Turkish'),
('uk', 'Ukrainian'),
('vi', 'Vietnamese'),
('zh-CN', 'Chinese (Simplified)'),
('zh-TW', 'Chinese (Traditional)'),
]
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
@api.model
def get_region_selection(self):
country_ids = self.env['res.country'].search([])
values = [(country.code, country.name) for country in country_ids]
return values
google_maps_view_api_key = fields.Char(string='Google Maps View Api Key')
google_maps_lang_localization = fields.Selection(
selection=GMAPS_LANG_LOCALIZATION,
string='Google Maps Language Localization')
google_maps_region_localization = fields.Selection(
selection=get_region_selection,
string='Google Maps Region Localization')
google_maps_theme = fields.Selection(
selection=[('default', 'Default'),
('aubergine', 'Aubergine'),
('night', 'Night'),
('dark', 'Dark'),
('retro', 'Retro'),
('silver', 'Silver')],
string='Map theme')
google_maps_places = fields.Boolean(string='Places', default=True)
google_maps_geometry = fields.Boolean(string='Geometry', default=True)
@api.onchange('google_maps_lang_localization')
def onchange_lang_localization(self):
if not self.google_maps_lang_localization:
self.google_maps_region_localization = ''
@api.multi
def set_values(self):
super(ResConfigSettings, self).set_values()
ICPSudo = self.env['ir.config_parameter'].sudo()
lang_localization = self._set_google_maps_lang_localization()
region_localization = self._set_google_maps_region_localization()
lib_places = self._set_google_maps_places()
lib_geometry = self._set_google_maps_geometry()
active_libraries = '%s,%s' % (lib_geometry, lib_places)
ICPSudo.set_param('google.api_key_geocode',
self.google_maps_view_api_key)
ICPSudo.set_param('google.lang_localization',
lang_localization)
ICPSudo.set_param('google.region_localization',
region_localization)
ICPSudo.set_param('google.maps_theme', self.google_maps_theme)
ICPSudo.set_param('google.maps_libraries', active_libraries)
@api.model
def get_values(self):
res = super(ResConfigSettings, self).get_values()
ICPSudo = self.env['ir.config_parameter'].sudo()
lang_localization = self._get_google_maps_lang_localization()
region_localization = self._get_google_maps_region_localization()
lib_places = self._get_google_maps_places()
lib_geometry = self._get_google_maps_geometry()
res.update({
'google_maps_view_api_key': ICPSudo.get_param(
'google.api_key_geocode', default=''),
'google_maps_lang_localization': lang_localization,
'google_maps_region_localization': region_localization,
'google_maps_theme': ICPSudo.get_param(
'google.maps_theme', default='default'),
'google_maps_places': lib_places,
'google_maps_geometry': lib_geometry
})
return res
@api.multi
def _set_google_maps_lang_localization(self):
if self.google_maps_lang_localization:
lang_localization = '&language=%s' % \
self.google_maps_lang_localization
else:
lang_localization = ''
return lang_localization
@api.model
def _get_google_maps_lang_localization(self):
ICPSudo = self.env['ir.config_parameter'].sudo()
google_maps_lang = ICPSudo.get_param(
'google.lang_localization', default='')
val = google_maps_lang.split('=')
lang = val and val[-1] or ''
return lang
@api.multi
def _set_google_maps_region_localization(self):
if self.google_maps_region_localization:
region_localization = '&region=%s' % \
self.google_maps_region_localization
else:
region_localization = ''
return region_localization
@api.model
def _get_google_maps_region_localization(self):
ICPSudo = self.env['ir.config_parameter'].sudo()
google_maps_region = ICPSudo.get_param(
'google.region_localization', default='')
val = google_maps_region.split('=')
region = val and val[-1] or ''
return region
@api.model
def _get_google_maps_geometry(self):
ICPSudo = self.env['ir.config_parameter'].sudo()
google_maps_libraries = ICPSudo.get_param(
'google.maps_libraries', default='')
libraries = google_maps_libraries.split(',')
return 'geometry' in libraries
@api.multi
def _set_google_maps_geometry(self):
return 'geometry' if self.google_maps_geometry else ''
@api.model
def _get_google_maps_places(self):
ICPSudo = self.env['ir.config_parameter'].sudo()
google_maps_libraries = ICPSudo.get_param(
'google.maps_libraries', default='')
libraries = google_maps_libraries.split(',')
return 'places' in libraries
@api.multi
def _set_google_maps_places(self):
return 'places' if self.google_maps_places else ''

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,77 @@
<section class="oe_container">
<div class="oe_row">
<h2 class="oe_slogan" style="color:#875A7B;">Integrate Google Maps into Odoo</h2>
<h4 class="oe_slogan">Show all your partners location on Google Maps</h4>
<div class="oe_span12">
<img class="oe_picture oe_screenshot" src="maps.png">
<div class="text-center">
<a href="https://youtu.be/5hvAubXgUnc" class="btn btn-primary" target="_blank">Demo Video</a>
</div>
</div>
<div class="oe_span12">
<p>This module brings four new features:</p>
<ul>
<li>New view <em>map</em> allows user to view all partners addresses on google maps.</li>
<li>
New widget <em>gplaces_address_autocomplete</em>, enabled <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform">Google places autocomplete address form</a> into partner form view,<br/>
provide autocomplete feature when you typing an address of partner (or any field using this widget)
</li>
<li>
New widget <em>gplaces_autocomplete</em>, enabled <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete">Google places autocomplete</a> into partner form view,<br/>
provide autocomplete feature when typing partner name (or any field using this widget)
</li>
<li>Map Localization</li>
</ul>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h4 class="oe_slogan">New Widget Google Place Autocomplete Address Form</h4>
<div class="oe_span12">
<img class="oe_picture oe_screenshot" src="widget_gplaces_address_form.gif">
</div>
<div class="oe_span12 oe_mb32">
<p>Provide autocomplete feature when typing an address</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h4 class="oe_slogan">New Widget Google Place Autocomplete</h4>
<div class="oe_span12">
<img class="oe_picture oe_screenshot" src="widget_gplaces_autocomplete.gif">
</div>
<div class="oe_span12 oe_mb32">
<p>Provide autocomplete feature when typing partner's name</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Bug Tracker</h2>
<p>Bugs are tracked on
<a class="reference external" target="_blank" href="https://github.com/gityopie/odoo-addons/issues">GitHub Issues</a>. In case of trouble, please check there if your issue has already been reported. If you spotted
it first, help us smashing it by providing a detailed and welcomed feedback.</p>
</div>
<div class="oe_span12">
<h3>Contributors</h3>
<ul class="simple">
<li>Yopi Angi &lt;
<a class="reference external" href="mailto:yopiangi@gmail.com">yopiangi@gmail.com</a>&gt;</li>
</ul>
</div>
<div class="oe_span12">
<h3>Maintainer</h3>
<p>This module is maintained by myself, if you are interested to contribute please let me know</p>
<a href="https://ko-fi.com/P5P4FOM0" target="_blank">
<img src="https://www.ko-fi.com/img/donate_sm.png" alt="ko-fi"/>
</a>
<p>Thank you</p>
</div>
</div>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,155 @@
/* https://github.com/twbs/bootstrap/issues/4160 */
/* put google places autocomplete dropdown results above the bootstrap modal 1050 zindex. */
.pac-container {
z-index: 1051 !important;
}
.o_map {
height: 100%;
width: 100%;
}
.o_map .o_map_view {
height: 100%;
width: 100%;
}
.o_map .o_map_sidenav {
left: 0;
top: 0;
bottom: 0;
max-width: 400px;
width: 400px;
position: absolute;
z-index: 1;
background-color: #f5f5f5;
overflow-x: hidden;
transition: 0.5s;
transform: translate3d(0, 0, 0);
-webkit-transform: translate3d(0, 0, 0);
}
.o_map .o_map_sidenav.whiteframe {
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12);
}
.o_map .o_map_sidenav.closed {
transform: translate3d(-100%, 0, 0);
-webkit-transform: translate3d(-100%, 0, 0);
}
.o_map .o_map_sidenav .sidenav-body .panel {
border: none;
}
.o_act_window .o_map .o_map_view,
.o_act_window .o_map .o_map_sidenav {
min-height: 400px;
height: 400px;
}
.o_map .o_map_sidenav .controls,
.o_map .o_map_sidenav .layers {
text-align: center;
}
.o_map .o_map_sidenav .controls span.active,
.o_map .o_map_sidenav .layers span.active {
color: #7c7bad;
font-weight: bold;
}
.o_map .o_map_sidenav .controls span img,
.o_map .o_map_sidenav .layers span img {
padding-left: 5px;
padding-right: 10px;
}
.o_map_control .btn_map_control {
margin-top: 10px;
margin-left: 10px;
cursor: pointer;
transition: margin-left .5s;
line-height: 20px;
padding: 4.5px 10px;
background: #ffffff;
border-radius: 2px;
box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 4px -1px;
}
.o_map_control .btn_map_control.opened {
margin-left: 410px;
}
.o_map_routes_window {
min-width: 100px;
border-radius: 2px;
font-size: 14px;
margin-left: 15px;
opacity: 0.8;
background: #ffffff;
padding: 5px;
}
.o_map_redirect_google {
cursor: pointer;
text-align: center;
width: 25px;
height: 25px;
margin-right: 15px;
background-color: #ffffff;
border-radius: 2px;
line-height: 25px;
}
.o_map_places_control .pac-card #pac-container #pac-button {
padding-top: 5px;
text-align: right;
}
.o_map_places_control .pac-card #pac-container #pac-result {
border-bottom: 1px solid #ccc;
}
.o_map_places_control .pac-card .pac-controls {
padding: 0 0 10px 0;
text-align: center;
}
.o_map_places_control .pac-card .pac-controls label {
font-size: 13px;
font-weight: 300;
}
.o_map_places_control .pac-card #pac-input {
font-size: 15px;
font-weight: 300;
text-overflow: ellipsis;
height: 35px;
}
.o_map_places_control .pac-card #pac-input:focus {
border-color: #7c7bad;
}
.o_map_place_result input#place-input-name {
background-color: #efefef;
font-size: 15px;
font-weight: 300;
text-overflow: ellipsis;
height: 35px;
}
.o_map_place_result ul {
list-style-type: none;
margin-left: -30px;
}
.o_map_place_result ul li i {
padding-right: 5px;
}
.o_control_panel .o_map_buttons_view>button:first-child {
float: left;
margin-right: 4px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View File

@ -0,0 +1,61 @@
odoo.define('web_google_maps.relational_fields', function (require) {
var core = require('web.core');
var relational_fields = require('web.relational_fields');
var MapRenderer = require('web_google_maps.MapRenderer');
var qweb = core.qweb;
relational_fields.FieldOne2Many.include({
_render: function () {
if (!this.view || this.renderer) {
return this._super();
}
var arch = this.view.arch;
var viewType;
if (arch.tag == 'map') {
viewType = 'map';
var record_options = {
editable: true,
deletable: true,
read_only_mode: this.isReadonly
}
this.renderer = new MapRenderer(this, this.value, {
arch: arch,
record_options: record_options,
viewType: viewType,
fieldLat: arch.attrs.lat,
fieldLng: arch.attrs.lng,
markerColor: arch.attrs.color,
mapLibrary: arch.attrs.library,
drawingMode: arch.attrs.drawing_mode,
drawingPath: arch.attrs.drawing_path
});
this.$el.addClass('o_field_x2many o_field_x2many_' + viewType);
return this.renderer.appendTo(this.$el);
}
return this._super();
},
/**
* Override
*/
_renderButtons: function () {
this._super.apply(this, arguments);
if (this.view.arch.tag === 'map') {
var options = {create_text: this.nodeOptions.create_text, widget: this};
this.$buttons = $(qweb.render('MapView.buttons', options));
this.$buttons.on('click', 'button.o-map-button-new', this._onAddRecord.bind(this));
this.$buttons.on('click', 'button.o-map-button-center-map', this._onMapCenter.bind(this));
}
},
_onMapCenter: function (event) {
event.stopPropagation();
if (this.renderer.mapLibrary === 'geometry') {
this.renderer.mapGeometryCentered();
} else if (this.renderer.mapLibrary === 'drawing') {
this.renderer.mapShapesCentered();
}
}
});
});

View File

@ -0,0 +1,181 @@
odoo.define('web_google_maps.MapController', function (require) {
'use strict';
var Context = require('web.Context');
var core = require('web.core');
var BasicController = require('web.BasicController');
var Domain = require('web.Domain');
var _t = core._t;
var qweb = core.qweb;
var MapController = BasicController.extend({
custom_events: _.extend({}, BasicController.prototype.custom_events, {
button_clicked: '_onButtonClicked',
kanban_record_delete: '_onRecordDelete',
kanban_record_update: '_onUpdateRecord',
kanban_column_archive_records: '_onArchiveRecords',
}),
/**
* @override
* @param {Object} params
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
this.on_create = params.on_create;
this.hasButtons = params.hasButtons;
},
/**
* @private
* @param {OdooEvent} event
*/
_onButtonClicked: function (event) {
event.stopPropagation();
var self = this;
var attrs = event.data.attrs;
var record = event.data.record;
if (attrs.context) {
attrs.context = new Context(attrs.context)
.set_eval_context({
active_id: record.res_id,
active_ids: [record.res_id],
active_model: record.model,
});
}
this.trigger_up('execute_action', {
action_data: attrs,
env: {
context: record.getContext(),
currentID: record.res_id,
model: record.model,
resIDs: record.res_ids,
},
on_closed: function () {
var recordModel = self.model.localData[record.id];
var group = self.model.localData[recordModel.parentID];
var parent = self.model.localData[group.parentID];
self.model.reload(record.id).then(function (db_id) {
var data = self.model.get(db_id);
var kanban_record = event.target;
kanban_record.update(data);
// Check if we still need to display the record. Some fields of the domain are
// not guaranteed to be in data. This is for example the case if the action
// contains a domain on a field which is not in the Kanban view. Therefore,
// we need to handle multiple cases based on 3 variables:
// domInData: all domain fields are in the data
// activeInDomain: 'active' is already in the domain
// activeInData: 'active' is available in the data
var domain = (parent ? parent.domain : group.domain) || [];
var domInData = _.every(domain, function (d) {
return d[0] in data.data;
});
var activeInDomain = _.pluck(domain, 0).indexOf('active') !== -1;
var activeInData = 'active' in data.data;
// Case # | domInData | activeInDomain | activeInData
// 1 | true | true | true => no domain change
// 2 | true | true | false => not possible
// 3 | true | false | true => add active in domain
// 4 | true | false | false => no domain change
// 5 | false | true | true => no evaluation
// 6 | false | true | false => no evaluation
// 7 | false | false | true => replace domain
// 8 | false | false | false => no evaluation
// There are 3 cases which cannot be evaluated since we don't have all the
// necessary information. The complete solution would be to perform a RPC in
// these cases, but this is out of scope. A simpler one is to do a try / catch.
if (domInData && !activeInDomain && activeInData) {
domain = domain.concat([
['active', '=', true]
]);
} else if (!domInData && !activeInDomain && activeInData) {
domain = [
['active', '=', true]
];
}
try {
var visible = new Domain(domain).compute(data.evalContext);
} catch (e) {
return;
}
if (!visible) {
kanban_record.destroy();
}
});
},
});
},
/**
* @private
* @param {OdooEvent} event
*/
_onRecordDelete: function (event) {
this._deleteRecords([event.data.id]);
},
/**
* @todo should simply use field_changed event...
*
* @private
* @param {OdooEvent} ev
*/
_onUpdateRecord: function (ev) {
var changes = _.clone(ev.data);
ev.data.force_save = true;
this._applyChanges(ev.target.db_id, changes, ev);
},
/**
* The interface allows in some case the user to archive a column. This is
* what this handler is for.
*
* @private
* @param {OdooEvent} event
*/
_onArchiveRecords: function (event) {
var self = this;
var active_value = !event.data.archive;
var column = event.target;
var record_ids = _.pluck(column.records, 'db_id');
if (record_ids.length) {
this.model
.toggleActive(record_ids, active_value, column.db_id)
.then(function (db_id) {
var data = self.model.get(db_id);
self._updateEnv();
});
}
},
renderButtons: function ($node) {
if (this.hasButtons) {
this.$buttons = $(qweb.render('MapView.buttons', {
widget: this
}));
this.$buttons.on('click', 'button.o-map-button-new', this._onButtonNew.bind(this));
this.$buttons.on('click', 'button.o-map-button-center-map', this._onButtonMapCenter.bind(this));
this.$buttons.appendTo($node);
}
},
_onButtonMapCenter: function (event) {
event.preventDefault();
if (this.renderer.mapLibrary === 'geometry') {
this.renderer.mapGeometryCentered();
} else if (this.renderer.mapLibrary === 'drawing') {
this.renderer.mapShapesCentered();
}
},
_onButtonNew: function (event) {
event.preventDefault();
this.trigger_up('switch_view', {
view_type: 'form',
res_id: undefined
});
},
});
return MapController;
});

View File

@ -0,0 +1,41 @@
odoo.define('web_google_maps.MapModel', function(require) {
'use strict';
var BasicModel = require('web.BasicModel');
var MapModel = BasicModel.extend({
/**
* @override
*/
reload: function (id, options) {
if (options && options.groupBy && !options.groupBy.length) {
options.groupBy = this.defaultGroupedBy;
}
return this._super.apply(this, arguments);
},
/**
* @override
*/
load: function (params) {
this.defaultGroupedBy = params.groupBy;
params.groupedBy = (params.groupedBy && params.groupedBy.length) ? params.groupedBy : this.defaultGroupedBy;
return this._super(params);
},
/**
* Ensures that there is no nested groups in Map (only the first grouping
* level is taken into account).
*
* @override
*/
_readGroup: function (list) {
var self = this;
if (list.groupedBy.length > 1) {
list.groupedBy = [list.groupedBy[0]];
}
return this._super.apply(this, arguments);
}
});
return MapModel;
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,81 @@
odoo.define('web_google_maps.MapView', function (require) {
'use strict';
var BasicView = require('web.BasicView');
var core = require('web.core');
var MapModel = require('web_google_maps.MapModel');
var MapRenderer = require('web_google_maps.MapRenderer');
var MapController = require('web_google_maps.MapController');
var _lt = core._lt;
var MapView = BasicView.extend({
accesskey: 'm',
display_name: _lt('Map'),
icon: 'fa-map-o',
jsLibs: [],
config: _.extend({}, BasicView.prototype.config, {
Model: MapModel,
Renderer: MapRenderer,
Controller: MapController
}),
viewType: 'map',
init: function (viewInfo, params) {
this._super.apply(this, arguments);
var arch = viewInfo.arch;
var attrs = arch.attrs;
var activeActions = this.controllerParams.activeActions;
var mode = arch.attrs.editable && !params.readonly ? "edit" : "readonly";
this.loadParams.limit = this.loadParams.limit || 40;
this.loadParams.openGroupByDefault = true;
this.loadParams.type = 'list';
this.loadParams.groupBy = arch.attrs.default_group_by ? [arch.attrs.default_group_by] : (params.groupBy || []);
this.rendererParams.arch = arch;
this.rendererParams.mapLibrary = attrs.library;
if (attrs.library === 'drawing') {
this.rendererParams.drawingMode = attrs.drawing_mode;
this.rendererParams.drawingPath = attrs.drawing_path;
} else if (attrs.library === 'geometry') {
var colors = this._setMarkersColor(attrs.colors);
this.rendererParams.markerColor = attrs.color;
this.rendererParams.markerColors = colors;
this.rendererParams.fieldLat = attrs.lat;
this.rendererParams.fieldLng = attrs.lng;
}
this.rendererParams.record_options = {
editable: activeActions.edit,
deletable: activeActions.delete,
read_only_mode: params.readOnlyMode || true,
};
this.controllerParams.mode = mode;
this.controllerParams.hasButtons = true;
},
_setMarkersColor: function (colors) {
var pair, color, expr;
if (!colors) {
return false;
}
return _(colors.split(';'))
.chain()
.compact()
.map(function (color_pair) {
pair = color_pair.split(':');
color = pair[0];
expr = pair[1];
return [color, py.parse(py.tokenize(expr)), expr];
}).value();
}
});
return MapView;
});

View File

@ -0,0 +1,9 @@
odoo.define('web_google_maps.view_registry', function (require) {
"use strict";
var MapView = require('web_google_maps.MapView');
var view_registry = require('web.view_registry');
view_registry.add('map', MapView);
});

View File

@ -0,0 +1,10 @@
odoo.define('web_google_maps.FieldsRegistry', function(require) {
'use strict';
var registry = require('web.field_registry');
var GplacesAutocomplete = require('web_google_maps.GplaceAutocompleteFields');
registry.add('gplaces_address_autocomplete', GplacesAutocomplete.GplacesAddressAutocompleteField);
registry.add('gplaces_autocomplete', GplacesAutocomplete.GplacesAutocompleteField);
});

View File

@ -0,0 +1,496 @@
odoo.define('web_google_maps.GplaceAutocompleteFields', function (require) {
'use strict';
var BasicFields = require('web.basic_fields');
var core = require('web.core');
var Utils = require('web_google_maps.Utils');
var _t = core._t;
var GplaceAutocomplete = BasicFields.InputField.extend({
className: 'o_field_char o_field_google_autocomplete',
tagName: 'span',
supportedFieldTypes: ['char'],
events: _.extend({}, BasicFields.InputField.prototype.events, {
'focus': '_geolocate'
}),
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this._type_relations = ['one2many', 'many2one', 'many2many'];
this.places_autocomplete = false;
this.component_form = Utils.GOOGLE_PLACES_COMPONENT_FORM;
this.address_form = Utils.ADDRESS_FORM;
this.fillfields_delimiter = {
street: " ",
street2: ", ",
};
this.fillfields = {};
this.lng = false;
this.lat = false;
this.setDefault();
},
/**
* @override
*/
start: function () {
return this._super.apply(this, arguments).then(this.prepareWidgetOptions.bind(this));
},
/**
* Set widget default value
*/
setDefault: function () {},
/**
* Prepare widget options
*/
prepareWidgetOptions: function () {
if (this.mode === 'edit') {
// update 'component_form', 'delimiter' if exists
if (this.attrs.options) {
if (this.attrs.options.hasOwnProperty('component_form')) {
this.component_form = _.defaults({}, this.attrs.options.component_form, this.component_form);
}
if (this.attrs.options.hasOwnProperty('delimiter')) {
this.fillfields_delimiter = _.defaults({}, this.attrs.options.delimiter, this.fillfields_delimiter);
}
if (this.attrs.options.hasOwnProperty('lat')) {
this.lat = this.attrs.options.lat;
}
if (this.attrs.options.hasOwnProperty('lng')) {
this.lng = this.attrs.options.lng;
}
if (this.attrs.options.hasOwnProperty('address_form')) {
this.address_form = _.defaults({}, this.attrs.options.address_form, this.address_form);
}
}
this.target_fields = this.getFillFieldsType();
}
},
/**
* To be overriden
*/
getFillFieldsType: function () {
return [];
},
/**
* Geolocate
* @private
*/
_geolocate: function () {
var self = this;
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle({
center: geolocation,
radius: position.coords.accuracy
});
self.places_autocomplete.setBounds(circle.getBounds());
});
}
},
/**
* @private
*/
_prepareValue: function (model, field_name, value) {
var model = model || false;
var field_name = field_name || false;
var value = value || false;
return Utils.fetchValues(model, field_name, value);
},
/**
* @private
*/
_populateAddress: function (place, fillfields, delimiter) {
var place = place || false;
var fillfields = fillfields || this.fillfields;
var delimiter = delimiter || this.fillfields_delimiter;
return Utils.gmaps_populate_address(place, fillfields, delimiter);
},
/**
* @private
*/
_populatePlaces: function (place, fillfields) {
var place = place || false;
var fillfields = fillfields || this.fillfields;
return Utils.gmaps_populate_places(place, fillfields);
},
/**
* @private
*/
_getCountryState: function (model, country, state) {
var model = model || false;
var country = country || false;
var state = state || false;
return Utils.fetchCountryState(model, country, state);
},
/**
* @private
*/
_setGeolocation: function (latitude, longitude) {
var res = {};
if (_.intersection(_.keys(this.record.fields), [this.lat, this.lng]).length == 2) {
res[this.lat] = latitude;
res[this.lng] = longitude;
}
return res;
},
/**
* @private
*/
_onUpdateWidgetFields: function (changes) {
var changes = changes || {};
this.trigger_up('field_changed', {
dataPointID: this.dataPointID,
changes: changes,
viewType: this.viewType,
});
},
/**
* @override
*/
_renderEdit: function () {
this._super.apply(this, arguments);
if (this.isValid()) {
this._initGplacesAutocomplete();
}
},
/**
* @override
*/
destroy: function () {
if (this.places_autocomplete) {
this.places_autocomplete.unbindAll();
}
// Remove all PAC container in DOM if any
$('.pac-container').remove();
return this._super();
}
});
var GplaceAddressAutocompleteField = GplaceAutocomplete.extend({
setDefault: function () {
this._super.apply(this, arguments);
this.fillfields = {
[this.address_form.street]: ['street_number', 'route'],
[this.address_form.street2]: ['administrative_area_level_3', 'administrative_area_level_4', 'administrative_area_level_5'],
[this.address_form.city]: ['locality', 'administrative_area_level_2'],
[this.address_form.zip]: 'postal_code',
[this.address_form.state_id]: 'administrative_area_level_1',
[this.address_form.country_id]: 'country'
};
},
/**
* @override
*/
prepareWidgetOptions: function () {
if (this.mode === 'edit' && this.attrs.options) {
if (this.attrs.options.hasOwnProperty('fillfields')) {
this.fillfields = _.defaults({}, this.attrs.options.fillfields, this.fillfields);
}
}
this._super();
},
/**
* Get fields attributes
* @override
*/
getFillFieldsType: function () {
var self = this;
var field;
var res = this._super();
if (this._isValid) {
_.each(this.fillfields, function (val, name) {
if (_.contains(self._type_relations, self.record.fields[name].type)) {
field = {
name: name,
type: self.record.fields[name].type,
relation: self.record.fields[name].relation
};
res.push(field);
} else {
field = {
name: name,
type: self.record.fields[name].type,
relation: false
};
res.push(field);
}
});
}
return res;
},
_initGplacesAutocomplete: function () {
var self = this;
if (!this.places_autocomplete) {
this.places_autocomplete = new google.maps.places.Autocomplete(this.$input.get(0), {
types: ['address']
});
}
// When the user selects an address from the dropdown, populate the address fields in the form.
this.place_listener = this.places_autocomplete.addListener('place_changed', function () {
var requests = [];
var place = this.getPlace();
if (place.hasOwnProperty('address_components')) {
var google_address = self._populateAddress(place);
_.each(self.target_fields, function (field) {
requests.push(self._prepareValue(field.relation, field.name, google_address[field.name]));
});
var partner_geometry = self._setGeolocation(place.geometry.location.lat(), place.geometry.location.lng());
_.each(partner_geometry, function (val, field) {
requests.push(self._prepareValue(false, field, val));
});
$.when.apply($, requests).done(function () {
var changes = {};
_.each(arguments, function (data, idx) {
_.each(data, function (val, key) {
if (typeof val === 'object') {
changes[key] = val;
} else {
changes[key] = self._parseValue(val);
}
});
});
if (!changes[self.address_form.state_id] &&
changes[self.address_form.country_id].hasOwnProperty('id') &&
google_address[self.address_form.state_id]) {
var state_id = _.find(self.target_fields, function(field) {
return field.name === self.address_form.state_id;
})
if (!state_id) return;
self._getCountryState(
state_id.relation,
changes[self.address_form.country_id].id,
google_address[self.address_form.state_id]
).then(function (result) {
var state = {
[self.address_form.state_id]: result.length == 1 ? result[0] : false,
}
_.extend(changes, state);
self._onUpdateWidgetFields(changes);
});
} else {
self._onUpdateWidgetFields(changes);
}
});
self.$input.val(google_address[self.name]);
}
});
},
/**
* @override
*/
isValid: function () {
this._super.apply(this, arguments);
var self = this,
unknown_fields;
unknown_fields = _.filter(_.keys(self.fillfields), function (field) {
return !self.record.fields.hasOwnProperty(field);
});
if (unknown_fields.length > 0) {
self.do_warn(_t('The following fields are invalid:'), _t('<ul><li>' + unknown_fields.join('</li><li>') + '</li></ul>'));
this._isValid = false;
}
return this._isValid;
},
/**
* @override
*/
destroy: function () {
if (this.places_autocomplete) {
google.maps.event.removeListener(this.place_listener);
google.maps.event.clearInstanceListeners(this.places_autocomplete);
}
return this._super();
}
});
var GplacesAutocompleteField = GplaceAutocomplete.extend({
setDefault: function () {
this._super.apply(this);
this.fillfields = {
general: {
name: 'name',
website: 'website',
phone: ['international_phone_number', 'formatted_phone_number']
},
address: {
[this.address_form.street]: ['street_number', 'route'],
[this.address_form.street2]: ['administrative_area_level_3', 'administrative_area_level_4', 'administrative_area_level_5'],
[this.address_form.city]: ['locality', 'administrative_area_level_2'],
[this.address_form.zip]: 'postal_code',
[this.address_form.state_id]: 'administrative_area_level_1',
[this.address_form.country_id]: 'country'
}
};
},
prepareWidgetOptions: function () {
if (this.mode === 'edit' && this.attrs.options) {
if (this.attrs.options.hasOwnProperty('fillfields')) {
if (this.attrs.options.fillfields.hasOwnProperty('address')) {
this.fillfields['address'] = _.defaults({}, this.attrs.options.fillfields.address, this.fillfields.address);
}
if (this.attrs.options.fillfields.hasOwnProperty('general')) {
this.fillfields['general'] = _.defaults({}, this.attrs.options.fillfields.general, this.fillfields.general);
}
if (this.attrs.options.fillfields.hasOwnProperty('geolocation')) {
this.fillfields['geolocation'] = this.attrs.options.fillfields.geolocation;
}
}
}
this._super();
},
getFillFieldsType: function () {
var self = this;
var field;
var res = this._super();
if (this._isValid) {
for (var option in this.fillfields) {
_.each(this.fillfields[option], function (val, name) {
if (_.contains(self._type_relations, self.record.fields[name].type)) {
field = {
name: name,
type: self.record.fields[name].type,
relation: self.record.fields[name].relation
};
res.push(field);
} else {
field = {
name: name,
type: self.record.fields[name].type,
relation: false
};
res.push(field);
}
});
}
}
return res;
},
_setGeolocation: function (lat, lng) {
var res = {};
if (this.lat && this.lng) {
var _res = this._super(lat, lng);
return _res;
} else if (this.fillfields.geolocation) {
_.each(this.fillfields.geolocation, function (alias, field) {
if (alias === 'latitude') {
res[field] = lat;
}
if (alias === 'longitude') {
res[field] = lng;
}
});
}
return res;
},
_initGplacesAutocomplete: function () {
var self = this;
this.places_autocomplete = new google.maps.places.Autocomplete(this.$input.get(0), {
types: ['establishment']
});
// When the user selects an address from the dropdown, populate the address fields in the form.
this.place_listener = this.places_autocomplete.addListener('place_changed', function () {
var values = {},
requests = [],
place;
place = this.getPlace();
if (place.hasOwnProperty('address_components')) {
// Get address
var google_address = self._populateAddress(place, self.fillfields.address, self.fillfields_delimiter);
_.extend(values, google_address);
// Get place (name, website, phone)
var google_place = self._populatePlaces(place, self.fillfields.general);
_.extend(values, google_place);
// Get place geolocation
var google_geolocation = self._setGeolocation(place.geometry.location.lat(), place.geometry.location.lng());
_.extend(values, google_geolocation);
_.each(self.target_fields, function (field) {
requests.push(self._prepareValue(field.relation, field.name, values[field.name]));
});
$.when.apply($, requests).done(function () {
var changes = {}
_.each(arguments, function (data, idx) {
_.each(data, function (val, key) {
if (typeof val === 'object') {
changes[key] = val;
} else {
changes[key] = self._parseValue(val);
}
});
});
if (!changes[self.address_form.state_id] &&
changes[self.address_form.country_id].hasOwnProperty('id') &&
google_address[self.address_form.state_id]) {
var state_id = _.find(self.target_fields, function(field) {
return field.name === self.address_form.state_id;
})
if (!state_id) return;
self._getCountryState(
state_id.relation,
changes[self.address_form.country_id].id,
google_address[self.address_form.state_id]
).then(function (result) {
var state = {
[self.address_form.state_id]: result.length == 1 ? result[0] : false,
}
_.extend(changes, state);
self._onUpdateWidgetFields(changes);
});
} else {
self._onUpdateWidgetFields(changes);
}
});
self.$input.val(place.name);
}
});
},
/**
* @override
*/
isValid: function () {
this._super.apply(this, arguments);
var self = this,
unknown_fields;
for (var option in this.fillfields) {
unknown_fields = _.filter(_.keys(this.fillfields[option]), function (field) {
return !self.record.fields.hasOwnProperty(field);
});
if (unknown_fields.length > 0) {
self.do_warn(_t('The following fields are invalid:'), _t('<ul><li>' + unknown_fields.join('</li><li>') + '</li></ul>'));
this._isValid = false;
}
}
return this._isValid;
},
/**
* @override
*/
destroy: function () {
if (this.places_autocomplete) {
google.maps.event.removeListener(this.place_listener);
google.maps.event.clearInstanceListeners(this.$input.get(0));
}
return this._super();
}
});
return {
GplacesAddressAutocompleteField: GplaceAddressAutocompleteField,
GplacesAutocompleteField: GplacesAutocompleteField
};
});

View File

@ -0,0 +1,164 @@
odoo.define('web_google_maps.Utils', function (require) {
'use strict';
var rpc = require("web.rpc");
var GOOGLE_PLACES_COMPONENT_FORM = {
street_number: 'long_name',
route: 'long_name',
intersection: 'short_name',
political: 'short_name',
country: 'short_name',
administrative_area_level_1: 'short_name',
administrative_area_level_2: 'short_name',
administrative_area_level_3: 'short_name',
administrative_area_level_4: 'short_name',
administrative_area_level_5: 'short_name',
colloquial_area: 'short_name',
locality: 'short_name',
ward: 'short_name',
sublocality_level_1: 'short_name',
sublocality_level_2: 'short_name',
sublocality_level_3: 'short_name',
sublocality_level_5: 'short_name',
neighborhood: 'short_name',
premise: 'short_name',
postal_code: 'short_name',
natural_feature: 'short_name',
airport: 'short_name',
park: 'short_name',
point_of_interest: 'long_name'
};
/**
* Mapping field with an alias
* key: alias
* value: field
*/
var ADDRESS_FORM = {
street: 'street',
street2: 'street2',
city: 'city',
zip: 'zip',
state_id: 'state_id',
country_id: 'country_id'
};
function fetchValues(model, field_name, value) {
var def = $.Deferred(),
res = {};
if (model && value) {
rpc.query({
'model': model,
'method': 'search_read',
'args': [['|', ['name', '=', value], ['code', '=', value]], ['display_name', ]]
}).then(function (record) {
res[field_name] = record.length == 1 ? record[0] : false;
def.resolve(res);
});
} else {
res[field_name] = value;
def.resolve(res);
}
return def;
}
function fetchCountryState(model, country, state) {
var def = $.Deferred();
if (country && state) {
rpc.query({
model: model,
method: 'search_read',
args: [[['country_id', '=', country], ['code', '=', state]], ['display_name']]
}).then(function(record) {
def.resolve(record);
});
} else {
def.resolve([]);
}
return def;
}
function gmaps_get_geolocation(place, options) {
if (!place) return {};
var vals = {};
_.each(options, function (alias, field) {
if (alias === 'latitude') {
vals[field] = place.geometry.location.lat();
} else if (alias === 'longitude') {
vals[field] = place.geometry.location.lng();
}
});
return vals;
}
function gmaps_populate_places(place, place_options) {
if (!place) return {};
var values = {},
vals;
_.each(place_options, function (option, field) {
if (option instanceof Array && !_.has(values, field)) {
vals = _.filter(_.map(option, function (opt) {
return place[opt] || false;
}));
values[field] = _.first(vals) || "";
} else {
values[field] = place[option] || "";
}
});
return values;
}
function gmaps_populate_address(place, address_options, delimiter) {
if (!place) return {};
var address_options = address_options || {},
fields_delimiter = delimiter || {
street: " ",
street2: ", "
},
fields_to_fill = {},
options, temp, dlmter, result = {};
// initialize object key and value
_.each(address_options, function (value, key) {
fields_to_fill[key] = [];
});
_.each(address_options, function (options, field) {
// turn all fields options into an Array
options = _.flatten([options]);
temp = {};
_.each(place.address_components, function (component) {
_.each(_.intersection(options, component.types), function (match) {
temp[match] = component[GOOGLE_PLACES_COMPONENT_FORM[match]] || false;
});
});
fields_to_fill[field] = _.map(options, function (item) {
return temp[item];
});
});
_.each(fields_to_fill, function (value, key) {
dlmter = fields_delimiter[key] || ' ';
if (key == 'city') {
result[key] = _.first(_.filter(value)) || '';
} else {
result[key] = _.filter(value).join(dlmter);
}
});
return result;
}
return {
'GOOGLE_PLACES_COMPONENT_FORM': GOOGLE_PLACES_COMPONENT_FORM,
'ADDRESS_FORM': ADDRESS_FORM,
'gmaps_populate_address': gmaps_populate_address,
'gmaps_populate_places': gmaps_populate_places,
'gmaps_get_geolocation': gmaps_get_geolocation,
'fetchValues': fetchValues,
'fetchCountryState': fetchCountryState
}
});

View File

@ -0,0 +1,101 @@
/* https://github.com/twbs/bootstrap/issues/4160 */
/* put google places autocomplete dropdown results above the bootstrap modal 1050 zindex. */
.pac-container {
z-index: 1051 !important;
}
.o_field_x2many_map {
margin-top: 10px;
}
.o_map_container {
min-height: 400px;
min-width: 100%;
height: 100%;
.o-flex-display();
.o_map_view {
.o-flex(1, 1, auto);
min-width: 0;
}
.o_map_left_sidenav {
left: 0;
top: 0;
bottom: 0;
max-width: 400px;
width: 400px;
position: absolute;
z-index: 1;
background-color: #f5f5f5;
overflow-x: hidden;
transition: 0.5s;
transform: translate3d(0, 0, 0);
-webkit-transform: translate3d(0, 0, 0);
&.closed {
transform: translate3d(-100%, 0, 0);
-webkit-transform: translate3d(-100%, 0, 0);
.sidenav-body .panel {
border: none;
}
}
}
.o_map_right_sidebar {
right: 0;
bottom: 0;
background-color: #f5f5f5;
&.closed {
width: 0;
transition: 0.5s;
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
&.open {
width: 250px;
transition: 0.5s;
transform: translateX(0);
-webkit-transform: translateX(0);
overflow-x: auto;
}
ul {
margin-top: 10px;
padding-left: 3px;
list-style: none;
font-size: 12px;
li {
padding-bottom: 8px;
span {
padding: 3px 6px;
&.total {
font-size: 10px;
background: #ddd;
border-radius: 3px;
}
}
}
}
}
.o_map_button {
cursor: pointer;
text-align: center;
width: auto;
padding: 0px 6px;
margin: 0px 15px 5px 0px;
background-color: #ffffff;
border-radius: 2px;
line-height: 25px;
font-family: 'Roboto,Arial,sans-serif';
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<div t-name="MapView.MapView" class="o_map_container">
<div class="o_map_view"></div>
<div class="o_map_right_sidebar closed"></div>
</div>
<t t-name="MapView.MapViewGroupInfo">
<ul t-if="widget.groups">
<t t-foreach="widget.groups" t-as="group">
<li>
<img t-att-src="group.marker"/>
<span class="title"><t t-esc="group.title"/></span>
<span class="total"><t t-esc="group.count"/></span>
</li>
</t>
</ul>
</t>
<t t-name="MapView.buttons">
<div class="o_list_buttons">
<t t-if="(widget.is_action_enabled !== undefined ? widget.is_action_enabled('create') : false) || !widget.isReadonly">
<button type="button" class="btn btn-primary btn-sm o-map-button-new" accesskey="c">
<t t-esc="create_text || _t('Create')"/>
</button>
</t>
<button type="button" class="btn btn-default btn-sm o-map-button-center-map">
<t t-esc="_t('Center Map')"/>
</button>
</div>
</t>
</template>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="web_google_maps.assets_gmaps">
<t t-set="google_maps_api_key" t-value="request.env['ir.config_parameter'].sudo().get_param('google.api_key_geocode')"/>
<t t-set="google_maps_lang_localization" t-value="request.env['ir.config_parameter'].sudo().get_param('google.lang_localization')"/>
<t t-set="google_maps_region_localization" t-value="request.env['ir.config_parameter'].sudo().get_param('google.region_localization')"/>
<t t-set="google_maps_libraries" t-value="request.env['ir.config_parameter'].sudo().get_param('google.maps_libraries')"/>
<t t-if="google_maps_api_key">
<script t-att-async="'async'" t-att-defer="'defer'" t-attf-src="https://maps.googleapis.com/maps/api/js?v=quarterly&amp;key=#{google_maps_api_key}&amp;libraries=#{google_maps_libraries}#{google_maps_lang_localization}#{google_maps_region_localization}"></script>
</t>
<t t-if="not google_maps_api_key">
<script t-att-async="'async'" t-att-defer="'defer'" t-attf-src="https://maps.googleapis.com/maps/api/js?v=quarterly&amp;libraries=#{google_maps_libraries}#{google_maps_lang_localization}#{google_maps_region_localization}"></script>
</t>
<script src="/web_google_maps/static/lib/markercluster/markerclusterer.js"></script>
</template>
<template id="webclient_bootstrap" name="webclient_bootstrap gmaps" inherit_id="web.webclient_bootstrap">
<xpath expr="//t[@t-call-assets='web.assets_common']" position="before">
<t t-call="web_google_maps.assets_gmaps"/>
</xpath>
</template>
<template id="assets_backend" name="web_google_maps assets backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/less" href="/web_google_maps/static/src/less/web_maps.less"/>
<script type="text/javascript" src="/web_google_maps/static/src/js/view/map/map_model.js"></script>
<script type="text/javascript" src="/web_google_maps/static/src/js/view/map/map_controller.js"></script>
<script type="text/javascript" src="/web_google_maps/static/src/js/view/map/map_renderer.js"></script>
<script type="text/javascript" src="/web_google_maps/static/src/js/view/map/map_view.js"></script>
<script type="text/javascript" src="/web_google_maps/static/src/js/view/view_registry.js"></script>
<script type="text/javascript" src="/web_google_maps/static/src/js/fields/relational_fields.js"></script>
<script type="text/javascript" src="/web_google_maps/static/src/js/widgets/utils.js"></script>
<script type="text/javascript" src="/web_google_maps/static/src/js/widgets/gplaces_autocomplete.js"></script>
<script type="text/javascript" src="/web_google_maps/static/src/js/widgets/fields_registry.js"></script>
</xpath>
</template>
</odoo>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_web_google_maps_config_settings" model="ir.ui.view">
<field name="name">view.web.google.config.settings</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[@name='integration']" position="after">
<div name="web_google_maps">
<h2>Google Maps View</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_right_pane">
<label string="Configure your Google Maps View"/>
<div class="text-muted">
<p>Set API keys and map localization</p>
<span>Visit the <a href="https://developers.google.com/maps/documentation/javascript/localization" target="_blank">page</a> about Localizing the Map</span>
</div>
<div class="content-group">
<div class="mt16">
<label for="google_maps_view_api_key" string="Api key"/>
<field name="google_maps_view_api_key"/>
</div>
<div class="mt16">
<label for="google_maps_theme" string="Theme"/>
<field name="google_maps_theme"/>
</div>
<div class="mt16">
<label for="google_maps_lang_localization" string="Language"/>
<field name="google_maps_lang_localization"/>
</div>
<div class="mt16" attrs="{'invisible': [('google_maps_lang_localization', 'in', [False, ''])]}">
<div class="text-muted">
If you set the language of the map, it's important to consider setting the region too. This helps ensure that your application complies with local laws.
</div>
<label for="google_maps_region_localization" string="Region"/>
<field name="google_maps_region_localization"/>
</div>
</div>
</div>
</div>
</div>
<h2> Google Maps Libraries </h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="google_maps_geometry"/>
</div>
<div class="o_setting_right_pane">
<label for="google_maps_geometry"/>
<div class="text-muted">
Geometry includes utility functions for calculating scalar geometric values (such as distance and area) on the surface of the earth.
Consult the <a href="https://developers.google.com/maps/documentation/javascript/geometry">Geometry library documentation</a> for more information.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="google_maps_places"/>
</div>
<div class="o_setting_right_pane">
<label for="google_maps_places"/>
<div class="text-muted">
Places enables your application to search for places such as establishments, geographic locations, or prominent points of interest, within a defined area.
Consult the <a href="https://developers.google.com/maps/documentation/javascript/places">Places library documentation</a> for more information.
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_res_partner_map" model="ir.ui.view">
<field name="name">view.res.partner.map</field>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<map class="o_res_partner_map" library="geometry" string="Map" lat="partner_latitude" lng="partner_longitude" colors="blue:company_type=='person';green:company_type=='company';">
<field name="id"/>
<field name="partner_latitude"/>
<field name="partner_longitude"/>
<field name="company_type"/>
<field name="color"/>
<field name="display_name"/>
<field name="title"/>
<field name="email"/>
<field name="parent_id"/>
<field name="is_company"/>
<field name="function"/>
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="mobile"/>
<field name="state_id"/>
<field name="category_id"/>
<field name="image_small"/>
<field name="type"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click o_res_partner_kanban">
<div class="o_kanban_image">
<t t-if="record.image_small.raw_value">
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.raw_value)"/>
</t>
<t t-if="!record.image_small.raw_value">
<t t-if="record.type.raw_value === 'delivery'">
<img t-att-src='_s + "/base/static/src/img/truck.png"' class="o_kanban_image oe_kanban_avatar_smallbox"/>
</t>
<t t-if="record.type.raw_value === 'invoice'">
<img t-att-src='_s + "/base/static/src/img/money.png"' class="o_kanban_image oe_kanban_avatar_smallbox"/>
</t>
<t t-if="record.type.raw_value != 'invoice' &amp;&amp; record.type.raw_value != 'delivery'">
<t t-if="record.is_company.raw_value === true">
<img t-att-src='_s + "/base/static/src/img/company_image.png"'/>
</t>
<t t-if="record.is_company.raw_value === false">
<img t-att-src='_s + "/base/static/src/img/avatar.png"'/>
</t>
</t>
</t>
</div>
<div class="oe_kanban_details">
<strong class="o_kanban_record_title oe_partner_heading">
<field name="display_name"/>
</strong>
<div class="o_kanban_tags_section oe_kanban_partner_categories">
<span class="oe_kanban_list_many2many">
<field name="category_id" widget="many2many_tags" options="{'color_field': 'color'}"/>
</span>
</div>
<ul>
<li t-if="record.parent_id.raw_value and !record.function.raw_value">
<field name="parent_id"/>
</li>
<li t-if="!record.parent_id.raw_value and record.function.raw_value">
<field name="function"/>
</li>
<li t-if="record.parent_id.raw_value and record.function.raw_value">
<field name="function"/> at <field name="parent_id"/>
</li>
<li t-if="record.city.raw_value and !record.country_id.raw_value">
<field name="city"/>
</li>
<li t-if="!record.city.raw_value and record.country_id.raw_value">
<field name="country_id"/>
</li>
<li t-if="record.city.raw_value and record.country_id.raw_value">
<field name="city"/>
, <field name="country_id"/>
</li>
<li t-if="record.email.raw_value" class="o_text_overflow">
<field name="email"/>
</li>
</ul>
<div class="oe_kanban_partner_links"/>
</div>
</div>
</t>
</templates>
</map>
</field>
</record>
<record id="base.action_partner_form" model="ir.actions.act_window">
<field name="view_mode">kanban,tree,form,map</field>
</record>
<record id="action_partner_form_view3" model="ir.actions.act_window.view">
<field eval="3" name="sequence"/>
<field name="view_mode">map</field>
<field name="view_id" ref="view_res_partner_map"/>
<field name="act_window_id" ref="base.action_partner_form"/>
</record>
</odoo>