new functionality for tzm.at
|  | @ -0,0 +1,3 @@ | |||
| .DS_Store | ||||
| *.pyc | ||||
| 
 | ||||
|  | @ -0,0 +1,289 @@ | |||
| Web Google Maps | ||||
| ===============    | ||||
| 
 | ||||
| [](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' && 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     | ||||
| 
 | ||||
| [](https://ko-fi.com/P5P4FOM0)     | ||||
| *if you want to support me to keep this project maintained. Thanks :)* | ||||
|  | @ -0,0 +1,5 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| # License AGPL-3 | ||||
| from . import models | ||||
| from . import controllers | ||||
| from .hooks import uninstall_hook | ||||
|  | @ -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', | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| from . import main | ||||
|  | @ -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 | ||||
|  | @ -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> | ||||
|  | @ -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';") | ||||
|  | @ -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" | ||||
|  | @ -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 | ||||
|  | @ -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')]) | ||||
|  | @ -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')]) | ||||
|  | @ -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 = '®ion=%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 '' | ||||
| After Width: | Height: | Size: 108 KiB | 
| After Width: | Height: | Size: 24 KiB | 
|  | @ -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 < | ||||
|                     <a class="reference external" href="mailto:yopiangi@gmail.com">yopiangi@gmail.com</a>></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> | ||||
| After Width: | Height: | Size: 334 KiB | 
| After Width: | Height: | Size: 255 KiB | 
| After Width: | Height: | Size: 246 KiB | 
| After Width: | Height: | Size: 457 KiB | 
| After Width: | Height: | Size: 303 KiB | 
| After Width: | Height: | Size: 2.9 KiB | 
| After Width: | Height: | Size: 3.2 KiB | 
| After Width: | Height: | Size: 3.9 KiB | 
| After Width: | Height: | Size: 5.6 KiB | 
| After Width: | Height: | Size: 6.7 KiB | 
|  | @ -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; | ||||
| } | ||||
| After Width: | Height: | Size: 313 B | 
| After Width: | Height: | Size: 185 B | 
| After Width: | Height: | Size: 217 B | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 1.0 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 1.0 KiB | 
| After Width: | Height: | Size: 1.0 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 313 B | 
| After Width: | Height: | Size: 198 B | 
| After Width: | Height: | Size: 217 B | 
| After Width: | Height: | Size: 231 B | 
|  | @ -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(); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
|  | @ -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; | ||||
| }); | ||||
|  | @ -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; | ||||
| 
 | ||||
| }); | ||||
|  | @ -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; | ||||
| 
 | ||||
| }); | ||||
|  | @ -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); | ||||
| 
 | ||||
| }); | ||||
|  | @ -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); | ||||
| 
 | ||||
| }); | ||||
|  | @ -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 | ||||
|     }; | ||||
| 
 | ||||
| }); | ||||
|  | @ -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 | ||||
|     } | ||||
| }); | ||||
|  | @ -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'; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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> | ||||
|  | @ -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&key=#{google_maps_api_key}&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&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> | ||||
|  | @ -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> | ||||
|  | @ -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' && 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> | ||||