From a921017a829ebef8442740fac964260d98566e6a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 12 Jun 2025 19:26:46 +0700 Subject: (andri) try gmaps sebagai pengganti openstreetmaps --- indoteknik_custom/models/res_partner.py | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index eeb8b67d..82aa1134 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' @@ -583,3 +585,78 @@ class ResPartner(models.Model): 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: + # address = self.reverse_geocode(rec.latitude, rec.longtitude) + # if not address: + # continue + + # updates = { + # 'street': address.get('road') or '', + # 'zip': address.get('postcode') or '', + # 'city': address.get('city') or address.get('town') or address.get('village') or '', + # } + + # # Kelurahan (vit.kelurahan) + # if address.get('suburb'): + # kel = self.env['vit.kelurahan'].search([ + # ('name', 'ilike', address['suburb']) + # ], limit=1) + # if kel: + # updates['kelurahan_id'] = kel.id + + # # Kecamatan (vit.kecamatan) + # kec_nama = address.get('district') or address.get('village') + # if kec_nama: + # kec = self.env['vit.kecamatan'].search([ + # ('name', 'ilike', kec_nama) + # ], limit=1) + # if kec: + # updates['kecamatan_id'] = kec.id + + # # Kota (vit.kota) + # kota_nama = address.get('city') or address.get('town') + # if kota_nama: + # kota = self.env['vit.kota'].search([ + # ('name', 'ilike', kota_nama) + # ], limit=1) + # if kota: + # updates['kota_id'] = kota.id + + # # Provinsi (res.country.state) + # if address.get('state'): + # state = self.env['res.country.state'].search([ + # ('name', 'ilike', address['state']) + # ], limit=1) + # if state: + # updates['state_id'] = state.id + + # # Negara (res.country) + # if address.get('country_code'): + # country = self.env['res.country'].search([ + # ('code', '=', address['country_code'].upper()) + # ], limit=1) + # if country: + # updates['country_id'] = country.id + + # rec.write(updates) + + + + # def reverse_geocode(self, lat, lon): + # try: + # url = f'https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}' + # headers = { + # 'User-Agent': 'Odoo/1.0 (andrifebriyadiputra@gmail.com)', # WAJIB: ganti dengan email domain kamu + # } + # response = requests.get(url, headers=headers, timeout=5) + # if response.ok: + # data = response.json() + # return data.get('address', {}) + # else: + # _logger.warning("Reverse geocode failed with status %s: %s", response.status_code, response.text) + # except Exception as e: + # _logger.exception("Exception during reverse geocode: %s", e) + # return {} -- cgit v1.2.3 From 0e7fdb8ea85c53de2c8ad5fa8674c5fbc489e45a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 13 Jun 2025 10:58:27 +0700 Subject: (andri) bisa ubah alamat via ubah pinpoin langsung --- indoteknik_custom/models/res_partner.py | 164 ++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 74 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 82aa1134..6ef5698c 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -188,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: @@ -199,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): @@ -579,84 +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: - # address = self.reverse_geocode(rec.latitude, rec.longtitude) - # if not address: - # continue - - # updates = { - # 'street': address.get('road') or '', - # 'zip': address.get('postcode') or '', - # 'city': address.get('city') or address.get('town') or address.get('village') or '', - # } - - # # Kelurahan (vit.kelurahan) - # if address.get('suburb'): - # kel = self.env['vit.kelurahan'].search([ - # ('name', 'ilike', address['suburb']) - # ], limit=1) - # if kel: - # updates['kelurahan_id'] = kel.id - - # # Kecamatan (vit.kecamatan) - # kec_nama = address.get('district') or address.get('village') - # if kec_nama: - # kec = self.env['vit.kecamatan'].search([ - # ('name', 'ilike', kec_nama) - # ], limit=1) - # if kec: - # updates['kecamatan_id'] = kec.id - - # # Kota (vit.kota) - # kota_nama = address.get('city') or address.get('town') - # if kota_nama: - # kota = self.env['vit.kota'].search([ - # ('name', 'ilike', kota_nama) - # ], limit=1) - # if kota: - # updates['kota_id'] = kota.id - - # # Provinsi (res.country.state) - # if address.get('state'): - # state = self.env['res.country.state'].search([ - # ('name', 'ilike', address['state']) - # ], limit=1) - # if state: - # updates['state_id'] = state.id - - # # Negara (res.country) - # if address.get('country_code'): - # country = self.env['res.country'].search([ - # ('code', '=', address['country_code'].upper()) - # ], limit=1) - # if country: - # updates['country_id'] = country.id - - # rec.write(updates) - - - - # def reverse_geocode(self, lat, lon): - # try: - # url = f'https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}' - # headers = { - # 'User-Agent': 'Odoo/1.0 (andrifebriyadiputra@gmail.com)', # WAJIB: ganti dengan email domain kamu - # } - # response = requests.get(url, headers=headers, timeout=5) - # if response.ok: - # data = response.json() - # return data.get('address', {}) - # else: - # _logger.warning("Reverse geocode failed with status %s: %s", response.status_code, response.text) - # except Exception as e: - # _logger.exception("Exception during reverse geocode: %s", e) - # return {} + 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'), + } -- cgit v1.2.3 From 80355d9de0d079ec2f1004efac0377b8c4bfa0eb Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 13 Jun 2025 15:04:06 +0700 Subject: (andri) add validasi tidak bisa pilih kurir instan bila alamat pengiriman belum pinpoint --- indoteknik_custom/models/sale_order.py | 83 ++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 35 deletions(-) (limited to 'indoteknik_custom/models') 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') -- cgit v1.2.3