summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIT Fixcomart <it@fixcomart.co.id>2025-06-13 08:06:50 +0000
committerIT Fixcomart <it@fixcomart.co.id>2025-06-13 08:06:50 +0000
commit655512eee7016f2e321b9994455f87a4c8e91884 (patch)
tree361752527dde4be9c136f37a97ebec80f5dbfe4d
parent8ac5d556a6686c6b81d5e9178bff5d308e8f176f (diff)
parent80355d9de0d079ec2f1004efac0377b8c4bfa0eb (diff)
Merged in try-gmaps (pull request #333)
Try gmaps
-rw-r--r--ab_openstreetmap/__manifest__.py2
-rw-r--r--ab_openstreetmap/static/src/js/googlemap_widget.js96
-rw-r--r--ab_openstreetmap/static/src/js/openstreetmap_widget.js106
-rw-r--r--ab_openstreetmap/static/src/xml/googlemap_template.xml7
-rw-r--r--ab_openstreetmap/static/src/xml/openstreetmap_template.xml8
-rw-r--r--ab_openstreetmap/views/templates.xml20
-rw-r--r--indoteknik_api/controllers/api_v1/partner.py2
-rw-r--r--indoteknik_custom/models/res_partner.py93
-rwxr-xr-xindoteknik_custom/models/sale_order.py83
-rw-r--r--indoteknik_custom/views/res_partner.xml7
10 files changed, 258 insertions, 166 deletions
diff --git a/ab_openstreetmap/__manifest__.py b/ab_openstreetmap/__manifest__.py
index 745e57ab..9d76c34c 100644
--- a/ab_openstreetmap/__manifest__.py
+++ b/ab_openstreetmap/__manifest__.py
@@ -12,6 +12,6 @@
'category': 'Uncategorized',
'version': '0.1',
'depends': ['base'],
- "qweb": ['static/src/xml/openstreetmap_template.xml'],
+ "qweb": ['static/src/xml/googlemap_template.xml'],
'data': ['views/templates.xml'],
}
diff --git a/ab_openstreetmap/static/src/js/googlemap_widget.js b/ab_openstreetmap/static/src/js/googlemap_widget.js
new file mode 100644
index 00000000..4c48d564
--- /dev/null
+++ b/ab_openstreetmap/static/src/js/googlemap_widget.js
@@ -0,0 +1,96 @@
+odoo.define("ab_openstreetmap.googlemap_widget", function (require) {
+ "use strict";
+
+ const AbstractField = require("web.AbstractField");
+ const fieldRegistry = require("web.field_registry");
+ const rpc = require("web.rpc");
+
+ const GoogleMapWidget = AbstractField.extend({
+ template: "googlemap_template",
+
+ start: async function () {
+ await this._super(...arguments);
+
+ this._waitForElement("#mapid", async () => {
+ const apiKey = await rpc.query({
+ model: "ir.config_parameter",
+ method: "get_param",
+ args: ["google.maps.api_key"],
+ });
+ const mapId = await rpc.query({
+ model: "ir.config_parameter",
+ method: "get_param",
+ args: ["google.maps.map_id"],
+ });
+ this._loadGoogleMaps(apiKey, mapId);
+ });
+ },
+
+ _waitForElement: function (selector, callback) {
+ const el = document.querySelector(selector);
+ if (el && el.offsetHeight > 0 && el.offsetWidth > 0) {
+ callback();
+ } else {
+ setTimeout(() => this._waitForElement(selector, callback), 100);
+ }
+ },
+
+ _loadGoogleMaps: function (apiKey, mapId) {
+ if (!window.google || !window.google.maps) {
+ const script = document.createElement("script");
+ script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&v=weekly&libraries=marker`;
+ script.async = true;
+ script.defer = true;
+ script.onload = () => this._initMap(mapId);
+ document.head.appendChild(script);
+ } else {
+ this._initMap(mapId);
+ }
+ },
+
+ _initMap: async function (mapId) {
+ const lat = parseFloat(this.recordData.latitude) || -6.2;
+ const lng = parseFloat(this.recordData.longtitude) || 106.816666;
+ const edit = this.mode === "edit";
+
+ const mapEl = document.getElementById("mapid");
+ if (!mapEl) return;
+
+ const { Map } = await google.maps.importLibrary("maps");
+ const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
+
+ const map = new Map(mapEl, {
+ center: { lat, lng },
+ zoom: 15,
+ mapId: mapId,
+ });
+
+ const marker = new AdvancedMarkerElement({
+ map,
+ position: { lat, lng },
+ gmpDraggable: edit,
+ title: "Lokasi",
+ });
+
+ if (edit) {
+ marker.addListener("dragend", () => {
+ const pos = marker.position;
+ this.trigger_up("field_changed", {
+ dataPointID: this.dataPointID,
+ changes: {
+ latitude: pos.lat.toString(),
+ longtitude: pos.lng.toString(),
+ },
+ viewType: this.viewType,
+ });
+ });
+ }
+ },
+
+ isSet: function () {
+ return true;
+ },
+ });
+
+ fieldRegistry.add("googlemap", GoogleMapWidget);
+});
diff --git a/ab_openstreetmap/static/src/js/openstreetmap_widget.js b/ab_openstreetmap/static/src/js/openstreetmap_widget.js
deleted file mode 100644
index c84a2293..00000000
--- a/ab_openstreetmap/static/src/js/openstreetmap_widget.js
+++ /dev/null
@@ -1,106 +0,0 @@
-odoo.define("ab_openstreetmap.openstreetmap_widget", function (require) {
- "use strict";
-
- const fieldRegistry = require("web.field_registry");
- const AbstractField = require("web.AbstractField");
-
- const OpenStreetMapWidget = AbstractField.extend({
- template: "openstreetmap_template",
-
- start: function () {
- const self = this;
- return this._super.apply(this, arguments).then(() => {
- setTimeout(() => {
- self._renderMapWhenReady();
- }, 100);
- });
- },
-
- _renderMapWhenReady: function () {
- const self = this;
- const check = () => {
- const container = document.getElementById("mapid");
- if (container && container.offsetWidth > 0 && container.offsetHeight > 0) {
- self._initMap();
- } else {
- setTimeout(check, 100);
- }
- };
- check();
- },
-
- _initMap: function () {
- const self = this;
- const container = document.getElementById("mapid");
-
- // Bersihkan Leaflet instance sebelumnya jika ada
- if (container && container._leaflet_id) {
- container._leaflet_id = null;
- }
-
- let lat = self.recordData.latitude;
- let lng = self.recordData.longtitude;
-
- if (!lat && !lng) {
- lat = -6.2349;
- lng = 106.9896;
- }
-
- const map = L.map("mapid").setView([lat, lng], 13);
-
- L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
- attribution:
- '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
- }).addTo(map);
-
- const edit = self.mode === "edit";
- const marker = L.marker([lat, lng], { draggable: edit }).addTo(map);
-
- // Simpan koordinat saat marker digeser
- marker.on("dragend", function (e) {
- const latlng = e.target._latlng;
- self.trigger_up("field_changed", {
- dataPointID: self.dataPointID,
- changes: {
- latitude: latlng.lat.toString(),
- longtitude: latlng.lng.toString(),
- },
- viewType: self.viewType,
- });
- });
-
- // Fitur cari lokasi (geocoder)
- if (edit) {
- const geocode = L.Control.geocoder({ defaultMarkGeocode: false }).addTo(map);
- geocode.on("markgeocode", function (e) {
- const lat = e.geocode.center.lat;
- const lng = e.geocode.center.lng;
- map.flyTo([lat, lng]);
- marker.setLatLng(new L.LatLng(lat, lng));
- self.trigger_up("field_changed", {
- dataPointID: self.dataPointID,
- changes: {
- latitude: lat.toString(),
- longtitude: lng.toString(),
- },
- viewType: self.viewType,
- });
- });
- }
-
- // Force render ulang map
- const interval = setInterval(() => {
- if (map && map._size.x > 0) {
- clearInterval(interval);
- }
- window.dispatchEvent(new Event("resize"));
- }, 500);
- },
-
- isSet: function () {
- return true;
- },
- });
-
- fieldRegistry.add("openstreetmap", OpenStreetMapWidget);
-});
diff --git a/ab_openstreetmap/static/src/xml/googlemap_template.xml b/ab_openstreetmap/static/src/xml/googlemap_template.xml
new file mode 100644
index 00000000..53aef261
--- /dev/null
+++ b/ab_openstreetmap/static/src/xml/googlemap_template.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+ <t t-name="googlemap_template">
+ <!-- <div id="mapid" style="height: 400px; width: 100%;"></div> -->
+ <div t-attf-id="mapid" style="height: 400px; width: 100%;" ></div>
+ </t>
+</templates>
diff --git a/ab_openstreetmap/static/src/xml/openstreetmap_template.xml b/ab_openstreetmap/static/src/xml/openstreetmap_template.xml
deleted file mode 100644
index 82672748..00000000
--- a/ab_openstreetmap/static/src/xml/openstreetmap_template.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates id="template" xml:space="preserve">
- <t t-name="openstreetmap_template">
- <div style="width:100%">
- <div id="mapid" style="height: 500px;"/>
- </div>
- </t>
-</templates> \ No newline at end of file
diff --git a/ab_openstreetmap/views/templates.xml b/ab_openstreetmap/views/templates.xml
index ed82ae84..1f5a729b 100644
--- a/ab_openstreetmap/views/templates.xml
+++ b/ab_openstreetmap/views/templates.xml
@@ -1,13 +1,9 @@
<odoo>
- <data>
- <template id="assets_backend" inherit_id="web.assets_backend">
- <xpath expr="." position="inside">
- <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
- <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
- <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
- <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
- <script type="text/javascript" src="/ab_openstreetmap/static/src/js/openstreetmap_widget.js"/>
- </xpath>
- </template>
- </data>
-</odoo> \ No newline at end of file
+ <data>
+ <template id="assets_backend" inherit_id="web.assets_backend">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/ab_openstreetmap/static/src/js/googlemap_widget.js"/>
+ </xpath>
+ </template>
+ </data>
+</odoo>
diff --git a/indoteknik_api/controllers/api_v1/partner.py b/indoteknik_api/controllers/api_v1/partner.py
index c0f8f630..37ae8d5f 100644
--- a/indoteknik_api/controllers/api_v1/partner.py
+++ b/indoteknik_api/controllers/api_v1/partner.py
@@ -1,6 +1,6 @@
from .. import controller
from odoo import http
-from odoo.http import request
+from odoo.http import request, Response
from odoo import fields
import json
import base64
diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py
index eeb8b67d..6ef5698c 100644
--- a/indoteknik_custom/models/res_partner.py
+++ b/indoteknik_custom/models/res_partner.py
@@ -4,6 +4,8 @@ from datetime import datetime
from odoo.http import request
import re
import requests
+import logging
+_logger = logging.getLogger(__name__)
class GroupPartner(models.Model):
_name = 'group.partner'
@@ -186,6 +188,10 @@ class ResPartner(models.Model):
def write(self, vals):
res = super(ResPartner, self).write(vals)
+
+ for rec in self:
+ if 'latitude' in vals or 'longtitude' in vals:
+ rec._update_address_from_coords()
#
# # if 'property_payment_term_id' in vals:
# # if not self.env.user.is_accounting and vals['property_payment_term_id'] != 26:
@@ -197,6 +203,14 @@ class ResPartner(models.Model):
# # raise UserError('You name it')
#
return res
+
+ @api.model
+ def create(self, vals):
+ records = super().create(vals)
+ for rec in records:
+ if vals.get('latitude') and vals.get('longtitude'):
+ rec._update_address_from_coords()
+ return records
@api.constrains('name')
def _check_duplicate_name(self):
@@ -577,9 +591,88 @@ class ResPartner(models.Model):
result = response.json()
if result.get('results'):
location = result['results'][0]['geometry']['location']
+ formatted_address = result['results'][0].get('formatted_address', '')
+
rec.latitude = location['lat']
rec.longtitude = location['lng']
+ rec.address_map = formatted_address # ✅ Simpan alamat lengkap
else:
raise UserError("Tidak ditemukan hasil geocode untuk alamat tersebut.")
else:
raise UserError("Permintaan ke Google Maps gagal. Periksa koneksi internet atau API Key.")
+
+ def _update_address_from_coords(self):
+ for rec in self:
+ if rec.latitude and rec.longtitude:
+ try:
+ components, formatted, parsed = rec._reverse_geocode(rec.latitude, rec.longtitude)
+ if not parsed:
+ continue
+
+ updates = {
+ 'street': parsed.get('road') or '',
+ 'zip': parsed.get('postcode') or '',
+ 'address_map': formatted or '',
+ }
+
+ state = self.env['res.country.state'].search([('name', 'ilike', parsed.get('state'))], limit=1)
+ if state:
+ updates['state_id'] = state.id
+
+ kota = self.env['vit.kota'].search([('name', 'ilike', parsed.get('city'))], limit=1)
+ if kota:
+ updates['kota_id'] = kota.id
+
+ kec = self.env['vit.kecamatan'].search([('name', 'ilike', parsed.get('district'))], limit=1)
+ if kec:
+ updates['kecamatan_id'] = kec.id
+
+ kel = self.env['vit.kelurahan'].search([('name', 'ilike', parsed.get('suburb'))], limit=1)
+ if kel:
+ updates['kelurahan_id'] = kel.id
+
+ rec.update(updates)
+
+ except Exception as e:
+ raise UserError(f"Gagal update alamat dari koordinat: {str(e)}")
+
+
+ def _reverse_geocode(self, lat, lng):
+ api_key = self.env['ir.config_parameter'].sudo().get_param('google.maps.api_key')
+ if not api_key:
+ raise UserError("API Key Google Maps belum dikonfigurasi.")
+
+ url = f'https://maps.googleapis.com/maps/api/geocode/json?latlng={lat},{lng}&key={api_key}'
+ response = requests.get(url)
+ if response.ok:
+ result = response.json()
+ if result.get('results'):
+ components = result['results'][0]['address_components']
+ formatted = result['results'][0]['formatted_address']
+ return components, formatted, self._parse_google_address(components)
+ return {}, '', {}
+
+ def _parse_google_address(self, components):
+ def get(types):
+ for comp in components:
+ if types in comp['types']:
+ return comp['long_name']
+ return ''
+
+ street_number = get('street_number')
+ route = get('route')
+ neighborhood = get('neighborhood') # Bisa jadi nama RW
+ subpremise = get('subpremise') # Bisa jadi no kamar/ruko
+
+ # Gabungkan informasi jalan
+ road = " ".join(filter(None, [route, street_number, subpremise, neighborhood]))
+
+ return {
+ 'road': road.strip(),
+ 'postcode': get('postal_code'),
+ 'state': get('administrative_area_level_1'),
+ 'city': get('administrative_area_level_2') or get('locality'),
+ 'district': get('administrative_area_level_3'),
+ 'suburb': get('administrative_area_level_4'),
+ 'formatted': get('formatted_address'),
+ }
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index f53d375b..e54053ff 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -421,39 +421,19 @@ class SaleOrder(models.Model):
@api.onchange('carrier_id')
def _onchange_carrier_id(self):
- # ───────────────────────────────────────────────────────────────
- # 1. abaikan onchange kalau SO masih draft / belum tersimpan
- # ───────────────────────────────────────────────────────────────
if not self._origin or not self._origin.id:
return
- sale_order_id = self._origin.id # id SO asli (sudah tersimpan)
-
- # ───────────────────────────────────────────────────────────────
- # 2. Jika SO BELUM mempunyai satupun shipping.option ⇒
- # jangan lakukan validasi apa-apa; cukup reset field & domain
- # ───────────────────────────────────────────────────────────────
- total_so_options = self.env['shipping.option'].search_count(
- [('sale_order_id', '=', sale_order_id)]
- )
- if total_so_options == 0:
- # belum pernah estimasi ongkir ⇒ biarkan user ganti carrier
- self.shipping_option_id = False
- return {'domain': {'shipping_option_id': [('id', '=', -1)]}}
-
- # ───────────────────────────────────────────────────────────────
- # 3. (kode lama) – mulai validasi hanya jika sudah ada option
- # ───────────────────────────────────────────────────────────────
+ sale_order_id = self._origin.id
self.shipping_option_id = False
if not self.carrier_id:
return {'domain': {'shipping_option_id': [('id', '=', -1)]}}
- # cari provider dari mapping rajaongkir_kurir
+ # Ambil provider dari mapping
self.env.cr.execute("""
SELECT name FROM rajaongkir_kurir
- WHERE delivery_carrier_id = %s
- LIMIT 1
+ WHERE delivery_carrier_id = %s LIMIT 1
""", (self.carrier_id.id,))
row = self.env.cr.fetchone()
provider = row[0].lower() if row and row[0] else (
@@ -462,36 +442,69 @@ class SaleOrder(models.Model):
_logger.info(f"[Carrier Changed] {self.carrier_id.name}, Detected Provider: {provider}")
- # hitung berapa option yg match provider BARU
- self.env.cr.execute("""
- SELECT COUNT(*) FROM shipping_option
- WHERE LOWER(provider) LIKE %s AND sale_order_id = %s
- """, (f'%{provider}%', sale_order_id))
- matched = self.env.cr.fetchone()[0] or 0
+ # ───────────────────────────────────────────────────────────────
+ # Validasi koordinat untuk kurir instan
+ # ───────────────────────────────────────────────────────────────
+ instan_kurir = ['gojek', 'grab', 'lalamove', 'borzo', 'rara', 'deliveree']
+ if provider in instan_kurir:
+ lat = self.real_shipping_id.latitude
+ lng = self.real_shipping_id.longtitude
+ def is_invalid(val):
+ try:
+ return not val or float(val) == 0.0
+ except (ValueError, TypeError):
+ return True
+
+ if is_invalid(lat) or is_invalid(lng):
+ self.carrier_id = self._origin.carrier_id
+ self.shipping_option_id = self._origin.shipping_option_id or False
+ return {
+ 'warning': {
+ 'title': "Alamat Belum Pin Point",
+ 'message': (
+ "Kurir instan seperti Gojek, Grab, Lalamove, Borzo, Rara, dan Deliveree "
+ "membutuhkan alamat pengiriman yang sudah Pin Point.\n\n"
+ "Silakan tentukan lokasi dengan tepat pada Pin Point Location yang tersedia di kontak."
+ )
+ },
+ 'domain': {'shipping_option_id': [('id', '=', -1)]}
+ }
+
+ # ───────────────────────────────────────────────────────────────
+ # Baru cek apakah shipping option sudah ada
+ # ───────────────────────────────────────────────────────────────
+ total_so_options = self.env['shipping.option'].search_count([
+ ('sale_order_id', '=', sale_order_id)
+ ])
+ if total_so_options == 0:
+ return {'domain': {'shipping_option_id': [('id', '=', -1)]}}
+
+ # Validasi: apakah shipping option ada untuk provider ini?
+ matched = self.env['shipping.option'].search_count([
+ ('sale_order_id', '=', sale_order_id),
+ ('provider', 'ilike', provider),
+ ])
if matched == 0:
- # provider baru tidak ada di option yang SUDAH dibuat → kembalikan ke carrier lama
- prev_carrier = self._origin.carrier_id
- self.carrier_id = prev_carrier
+ self.carrier_id = self._origin.carrier_id
self.shipping_option_id = self._origin.shipping_option_id or False
return {
'warning': {
'title': "Shipping Option Tidak Ditemukan",
'message': (
- "Layanan kurir tidak tersedia untuk pengiriman ini.\n"
+ "Layanan kurir ini tidak tersedia pada pengiriman ini. "
"Pilihan dikembalikan ke sebelumnya."
)
},
'domain': {'shipping_option_id': [('id', '=', -1)]}
}
- # kalau match ada → set domain normal (hanya option dengan provider itu)
+ # Kalau semua valid, kembalikan domain normal
domain = [
'|',
'&', ('sale_order_id', '=', sale_order_id), ('provider', 'ilike', f'%{provider}%'),
'&', ('sale_order_id', '=', False), ('provider', 'ilike', f'%{provider}%')
]
-
return {'domain': {'shipping_option_id': domain}}
@api.onchange('shipping_option_id')
diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml
index 85ee5ef0..f739cf14 100644
--- a/indoteknik_custom/views/res_partner.xml
+++ b/indoteknik_custom/views/res_partner.xml
@@ -71,11 +71,12 @@
<button name="geocode_address" type="object" string="Get Pin Point Location" class="btn btn-primary"/>
</group>
<div style="margin: 16px 0;">
- <field name="map_view" widget="openstreetmap" nolabel="1" style="width: 100%;"/>
+ <field name="map_view" widget="googlemap" nolabel="1"/>
</div>
<group>
- <field name="latitude" readonly="1"/>
- <field name="longtitude" readonly="1"/>
+ <field name="address_map"/>
+ <field name="latitude" readonly="1" force_save="1"/>
+ <field name="longtitude" readonly="1" force_save="1"/>
</group>
</page>
</xpath>