summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIndoteknik . <it@fixcomart.co.id>2025-05-19 00:11:58 +0700
committerIndoteknik . <it@fixcomart.co.id>2025-05-19 00:11:58 +0700
commit0f7e05108336ea9de64348783cbec6e97edd1d64 (patch)
tree12ceb3746d8b58769724884fd05074ecb92d2c84
parent2c4ab23bdf0ab6073195144879639a0dae863fde (diff)
(andri) mendapatkan tarif pengiriman
-rwxr-xr-xindoteknik_custom/models/sale_order.py383
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'