summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2025-06-02 02:02:31 +0700
committerit-fixcomart <it@fixcomart.co.id>2025-06-02 02:02:31 +0700
commit00fb3752ece083d28accad7b0a5ec8971a1cbb34 (patch)
treeaa98a7bbce425e0600b2727a1da47c5ef6ffa82a
parentfe458043667bb7f1cde757659fefe0174252002d (diff)
parent1c0bec8dadc593348df9ca585dae13b8ff65c316 (diff)
<hafid> estimasi barang
-rwxr-xr-xindoteknik_custom/models/sale_order.py139
-rw-r--r--indoteknik_custom/models/stock_picking.py75
-rw-r--r--indoteknik_custom/views/stock_picking.xml7
3 files changed, 147 insertions, 74 deletions
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index aa1b4b49..a76d8011 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -249,7 +249,7 @@ class SaleOrder(models.Model):
string="Attachment Bukti Cancel", readonly=False,
)
nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3)
- shipping_option_id = fields.Many2one("shipping.option", string="Selected Service Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]")
+ shipping_option_id = fields.Many2one("shipping.option", string="Selected Service Option", help="Selected shipping option for delivery", tracking=True, domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]")
select_shipping_option = fields.Selection([
('biteship', 'Biteship'),
@@ -306,21 +306,18 @@ class SaleOrder(models.Model):
@api.onchange('carrier_id')
def _onchange_carrier_id(self):
+ # Jika record masih baru (belum disimpan), jangan jalankan onchange
+ if not self._origin or not self._origin.id:
+ return
+
+ self.shipping_option_id = False
+ # self.delivery_amt = 0
+ # self.delivery_service_type = False
+
if not self.carrier_id:
- self.shipping_option_id = False
- self.delivery_amt = 0
return {'domain': {'shipping_option_id': [('id', '=', -1)]}}
- # ✅ Lewati validasi jika carrier bukan Biteship
- if self.carrier_id.delivery_type != 'biteship':
- _logger.info(f"Carrier {self.carrier_id.name} bertipe custom ({self.carrier_id.delivery_type}), tidak divalidasi.")
- self.shipping_option_id = False
- self.delivery_amt = 0
- return {
- 'domain': {'shipping_option_id': [('id', '=', -1)]}
- }
-
- # Ambil provider dari rajaongkir_kurir
+ # Ambil provider dari mapping
self.env.cr.execute("""
SELECT name FROM rajaongkir_kurir
WHERE delivery_carrier_id = %s
@@ -329,59 +326,66 @@ class SaleOrder(models.Model):
result = self.env.cr.fetchone()
provider = result[0].lower() if result and result[0] else False
- # Fallback ke nama carrier jika tidak ada di rajaongkir_kurir
if not provider:
provider = self.carrier_id.name.lower().split()[0] if self.carrier_id.name else False
- _logger.info(f"Carrier changed to {self.carrier_id.name}, provider: {provider}")
+ _logger.info(f"[Carrier Changed] {self.carrier_id.name}, Detected Provider: {provider}")
- sale_order_id = self._origin.id if self._origin and self._origin.id else None
-
- # Cek shipping option untuk provider ini
- if sale_order_id:
- self.env.cr.execute("""
- SELECT COUNT(*) FROM shipping_option
- WHERE LOWER(provider) LIKE %s AND sale_order_id = %s
- """, (f'%{provider}%', sale_order_id))
- else:
- self.env.cr.execute("""
- SELECT COUNT(*) FROM shipping_option
- WHERE LOWER(provider) LIKE %s AND sale_order_id IS NULL
- """, (f'%{provider}%',))
+ sale_order_id = self._origin.id
+ # Cek apakah ada shipping_option yang cocok
+ self.env.cr.execute("""
+ SELECT COUNT(*) FROM shipping_option
+ WHERE LOWER(provider) LIKE %s AND sale_order_id = %s
+ """, (f'%{provider}%', sale_order_id))
count = self.env.cr.fetchone()[0]
- _logger.info(f"Found {count} shipping options for provider {provider}")
- # VALIDASI GAGAL
+ _logger.info(f"[Shipping Option Count] Provider: {provider} | SO ID: {sale_order_id} | Count: {count}")
+
if count == 0:
previous_carrier = self._origin.carrier_id if self._origin else False
+ previous_provider = False
+
self.carrier_id = previous_carrier
+ self.shipping_option_id = self._origin.shipping_option_id if self._origin else False
+
+ # Ambil kembali domain provider sebelumnya
+ if previous_carrier:
+ self.env.cr.execute("""
+ SELECT name FROM rajaongkir_kurir
+ WHERE delivery_carrier_id = %s LIMIT 1
+ """, (previous_carrier.id,))
+ prev_row = self.env.cr.fetchone()
+ previous_provider = prev_row[0].lower() if prev_row and prev_row[0] else previous_carrier.name.lower().split()[0]
+
+ fallback_domain = [('id', '=', -1)]
+ if previous_provider:
+ fallback_domain = [
+ '|',
+ '&', ('sale_order_id', '=', sale_order_id), ('provider', 'ilike', f'%{previous_provider}%'),
+ '&', ('sale_order_id', '=', False), ('provider', 'ilike', f'%{previous_provider}%')
+ ]
return {
'warning': {
- 'title': "Shipping Method Tidak Tersedia",
+ 'title': "Shipping Option Tidak Ditemukan",
'message': (
- f"Shipping method '{self.carrier_id.name}' tidak tersedia pada pengiriman ini.\n"
+ f"Layanan kurir tidak tersedia untuk pengiriman ini.\n"
f"Pilihan dikembalikan ke sebelumnya."
)
},
- 'domain': {'shipping_option_id': [('id', '=', -1)]}
+ 'domain': {'shipping_option_id': fallback_domain}
}
- # ✅ Valid, baru reset shipping_option dan delivery amount
- self.shipping_option_id = False
- self.delivery_amt = 0
-
+ # Jika data ada, kembalikan domain biasa
domain = [
'|',
'&', ('sale_order_id', '=', sale_order_id), ('provider', 'ilike', f'%{provider}%'),
'&', ('sale_order_id', '=', False), ('provider', 'ilike', f'%{provider}%')
]
- _logger.info(f"Final domain for shipping_option_id: {domain}")
return {'domain': {'shipping_option_id': domain}}
-
@api.onchange('shipping_option_id')
def _onchange_shipping_option_id(self):
if not self.shipping_option_id:
@@ -692,10 +696,12 @@ class SaleOrder(models.Model):
if weight_gram < 100:
weight_gram = 100
+ value = int(self.amount_untaxed or sum(line.price_subtotal for line in self.order_line))
+
items = [{
"name": "Paket Pesanan",
"description": f"Sale Order {self.name}",
- "value": int(self.amount_untaxed),
+ "value": value,
"weight": weight_gram,
"quantity": 1,
}]
@@ -807,6 +813,7 @@ class SaleOrder(models.Model):
if not shipping_options:
raise UserError(f"Tidak ada layanan pengiriman ditemukan untuk kode pos {destination_data.get('destination_postal_code', '')}. Mohon periksa kembali kode pos atau gunakan metode pengiriman lain.")
+ # Temukan shipping option yang cocok berdasarkan carrier_id
selected_option = None
if self.carrier_id:
@@ -818,44 +825,44 @@ class SaleOrder(models.Model):
courier_code = rajaongkir_kurir.name.lower()
carrier_name = self.carrier_id.name.lower()
- possible_codes = [
+ possible_codes = list({
courier_code,
carrier_name,
carrier_name.split()[0] if ' ' in carrier_name else carrier_name
- ]
+ })
- _logger.info(f"Mencari shipping option untuk kurir: {possible_codes}")
+ _logger.info(f"[MATCHING] Mencari shipping option untuk kurir: {possible_codes}")
for option in shipping_options:
- option_provider = option.provider.lower() if option.provider else ''
- option_name = option.name.lower() if option.name else ''
-
- for code in possible_codes:
- if code in option_provider or code in option_name:
- selected_option = option
- _logger.info(f"Menemukan shipping option yang cocok: {option.name}")
- break
+ option_provider = (option.provider or '').lower()
+ option_name = (option.name or '').lower()
- if selected_option:
+ if any(code in option_provider or code in option_name for code in possible_codes):
+ selected_option = option
+ _logger.info(f"[MATCHED] Shipping option cocok: {option.name}")
break
if not selected_option and shipping_options:
selected_option = shipping_options[0]
- _logger.info(f"Menggunakan opsi pertama: {selected_option.name}")
-
- # Ganti carrier_id otomatis sesuai provider dari shipping option
- provider = selected_option.provider.lower()
- self.env.cr.execute("""
- SELECT delivery_carrier_id FROM rajaongkir_kurir
- WHERE LOWER(name) = %s AND delivery_carrier_id IS NOT NULL
- LIMIT 1
- """, (provider,))
- row = self.env.cr.fetchone()
- matched_carrier_id = row[0] if row else False
- if matched_carrier_id:
- self.carrier_id = matched_carrier_id
- _logger.info(f"Carrier diganti otomatis ke ID {matched_carrier_id} berdasarkan provider {provider}")
+ _logger.info(f"[DEFAULT] Tidak ada yang cocok, pakai opsi pertama: {selected_option.name}")
+
+ # ❗ Ganti carrier_id hanya jika BELUM terisi sama sekali (contoh: user dari backend)
+ if not self.carrier_id:
+ provider = selected_option.provider.lower()
+ self.env.cr.execute("""
+ SELECT delivery_carrier_id FROM rajaongkir_kurir
+ WHERE LOWER(name) = %s AND delivery_carrier_id IS NOT NULL
+ LIMIT 1
+ """, (provider,))
+ row = self.env.cr.fetchone()
+ matched_carrier_id = row[0] if row else False
+ if matched_carrier_id:
+ self.carrier_id = matched_carrier_id
+ _logger.info(f"[AUTO-SET] Carrier diisi otomatis ke ID {matched_carrier_id} (provider: {provider})")
+ else:
+ _logger.warning(f"[WARNING] Provider {provider} tidak ditemukan di rajaongkir_kurir")
+ # Set shipping option dan nilai ongkir
if selected_option:
self.shipping_option_id = selected_option.id
self.delivery_amt = selected_option.price
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 54da700b..4517a941 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1551,18 +1551,77 @@ class StockPicking(models.Model):
except Exception as e :
_logger.error(f"Error fetching Biteship order for picking {self.id}: {str(e)}")
return { 'error': str(e) }
-
+
+ def action_sync_biteship_tracking(self):
+ for picking in self:
+ if not picking.biteship_id:
+ raise UserError("Tracking Biteship tidak tersedia.")
+
+ histori = picking.get_manifest_biteship()
+ updated_fields = {}
+ seen_logs = set()
+
+ manifests = sorted(histori.get("manifests", []), key=lambda m: m.get("datetime") or "")
+
+ for manifest in manifests:
+ status = manifest.get("status", "").lower()
+ dt_str = manifest.get("datetime")
+ desc = manifest.get("description")
+ dt = False
+
+ try:
+ dt = picking._convert_to_local_time(dt_str)
+ except Exception as e:
+ _logger.warning(f"[Biteship Sync] Gagal parse datetime: {e}")
+ continue
+
+ # Update tanggal ke field (pastikan naive datetime UTC)
+ if status == "picked" and dt and not picking.driver_departure_date:
+ updated_fields["driver_departure_date"] = fields.Datetime.to_string(dt)
+
+ if status == "delivered" and dt and not picking.driver_arrival_date:
+ updated_fields["driver_arrival_date"] = fields.Datetime.to_string(dt)
+
+ # Buat log unik dengan waktu lokal Asia/Jakarta
+ if dt and desc:
+ dt_local = pytz.utc.localize(dt).astimezone(pytz.timezone("Asia/Jakarta"))
+ desc_clean = ' '.join(desc.strip().split())
+ log_line = f"[TRACKING] {status} - {dt_local.strftime('%d %b %Y %H:%M')}: {desc_clean}"
+ if not picking._has_existing_log(log_line):
+ picking.message_post(body=log_line)
+ seen_logs.add(log_line)
+
+ if updated_fields:
+ picking.write(updated_fields)
+
+ def _has_existing_log(self, log_line):
+ self.ensure_one()
+ self.env.cr.execute("""
+ SELECT 1 FROM mail_message
+ WHERE model = %s AND res_id = %s
+ AND subtype_id IS NOT NULL
+ AND body ILIKE %s
+ LIMIT 1
+ """, (self._name, self.id, f"%{log_line}%"))
+ return self.env.cr.fetchone() is not None
+
def _convert_to_local_time(self, iso_date):
try:
- dt_with_tz = waktu.fromisoformat(iso_date)
- utc_dt = dt_with_tz.astimezone(pytz.utc)
-
+ from dateutil import parser
+ import pytz
+ if isinstance(iso_date, str):
+ waktu = parser.parse(iso_date)
+ else:
+ waktu = iso_date
+ if waktu.tzinfo is None:
+ waktu = waktu.replace(tzinfo=pytz.utc)
local_tz = pytz.timezone("Asia/Jakarta")
- local_dt = utc_dt.astimezone(local_tz)
-
- return local_dt.strftime("%Y-%m-%d %H:%M:%S")
+ local_dt = waktu.astimezone(local_tz)
+ utc_dt = local_dt.astimezone(pytz.utc).replace(tzinfo=None)
+ return utc_dt
except Exception as e:
- return str(e)
+ _logger.warning(f"[Biteship] Gagal konversi waktu lokal: {e}")
+ return False
def _map_status_biteship(self, status):
status_mapping = {
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index c916f2ef..9b16639c 100644
--- a/indoteknik_custom/views/stock_picking.xml
+++ b/indoteknik_custom/views/stock_picking.xml
@@ -62,6 +62,12 @@
string="Biteship"
type="object"
/>
+ <button name="action_sync_biteship_tracking"
+ type="object"
+ string="Lacak dari Biteship"
+ class="btn-primary"
+ attrs="{'invisible': [('biteship_id', '=', False)]}"
+ />
<button name="track_envio_shipment"
string="Tracking Envio"
type="object"
@@ -178,6 +184,7 @@
<field name="note_info"/>
<field name="responsible" />
<field name="carrier_id"/>
+ <field name="biteship_id" invisible="1"/>
<field name="out_code" attrs="{'invisible': [['out_code', '=', False]]}"/>
<field name="picking_code" attrs="{'invisible': [['picking_code', '=', False]]}"/>
<field name="picking_code" string="Picking code (akan digenerate ketika sudah di-validate)" attrs="{'invisible': [['picking_code', '!=', False]]}"/>