diff options
| author | Indoteknik . <it@fixcomart.co.id> | 2025-05-19 00:11:58 +0700 |
|---|---|---|
| committer | Indoteknik . <it@fixcomart.co.id> | 2025-05-19 00:11:58 +0700 |
| commit | 0f7e05108336ea9de64348783cbec6e97edd1d64 (patch) | |
| tree | 12ceb3746d8b58769724884fd05074ecb92d2c84 | |
| parent | 2c4ab23bdf0ab6073195144879639a0dae863fde (diff) | |
(andri) mendapatkan tarif pengiriman
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 383 |
1 files changed, 335 insertions, 48 deletions
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 902c6db1..69982a47 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -261,11 +261,9 @@ class SaleOrder(models.Model): def _get_biteship_courier_codes(self): return [ - 'jne', 'pos', 'tiki', 'rpx', 'wahana', 'sicepat', 'jnt', 'sap', - 'ninja', 'lion', 'anteraja', 'paxel', 'idexpress', 'rex', 'ide', - 'sentral', 'first', 'dse', 'ncs', 'jdl', 'slis', 'expedito' + 'gojek','grab','deliveree','lalamove','jne','tiki','ninja','lion','rara','sicepat','jnt','pos','idexpress','rpx','wahana','jdl','pos','anteraja','sap','paxel','borzo' ] - + @api.onchange('select_shipping_option') def _onchange_select_shipping_option(self): self.shipping_option_id = False @@ -385,10 +383,78 @@ class SaleOrder(models.Model): ) def action_estimate_shipping(self): - if self.carrier_id.id in [1, 151]: - self.action_indoteknik_estimate_shipping() - return + # if self.carrier_id.id in [1, 151]: + # self.action_indoteknik_estimate_shipping() + # return + + if self.select_shipping_option == 'biteship': + return self.action_estimate_shipping_biteship() + elif self.carrier_id.id in [1, 151]: # ID untuk Indoteknik Delivery + return self.action_indoteknik_estimate_shipping() + else: + total_weight = 0 + missing_weight_products = [] + + for line in self.order_line: + if line.weight > 0: + total_weight += line.weight * line.product_uom_qty + line.product_id.weight = line.weight + else: + missing_weight_products.append(line.product_id.name) + + if missing_weight_products: + product_names = '<br/>'.join(missing_weight_products) + self.message_post(body=f"Produk berikut tidak memiliki berat:<br/>{product_names}") + + if total_weight == 0: + raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") + + destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id + if not destination_subsdistrict_id: + raise UserError("Gagal mendapatkan ID kota tujuan.") + + result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id) + if result: + shipping_options = [] + for courier in result['rajaongkir']['results']: + for cost_detail in courier['costs']: + service = cost_detail['service'] + description = cost_detail['description'] + etd = cost_detail['cost'][0]['etd'] + value = cost_detail['cost'][0]['value'] + shipping_options.append((service, description, etd, value, courier['code'])) + + self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() + + _logger.info(f"Shipping options: {shipping_options}") + + for service, description, etd, value, provider in shipping_options: + self.env["shipping.option"].create({ + "name": service, + "price": value, + "provider": provider, + "etd": etd, + "sale_order_id": self.id, + }) + + + self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id + + _logger.info(f"Shipping option SO ID: {self.shipping_option_id}") + + self.message_post( + body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>" + f"{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", + message_type="comment" + ) + + # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment") + else: + raise UserError("Gagal mendapatkan estimasi ongkir.") + + def _validate_for_shipping_estimate(self): + # Cek berat produk total_weight = 0 missing_weight_products = [] @@ -405,59 +471,280 @@ class SaleOrder(models.Model): if total_weight == 0: raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") + + # Validasi alamat pengiriman + if not self.real_shipping_id: + raise UserError("Alamat pengiriman (Real Delivery Address) harus diisi.") + + if not self.real_shipping_id.kota_id: + raise UserError("Kota pada alamat pengiriman harus diisi.") + + if not self.real_shipping_id.zip: + raise UserError("Kode pos pada alamat pengiriman harus diisi.") + + if not self.real_shipping_id.state_id: + raise UserError("Provinsi pada alamat pengiriman harus diisi.") + + return total_weight - destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id - if not destination_subsdistrict_id: - raise UserError("Gagal mendapatkan ID kota tujuan.") - - result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id) - if result: - shipping_options = [] - for courier in result['rajaongkir']['results']: - for cost_detail in courier['costs']: - service = cost_detail['service'] - description = cost_detail['description'] - etd = cost_detail['cost'][0]['etd'] - value = cost_detail['cost'][0]['value'] - shipping_options.append((service, description, etd, value, courier['code'])) - - self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() + def action_estimate_shipping_biteship(self): + # Validasi data + total_weight = self._validate_for_shipping_estimate() + + # Konversi berat ke gram untuk Biteship + weight_gram = int(total_weight * 1000) + if weight_gram < 100: + weight_gram = 100 # Minimum weight untuk Biteship + + # Persiapkan data item + items = [{ + "name": "Paket Pesanan", + "description": f"Sale Order {self.name}", + "value": int(self.amount_untaxed), + "weight": weight_gram, + "quantity": 1, + "height": 10, + "width": 10, + "length": 10 + }] + + # Coba dapatkan data alamat tujuan + destination_data = {} + origin_data = {} + + # Persiapkan data alamat asal (Gudang) + # 1. Data tetap gudang Bandengan + origin_data = { + "origin_postal_code" : 14440, + # "origin_latitude": -6.1753924, + # "origin_longitude": 106.7794935, + } - _logger.info(f"Shipping options: {shipping_options}") + # Coba dapatkan data alamat tujuan + if not self.real_shipping_id: + raise UserError("Alamat pengiriman (Real Delivery Address) harus diisi.") + + shipping_address = self.real_shipping_id + _logger.info(f"Shipping Address: {shipping_address}") + _logger.info(f"Shipping Address: {shipping_address.zip}") + + # # Coba dapatkan koordinat dulu (jika tersedia) + # if hasattr(shipping_address, 'partner_latitude') and hasattr(shipping_address, 'partner_longitude'): + # if shipping_address.partner_latitude and shipping_address.partner_longitude: + # destination_data = { + # "destination_latitude": shipping_address.partner_latitude, + # "destination_longitude": shipping_address.partner_longitude + # } + + # Jika koordinat tidak tersedia, gunakan kode pos + if not destination_data and shipping_address.zip: + destination_data = { + "destination_postal_code": shipping_address.zip + } + + # Jika kode pos tidak tersedia, gunakan alamat lengkap + # if not destination_data: + # # Buat alamat lengkap + # full_address = f"{shipping_address.street or ''}" + # if shipping_address.street2: + # full_address += f", {shipping_address.street2}" + + # destination_data = { + # "address": full_address, + # "area": shipping_address.kota_id.name if shipping_address.kota_id else "", + # "suburb": shipping_address.kecamatan_id.name if shipping_address.kecamatan_id else "", + # "province": shipping_address.state_id.name if shipping_address.state_id else "", + # "postcode": shipping_address.zip or "" + # } + + # Siapkan daftar kurir yang valid untuk Biteship + couriers = ','.join(self._get_biteship_courier_codes()) + + # Jika tidak ada data tujuan yang valid + if not destination_data: + raise UserError("Tidak dapat mengestimasikan ongkir: Alamat pengiriman tidak lengkap.") + + # Panggil API Biteship dengan format yang benar + result = self._call_biteship_api(origin_data, destination_data, items, couriers) + + if not result: + raise UserError("Gagal mendapatkan estimasi ongkir dari Biteship.") + + # Hapus shipping_option lama + self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() + + # Proses hasil API + shipping_options = [] + shipping_services = result.get('pricing', []) + + _logger.info(f"Ditemukan {len(shipping_services)} layanan pengiriman") + + for service in shipping_services: + courier_code = service.get('courier_code', '').lower() + courier_name = service.get('courier_name', '') + service_name = service.get('courier_service_name', '') + price = service.get('price', 0) + + _logger.info(f"Layanan: {courier_name} - {service_name}, Harga: {price}") + + # Lewati layanan dengan harga 0 + if not price: + _logger.warning(f"Melewati layanan dengan harga 0: {courier_name} - {service_name}") + continue - for service, description, etd, value, provider in shipping_options: - self.env["shipping.option"].create({ - "name": service, - "price": value, - "provider": provider, + # Format estimasi waktu + duration = service.get('duration', '') + shipment_range = service.get('shipment_duration_range', '') + shipment_unit = service.get('shipment_duration_unit', 'days') + + # Gunakan duration jika tersedia, jika tidak, buat dari range + if duration: + etd = duration + elif shipment_range: + etd = f"{shipment_range} {shipment_unit}" + else: + etd = "1-3 days" # Default fallback + + # Buat shipping option + try: + shipping_option = self.env["shipping.option"].create({ + "name": f"{courier_name} - {service_name}", + "price": price, + "provider": courier_code, "etd": etd, "sale_order_id": self.id, }) - - - self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id - - _logger.info(f"Shipping option SO ID: {self.shipping_option_id}") - - self.message_post( - body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>" - f"{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", - message_type="comment" - ) - - # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment") - - else: - raise UserError("Gagal mendapatkan estimasi ongkir.") + + shipping_options.append(shipping_option) + _logger.info(f"Berhasil membuat opsi pengiriman: {courier_name} - {service_name}") + except Exception as e: + _logger.error(f"Gagal membuat opsi pengiriman: {str(e)}") + + # Jika tidak ada opsi pengiriman + if not shipping_options: + raise UserError(f"Tidak ada layanan pengiriman ditemukan untuk kode pos {destination_data}. Mohon periksa kembali kode pos atau gunakan metode pengiriman lain.") + + # Set opsi pertama sebagai default + self.shipping_option_id = shipping_options[0].id + self.delivery_amt = shipping_options[0].price + + # Format pesan untuk log + option_list = '<br/>'.join([ + f"{opt.name}: Rp {opt.price:,.0f} ({opt.etd})" + for opt in shipping_options + ]) + + # Log hasil estimasi + self.message_post( + body=f"<b>Estimasi Ongkir Biteship (Kode Pos {origin_data} → {destination_data}):</b><br/>{option_list}", + message_type="comment" + ) + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Estimasi Ongkir Berhasil', + 'message': f'Mendapatkan {len(shipping_options)} opsi pengiriman', + 'type': 'success', + 'sticky': False, + } + } - def _call_biteship_api(self, total_weight, destination_zip): + def _call_biteship_api(self, origin_data, destination_data, items, couriers=None): + url = 'https://api.biteship.com/v1/rates/couriers' - api_key = 'biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUFQuSW5kb3Rla25payBEb3Rjb20gR2VtaWxhbmciLCJ1c2VySWQiOiI2NTIxMTU5YmRkNGIzZTAwMTUzNWIzMmYiLCJlbWFpbCI6InNhbGVzQGluZG90ZWtuaWsuY29tIiwibWVyY2hhbnRJZCI6IjY1MjExNTliOWNkN2JkMDAxNTJhNDM1ZSIsImlhdCI6MTcwNTE0Njc0NywiZXhwIjoxNzczMjk5MTQ3fQ.yR20t00sRR3fj-5eHI7G_rJPt9gv4Bi5iOIgB9sZ67c' - + api_key = 'biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA' + headers = { 'Authorization': api_key, 'Content-Type': 'application/json' } + + if not couriers: + couriers = ','.join(self._get_biteship_courier_codes()) + + # # Cek apakah kita menggunakan koordinat (paling akurat) + # has_origin_coords = ('origin_latitude' in origin_data and 'origin_longitude' in origin_data) + # has_dest_coords = ('destination_latitude' in destination_data and 'destination_longitude' in destination_data) + + # Cek apakah kita menggunakan kode pos + has_origin_postal = ('origin_postal_code' in origin_data) + has_dest_postal = ('destination_postal_code' in destination_data) + _logger.info(f"Origin Data: {has_origin_postal}") + _logger.info(f"Destination Data: {has_dest_postal}") + # Tentukan payload berdasarkan data yang tersedia + # if has_origin_coords and has_dest_coords: + # # Mode koordinat - paling akurat + # payload = { + # "origin_latitude": origin_data.get('origin_latitude'), + # "origin_longitude": origin_data.get('origin_longitude'), + # "destination_latitude": destination_data.get('destination_latitude'), + # "destination_longitude": destination_data.get('destination_longitude'), + # "couriers": couriers, + # "items": items + # } + # api_mode = "koordinat" + # elif has_origin_postal and has_dest_postal: + if has_origin_postal and has_dest_postal: + # Mode kode pos - fallback 1 + payload = { + "origin_postal_code": origin_data.get('origin_postal_code'), + "destination_postal_code": destination_data.get('destination_postal_code'), + "couriers": couriers, + "items": items + } + api_mode = "kode_pos" + # else: + # # Mode alamat lengkap - fallback 2 + # payload = { + # "origin": { + # "address": origin_data.get('address', ''), + # "area": origin_data.get('area', ''), + # "suburb": origin_data.get('suburb', ''), + # "province": origin_data.get('province', ''), + # "postcode": origin_data.get('postcode', '') + # }, + # "destination": { + # "address": destination_data.get('address', ''), + # "area": destination_data.get('area', ''), + # "suburb": destination_data.get('suburb', ''), + # "province": destination_data.get('province', ''), + # "postcode": destination_data.get('postcode', '') + # }, + # "couriers": couriers, + # "items": items + # } + # api_mode = "alamat_lengkap" + + try: + _logger.info(f"Calling Biteship API with mode: {api_mode}") + _logger.info(f"Payload: {payload}") + + response = requests.post(url, headers=headers, json=payload, timeout=30) + + # Log response untuk debugging + _logger.info(f"Biteship API Status Code: {response.status_code}") + if response.status_code != 200: + _logger.error(f"Biteship API Error Response: {response.text}") + + if response.status_code == 200: + result = response.json() + result['api_mode'] = api_mode # Tambahkan info mode API untuk referensi + return result + else: + error_msg = response.text + _logger.error(f"Error calling Biteship API: {response.status_code} - {error_msg}") + return False + except requests.exceptions.Timeout: + _logger.error("Timeout connecting to Biteship API") + return False + except requests.exceptions.ConnectionError: + _logger.error("Connection error to Biteship API") + return False + except Exception as e: + _logger.error(f"Exception calling Biteship API: {str(e)}") + return False def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' |
