diff options
Diffstat (limited to 'indoteknik_custom/models/stock_picking.py')
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 151 |
1 files changed, 133 insertions, 18 deletions
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 31c45531..e6506a0b 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1,10 +1,11 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.float_utils import float_is_zero -from datetime import datetime +from datetime import timedelta, datetime from itertools import groupby -import pytz, datetime, requests, json -from odoo.http import request +import pytz, requests, json, requests +from dateutil import parser +import datetime class StockPicking(models.Model): @@ -105,7 +106,7 @@ class StockPicking(models.Model): ('no', 'Nothing to Invoice') ], string='Invoice Status', related="sale_id.invoice_status") note_return = fields.Text(string="Note Return", help="Catatan untuk kirim barang kembali") - + state_reserve = fields.Selection([ ('waiting', 'Waiting For Fullfilment'), ('ready', 'Ready to Ship'), @@ -114,6 +115,120 @@ class StockPicking(models.Model): ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") notee = fields.Text(string="Note") + # Envio Tracking Section + envio_id = fields.Char(string="Envio ID", readonly=True) + envio_code = fields.Char(string="Envio Code", readonly=True) + envio_ref_code = fields.Char(string="Envio Reference Code", readonly=True) + envio_eta_at = fields.Datetime(string="Estimated Time of Arrival (ETA)", readonly=True) + envio_ata_at = fields.Datetime(string="Actual Time of Arrival (ATA)", readonly=True) + envio_etd_at = fields.Datetime(string="Estimated Time of Departure (ETD)", readonly=True) + envio_atd_at = fields.Datetime(string="Actual Time of Departure (ATD)", readonly=True) + envio_received_by = fields.Char(string="Received By", readonly=True) + envio_status = fields.Char(string="Status", readonly=True) + envio_cod_value = fields.Float(string="COD Value", readonly=True) + envio_cod_status = fields.Char(string="COD Status", readonly=True) + envio_logs = fields.Text(string="Logs", readonly=True) + envio_latest_message = fields.Text(string="Latest Log Message", readonly=True) + envio_latest_recorded_at = fields.Datetime(string="Log Recorded At", readonly=True) + envio_latest_latitude = fields.Float(string="Log Latitude", readonly=True) + envio_latest_longitude = fields.Float(string="Log Longitude", readonly=True) + tracking_by = fields.Many2one('res.users', string='Tracking By', readonly=True, tracking=True) + + def _convert_to_wib(self, date_str): + """ + Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB) + """ + if not date_str: + return False + try: + utc_time = datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%SZ') + wib_time = utc_time + timedelta(hours=7) + return wib_time.strftime('%d-%m-%Y %H:%M:%S') + except ValueError: + raise UserError(f"Format waktu tidak sesuai: {date_str}") + + def _convert_to_datetime(self, date_str): + """Mengonversi string waktu dari API ke datetime.""" + if not date_str: + return False + try: + # Format waktu dengan milidetik + date = datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S.%fZ') + return date + except ValueError: + try: + # Format waktu tanpa milidetik + date = datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%SZ') + return date + except ValueError: + raise UserError(f"Format waktu tidak sesuai: {date_str}") + + def track_envio_shipment(self): + pickings = self.env['stock.picking'].search([ + ('picking_type_code', '=', 'outgoing'), + ('state', '=', 'done'), + ('carrier_id', '=', 151) + ]) + for picking in pickings: + if not picking.name: + raise UserError("Name pada stock.picking tidak ditemukan.") + + # API URL dan headers + url = f"https://api.envio.co.id/v1/tracking/distribution?code={picking.name}" + headers = { + 'Authorization': 'Bearer JZ0Seh6qpYJAC3CJHdhF7sPqv8B/uSSfZe1VX5BL?vPYdo', + 'Content-Type': 'application/json', + } + + try: + # Request ke API + response = requests.get(url, headers=headers, timeout=10) + response.raise_for_status() # Raise error jika status code bukan 200 + response_data = response.json() + + # Validasi jika respons tidak sesuai format yang diharapkan + if not response_data or "data" not in response_data: + raise UserError("Respons API tidak sesuai format yang diharapkan.") + + data = response_data.get("data") + if not data: + continue + + # Menyimpan data ke field masing-masing + picking.envio_id = data.get("id") + picking.envio_code = data.get("code") + picking.envio_ref_code = data.get("ref_code") + picking.envio_eta_at = self._convert_to_datetime(data.get("eta_at")) + picking.envio_ata_at = self._convert_to_datetime(data.get("ata_at")) + picking.envio_etd_at = self._convert_to_datetime(data.get("etd_at")) + picking.envio_atd_at = self._convert_to_datetime(data.get("atd_at")) + picking.envio_received_by = data.get("received_by") + picking.envio_status = data.get("status") + picking.envio_cod_value = data.get("cod_value", 0.0) + picking.envio_cod_status = data.get("cod_status") + + # Menyimpan log terbaru + logs = data.get("logs", []) + if logs and isinstance(logs, list) and logs[0]: + latest_log = logs[0] + picking.envio_latest_message = latest_log.get("message", "Log kosong.") + picking.envio_latest_recorded_at = self._convert_to_datetime(latest_log.get("recorded_at")) + picking.envio_latest_latitude = latest_log.get("latitude", 0.0) + picking.envio_latest_longitude = latest_log.get("longitude", 0.0) + + picking.tracking_by = self.env.user.id + ata_at_str = data.get("ata_at") + envio_ata = self._convert_to_datetime(data.get("ata_at")) + + picking.driver_arrival_date = envio_ata + if data.get("status") != 'delivered': + picking.driver_arrival_date = False + picking.envio_ata_at = False + except requests.exceptions.RequestException as e: + raise UserError(f"Terjadi kesalahan saat menghubungi API Envio: {str(e)}") + except Exception as e: + raise UserError(f"Kesalahan tidak terduga: {str(e)}") + def action_send_to_biteship(self): url = "https://api.biteship.com/v1/orders" api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" @@ -142,7 +257,7 @@ class StockPicking(models.Model): ('order_id', '=', self.sale_id.id), ('product_id', '=', move_line.product_id.id) ], limit=1) - + if order_line: items_data_instant.append({ "name": order_line.product_id.name, @@ -193,10 +308,10 @@ class StockPicking(models.Model): return response.json() else: raise UserError(f"Error saat mengirim ke Biteship: {response.content}") - + @api.constrains('driver_departure_date') def constrains_driver_departure_date(self): - if not self.date_doc_kirim: + if not self.date_doc_kirim: self.date_doc_kirim = self.driver_departure_date @api.constrains('arrival_time') @@ -229,7 +344,7 @@ class StockPicking(models.Model): current_time = datetime.datetime.utcnow() self.date_unreserve = current_time # self.check_state_reserve() - + return res # def check_state_reserve(self): @@ -241,7 +356,7 @@ class StockPicking(models.Model): # for rec in do: # rec.state_reserve = 'ready' # rec.date_reserved = datetime.datetime.utcnow() - + # for line in rec.move_ids_without_package: # if line.product_uom_qty > line.reserved_availability: # rec.state_reserve = 'waiting' @@ -253,19 +368,19 @@ class StockPicking(models.Model): ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'outgoing') ]) - + for picking in pickings: fullfillments = self.env['sales.order.fullfillment'].search([ ('sales_order_id', '=', picking.sale_id.id) ]) - + picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - + if any(rec.reserved_from not in ['Inventory On Hand', 'Reserved from stock', 'Free Stock'] for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' - + def _create_approval_notification(self, approval_role): title = 'Warning' message = f'Butuh approval sales untuk unreserved' @@ -470,7 +585,7 @@ class StockPicking(models.Model): raise UserError('Harus Purchasing yang Ask Return') def calculate_line_no(self): - + for picking in self: name = picking.group_id.name for move in picking.move_ids_without_package: @@ -524,7 +639,7 @@ class StockPicking(models.Model): and quant.inventory_quantity < line.product_uom_qty ): raise UserError('Quantity reserved lebih besar dari quantity onhand di product') - + def check_qty_done_stock(self): for line in self.move_line_ids_without_package: def check_qty_per_inventory(self, product, location): @@ -537,16 +652,16 @@ class StockPicking(models.Model): return quant.quantity return 0 - + qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) if line.qty_done > qty_onhand: - raise UserError(f'{line.product_id.display_name} : Quantity Done melebihi Quantity Onhand') + raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") - + if self.picking_type_code == 'internal': self.check_qty_done_stock() |
