From 3ca53ea0afef07cb79040c9f3c5aa29fa2355c90 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 27 Mar 2025 08:48:27 +0700 Subject: sale order delay --- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/sale_order_delay.py | 25 +++++++++++++++++++++++++ indoteknik_custom/models/stock_picking.py | 6 +++--- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 indoteknik_custom/models/sale_order_delay.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 37a49332..d5cededa 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -147,3 +147,4 @@ from . import ir_actions_report from . import barcoding_product from . import account_payment_register from . import stock_inventory +from . import sale_order_delay diff --git a/indoteknik_custom/models/sale_order_delay.py b/indoteknik_custom/models/sale_order_delay.py new file mode 100644 index 00000000..7440cd2d --- /dev/null +++ b/indoteknik_custom/models/sale_order_delay.py @@ -0,0 +1,25 @@ +from odoo import api, fields, models + + +class SaleOrderDelay(models.Model): + _name = 'sale.order.delay' + _description = 'Sale Order Delay' + _rec_name = 'so_number' + + so_number = fields.Char(string="SO Number", required=True) + days_delayed = fields.Integer(string="Day ", required=True) + status = fields.Selection([ + ('delayed', 'Delayed'), + ('on track', 'On Track'), + ('early', 'Early') + ], string='Status', required=True) + + @api.model + def create(self, vals): + vals['updated_at'] = fields.Datetime.now() + return super(SaleOrderDelay, self).create(vals) + + def write(self, vals): + vals['updated_at'] = fields.Datetime.now() + return super(SaleOrderDelay, self).write(vals) + \ No newline at end of file diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6c6cbaa1..a11bf29f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,8 +16,8 @@ import re _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" -_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" -# _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" +# _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" +_biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" @@ -1241,7 +1241,7 @@ class StockPicking(models.Model): 'confirmed' : 'Indoteknik telah melakukan permintaan pick-up', 'allocated' : 'Kurir akan melakukan pick-up pesanan', 'picking_up' : 'Kurir sedang dalam perjalanan menuju lokasi pick-up', - 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("name", ""), + 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("company", ""), 'on_hold' : 'Pesanan ditahan sementara karena masalah pengiriman', 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', 'delivered' : 'Pesanan telah sampai dan diterima oleh '+result.get("destination", {}).get("contact_name", "") -- cgit v1.2.3 From 87dad63e8ee0ace13b2d87bae26a045b80409572 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 27 Mar 2025 10:21:06 +0700 Subject: schduled --- indoteknik_custom/models/sale_order_delay.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order_delay.py b/indoteknik_custom/models/sale_order_delay.py index 7440cd2d..e2735a3c 100644 --- a/indoteknik_custom/models/sale_order_delay.py +++ b/indoteknik_custom/models/sale_order_delay.py @@ -14,6 +14,10 @@ class SaleOrderDelay(models.Model): ('early', 'Early') ], string='Status', required=True) + def update_delay(self): + query = "SELECT check_so_delay();" + self.env.cr.execute(query) + @api.model def create(self, vals): vals['updated_at'] = fields.Datetime.now() -- cgit v1.2.3 From 337e86c31691544a49a04e3f8d3a4b259e6b126a Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 10 Apr 2025 08:51:12 +0700 Subject: testing biteship dinamis eta --- indoteknik_custom/models/sale_order_delay.py | 10 +-- indoteknik_custom/models/stock_picking.py | 91 ++++++++++------------------ 2 files changed, 39 insertions(+), 62 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order_delay.py b/indoteknik_custom/models/sale_order_delay.py index e2735a3c..dfd94650 100644 --- a/indoteknik_custom/models/sale_order_delay.py +++ b/indoteknik_custom/models/sale_order_delay.py @@ -4,26 +4,28 @@ from odoo import api, fields, models class SaleOrderDelay(models.Model): _name = 'sale.order.delay' _description = 'Sale Order Delay' - _rec_name = 'so_number' + _primary_key = 'so_number' so_number = fields.Char(string="SO Number", required=True) - days_delayed = fields.Integer(string="Day ", required=True) + days_delayed = fields.Integer(string="Day Delayed or Erly") status = fields.Selection([ ('delayed', 'Delayed'), ('on track', 'On Track'), ('early', 'Early') ], string='Status', required=True) + _sql_constraints = [ + ('unique_so_number', 'unique(so_number)', 'SO Number must be unique!') + ] + def update_delay(self): query = "SELECT check_so_delay();" self.env.cr.execute(query) @api.model def create(self, vals): - vals['updated_at'] = fields.Datetime.now() return super(SaleOrderDelay, self).create(vals) def write(self, vals): - vals['updated_at'] = fields.Datetime.now() return super(SaleOrderDelay, self).write(vals) \ No newline at end of file diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a11bf29f..b741e94e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,8 +16,8 @@ import re _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" -# _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" -_biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" +_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" +# _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" @@ -189,63 +189,12 @@ class StockPicking(models.Model): biteship_id = fields.Char(string="Biteship Respon ID") biteship_tracking_id = fields.Char(string="Biteship Trackcking ID") biteship_waybill_id = fields.Char(string="Biteship Waybill ID") - # estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, related='sale_id.estimated_ready_ship_date') - # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) - # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) final_seq = fields.Float(string='Remaining Time') def schduled_update_sequance(self): query = "SELECT update_sequance_stock_picking();" self.env.cr.execute(query) - - - # @api.depends('estimated_ready_ship_date', 'state') - # def _callculate_sequance(self): - # for record in self: - # try : - # if record.estimated_ready_ship_date and record.state not in ('cancel', 'done'): - # rts = record.estimated_ready_ship_date - waktu.now() - # rts_days = rts.days - # rts_hours = divmod(rts.seconds, 3600) - - # estimated_by_erts = rts.total_seconds() / 3600 - - # record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours" - # record.countdown_hours = estimated_by_erts - # else: - # record.countdown_hours = 999999999999 - # record.countdown_ready_to_ship = False - # except Exception as e : - # _logger.error(f"Error calculating sequance {record.id}: {str(e)}") - # print(str(e)) - # return { 'error': str(e) } - - - # @api.depends('estimated_ready_ship_date', 'state') - # def _compute_countdown_hours(self): - # for record in self: - # if record.state in ('cancel', 'done') or not record.estimated_ready_ship_date: - # # Gunakan nilai yang sangat besar sebagai placeholder - # record.countdown_hours = 999999 - # else: - # delta = record.estimated_ready_ship_date - waktu.now() - # record.countdown_hours = delta.total_seconds() / 3600 - - # @api.depends('estimated_ready_ship_date', 'state') - # def _compute_countdown_ready_to_ship(self): - # for record in self: - # if record.state in ('cancel', 'done'): - # record.countdown_ready_to_ship = False - # else: - # if record.estimated_ready_ship_date: - # delta = record.estimated_ready_ship_date - waktu.now() - # days = delta.days - # hours, remainder = divmod(delta.seconds, 3600) - # record.countdown_ready_to_ship = f"{days} days, {hours} hours" - # record.countdown_hours = delta.total_seconds() / 3600 - # else: - # record.countdown_ready_to_ship = False def _compute_lalamove_image_html(self): for record in self: @@ -1182,6 +1131,8 @@ class StockPicking(models.Model): self.ensure_one() order = self.env['sale.order'].search([('name', '=', self.sale_id.name)], limit=1) + + sale_order_delay = self.env['sale.order.delay'].search([('so_number', '=', order.name)], limit=1) response = { 'delivery_order': { @@ -1197,13 +1148,24 @@ class StockPicking(models.Model): 'delivery_status': None, 'eta': self.generate_eta_delivery(), 'is_biteship': True if self.biteship_id else False, - 'manifests': self.get_manifests() + 'manifests': self.get_manifests(), + 'is_delay': True if sale_order_delay and sale_order_delay.status == 'delayed' else False } if self.biteship_id : histori = self.get_manifest_biteship() - eta_start = order.date_order + timedelta(days=order.estimated_arrival_days_start) - eta_end = order.date_order + timedelta(days=order.estimated_arrival_days) + day_start = order.estomated_arrival_days_start + day_end = order.estomated_arrival_days + if sale_order_delay: + if sale_order_delay.status == 'delayed': + day_start = day_start + sale_order_delay.days_delayed + day_end = day_end + sale_order_delay.days_delayed + elif sale_order_delay.status == 'early': + day_start = day_start - sale_order_delay.days_delayed + day_end = day_end - sale_order_delay.days_delayed + + eta_start = order.date_order + timedelta(days=day_start) + eta_end = order.date_order + timedelta(days=day_end) formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}" response['eta'] = formatted_eta response['manifests'] = histori.get("manifests", []) @@ -1297,18 +1259,31 @@ class StockPicking(models.Model): current_date = datetime.datetime.now() prepare_days = 3 start_date = self.driver_departure_date or self.create_date + + + add_day_start = 0 + add_day_end = 0 + sale_order_delay = self.env['sale.order.delay'].search([('so_number', '=', self.sale_id.name)], limit=1) + if sale_order_delay: + if sale_order_delay.status == 'delayed': + add_day_start = sale_order_delay.days_delayed + add_day_end = sale_order_delay.days_delayed + elif sale_order_delay.status == 'early': + add_day_start = -abs(sale_order_delay.days_delayed) + add_day_end = -abs(sale_order_delay.days_delayed) ead = self.sale_id.estimated_arrival_days or 0 if not self.driver_departure_date: ead += prepare_days ead_datetime = datetime.timedelta(days=ead) - fastest_eta = start_date + ead_datetime + fastest_eta = start_date + ead_datetime + datetime.timedelta(days=add_day_start) + if not self.driver_departure_date and fastest_eta < current_date: fastest_eta = current_date + ead_datetime longest_days = 3 - longest_eta = fastest_eta + datetime.timedelta(days=longest_days) + longest_eta = fastest_eta + datetime.timedelta(days=longest_days + add_day_end) format_time = '%d %b %Y' format_time_fastest = '%d %b' if fastest_eta.year == longest_eta.year else format_time -- cgit v1.2.3 From 62f9c93c02a1f8b12ecd7bf50f850c43dd7c2c49 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Fri, 11 Apr 2025 10:14:08 +0700 Subject: dirver departure date --- indoteknik_custom/models/stock_picking.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b741e94e..d7e8e0e8 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,8 +16,8 @@ import re _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" -_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" -# _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" +# _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" +_biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" @@ -1142,6 +1142,7 @@ class StockPicking(models.Model): 'receiver_name': '', 'receiver_city': '' }, + 'delivered_date': self.driver_departure_date or False, 'delivered': False, 'status': self.shipping_status, 'waybill_number': self.delivery_tracking_no or '', @@ -1154,8 +1155,8 @@ class StockPicking(models.Model): if self.biteship_id : histori = self.get_manifest_biteship() - day_start = order.estomated_arrival_days_start - day_end = order.estomated_arrival_days + day_start = order.estimated_arrival_days_start + day_end = order.estimated_arrival_days if sale_order_delay: if sale_order_delay.status == 'delayed': day_start = day_start + sale_order_delay.days_delayed @@ -1192,7 +1193,6 @@ class StockPicking(models.Model): "Content-Type": "application/json" } - manifests = [] try: @@ -1206,7 +1206,7 @@ class StockPicking(models.Model): 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("company", ""), 'on_hold' : 'Pesanan ditahan sementara karena masalah pengiriman', 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', - 'delivered' : 'Pesanan telah sampai dan diterima oleh '+result.get("destination", {}).get("contact_name", "") + 'delivered' : f'Pesanan telah sampai dan diterima oleh {result.get("destination", {}).get("contact_name", "")}' } if(result.get('success') == True): history = result.get("history", []) -- cgit v1.2.3 From ef00237c7b6b3aed4f6040d1f124199d3551561e Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Fri, 11 Apr 2025 14:50:53 +0700 Subject: expected delivery date manifest --- indoteknik_custom/models/stock_picking.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d7e8e0e8..54256299 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1137,15 +1137,15 @@ class StockPicking(models.Model): response = { 'delivery_order': { 'name': self.name, - 'carrier': self.carrier_id.name or '', - 'service' : order.delivery_service_type or '', + 'carrier': self.carrier_id.name or '-', + 'service' : order.delivery_service_type or '-', 'receiver_name': '', 'receiver_city': '' }, - 'delivered_date': self.driver_departure_date or False, + 'delivered_date': self.driver_departure_date.strftime('%d %b %Y') if self.driver_departure_date != False else '-', 'delivered': False, 'status': self.shipping_status, - 'waybill_number': self.delivery_tracking_no or '', + 'waybill_number': self.delivery_tracking_no or '-', 'delivery_status': None, 'eta': self.generate_eta_delivery(), 'is_biteship': True if self.biteship_id else False, @@ -1205,7 +1205,7 @@ class StockPicking(models.Model): 'picking_up' : 'Kurir sedang dalam perjalanan menuju lokasi pick-up', 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("company", ""), 'on_hold' : 'Pesanan ditahan sementara karena masalah pengiriman', - 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', + 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', 'delivered' : f'Pesanan telah sampai dan diterima oleh {result.get("destination", {}).get("contact_name", "")}' } if(result.get('success') == True): -- cgit v1.2.3 From 6eb0b48ad5c418f565efdf1a60d221a10465b0b8 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 14 Apr 2025 16:48:09 +0700 Subject: stock picking mapping --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 54256299..ba7a9452 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,8 +16,8 @@ import re _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" -# _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" -_biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" +_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" +# _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" -- cgit v1.2.3 From fb50d10576f2e5d16faba612dfd1565f7168f655 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 16 Apr 2025 14:33:31 +0700 Subject: FEEDBACK --- indoteknik_custom/models/stock_picking.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4a200ac5..f2b69b55 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1406,7 +1406,10 @@ class StockPicking(models.Model): "delivered": status } - return manifests + return { + "manifests": [], + "delivered": False + } except Exception as e : _logger.error(f"Error fetching Biteship order for picking {self.id}: {str(e)}") return { 'error': str(e) } -- cgit v1.2.3 From d9d8b9f3afc0ad60ca1199b08ab6e2836663a0de Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 24 Apr 2025 13:54:06 +0700 Subject: fixing revisi renca --- indoteknik_custom/models/sale_order.py | 22 +++++++++++++++++----- indoteknik_custom/models/stock_picking.py | 19 ++++++------------- 2 files changed, 23 insertions(+), 18 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c83ffd61..a7ee9db8 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -496,7 +496,7 @@ class SaleOrder(models.Model): @api.depends('date_order', 'estimated_arrival_days', 'state', 'estimated_arrival_days_start') def _compute_eta_date(self): - current_date = datetime.now().date() + current_date = datetime.now() for rec in self: if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start: rec.eta_date = current_date + timedelta(days=rec.estimated_arrival_days) @@ -507,7 +507,19 @@ class SaleOrder(models.Model): def get_days_until_next_business_day(self,start_date=None, *args, **kwargs): - today = start_date or datetime.today().date() + now = start_date or datetime.now() + + # Jika hanya diberikan tanggal (tanpa jam), asumsikan jam 00:00 + if isinstance(now, datetime): + order_datetime = now + else: + order_datetime = datetime.combine(now, datetime.min.time()) + + today = order_datetime.date() + + if order_datetime.time() > datetime.strptime("15:00", "%H:%M").time(): + today += timedelta(days=1) + offset = 0 # Counter jumlah hari yang ditambahkan holiday = self.env['hr.public.holiday'] @@ -568,13 +580,13 @@ class SaleOrder(models.Model): rec.expected_ready_to_ship = False return - current_date = datetime.now().date() + current_date = datetime.now() max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) if not rec.estimated_arrival_days: rec.estimated_arrival_days = sum_days @@ -597,7 +609,7 @@ class SaleOrder(models.Model): max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) eta_minimum = current_date + timedelta(days=sum_days) if expected_date < eta_minimum: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f2b69b55..aa616e62 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1442,8 +1442,10 @@ class StockPicking(models.Model): def generate_eta_delivery(self): current_date = datetime.datetime.now() - prepare_days = 3 - start_date = self.driver_departure_date or self.create_date + days_start = self.sale_id.estimated_arrival_days_start or self.sale_id.estimated_arrival_days + days_end = self.sale_id.estimated_arrival_days or (self.sale_id.estimated_arrival_days + 3) + start_date = self.sale_id.create_date + datetime.timedelta(days=days_start) + end_date = self.sale_id.create_date + datetime.timedelta(days=days_end) add_day_start = 0 @@ -1456,19 +1458,10 @@ class StockPicking(models.Model): elif sale_order_delay.status == 'early': add_day_start = -abs(sale_order_delay.days_delayed) add_day_end = -abs(sale_order_delay.days_delayed) - - ead = self.sale_id.estimated_arrival_days or 0 - if not self.driver_departure_date: - ead += prepare_days - - ead_datetime = datetime.timedelta(days=ead) - fastest_eta = start_date + ead_datetime + datetime.timedelta(days=add_day_start) - if not self.driver_departure_date and fastest_eta < current_date: - fastest_eta = current_date + ead_datetime + fastest_eta = start_date +datetime.timedelta(days=add_day_start + add_day_start) - longest_days = 3 - longest_eta = fastest_eta + datetime.timedelta(days=longest_days + add_day_end) + longest_eta = end_date + datetime.timedelta(days=add_day_end) format_time = '%d %b %Y' format_time_fastest = '%d %b' if fastest_eta.year == longest_eta.year else format_time -- cgit v1.2.3 From 914705630f61f2e02f15ee24a479191e945a0f22 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Sat, 26 Apr 2025 08:39:32 +0700 Subject: handle bugs additional time when checkout > 15.00 --- indoteknik_custom/models/sale_order.py | 49 ++++++++++++++++++------------- indoteknik_custom/models/stock_picking.py | 8 ++--- 2 files changed, 33 insertions(+), 24 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 3117a330..1e40d15e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -6,6 +6,7 @@ from datetime import datetime, timedelta import logging, random, string, requests, math, json, re, qrcode, base64 from io import BytesIO from collections import defaultdict +import pytz _logger = logging.getLogger(__name__) @@ -540,34 +541,42 @@ class SaleOrder(models.Model): rec.eta_date_start = False - def get_days_until_next_business_day(self,start_date=None, *args, **kwargs): - now = start_date or datetime.now() - - # Jika hanya diberikan tanggal (tanpa jam), asumsikan jam 00:00 - if isinstance(now, datetime): - order_datetime = now - else: - order_datetime = datetime.combine(now, datetime.min.time()) - - today = order_datetime.date() - - if order_datetime.time() > datetime.strptime("15:00", "%H:%M").time(): - today += timedelta(days=1) + def get_days_until_next_business_day(self, start_date=None, *args, **kwargs): + jakarta = pytz.timezone("Asia/Jakarta") + current = datetime.now(jakarta) - offset = 0 # Counter jumlah hari yang ditambahkan + offset = 0 + + # Gunakan current time kalau start_date tidak diberikan + if start_date is None: + start_date = current + + # Pastikan start_date pakai timezone Jakarta + if start_date.tzinfo is None: + start_date = jakarta.localize(start_date) + + # Jika sudah lewat jam 15:00, mulai dari hari berikutnya + batas_waktu = datetime.strptime("15:00", "%H:%M").time() + if start_date.time() > batas_waktu: + offset += 1 + + current_day = start_date.date() holiday = self.env['hr.public.holiday'] - while True : - today += timedelta(days=1) + while True: + # Tambah satu hari, cek apakah hari kerja + current_day += timedelta(days=1) offset += 1 - - if today.weekday() >= 5: + + # Lewati weekend + if current_day.weekday() >= 5: continue - is_holiday = holiday.search([("start_date", "=", today)]) + # Lewati hari libur + is_holiday = holiday.search([("start_date", "=", current_day)]) if is_holiday: continue - + break return offset diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 36129f00..38a1173c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -562,6 +562,10 @@ class StockPicking(models.Model): }) payload = { + "origin_coordinate" :{ + "latitude": -6.3031123, + "longitude" : 106.7794934999 + }, "reference_id " : self.sale_id.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', @@ -585,10 +589,6 @@ class StockPicking(models.Model): # Cek jika pengiriman instant atau same_day if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): payload.update({ - "origin_coordinate" :{ - "latitude": -6.3031123, - "longitude" : 106.7794934999 - }, "destination_coordinate" : { "latitude": self.real_shipping_id.latitude, "longitude": self.real_shipping_id.longtitude, -- cgit v1.2.3 From 509eb9406e6c48caf3e6366c3d8ac60643b71546 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Tue, 29 Apr 2025 09:09:05 +0700 Subject: fixing jam 15 --- indoteknik_custom/models/sale_order.py | 24 +++++++++++++++--------- indoteknik_custom/models/stock_picking.py | 1 - 2 files changed, 15 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 1e40d15e..91905037 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2,7 +2,7 @@ from re import search from odoo import fields, models, api, _ from odoo.exceptions import UserError, ValidationError -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import logging, random, string, requests, math, json, re, qrcode, base64 from io import BytesIO from collections import defaultdict @@ -566,16 +566,20 @@ class SaleOrder(models.Model): while True: # Tambah satu hari, cek apakah hari kerja current_day += timedelta(days=1) - offset += 1 # Lewati weekend - if current_day.weekday() >= 5: - continue + is_weekend = current_day.weekday() >= 5 + + if is_weekend: + offset += 1 + continue # Lewati hari libur is_holiday = holiday.search([("start_date", "=", current_day)]) + if is_holiday: - continue + offset += 1 + continue break @@ -588,7 +592,7 @@ class SaleOrder(models.Model): # Cek apakah SEMUA produk memiliki qty_free_bandengan >= qty_needed all_fast_products = all(product.product_id.qty_free_bandengan >= product.product_uom_qty for product in products) if all_fast_products: - return {'slatime': 1, 'include_instant': include_instant} + return {'slatime': 0, 'include_instant': include_instant} # Cari semua vendor pemenang untuk produk yang diberikan vendors = self.env['purchase.pricelist'].search([ @@ -623,7 +627,8 @@ class SaleOrder(models.Model): rec.expected_ready_to_ship = False return - current_date = datetime.now() + jakarta = pytz.timezone("Asia/Jakarta") + current_date = datetime.now(jakarta) max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) @@ -634,6 +639,7 @@ class SaleOrder(models.Model): rec.estimated_arrival_days = sum_days eta_date = current_date + timedelta(days=sum_days) + eta_date = eta_date.astimezone(timezone.utc).replace(tzinfo=None) rec.commitment_date = eta_date rec.expected_ready_to_ship = eta_date @@ -645,7 +651,7 @@ class SaleOrder(models.Model): def _validate_expected_ready_ship_date(self): for rec in self: if rec.expected_ready_to_ship and rec.commitment_date: - current_date = datetime.now().date() + current_date = datetime.now() # Hanya membandingkan tanggal saja, tanpa jam expected_date = rec.expected_ready_to_ship.date() @@ -655,7 +661,7 @@ class SaleOrder(models.Model): sum_days = max_slatime + self.get_days_until_next_business_day(current_date) eta_minimum = current_date + timedelta(days=sum_days) - if expected_date < eta_minimum: + if expected_date < eta_minimum.date(): rec.expected_ready_to_ship = eta_minimum raise ValidationError( "Tanggal 'Expected Ready to Ship' tidak boleh lebih kecil dari {}. Mohon pilih tanggal minimal {}." diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 38a1173c..39c74aa2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -523,7 +523,6 @@ class StockPicking(models.Model): raise UserError(f"Kesalahan tidak terduga: {str(e)}") def action_send_to_biteship(self): - if self.biteship_tracking_id: raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") -- cgit v1.2.3 From c77d250353dbed0ba1ec5cd8abd940ba95a011fc Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 30 Apr 2025 13:55:26 +0700 Subject: handle 15am dan holidays --- indoteknik_custom/models/sale_order.py | 45 +++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 17 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 91905037..25c1b3a8 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -541,12 +541,13 @@ class SaleOrder(models.Model): rec.eta_date_start = False - def get_days_until_next_business_day(self, start_date=None, *args, **kwargs): + def handling_order_after_3pm(self, start_date=None, *args, **kwargs): jakarta = pytz.timezone("Asia/Jakarta") current = datetime.now(jakarta) offset = 0 - + + # Gunakan current time kalau start_date tidak diberikan if start_date is None: start_date = current @@ -559,28 +560,31 @@ class SaleOrder(models.Model): batas_waktu = datetime.strptime("15:00", "%H:%M").time() if start_date.time() > batas_waktu: offset += 1 + + return offset + + def get_days_until_next_business_day(self, start_date=None, *args, **kwargs): + jakarta = pytz.timezone("Asia/Jakarta") + current = datetime.now(jakarta) + + offset = 0 + i = 0 current_day = start_date.date() holiday = self.env['hr.public.holiday'] - + while True: - # Tambah satu hari, cek apakah hari kerja - current_day += timedelta(days=1) - + # # Tambah satu hari, cek apakah hari kerja + current_day += timedelta(days=i) + i = 1 + # Lewati weekend is_weekend = current_day.weekday() >= 5 - - if is_weekend: - offset += 1 - continue - - # Lewati hari libur is_holiday = holiday.search([("start_date", "=", current_day)]) - - if is_holiday: + if is_weekend or is_holiday: offset += 1 continue - + break return offset @@ -633,8 +637,11 @@ class SaleOrder(models.Model): max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) + + days_after_3pm = self.handling_order_after_3pm(current_date) + date_after_3pm = current_date + timedelta(days=days_after_3pm) - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) + sum_days = max_slatime + self.get_days_until_next_business_day(date_after_3pm) if not rec.estimated_arrival_days: rec.estimated_arrival_days = sum_days @@ -658,7 +665,11 @@ class SaleOrder(models.Model): max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) + + days_after_3pm = self.handling_order_after_3pm(current_date) + date_after_3pm = current_date + timedelta(days=days_after_3pm) + + sum_days = max_slatime + self.get_days_until_next_business_day(date_after_3pm) eta_minimum = current_date + timedelta(days=sum_days) if expected_date < eta_minimum.date(): -- cgit v1.2.3 From 7897614cee8a347dfdd933df72db95859cb1a558 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 30 Apr 2025 16:43:38 +0700 Subject: fixing handle 15 pm, weekend, and holidays --- indoteknik_custom/models/product_template.py | 3 ++ indoteknik_custom/models/sale_order.py | 71 +++++++++++++++++++--------- 2 files changed, 51 insertions(+), 23 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index e6a01a04..56ae3087 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -341,6 +341,9 @@ class ProductTemplate(models.Model): 'search_key':[item_code], } response = requests.post(url, headers=headers, json=json_data) + if response.status_code != 200: + return 0 + datas = json.loads(response.text)['data'] qty = 0 for data in datas: diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 25c1b3a8..795bfa0b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -565,29 +565,58 @@ class SaleOrder(models.Model): def get_days_until_next_business_day(self, start_date=None, *args, **kwargs): jakarta = pytz.timezone("Asia/Jakarta") - current = datetime.now(jakarta) - + now = datetime.now(jakarta) + + if start_date is None: + start_date = now + + if start_date.tzinfo is None: + start_date = jakarta.localize(start_date) + + holiday = self.env['hr.public.holiday'] + batas_waktu = datetime.strptime("15:00", "%H:%M").time() + current_day = start_date.date() offset = 0 + + # Step 1: Lewat jam 15 → Tambah 1 hari + if start_date.time() > batas_waktu: + offset += 1 + + # Step 2: Hitung hari libur selama offset itu + i = 0 + total_days = 0 + while i < offset: + current_day += timedelta(days=1) + total_days += 1 + is_weekend = current_day.weekday() >= 5 + is_holiday = holiday.search([("start_date", "=", current_day)]) + if not is_weekend and not is_holiday: + i += 1 # hanya hitung hari kerja + + # Step 3: Tambah 1 hari masa persiapan gudang i = 0 + while i < 1: + current_day += timedelta(days=1) + total_days += 1 + is_weekend = current_day.weekday() >= 5 + is_holiday = holiday.search([("start_date", "=", current_day)]) + if not is_weekend and not is_holiday: + i += 1 - current_day = start_date.date() - holiday = self.env['hr.public.holiday'] - + # Step 4: Kalau current_day ternyata weekend/libur, cari hari kerja berikutnya while True: - # # Tambah satu hari, cek apakah hari kerja - current_day += timedelta(days=i) - i = 1 - - # Lewati weekend is_weekend = current_day.weekday() >= 5 is_holiday = holiday.search([("start_date", "=", current_day)]) if is_weekend or is_holiday: - offset += 1 - continue - - break + current_day += timedelta(days=1) + total_days += 1 + else: + break + + final_offset = (current_day - start_date.date()).days + return final_offset + - return offset def calculate_sla_by_vendor(self, products): product_ids = products.mapped('product_id.id') # Kumpulkan semua ID produk @@ -637,11 +666,9 @@ class SaleOrder(models.Model): max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) - - days_after_3pm = self.handling_order_after_3pm(current_date) - date_after_3pm = current_date + timedelta(days=days_after_3pm) - sum_days = max_slatime + self.get_days_until_next_business_day(date_after_3pm) + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) + sum_days -= 1 if not rec.estimated_arrival_days: rec.estimated_arrival_days = sum_days @@ -665,11 +692,9 @@ class SaleOrder(models.Model): max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) - - days_after_3pm = self.handling_order_after_3pm(current_date) - date_after_3pm = current_date + timedelta(days=days_after_3pm) - sum_days = max_slatime + self.get_days_until_next_business_day(date_after_3pm) + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) + sum_days -= 1 eta_minimum = current_date + timedelta(days=sum_days) if expected_date < eta_minimum.date(): -- cgit v1.2.3 From 4688c123005c7c8038c4d56ef25307e309ef815e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 13 May 2025 15:26:01 +0700 Subject: (andri) add field Selection Option dan menambahkan opsi biteship --- indoteknik_custom/models/sale_order.py | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 795bfa0b..90c32e2a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -249,6 +249,30 @@ class SaleOrder(models.Model): nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") + select_shipping_option = fields.Selection([ + ('biteship', 'Biteship'), + ('custom', 'Custom'), + ], string='Select Shipping Option', help="Select shipping option for delivery") + + @api.onchange('shipping_option_id') + def _onchange_shipping_option_id(self): + if self.shipping_option_id: + self.delivery_amt = self.shipping_option_id.price + + @api.onchange('select_shipping_option') + def _onchange_select_shipping_option(self): + # Reset shipping option when the shipping type changes + self.shipping_option_id = False + + if self.select_shipping_option == 'custom': + return {'domain': {'carrier_id': []}} + + elif self.select_shipping_option == 'biteship': + # Reset delivery amount as it will be calculated through Biteship API + self.delivery_amt = 0 + self.carrier_id = False + return {'domain': {'carrier_id': [('id', '=', -1)]}} + @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') def _check_total_margin_excl_third_party(self): for rec in self: @@ -404,6 +428,18 @@ class SaleOrder(models.Model): else: raise UserError("Gagal mendapatkan estimasi ongkir.") + def _call_biteship_api(self, total_weight, destination_zip): + + url = 'https://api.biteship.com/v1/rates/couriers' + api_key = 'biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUFQuSW5kb3Rla25payBEb3Rjb20gR2VtaWxhbmciLCJ1c2VySWQiOiI2NTIxMTU5YmRkNGIzZTAwMTUzNWIzMmYiLCJlbWFpbCI6InNhbGVzQGluZG90ZWtuaWsuY29tIiwibWVyY2hhbnRJZCI6IjY1MjExNTliOWNkN2JkMDAxNTJhNDM1ZSIsImlhdCI6MTcwNTE0Njc0NywiZXhwIjoxNzczMjk5MTQ3fQ.yR20t00sRR3fj-5eHI7G_rJPt9gv4Bi5iOIgB9sZ67c' + + headers = { + 'Authorization': api_key, + 'Content-Type': 'application/json' + } + + + def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' -- cgit v1.2.3 From 847a025e889ae9dcfe9ff97c913c9310695fc2c5 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Fri, 16 May 2025 08:46:53 +0700 Subject: handle is 3 pm --- indoteknik_custom/models/sale_order.py | 45 ++++++++++++---------------------- 1 file changed, 16 insertions(+), 29 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 795bfa0b..1d318c46 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2,7 +2,7 @@ from re import search from odoo import fields, models, api, _ from odoo.exceptions import UserError, ValidationError -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta, timezone, time import logging, random, string, requests, math, json, re, qrcode, base64 from io import BytesIO from collections import defaultdict @@ -541,28 +541,6 @@ class SaleOrder(models.Model): rec.eta_date_start = False - def handling_order_after_3pm(self, start_date=None, *args, **kwargs): - jakarta = pytz.timezone("Asia/Jakarta") - current = datetime.now(jakarta) - - offset = 0 - - - # Gunakan current time kalau start_date tidak diberikan - if start_date is None: - start_date = current - - # Pastikan start_date pakai timezone Jakarta - if start_date.tzinfo is None: - start_date = jakarta.localize(start_date) - - # Jika sudah lewat jam 15:00, mulai dari hari berikutnya - batas_waktu = datetime.strptime("15:00", "%H:%M").time() - if start_date.time() > batas_waktu: - offset += 1 - - return offset - def get_days_until_next_business_day(self, start_date=None, *args, **kwargs): jakarta = pytz.timezone("Asia/Jakarta") now = datetime.now(jakarta) @@ -577,9 +555,11 @@ class SaleOrder(models.Model): batas_waktu = datetime.strptime("15:00", "%H:%M").time() current_day = start_date.date() offset = 0 + is3pm = False # Step 1: Lewat jam 15 → Tambah 1 hari if start_date.time() > batas_waktu: + is3pm = True offset += 1 # Step 2: Hitung hari libur selama offset itu @@ -613,8 +593,8 @@ class SaleOrder(models.Model): else: break - final_offset = (current_day - start_date.date()).days - return final_offset + offset = (current_day - start_date.date()).days + return offset, is3pm @@ -666,13 +646,19 @@ class SaleOrder(models.Model): max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) - - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) + + offset , is3pm = self.get_days_until_next_business_day(current_date) + sum_days = max_slatime + offset sum_days -= 1 if not rec.estimated_arrival_days: rec.estimated_arrival_days = sum_days eta_date = current_date + timedelta(days=sum_days) + if is3pm: + eta_date = datetime.combine(eta_date, time(10, 0)) # jam 10:00 + eta_date = jakarta.localize(eta_date).astimezone(timezone.utc) # ubah ke UTC + + eta_date = eta_date.astimezone(timezone.utc).replace(tzinfo=None) rec.commitment_date = eta_date rec.expected_ready_to_ship = eta_date @@ -692,8 +678,9 @@ class SaleOrder(models.Model): max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) - - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) + + offset , is3pm = self.get_days_until_next_business_day(current_date) + sum_days = max_slatime + offset sum_days -= 1 eta_minimum = current_date + timedelta(days=sum_days) -- cgit v1.2.3 From 2c4ab23bdf0ab6073195144879639a0dae863fde Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 16 May 2025 14:45:54 +0700 Subject: (andri) revisi field shipping option --- indoteknik_custom/models/sale_order.py | 38 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 90c32e2a..902c6db1 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -259,19 +259,41 @@ class SaleOrder(models.Model): if self.shipping_option_id: self.delivery_amt = self.shipping_option_id.price + 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' + ] + @api.onchange('select_shipping_option') def _onchange_select_shipping_option(self): - # Reset shipping option when the shipping type changes self.shipping_option_id = False + self.delivery_amt = 0 + + biteship_courier_codes = self._get_biteship_courier_codes() + + # Cari carrier yang namanya mengandung kode Biteship + biteship_carrier_ids = [] + for code in biteship_courier_codes: + carriers = self.env['delivery.carrier'].search([ + ('name', 'ilike', code) + ]) + if carriers: + biteship_carrier_ids.extend(carriers.ids) + + # Hapus duplikat + biteship_carrier_ids = list(set(biteship_carrier_ids)) if self.select_shipping_option == 'custom': - return {'domain': {'carrier_id': []}} + # Tampilkan carrier yang bukan dari Biteship + domain = [('id', 'not in', biteship_carrier_ids)] if biteship_carrier_ids else [] + return {'domain': {'carrier_id': domain}} elif self.select_shipping_option == 'biteship': - # Reset delivery amount as it will be calculated through Biteship API - self.delivery_amt = 0 - self.carrier_id = False - return {'domain': {'carrier_id': [('id', '=', -1)]}} + # Tampilkan hanya carrier dari Biteship + domain = [('id', 'in', biteship_carrier_ids)] if biteship_carrier_ids else [] + return {'domain': {'carrier_id': domain}} @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') def _check_total_margin_excl_third_party(self): @@ -429,7 +451,6 @@ class SaleOrder(models.Model): raise UserError("Gagal mendapatkan estimasi ongkir.") def _call_biteship_api(self, total_weight, destination_zip): - url = 'https://api.biteship.com/v1/rates/couriers' api_key = 'biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUFQuSW5kb3Rla25payBEb3Rjb20gR2VtaWxhbmciLCJ1c2VySWQiOiI2NTIxMTU5YmRkNGIzZTAwMTUzNWIzMmYiLCJlbWFpbCI6InNhbGVzQGluZG90ZWtuaWsuY29tIiwibWVyY2hhbnRJZCI6IjY1MjExNTliOWNkN2JkMDAxNTJhNDM1ZSIsImlhdCI6MTcwNTE0Njc0NywiZXhwIjoxNzczMjk5MTQ3fQ.yR20t00sRR3fj-5eHI7G_rJPt9gv4Bi5iOIgB9sZ67c' @@ -438,9 +459,6 @@ class SaleOrder(models.Model): 'Content-Type': 'application/json' } - - - def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' headers = { -- cgit v1.2.3 From 0f7e05108336ea9de64348783cbec6e97edd1d64 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 19 May 2025 00:11:58 +0700 Subject: (andri) mendapatkan tarif pengiriman --- indoteknik_custom/models/sale_order.py | 383 ++++++++++++++++++++++++++++----- 1 file changed, 335 insertions(+), 48 deletions(-) (limited to 'indoteknik_custom/models') 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 = '
'.join(missing_weight_products) + self.message_post(body=f"Produk berikut tidak memiliki berat:
{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}
Detail Lain:
" + f"{'
'.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}
Detail Lain:
{'
'.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}
Detail Lain:
" - f"{'
'.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}
Detail Lain:
{'
'.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 = '
'.join([ + f"{opt.name}: Rp {opt.price:,.0f} ({opt.etd})" + for opt in shipping_options + ]) + + # Log hasil estimasi + self.message_post( + body=f"Estimasi Ongkir Biteship (Kode Pos {origin_data} → {destination_data}):
{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' -- cgit v1.2.3 From 60f1f3b3dbe988d8c0fda706fd25d3abe892238a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 19 May 2025 11:08:11 +0700 Subject: (andri) penyesuaian selected shipping ketika estimate shipping --- indoteknik_custom/models/sale_order.py | 193 ++++++++++++--------------------- 1 file changed, 68 insertions(+), 125 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 12c04f19..4ee82100 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -252,12 +252,13 @@ class SaleOrder(models.Model): select_shipping_option = fields.Selection([ ('biteship', 'Biteship'), ('custom', 'Custom'), - ], string='Select Shipping Option', help="Select shipping option for delivery") + ], string='Select Shipping Option', help="Select shipping option for delivery", Tracking=True) @api.onchange('shipping_option_id') def _onchange_shipping_option_id(self): if self.shipping_option_id: self.delivery_amt = self.shipping_option_id.price + self.note_ekspedisi = f"Pengiriman: {self.shipping_option_id.name} - Rp {self.shipping_option_id.price:,.0f} ({self.shipping_option_id.etd})" def _get_biteship_courier_codes(self): return [ @@ -267,6 +268,7 @@ class SaleOrder(models.Model): @api.onchange('select_shipping_option') def _onchange_select_shipping_option(self): self.shipping_option_id = False + self.carrier_id = False self.delivery_amt = 0 biteship_courier_codes = self._get_biteship_courier_codes() @@ -488,7 +490,6 @@ class SaleOrder(models.Model): return total_weight def action_estimate_shipping_biteship(self): - # Validasi data total_weight = self._validate_for_shipping_estimate() # Konversi berat ke gram untuk Biteship @@ -508,63 +509,47 @@ class SaleOrder(models.Model): "length": 10 }] - # Coba dapatkan data alamat tujuan - destination_data = {} - origin_data = {} - - # Persiapkan data alamat asal (Gudang) - # 1. Data tetap gudang Bandengan + # Data asal (tetap gudang Bandengan) origin_data = { - "origin_postal_code" : 14440, - # "origin_latitude": -6.1753924, - # "origin_longitude": 106.7794935, + "origin_latitude": -6.3031123, + "origin_longitude": 106.7794934, } - - # Coba dapatkan data alamat tujuan - if not self.real_shipping_id: - raise UserError("Alamat pengiriman (Real Delivery Address) harus diisi.") + + # Prioritaskan penggunaan koordinat jika tersedia + destination_data = {} + use_coordinate = False 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 - # } + # Cek apakah latitude dan longitude tersedia dan valid + if hasattr(shipping_address, 'latitude') and hasattr(shipping_address, 'longitude'): + if shipping_address.latitude and shipping_address.longitude: + try: + lat = float(shipping_address.latitude) + lng = float(shipping_address.longitude) + destination_data = { + "destination_latitude": lat, + "destination_longitude": lng + } + use_coordinate = True + except (ValueError, TypeError): + use_coordinate = False # Jika koordinat tidak tersedia, gunakan kode pos - if not destination_data and shipping_address.zip: - destination_data = { - "destination_postal_code": shipping_address.zip - } + if not use_coordinate: + if shipping_address.zip: + origin_data = {"origin_postal_code": 14440} + destination_data = { + "destination_postal_code": shipping_address.zip + } + else: + raise UserError("Tidak dapat mengestimasikan ongkir: Kode pos tujuan tidak tersedia.") - # 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 + # Filter kurir berdasarkan shipping method jika dipilih + couriers = self.carrier_id.name.lower() if self.carrier_id else ','.join(self._get_biteship_courier_codes()) + + # Panggil API Biteship + api_mode = "koordinat" if use_coordinate else "kode_pos" result = self._call_biteship_api(origin_data, destination_data, items, couriers) if not result: @@ -577,19 +562,14 @@ class SaleOrder(models.Model): 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 # Format estimasi waktu @@ -597,13 +577,12 @@ class SaleOrder(models.Model): 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 + etd = "1-3 days" # Buat shipping option try: @@ -616,36 +595,50 @@ class SaleOrder(models.Model): }) 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.") + raise UserError(f"Tidak ada layanan pengiriman ditemukan untuk kode pos {destination_data}.") # Set opsi pertama sebagai default self.shipping_option_id = shipping_options[0].id self.delivery_amt = shipping_options[0].price - # Format pesan untuk log + # Tampilkan lokasi pengiriman dengan format yang lebih baik + if use_coordinate: + origin_info = f"Koordinat ({origin_data.get('origin_latitude')}, {origin_data.get('origin_longitude')})" + destination_info = f"Koordinat ({destination_data.get('destination_latitude')}, {destination_data.get('destination_longitude')})" + else: + origin_info = f"Kode Pos {origin_data.get('origin_postal_code')}" + destination_info = f"Kode Pos {destination_data.get('destination_postal_code')}" + + # Format daftar opsi pengiriman option_list = '
'.join([ f"{opt.name}: Rp {opt.price:,.0f} ({opt.etd})" for opt in shipping_options ]) - # Log hasil estimasi + # Tampilkan informasi tentang kurir yang dipilih + selected_option = shipping_options[0] + shipping_method_info = f"Metode Pengiriman: {self.carrier_id.name}" if self.carrier_id else "" + + # Log hasil estimasi dengan format yang lebih baik dan informasi kurir self.message_post( - body=f"Estimasi Ongkir Biteship (Kode Pos {origin_data} → {destination_data}):
{option_list}", + body=f"Estimasi Ongkir Biteship ({origin_info} → {destination_info}):
{shipping_method_info}
{option_list}", message_type="comment" ) + # Simpan informasi untuk note ekspedisi + self.note_ekspedisi = f"Pengiriman: {selected_option.name} - Rp {selected_option.price:,.0f} ({selected_option.etd}) [via {api_mode}]" + return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': 'Estimasi Ongkir Berhasil', - 'message': f'Mendapatkan {len(shipping_options)} opsi pengiriman', + 'message': f'Mendapatkan {len(shipping_options)} opsi pengiriman menggunakan {api_mode}', 'type': 'success', 'sticky': False, } @@ -663,59 +656,16 @@ class SaleOrder(models.Model): 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" + + # Persiapkan payload dengan menggabungkan origin, destination, dan items + payload = { + **origin_data, + **destination_data, + "couriers": couriers, + "items": items + } + + api_mode = "koordinat" if "destination_latitude" in destination_data else "kode_pos" try: _logger.info(f"Calling Biteship API with mode: {api_mode}") @@ -723,25 +673,18 @@ class SaleOrder(models.Model): 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 + result['api_mode'] = api_mode # Tambahkan info mode API 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 -- cgit v1.2.3 From 63243a7b70292e9c48a21e2badbb07c398bc4166 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 19 May 2025 16:07:03 +0700 Subject: (andri) add field langitude & longitude pada customer & perbaikan biteship --- indoteknik_custom/models/sale_order.py | 90 ++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 41 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4ee82100..f09869da 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -258,7 +258,6 @@ class SaleOrder(models.Model): def _onchange_shipping_option_id(self): if self.shipping_option_id: self.delivery_amt = self.shipping_option_id.price - self.note_ekspedisi = f"Pengiriman: {self.shipping_option_id.name} - Rp {self.shipping_option_id.price:,.0f} ({self.shipping_option_id.etd})" def _get_biteship_courier_codes(self): return [ @@ -271,29 +270,25 @@ class SaleOrder(models.Model): self.carrier_id = False self.delivery_amt = 0 - biteship_courier_codes = self._get_biteship_courier_codes() - - # Cari carrier yang namanya mengandung kode Biteship + # Dapatkan semua ID carrier untuk Biteship biteship_carrier_ids = [] - for code in biteship_courier_codes: - carriers = self.env['delivery.carrier'].search([ - ('name', 'ilike', code) - ]) - if carriers: - biteship_carrier_ids.extend(carriers.ids) - # Hapus duplikat - biteship_carrier_ids = list(set(biteship_carrier_ids)) + # Gunakan SQL langsung untuk menghindari masalah ORM + self.env.cr.execute(""" + SELECT delivery_carrier_id + FROM rajaongkir_kurir + WHERE name IN %s + """, (tuple(self._get_biteship_courier_codes()),)) - if self.select_shipping_option == 'custom': - # Tampilkan carrier yang bukan dari Biteship - domain = [('id', 'not in', biteship_carrier_ids)] if biteship_carrier_ids else [] - return {'domain': {'carrier_id': domain}} + # Ambil ID numerik hasil query + biteship_carrier_ids = [row[0] for row in self.env.cr.fetchall() if row[0]] - elif self.select_shipping_option == 'biteship': - # Tampilkan hanya carrier dari Biteship + if self.select_shipping_option == 'biteship': domain = [('id', 'in', biteship_carrier_ids)] if biteship_carrier_ids else [] - return {'domain': {'carrier_id': domain}} + else: # 'custom' + domain = [('id', 'not in', biteship_carrier_ids)] if biteship_carrier_ids else [] + + return {'domain': {'carrier_id': domain}} @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') def _check_total_margin_excl_third_party(self): @@ -490,6 +485,7 @@ class SaleOrder(models.Model): return total_weight def action_estimate_shipping_biteship(self): + total_weight = self._validate_for_shipping_estimate() # Konversi berat ke gram untuk Biteship @@ -509,6 +505,10 @@ class SaleOrder(models.Model): "length": 10 }] + # Coba dapatkan data koordinat dari alamat pengiriman + shipping_address = self.real_shipping_id + _logger.info(f"Shipping Address: {shipping_address}") + # Data asal (tetap gudang Bandengan) origin_data = { "origin_latitude": -6.3031123, @@ -519,37 +519,41 @@ class SaleOrder(models.Model): destination_data = {} use_coordinate = False - shipping_address = self.real_shipping_id - # Cek apakah latitude dan longitude tersedia dan valid - if hasattr(shipping_address, 'latitude') and hasattr(shipping_address, 'longitude'): - if shipping_address.latitude and shipping_address.longitude: + if hasattr(shipping_address, 'latitude') and hasattr(shipping_address, 'longtitude'): + if shipping_address.latitude and shipping_address.longtitude: try: + # Validasi format koordinat lat = float(shipping_address.latitude) - lng = float(shipping_address.longitude) + lng = float(shipping_address.longtitude) destination_data = { "destination_latitude": lat, "destination_longitude": lng } use_coordinate = True + _logger.info(f"Using coordinates: lat={lat}, lng={lng}") except (ValueError, TypeError): + _logger.warning(f"Invalid coordinates, falling back to postal code") use_coordinate = False - # Jika koordinat tidak tersedia, gunakan kode pos + # Jika koordinat tidak tersedia atau tidak valid, gunakan kode pos if not use_coordinate: if shipping_address.zip: - origin_data = {"origin_postal_code": 14440} + origin_data = {"origin_postal_code": 14440} # Reset origin untuk mode kode pos destination_data = { "destination_postal_code": shipping_address.zip } + _logger.info(f"Using postal code: {shipping_address.zip}") else: raise UserError("Tidak dapat mengestimasikan ongkir: Kode pos tujuan tidak tersedia.") - # Filter kurir berdasarkan shipping method jika dipilih - couriers = self.carrier_id.name.lower() if self.carrier_id else ','.join(self._get_biteship_courier_codes()) + # Siapkan daftar kurir + couriers = ','.join(self._get_biteship_courier_codes()) - # Panggil API Biteship + # Panggil API Biteship dengan format yang benar api_mode = "koordinat" if use_coordinate else "kode_pos" + _logger.info(f"Calling Biteship API with mode: {api_mode}") + result = self._call_biteship_api(origin_data, destination_data, items, couriers) if not result: @@ -562,14 +566,19 @@ class SaleOrder(models.Model): 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 # Format estimasi waktu @@ -577,12 +586,13 @@ class SaleOrder(models.Model): 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" + etd = "1-3 days" # Default fallback # Buat shipping option try: @@ -595,18 +605,19 @@ class SaleOrder(models.Model): }) 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}.") + 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 - # Tampilkan lokasi pengiriman dengan format yang lebih baik + # Format pesan untuk log yang lebih informatif if use_coordinate: origin_info = f"Koordinat ({origin_data.get('origin_latitude')}, {origin_data.get('origin_longitude')})" destination_info = f"Koordinat ({destination_data.get('destination_latitude')}, {destination_data.get('destination_longitude')})" @@ -620,17 +631,14 @@ class SaleOrder(models.Model): for opt in shipping_options ]) - # Tampilkan informasi tentang kurir yang dipilih - selected_option = shipping_options[0] - shipping_method_info = f"Metode Pengiriman: {self.carrier_id.name}" if self.carrier_id else "" - - # Log hasil estimasi dengan format yang lebih baik dan informasi kurir + # Log hasil estimasi dengan format yang lebih baik self.message_post( - body=f"Estimasi Ongkir Biteship ({origin_info} → {destination_info}):
{shipping_method_info}
{option_list}", + body=f"Estimasi Ongkir Biteship ({origin_info} → {destination_info}):
{option_list}", message_type="comment" ) # Simpan informasi untuk note ekspedisi + selected_option = shipping_options[0] # Opsi pertama dipilih sebagai default self.note_ekspedisi = f"Pengiriman: {selected_option.name} - Rp {selected_option.price:,.0f} ({selected_option.etd}) [via {api_mode}]" return { @@ -1435,7 +1443,7 @@ class SaleOrder(models.Model): raise UserError("This order not yet approved by customer procurement or director") if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): - raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.commitment_date and order.create_date > datetime(2024, 9, 12): raise UserError("Expected Delivery Date kosong, wajib diisi") @@ -1462,7 +1470,7 @@ class SaleOrder(models.Model): if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.sppkp != partner.sppkp: raise UserError("SPPKP berbeda pada Master Data Customer") if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): - raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.user_id.active: raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact") @@ -1673,7 +1681,7 @@ class SaleOrder(models.Model): raise UserError("This order not yet approved by customer procurement or director") if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): - raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.commitment_date and order.create_date > datetime(2024, 9, 12): raise UserError("Expected Delivery Date kosong, wajib diisi") -- cgit v1.2.3 From e987dc891e999ebd1d04fb4f8cdeb44134c67aed Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 19 May 2025 22:21:31 +0700 Subject: (andri) fix bug shipping method --- indoteknik_custom/models/sale_order.py | 40 +++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index f09869da..382272b9 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -7,6 +7,7 @@ import logging, random, string, requests, math, json, re, qrcode, base64 from io import BytesIO from collections import defaultdict import pytz +from lxml import etree _logger = logging.getLogger(__name__) @@ -252,7 +253,44 @@ class SaleOrder(models.Model): select_shipping_option = fields.Selection([ ('biteship', 'Biteship'), ('custom', 'Custom'), - ], string='Select Shipping Option', help="Select shipping option for delivery", Tracking=True) + ], string='Select Shipping Option', help="Select shipping option for delivery", tracking=True) + + def get_biteship_carrier_ids(self): + courier_codes = tuple(self._get_biteship_courier_codes() or []) + if not courier_codes: + return [] + + self.env.cr.execute(""" + SELECT delivery_carrier_id + FROM rajaongkir_kurir + WHERE name IN %s AND delivery_carrier_id IS NOT NULL + """, (courier_codes,)) + result = self.env.cr.fetchall() + carrier_ids = [row[0] for row in result if row[0]] + return carrier_ids + + @api.model + def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): + res = super(SaleOrder, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) + + if view_type == 'form': + doc = etree.XML(res['arch']) + + carrier_ids = self.get_biteship_carrier_ids() + if carrier_ids: + carrier_ids_str = '(' + ','.join(str(x) for x in carrier_ids) + ')' + else: + carrier_ids_str = '(-1,)' # aman kalau kosong + + # ✅ Tambahkan log di sini + _logger.info("🛰️ Biteship Carrier IDs: %s", carrier_ids) + _logger.info("📦 Domain string to apply: [('id', 'in', %s)]", carrier_ids_str) + + for node in doc.xpath("//field[@name='carrier_id']"): + node.set('domain', "[('id', 'in', %s)]" % carrier_ids_str) + + res['arch'] = etree.tostring(doc, encoding='unicode') + return res @api.onchange('shipping_option_id') def _onchange_shipping_option_id(self): -- cgit v1.2.3 From 2acd9dadd20499ab93c8ece237518f2a9cc7e296 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 19 May 2025 23:02:46 +0700 Subject: (andri) merapihkan log note estimate shipping --- indoteknik_custom/models/sale_order.py | 59 ++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 25 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 382272b9..f21f35b4 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -523,7 +523,6 @@ class SaleOrder(models.Model): return total_weight def action_estimate_shipping_biteship(self): - total_weight = self._validate_for_shipping_estimate() # Konversi berat ke gram untuk Biteship @@ -602,6 +601,7 @@ class SaleOrder(models.Model): # Proses hasil API shipping_options = [] + courier_options = {} # Dictionary untuk mengelompokkan opsi per kurir shipping_services = result.get('pricing', []) _logger.info(f"Ditemukan {len(shipping_services)} layanan pengiriman") @@ -643,19 +643,30 @@ class SaleOrder(models.Model): }) shipping_options.append(shipping_option) + + # Kelompokkan opsi berdasarkan kurir + courier_upper = courier_code.upper() + if courier_upper not in courier_options: + courier_options[courier_upper] = [] + courier_options[courier_upper].append({ + "name": service_name, + "etd": etd, + "price": price + }) + _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.") + 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.") # Set opsi pertama sebagai default - self.shipping_option_id = shipping_options[0].id - self.delivery_amt = shipping_options[0].price + # self.shipping_option_id = shipping_options[0].id + # self.delivery_amt = shipping_options[0].price - # Format pesan untuk log yang lebih informatif + # Format untuk pesan log if use_coordinate: origin_info = f"Koordinat ({origin_data.get('origin_latitude')}, {origin_data.get('origin_longitude')})" destination_info = f"Koordinat ({destination_data.get('destination_latitude')}, {destination_data.get('destination_longitude')})" @@ -663,32 +674,30 @@ class SaleOrder(models.Model): origin_info = f"Kode Pos {origin_data.get('origin_postal_code')}" destination_info = f"Kode Pos {destination_data.get('destination_postal_code')}" - # Format daftar opsi pengiriman - option_list = '
'.join([ - f"{opt.name}: Rp {opt.price:,.0f} ({opt.etd})" - for opt in shipping_options - ]) + # PENTING: Gunakan HTML untuk teks preformatted agar jarak baris terjaga + message_lines = [f"Estimasi Ongkos Kirim Biteship ({origin_info} → {destination_info}):
"] + + # Format setiap kurir dan layanannya + for courier, options in courier_options.items(): + message_lines.append(f"{courier}:
") + for opt in options: + message_lines.append(f"Service: {opt['name']}, ETD: {opt['etd']}, Cost: Rp {int(opt['price']):,}
") + # Tambahkan baris kosong setelah setiap kurir (kecuali yang terakhir) + if courier != list(courier_options.keys())[-1]: + message_lines.append("
") + + # Gabungkan baris pesan dengan HTML line breaks + message_body = "".join(message_lines) - # Log hasil estimasi dengan format yang lebih baik + # Log hasil estimasi dengan format yang diinginkan self.message_post( - body=f"Estimasi Ongkir Biteship ({origin_info} → {destination_info}):
{option_list}", + body=message_body, message_type="comment" ) # Simpan informasi untuk note ekspedisi - selected_option = shipping_options[0] # Opsi pertama dipilih sebagai default - self.note_ekspedisi = f"Pengiriman: {selected_option.name} - Rp {selected_option.price:,.0f} ({selected_option.etd}) [via {api_mode}]" - - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': 'Estimasi Ongkir Berhasil', - 'message': f'Mendapatkan {len(shipping_options)} opsi pengiriman menggunakan {api_mode}', - 'type': 'success', - 'sticky': False, - } - } + # selected_option = shipping_options[0] # Opsi pertama dipilih sebagai default + # self.note_ekspedisi = f"Pengiriman: {selected_option.name} - Rp {selected_option.price:,.0f} ({selected_option.etd}) [via {api_mode}]" def _call_biteship_api(self, origin_data, destination_data, items, couriers=None): -- cgit v1.2.3 From d38635f75fe52cbc958d96e577a83d8b3e1e1272 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 20 May 2025 13:47:18 +0700 Subject: (andri) fix set shipping option sesuai dengan method yang dipilih --- indoteknik_custom/models/sale_order.py | 60 +++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index f21f35b4..2b2e518a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -248,7 +248,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 Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") + shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") select_shipping_option = fields.Selection([ ('biteship', 'Biteship'), @@ -295,7 +295,7 @@ class SaleOrder(models.Model): @api.onchange('shipping_option_id') def _onchange_shipping_option_id(self): if self.shipping_option_id: - self.delivery_amt = self.shipping_option_id.price + self.delivery_amt = self.shipping_option_id.price def _get_biteship_courier_codes(self): return [ @@ -340,12 +340,6 @@ class SaleOrder(models.Model): """, (rec.total_percent_margin, rec.id)) self.invalidate_cache() - @api.constrains('shipping_option_id') - def _check_shipping_option(self): - for rec in self: - if rec.shipping_option_id: - rec.delivery_amt = rec.shipping_option_id.price - def _compute_shipping_method_picking(self): for order in self: if order.picking_ids: @@ -662,9 +656,53 @@ 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.") - # Set opsi pertama sebagai default - # self.shipping_option_id = shipping_options[0].id - # self.delivery_amt = shipping_options[0].price + # Set opsi sesuai dengan carrier yang sudah dipilih, atau set opsi pertama sebagai default + selected_option = None + + if self.carrier_id: + # Dapatkan kode kurir dari carrier + rajaongkir_kurir = self.env['rajaongkir.kurir'].search([ + ('delivery_carrier_id', '=', self.carrier_id.id) + ], limit=1) + + # Jika ditemukan rajaongkir_kurir, cari shipping option yang sesuai + if rajaongkir_kurir: + courier_code = rajaongkir_kurir.name.lower() + carrier_name = self.carrier_id.name.lower() + + # Mencoba beberapa kemungkinan format untuk pencocokan + possible_codes = [ + 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}") + + # Coba temukan shipping option yang sesuai dengan carrier + for option in shipping_options: + option_provider = option.provider.lower() if option.provider else '' + option_name = option.name.lower() if option.name else '' + + # Cek pencocokan untuk provider atau nama + 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 + + if selected_option: + break + + # Jika tidak ada opsi yang cocok dengan carrier, gunakan opsi pertama + if not selected_option and shipping_options: + selected_option = shipping_options[0] + _logger.info(f"Menggunakan opsi pertama: {selected_option.name}") + + # Set shipping option yang terpilih + if selected_option: + self.shipping_option_id = selected_option.id + self.delivery_amt = selected_option.price # Format untuk pesan log if use_coordinate: -- cgit v1.2.3 From c109f6704106c59d37715b9e22f464e6f5b106db Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 20 May 2025 23:16:24 +0700 Subject: (andri) fix list shipping option sesuai dengan method yang dipilih --- indoteknik_custom/models/sale_order.py | 130 +++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 2b2e518a..b2faf9f3 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -302,6 +302,136 @@ class SaleOrder(models.Model): 'gojek','grab','deliveree','lalamove','jne','tiki','ninja','lion','rara','sicepat','jnt','pos','idexpress','rpx','wahana','jdl','pos','anteraja','sap','paxel','borzo' ] + @api.onchange('carrier_id') + def _onchange_carrier_id(self): + self.shipping_option_id = False + self.delivery_amt = 0 + + if not self.carrier_id: + return {'domain': {'shipping_option_id': [('id', '=', -1)]}} + + # Cari provider dari carrier yang dipilih langsung dari rajaongkir_kurir + self.env.cr.execute(""" + SELECT name FROM rajaongkir_kurir + WHERE delivery_carrier_id = %s + LIMIT 1 + """, (self.carrier_id.id,)) + + result = self.env.cr.fetchone() + provider = result[0].lower() if result and result[0] else False + + # Fallback jika tidak ditemukan di rajaongkir_kurir + if not provider: + # Gunakan nama carrier, ambil kata pertama + provider = self.carrier_id.name.lower().split()[0] if self.carrier_id.name else False + + # Log untuk debugging + _logger.info(f"Carrier changed to {self.carrier_id.name}, provider: {provider}") + + # PENTING: self.id mungkin False atau NewId pada saat onchange + # Perlu memeriksa apakah ini adalah record baru atau yang sudah ada + sale_order_id = False + if hasattr(self, '_origin') and self._origin: + sale_order_id = self._origin.id + + # Cek jika ada shipping options dengan provider ini (tanpa filter sale_order_id dulu) + self.env.cr.execute(""" + SELECT COUNT(*) FROM shipping_option + WHERE LOWER(provider) LIKE %s + """, (f'%{provider}%',)) + + count = self.env.cr.fetchone()[0] + _logger.info(f"Found {count} shipping options for provider {provider}") + + # Buat domain untuk shipping_option_id + if count > 0: + # Jika ada options yang tersedia, buat domain yang lebih permisif + domain = [ + '|', + ('provider', 'ilike', f'%{provider}%'), + ('provider', '=', provider) + ] + + # Jika ini record yang sudah ada, tambahkan filter sale_order_id + if sale_order_id: + domain = [ + '|', + '&', ('sale_order_id', '=', sale_order_id), ('provider', 'ilike', f'%{provider}%'), + '&', ('sale_order_id', '=', False), ('provider', 'ilike', f'%{provider}%') + ] + else: + domain = [('id', '=', -1)] # Tidak ada opsi + + _logger.info(f"Final domain for shipping_option_id: {domain}") + + # Masih menggunakan pendekatan mengembalikan domain karena ini yang paling efektif + # meskipun ada peringatan deprecated + return {'domain': {'shipping_option_id': domain}} + + @api.model + def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): + res = super(SaleOrder, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) + + if view_type == 'form': + doc = etree.XML(res['arch']) + + # Filter carrier_id: hanya yang dari Biteship + carrier_ids = self.get_biteship_carrier_ids() + carrier_ids_str = '(' + ','.join(str(x) for x in carrier_ids) + ')' if carrier_ids else '(-1,)' + for node in doc.xpath("//field[@name='carrier_id']"): + node.set('domain', "[('id', 'in', %s)]" % carrier_ids_str) + + # PERBAIKAN UTAMA: Filter shipping_option_id saat form dibuka dalam mode edit + sale_id = self._context.get('active_id') + if sale_id: + # Ambil carrier_id dari database untuk record yang sedang diedit + self.env.cr.execute("SELECT carrier_id FROM sale_order WHERE id = %s", (sale_id,)) + carrier_result = self.env.cr.fetchone() + carrier_id = carrier_result[0] if carrier_result else None + + if carrier_id: + # Cari provider dari rajaongkir_kurir + self.env.cr.execute(""" + SELECT name FROM rajaongkir_kurir + WHERE delivery_carrier_id = %s + LIMIT 1 + """, (carrier_id,)) + row = self.env.cr.fetchone() + provider = None + + if row and row[0]: + provider = row[0].lower() + else: + # Fallback ke nama carrier + self.env.cr.execute("SELECT name FROM delivery_carrier WHERE id = %s", (carrier_id,)) + row = self.env.cr.fetchone() + if row and row[0]: + provider = row[0].lower().split()[0] + + if provider: + _logger.info(f"fields_view_get - Found provider: {provider} for carrier_id: {carrier_id}") + + # PENTING: Query untuk mendapatkan shipping options yang sesuai + # Ambil semua options yang memiliki provider yang cocok + # Dan prioritaskan yang terkait dengan sale_order ini + domain_str = f""" + [ + '|', + '&', ('sale_order_id', '=', {sale_id}), ('provider', 'ilike', '%{provider}%'), + '&', ('sale_order_id', '=', False), ('provider', 'ilike', '%{provider}%') + ] + """ + + # Set domain ke field shipping_option_id + for node in doc.xpath("//field[@name='shipping_option_id']"): + node.set('domain', domain_str) + # Tambahkan options untuk mencegah quick create + node.set('options', "{'no_create': True, 'no_quick_create': True}") + + _logger.info(f"Setting domain in fields_view_get: {domain_str}") + + res['arch'] = etree.tostring(doc, encoding='unicode') + return res @api.onchange('select_shipping_option') def _onchange_select_shipping_option(self): self.shipping_option_id = False -- cgit v1.2.3 From afb745b1e000f4d3c3dba1723ce4a19f44b8c510 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 21 May 2025 09:18:39 +0700 Subject: (andri) fix edit shipping option --- indoteknik_custom/models/sale_order.py | 95 +++++++++++++--------------------- 1 file changed, 35 insertions(+), 60 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b2faf9f3..bcc4d5c4 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -363,75 +363,50 @@ class SaleOrder(models.Model): domain = [('id', '=', -1)] # Tidak ada opsi _logger.info(f"Final domain for shipping_option_id: {domain}") - - # Masih menggunakan pendekatan mengembalikan domain karena ini yang paling efektif - # meskipun ada peringatan deprecated return {'domain': {'shipping_option_id': domain}} @api.model - def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): - res = super(SaleOrder, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) + def fields_get(self, allfields=None, attributes=None): + res = super().fields_get(allfields=allfields, attributes=attributes) - if view_type == 'form': - doc = etree.XML(res['arch']) + # Aktifkan hanya kalau sedang buka form Sales Order (safety check) + if self.env.context.get('params', {}).get('model') == 'sale.order' and \ + self.env.context.get('params', {}).get('id'): - # Filter carrier_id: hanya yang dari Biteship - carrier_ids = self.get_biteship_carrier_ids() - carrier_ids_str = '(' + ','.join(str(x) for x in carrier_ids) + ')' if carrier_ids else '(-1,)' - for node in doc.xpath("//field[@name='carrier_id']"): - node.set('domain', "[('id', 'in', %s)]" % carrier_ids_str) + sale_id = self.env.context['params']['id'] - # PERBAIKAN UTAMA: Filter shipping_option_id saat form dibuka dalam mode edit - sale_id = self._context.get('active_id') - if sale_id: - # Ambil carrier_id dari database untuk record yang sedang diedit - self.env.cr.execute("SELECT carrier_id FROM sale_order WHERE id = %s", (sale_id,)) - carrier_result = self.env.cr.fetchone() - carrier_id = carrier_result[0] if carrier_result else None - - if carrier_id: - # Cari provider dari rajaongkir_kurir - self.env.cr.execute(""" - SELECT name FROM rajaongkir_kurir - WHERE delivery_carrier_id = %s - LIMIT 1 - """, (carrier_id,)) + # Ambil carrier_id dari SO yang sedang dibuka + self.env.cr.execute("SELECT carrier_id FROM sale_order WHERE id = %s", (sale_id,)) + row = self.env.cr.fetchone() + carrier_id = row[0] if row else None + + provider = None + if carrier_id: + self.env.cr.execute(""" + SELECT name FROM rajaongkir_kurir WHERE delivery_carrier_id = %s LIMIT 1 + """, (carrier_id,)) + row = self.env.cr.fetchone() + if row and row[0]: + provider = row[0].lower() + else: + self.env.cr.execute("SELECT name FROM delivery_carrier WHERE id = %s", (carrier_id,)) row = self.env.cr.fetchone() - provider = None - - if row and row[0]: - provider = row[0].lower() - else: - # Fallback ke nama carrier - self.env.cr.execute("SELECT name FROM delivery_carrier WHERE id = %s", (carrier_id,)) - row = self.env.cr.fetchone() - if row and row[0]: - provider = row[0].lower().split()[0] - - if provider: - _logger.info(f"fields_view_get - Found provider: {provider} for carrier_id: {carrier_id}") - - # PENTING: Query untuk mendapatkan shipping options yang sesuai - # Ambil semua options yang memiliki provider yang cocok - # Dan prioritaskan yang terkait dengan sale_order ini - domain_str = f""" - [ - '|', - '&', ('sale_order_id', '=', {sale_id}), ('provider', 'ilike', '%{provider}%'), - '&', ('sale_order_id', '=', False), ('provider', 'ilike', '%{provider}%') - ] - """ - - # Set domain ke field shipping_option_id - for node in doc.xpath("//field[@name='shipping_option_id']"): - node.set('domain', domain_str) - # Tambahkan options untuk mencegah quick create - node.set('options', "{'no_create': True, 'no_quick_create': True}") - - _logger.info(f"Setting domain in fields_view_get: {domain_str}") + provider = row[0].lower().split()[0] if row and row[0] else '' + + if provider: + domain = [ + '|', + '&', ('sale_order_id', '=', sale_id), ('provider', 'ilike', f'%{provider}%'), + '&', ('sale_order_id', '=', False), ('provider', 'ilike', f'%{provider}%') + ] + + if 'shipping_option_id' in res: + res['shipping_option_id']['domain'] = domain + _logger.info(f"fields_get - Injected domain for shipping_option_id: {domain}") - res['arch'] = etree.tostring(doc, encoding='unicode') return res + + @api.onchange('select_shipping_option') def _onchange_select_shipping_option(self): self.shipping_option_id = False -- cgit v1.2.3 From 34174e95638e1337169dfa5f3be56b9ef57021a1 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 22 May 2025 11:14:35 +0700 Subject: (andri) sync quotation web ke odoo terkait kurir, service, & tarif --- indoteknik_custom/models/sale_order.py | 173 ++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 76 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bcc4d5c4..5a5255b3 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -64,6 +64,7 @@ class ShippingOption(models.Model): price = fields.Float(string="Price", required=True) provider = fields.Char(string="Provider") etd = fields.Char(string="Estimated Delivery Time") + courier_service_code = fields.Char(string="Courier Service Code") sale_order_id = fields.Many2one('sale.order', string="Sale Order", ondelete="cascade") class SaleOrder(models.Model): @@ -253,7 +254,7 @@ class SaleOrder(models.Model): select_shipping_option = fields.Selection([ ('biteship', 'Biteship'), ('custom', 'Custom'), - ], string='Select Shipping Option', help="Select shipping option for delivery", tracking=True) + ], string='Shipping Option', help="Select shipping option for delivery", tracking=True) def get_biteship_carrier_ids(self): courier_codes = tuple(self._get_biteship_courier_codes() or []) @@ -603,8 +604,8 @@ class SaleOrder(models.Model): product_names = '
'.join(missing_weight_products) self.message_post(body=f"Produk berikut tidak memiliki berat:
{product_names}") - if total_weight == 0: - raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") + # if total_weight == 0: + # raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") # Validasi alamat pengiriman if not self.real_shipping_id: @@ -623,13 +624,11 @@ class SaleOrder(models.Model): def action_estimate_shipping_biteship(self): 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 + weight_gram = 100 + items = [{ "name": "Paket Pesanan", "description": f"Sale Order {self.name}", @@ -640,26 +639,21 @@ class SaleOrder(models.Model): "width": 10, "length": 10 }] - - # Coba dapatkan data koordinat dari alamat pengiriman + shipping_address = self.real_shipping_id _logger.info(f"Shipping Address: {shipping_address}") - - # Data asal (tetap gudang Bandengan) + origin_data = { "origin_latitude": -6.3031123, "origin_longitude": 106.7794934, } - - # Prioritaskan penggunaan koordinat jika tersedia + destination_data = {} use_coordinate = False - - # Cek apakah latitude dan longitude tersedia dan valid + if hasattr(shipping_address, 'latitude') and hasattr(shipping_address, 'longtitude'): if shipping_address.latitude and shipping_address.longtitude: try: - # Validasi format koordinat lat = float(shipping_address.latitude) lng = float(shipping_address.longtitude) destination_data = { @@ -671,79 +665,72 @@ class SaleOrder(models.Model): except (ValueError, TypeError): _logger.warning(f"Invalid coordinates, falling back to postal code") use_coordinate = False - - # Jika koordinat tidak tersedia atau tidak valid, gunakan kode pos + if not use_coordinate: if shipping_address.zip: - origin_data = {"origin_postal_code": 14440} # Reset origin untuk mode kode pos + origin_data = {"origin_postal_code": 14440} destination_data = { "destination_postal_code": shipping_address.zip } _logger.info(f"Using postal code: {shipping_address.zip}") else: raise UserError("Tidak dapat mengestimasikan ongkir: Kode pos tujuan tidak tersedia.") - - # Siapkan daftar kurir + couriers = ','.join(self._get_biteship_courier_codes()) - - # Panggil API Biteship dengan format yang benar + api_mode = "koordinat" if use_coordinate else "kode_pos" _logger.info(f"Calling Biteship API with mode: {api_mode}") - + 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 = [] - courier_options = {} # Dictionary untuk mengelompokkan opsi per kurir + courier_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) - + raw_price = service.get('price', 0) + markup_price = int(raw_price * 1.1) + price = round(markup_price / 1000) * 1000 + _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 - - # 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 + etd = "1-3 days" + try: shipping_option = self.env["shipping.option"].create({ "name": f"{courier_name} - {service_name}", "price": price, "provider": courier_code, "etd": etd, + "courier_service_code": service.get('courier_service_code'), "sale_order_id": self.id, }) - + shipping_options.append(shipping_option) - - # Kelompokkan opsi berdasarkan kurir + courier_upper = courier_code.upper() if courier_upper not in courier_options: courier_options[courier_upper] = [] @@ -752,87 +739,72 @@ class SaleOrder(models.Model): "etd": etd, "price": price }) - + _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.get('destination_postal_code', '')}. Mohon periksa kembali kode pos atau gunakan metode pengiriman lain.") - - # Set opsi sesuai dengan carrier yang sudah dipilih, atau set opsi pertama sebagai default + selected_option = None if self.carrier_id: - # Dapatkan kode kurir dari carrier rajaongkir_kurir = self.env['rajaongkir.kurir'].search([ ('delivery_carrier_id', '=', self.carrier_id.id) ], limit=1) - - # Jika ditemukan rajaongkir_kurir, cari shipping option yang sesuai + if rajaongkir_kurir: courier_code = rajaongkir_kurir.name.lower() carrier_name = self.carrier_id.name.lower() - - # Mencoba beberapa kemungkinan format untuk pencocokan + possible_codes = [ 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}") - - # Coba temukan shipping option yang sesuai dengan carrier + for option in shipping_options: option_provider = option.provider.lower() if option.provider else '' option_name = option.name.lower() if option.name else '' - - # Cek pencocokan untuk provider atau nama + 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 - + if selected_option: break - # Jika tidak ada opsi yang cocok dengan carrier, gunakan opsi pertama if not selected_option and shipping_options: selected_option = shipping_options[0] _logger.info(f"Menggunakan opsi pertama: {selected_option.name}") - # Set shipping option yang terpilih if selected_option: self.shipping_option_id = selected_option.id self.delivery_amt = selected_option.price - - # Format untuk pesan log + if use_coordinate: origin_info = f"Koordinat ({origin_data.get('origin_latitude')}, {origin_data.get('origin_longitude')})" destination_info = f"Koordinat ({destination_data.get('destination_latitude')}, {destination_data.get('destination_longitude')})" else: origin_info = f"Kode Pos {origin_data.get('origin_postal_code')}" destination_info = f"Kode Pos {destination_data.get('destination_postal_code')}" - - # PENTING: Gunakan HTML untuk teks preformatted agar jarak baris terjaga + message_lines = [f"Estimasi Ongkos Kirim Biteship ({origin_info} → {destination_info}):
"] - - # Format setiap kurir dan layanannya + for courier, options in courier_options.items(): message_lines.append(f"{courier}:
") for opt in options: message_lines.append(f"Service: {opt['name']}, ETD: {opt['etd']}, Cost: Rp {int(opt['price']):,}
") - # Tambahkan baris kosong setelah setiap kurir (kecuali yang terakhir) if courier != list(courier_options.keys())[-1]: message_lines.append("
") - - # Gabungkan baris pesan dengan HTML line breaks + message_body = "".join(message_lines) - - # Log hasil estimasi dengan format yang diinginkan + self.message_post( body=message_body, message_type="comment" @@ -2290,10 +2262,59 @@ class SaleOrder(models.Model): order_line.discount = discount order_line.order_id.use_button = True + def _auto_set_shipping_from_website(self): + for order in self: + # Jalankan hanya untuk SO dari website (ID 59) + if not order.source_id or order.source_id.id != 59: + continue + + # Jika shipping method adalah Self Pick Up (ID 32), atur ke custom + if order.carrier_id and order.carrier_id.id == 32: + order.select_shipping_option = 'custom' + continue + + # Set shipping option ke biteship dan jalankan estimasi + order.select_shipping_option = 'biteship' + order.action_estimate_shipping() + + if not (order.delivery_service_type and order.carrier_id): + continue + + # Ambil provider dari rajaongkir_kurir + self.env.cr.execute(""" + SELECT name FROM rajaongkir_kurir + WHERE delivery_carrier_id = %s LIMIT 1 + """, (order.carrier_id.id,)) + result = self.env.cr.fetchone() + provider = result[0].lower() if result and result[0] else '' + + if not provider and order.carrier_id.name: + provider = order.carrier_id.name.lower().split()[0] + + if not provider: + _logger.warning(f"[AutoSetShipping] Provider tidak ditemukan untuk carrier_id: {order.carrier_id.id}") + continue + + # Cari shipping option berdasarkan provider dan courier_service_code + matched_option = self.env['shipping.option'].search([ + ('sale_order_id', '=', order.id), + ('courier_service_code', '=', order.delivery_service_type), + ('provider', 'ilike', provider), + ], limit=1) + + if matched_option: + order.shipping_option_id = matched_option.id + order.delivery_amt = matched_option.price + _logger.info(f"[AutoSetShipping] Matched via courier_service_code: {matched_option.name} | Provider: {provider}") + else: + _logger.warning(f"[AutoSetShipping] No match for service code '{order.delivery_service_type}' and provider '{provider}' in SO {order.name}") + + @api.model def create(self, vals): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) + order._auto_set_shipping_from_website() order._compute_etrts_date() order._validate_expected_ready_ship_date() order._validate_delivery_amt() -- cgit v1.2.3 From fad209db285b0a6204dc1fcbf2e2e0cb13f872b0 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 23 May 2025 09:05:27 +0700 Subject: (andri) penyesuaian data quotation SO ke delivery biteship --- indoteknik_custom/models/sale_order.py | 6 ++++-- indoteknik_custom/models/stock_picking.py | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 5a5255b3..38d2505d 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 Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") + shipping_option_id = fields.Many2one("shipping.option", string="Selected Service Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") select_shipping_option = fields.Selection([ ('biteship', 'Biteship'), @@ -296,7 +296,8 @@ class SaleOrder(models.Model): @api.onchange('shipping_option_id') def _onchange_shipping_option_id(self): if self.shipping_option_id: - self.delivery_amt = self.shipping_option_id.price + self.delivery_amt = self.shipping_option_id.price + self.delivery_service_type = self.shipping_option_id.courier_service_code def _get_biteship_courier_codes(self): return [ @@ -786,6 +787,7 @@ class SaleOrder(models.Model): if selected_option: self.shipping_option_id = selected_option.id self.delivery_amt = selected_option.price + self.delivery_service_type = selected_option.courier_service_code if use_coordinate: origin_info = f"Koordinat ({origin_data.get('origin_latitude')}, {origin_data.get('origin_longitude')})" diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 39c74aa2..5548db75 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -565,7 +565,7 @@ class StockPicking(models.Model): "latitude": -6.3031123, "longitude" : 106.7794934999 }, - "reference_id " : self.sale_id.name, + "reference_id" : self.sale_id.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -585,6 +585,8 @@ class StockPicking(models.Model): "items": items_data_standard } + _logger.info(f"Payload untuk Biteship: {payload}") + # Cek jika pengiriman instant atau same_day if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): payload.update({ @@ -603,6 +605,7 @@ class StockPicking(models.Model): # Kirim request ke Biteship response = requests.post(_biteship_url+'/orders', headers=headers, json=payload) + _logger.info(f"Response dari Biteship: {response.text}") if response.status_code == 200: data = response.json() -- cgit v1.2.3 From 48a2eae94b66f7bb8916dcd984bce17fbb36d45e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 23 May 2025 09:44:34 +0700 Subject: (andri) add flag di API untuk mengetahui quotation SO dibuat dari website atau bukan --- indoteknik_custom/models/sale_order.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 38d2505d..a41e001b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2265,24 +2265,33 @@ class SaleOrder(models.Model): order_line.order_id.use_button = True def _auto_set_shipping_from_website(self): + # Jalankan hanya jika context menandakan ini proses checkout + if not self.env.context.get('from_website_checkout'): + _logger.info("[AutoSetShipping] Dilewati karena bukan dari website checkout (context tidak ada)") + return + for order in self: - # Jalankan hanya untuk SO dari website (ID 59) + _logger.info(f"[AutoSetShipping] Proses otomatis untuk SO: {order.name}") + + # Validasi: pastikan source_id = Website if not order.source_id or order.source_id.id != 59: + _logger.warning(f"[AutoSetShipping] SO {order.name} bukan dari Website (source_id.id != 59)") continue - # Jika shipping method adalah Self Pick Up (ID 32), atur ke custom + # Abaikan jika shipping method adalah Self Pick Up if order.carrier_id and order.carrier_id.id == 32: order.select_shipping_option = 'custom' + _logger.info(f"[AutoSetShipping] SO {order.name} menggunakan Self Pick Up. Set ke custom.") continue - # Set shipping option ke biteship dan jalankan estimasi order.select_shipping_option = 'biteship' order.action_estimate_shipping() if not (order.delivery_service_type and order.carrier_id): + _logger.warning(f"[AutoSetShipping] SO {order.name} tidak memiliki delivery_service_type atau carrier_id") continue - # Ambil provider dari rajaongkir_kurir + # Cari provider dari mapping rajaongkir_kurir self.env.cr.execute(""" SELECT name FROM rajaongkir_kurir WHERE delivery_carrier_id = %s LIMIT 1 @@ -2297,7 +2306,7 @@ class SaleOrder(models.Model): _logger.warning(f"[AutoSetShipping] Provider tidak ditemukan untuk carrier_id: {order.carrier_id.id}") continue - # Cari shipping option berdasarkan provider dan courier_service_code + # Cari shipping option berdasarkan courier_service_code + provider matched_option = self.env['shipping.option'].search([ ('sale_order_id', '=', order.id), ('courier_service_code', '=', order.delivery_service_type), @@ -2307,15 +2316,16 @@ class SaleOrder(models.Model): if matched_option: order.shipping_option_id = matched_option.id order.delivery_amt = matched_option.price - _logger.info(f"[AutoSetShipping] Matched via courier_service_code: {matched_option.name} | Provider: {provider}") + _logger.info(f"[AutoSetShipping] Match: {matched_option.name} | Provider: {provider} | Price: {matched_option.price}") else: - _logger.warning(f"[AutoSetShipping] No match for service code '{order.delivery_service_type}' and provider '{provider}' in SO {order.name}") + _logger.warning(f"[AutoSetShipping] Tidak ditemukan match untuk SO {order.name} dengan service_code '{order.delivery_service_type}' dan provider '{provider}'") @api.model def create(self, vals): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) + _logger.info(f"[CREATE CONTEXT] {self.env.context}") order._auto_set_shipping_from_website() order._compute_etrts_date() order._validate_expected_ready_ship_date() -- cgit v1.2.3 From 89b157500f517659bb931f6ec81d47f2390ebfd2 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 24 May 2025 10:15:37 +0700 Subject: (andri) validasi shipping option jika tidak sesuai dengan shipping method --- indoteknik_custom/models/sale_order.py | 100 +++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 36 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a41e001b..d61e3641 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -367,46 +367,74 @@ class SaleOrder(models.Model): _logger.info(f"Final domain for shipping_option_id: {domain}") return {'domain': {'shipping_option_id': domain}} - @api.model - def fields_get(self, allfields=None, attributes=None): - res = super().fields_get(allfields=allfields, attributes=attributes) - - # Aktifkan hanya kalau sedang buka form Sales Order (safety check) - if self.env.context.get('params', {}).get('model') == 'sale.order' and \ - self.env.context.get('params', {}).get('id'): - - sale_id = self.env.context['params']['id'] - - # Ambil carrier_id dari SO yang sedang dibuka - self.env.cr.execute("SELECT carrier_id FROM sale_order WHERE id = %s", (sale_id,)) - row = self.env.cr.fetchone() - carrier_id = row[0] if row else None + @api.onchange('shipping_option_id') + def _onchange_shipping_option_id(self): + if not self.shipping_option_id or not self.carrier_id: + return - provider = None - if carrier_id: - self.env.cr.execute(""" - SELECT name FROM rajaongkir_kurir WHERE delivery_carrier_id = %s LIMIT 1 - """, (carrier_id,)) - row = self.env.cr.fetchone() - if row and row[0]: - provider = row[0].lower() - else: - self.env.cr.execute("SELECT name FROM delivery_carrier WHERE id = %s", (carrier_id,)) - row = self.env.cr.fetchone() - provider = row[0].lower().split()[0] if row and row[0] else '' + # Ambil provider dari carrier + self.env.cr.execute(""" + SELECT name FROM rajaongkir_kurir + 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 self.carrier_id.name.lower().split()[0] - if provider: - domain = [ - '|', - '&', ('sale_order_id', '=', sale_id), ('provider', 'ilike', f'%{provider}%'), - '&', ('sale_order_id', '=', False), ('provider', 'ilike', f'%{provider}%') - ] + selected_provider = (self.shipping_option_id.provider or '').lower() - if 'shipping_option_id' in res: - res['shipping_option_id']['domain'] = domain - _logger.info(f"fields_get - Injected domain for shipping_option_id: {domain}") + if provider not in selected_provider: + warning_msg = { + 'title': "Opsi Tidak Valid", + 'message': f"Opsi pengiriman '{self.shipping_option_id.name}' tidak cocok dengan metode '{self.carrier_id.name}'. Dikembalikan ke sebelumnya." + } - return res + # Kembalikan ke nilai lama (jika record sudah disimpan) + self.shipping_option_id = self._origin.shipping_option_id if self._origin else False + return {'warning': warning_msg} + + # Jika valid + self.delivery_amt = self.shipping_option_id.price + self.delivery_service_type = self.shipping_option_id.courier_service_code + + # @api.model + # def fields_get(self, allfields=None, attributes=None): + # res = super().fields_get(allfields=allfields, attributes=attributes) + + # # Aktifkan hanya kalau sedang buka form Sales Order (safety check) + # if self.env.context.get('params', {}).get('model') == 'sale.order' and \ + # self.env.context.get('params', {}).get('id'): + + # sale_id = self.env.context['params']['id'] + + # # Ambil carrier_id dari SO yang sedang dibuka + # self.env.cr.execute("SELECT carrier_id FROM sale_order WHERE id = %s", (sale_id,)) + # row = self.env.cr.fetchone() + # carrier_id = row[0] if row else None + + # provider = None + # if carrier_id: + # self.env.cr.execute(""" + # SELECT name FROM rajaongkir_kurir WHERE delivery_carrier_id = %s LIMIT 1 + # """, (carrier_id,)) + # row = self.env.cr.fetchone() + # if row and row[0]: + # provider = row[0].lower() + # else: + # self.env.cr.execute("SELECT name FROM delivery_carrier WHERE id = %s", (carrier_id,)) + # row = self.env.cr.fetchone() + # provider = row[0].lower().split()[0] if row and row[0] else '' + + # if provider: + # domain = [ + # '|', + # '&', ('sale_order_id', '=', sale_id), ('provider', 'ilike', f'%{provider}%'), + # '&', ('sale_order_id', '=', False), ('provider', 'ilike', f'%{provider}%') + # ] + + # if 'shipping_option_id' in res: + # res['shipping_option_id']['domain'] = domain + # _logger.info(f"fields_get - Injected domain for shipping_option_id: {domain}") + # return res @api.onchange('select_shipping_option') -- cgit v1.2.3 From efb7581b37cb6b007e249c201a4e48a3e5261dd1 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sun, 25 May 2025 17:47:13 +0700 Subject: (andri) fix bug value delivery service type yang kembali ke nilai awal ketika di save --- indoteknik_custom/models/sale_order.py | 40 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index d61e3641..8cf38040 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -293,11 +293,11 @@ class SaleOrder(models.Model): res['arch'] = etree.tostring(doc, encoding='unicode') return res - @api.onchange('shipping_option_id') - def _onchange_shipping_option_id(self): - if self.shipping_option_id: - self.delivery_amt = self.shipping_option_id.price - self.delivery_service_type = self.shipping_option_id.courier_service_code + # @api.onchange('shipping_option_id') + # def _onchange_shipping_option_id(self): + # if self.shipping_option_id: + # self.delivery_amt = self.shipping_option_id.price + # self.delivery_service_type = self.shipping_option_id.courier_service_code def _get_biteship_courier_codes(self): return [ @@ -369,7 +369,13 @@ class SaleOrder(models.Model): @api.onchange('shipping_option_id') def _onchange_shipping_option_id(self): - if not self.shipping_option_id or not self.carrier_id: + if not self.shipping_option_id: + return + + if not self.carrier_id: + # Jika belum pilih carrier, tetap update harga dan service type + self.delivery_amt = self.shipping_option_id.price + self.delivery_service_type = self.shipping_option_id.courier_service_code return # Ambil provider dari carrier @@ -396,6 +402,19 @@ class SaleOrder(models.Model): self.delivery_amt = self.shipping_option_id.price self.delivery_service_type = self.shipping_option_id.courier_service_code + def _update_delivery_service_type_from_shipping_option(self, vals): + shipping_option_id = vals.get('shipping_option_id') or self.shipping_option_id.id + if shipping_option_id: + shipping_option = self.env['shipping.option'].browse(shipping_option_id) + if shipping_option.exists(): + courier_service = shipping_option.courier_service_code + vals['delivery_service_type'] = courier_service + _logger.info("🛰️ Set delivery_service_type: %s from shipping_option_id: %s", courier_service, shipping_option_id) + else: + _logger.warning("⚠️ shipping_option_id %s not found or invalid.", shipping_option_id) + else: + _logger.info("ℹ️ shipping_option_id not found in vals or record.") + # @api.model # def fields_get(self, allfields=None, attributes=None): # res = super().fields_get(allfields=allfields, attributes=attributes) @@ -2390,6 +2409,13 @@ class SaleOrder(models.Model): 'customer_type': partner.customer_type, }) + def write(self, vals): + if 'shipping_option_id' in vals and vals['shipping_option_id']: + shipping_option = self.env['shipping.option'].browse(vals['shipping_option_id']) + if shipping_option: + vals['delivery_service_type'] = shipping_option.courier_service_code + return super(SaleOrder, self).write(vals) + def write(self, vals): for order in self: if order.state in ['sale', 'cancel']: @@ -2399,6 +2425,8 @@ class SaleOrder(models.Model): if command[0] == 0: # A new line is being added raise UserError( "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") + + order._update_delivery_service_type_from_shipping_option(vals) res = super(SaleOrder, self).write(vals) # self._check_total_margin_excl_third_party() -- cgit v1.2.3 From 85f9481cce4fbec278d2cde48f009d480b8a35ed Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 26 May 2025 17:40:50 +0700 Subject: (andri) match selected service type pada web dan juga odoo --- indoteknik_custom/models/sale_order.py | 91 +++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 41 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8cf38040..e5297011 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2312,61 +2312,70 @@ class SaleOrder(models.Model): order_line.order_id.use_button = True def _auto_set_shipping_from_website(self): - # Jalankan hanya jika context menandakan ini proses checkout if not self.env.context.get('from_website_checkout'): - _logger.info("[AutoSetShipping] Dilewati karena bukan dari website checkout (context tidak ada)") return for order in self: - _logger.info(f"[AutoSetShipping] Proses otomatis untuk SO: {order.name}") - - # Validasi: pastikan source_id = Website + # Validasi source website if not order.source_id or order.source_id.id != 59: - _logger.warning(f"[AutoSetShipping] SO {order.name} bukan dari Website (source_id.id != 59)") continue - # Abaikan jika shipping method adalah Self Pick Up + # Skip jika Self Pick Up if order.carrier_id and order.carrier_id.id == 32: order.select_shipping_option = 'custom' - _logger.info(f"[AutoSetShipping] SO {order.name} menggunakan Self Pick Up. Set ke custom.") continue + # Simpan pilihan user sebelum estimasi + user_carrier_id = order.carrier_id.id if order.carrier_id else None + user_service = order.delivery_service_type + user_amount = order.delivery_amt + + # Jalankan estimasi untuk refresh data order.select_shipping_option = 'biteship' order.action_estimate_shipping() - if not (order.delivery_service_type and order.carrier_id): - _logger.warning(f"[AutoSetShipping] SO {order.name} tidak memiliki delivery_service_type atau carrier_id") - continue - - # Cari provider dari mapping rajaongkir_kurir - self.env.cr.execute(""" - SELECT name FROM rajaongkir_kurir - WHERE delivery_carrier_id = %s LIMIT 1 - """, (order.carrier_id.id,)) - result = self.env.cr.fetchone() - provider = result[0].lower() if result and result[0] else '' - - if not provider and order.carrier_id.name: - provider = order.carrier_id.name.lower().split()[0] - - if not provider: - _logger.warning(f"[AutoSetShipping] Provider tidak ditemukan untuk carrier_id: {order.carrier_id.id}") - continue - - # Cari shipping option berdasarkan courier_service_code + provider - matched_option = self.env['shipping.option'].search([ - ('sale_order_id', '=', order.id), - ('courier_service_code', '=', order.delivery_service_type), - ('provider', 'ilike', provider), - ], limit=1) - - if matched_option: - order.shipping_option_id = matched_option.id - order.delivery_amt = matched_option.price - _logger.info(f"[AutoSetShipping] Match: {matched_option.name} | Provider: {provider} | Price: {matched_option.price}") - else: - _logger.warning(f"[AutoSetShipping] Tidak ditemukan match untuk SO {order.name} dengan service_code '{order.delivery_service_type}' dan provider '{provider}'") - + # Restore pilihan user setelah estimasi + if user_carrier_id and user_service: + # Dapatkan provider + self.env.cr.execute("SELECT name FROM rajaongkir_kurir WHERE delivery_carrier_id = %s LIMIT 1", (user_carrier_id,)) + result = self.env.cr.fetchone() + provider = result[0].lower() if result else order.env['delivery.carrier'].browse(user_carrier_id).name.lower().split()[0] + + # Cari opsi yang cocok (prioritas: service code > nama > harga > fallback) + domain_options = [ + [('courier_service_code', '=', user_service), ('provider', 'ilike', provider)], # exact service + [('name', 'ilike', user_service), ('provider', 'ilike', provider)], # nama service + [('price', '=', user_amount), ('provider', 'ilike', provider)] if user_amount > 0 else None, # harga sama + [('provider', 'ilike', provider)] # fallback + ] + + matched_option = None + for domain in domain_options: + if domain: + matched_option = self.env['shipping.option'].search([('sale_order_id', '=', order.id)] + domain, limit=1) + if matched_option: + break + + # Set opsi yang cocok atau buat manual + if matched_option: + order.shipping_option_id = matched_option.id + order.delivery_amt = matched_option.price + order.delivery_service_type = matched_option.courier_service_code + + # Notif jika harga berubah + if user_amount > 0 and abs(matched_option.price - user_amount) > 1000: + order.message_post(body=f"Harga shipping berubah dari Rp {user_amount:,} ke Rp {matched_option.price:,}") + + elif user_amount > 0: + # Buat opsi manual jika tidak ada yang cocok + manual_option = self.env['shipping.option'].create({ + 'name': f"{provider.upper()} - {user_service}", + 'price': user_amount, + 'provider': provider, + 'courier_service_code': user_service, + 'sale_order_id': order.id, + }) + order.shipping_option_id = manual_option.id @api.model def create(self, vals): -- cgit v1.2.3 From 4ed94e9e1de027aba326ff3dce954b765f752009 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 28 May 2025 09:00:30 +0700 Subject: (andri) penambahan log note ketika biteship berhasil & penyesuaian field required pada SO --- indoteknik_custom/models/sale_order.py | 11 ++++------- indoteknik_custom/models/stock_picking.py | 8 ++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index e5297011..f2bb27ad 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -652,8 +652,8 @@ class SaleOrder(models.Model): product_names = '
'.join(missing_weight_products) self.message_post(body=f"Produk berikut tidak memiliki berat:
{product_names}") - # if total_weight == 0: - # raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") + if total_weight == 0: + raise UserError("Tidak dapat mengestimasi ongkir tanpa karena berat produk = 0 kg.") # Validasi alamat pengiriman if not self.real_shipping_id: @@ -674,8 +674,8 @@ class SaleOrder(models.Model): total_weight = self._validate_for_shipping_estimate() weight_gram = int(total_weight * 1000) - if weight_gram < 100: - weight_gram = 100 + # if weight_gram < 100: + # weight_gram = 100 items = [{ "name": "Paket Pesanan", @@ -683,9 +683,6 @@ class SaleOrder(models.Model): "value": int(self.amount_untaxed), "weight": weight_gram, "quantity": 1, - "height": 10, - "width": 10, - "length": 10 }] shipping_address = self.real_shipping_id diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 5548db75..4522dac0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -617,6 +617,14 @@ class StockPicking(models.Model): waybill_id = data.get("courier", {}).get("waybill_id", "") + self.message_post( + body=f"📦 Biteship berhasil dilakukan.
" + f"Kurir: {self.carrier_id.name}
" + f"Tracking ID: {self.biteship_tracking_id or '-'}
" + f"Resi: {waybill_id or '-'}", + message_type="comment" + ) + message = f"✅ Berhasil Order ke Biteship! Resi: {waybill_id}" if waybill_id else "⚠️ Order berhasil, tetapi tidak ada nomor resi." return { -- cgit v1.2.3 From 6e56ffc2c5b5ee22bc97d2518274eeb8a959e80e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 28 May 2025 15:16:40 +0700 Subject: (andri) penyesuaian request biteship di stock picking --- indoteknik_custom/models/sale_order.py | 4 +- indoteknik_custom/models/stock_picking.py | 73 ++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 28 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index f2bb27ad..a4166016 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -862,8 +862,8 @@ class SaleOrder(models.Model): def _call_biteship_api(self, origin_data, destination_data, items, couriers=None): - url = 'https://api.biteship.com/v1/rates/couriers' - api_key = 'biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA' + url = "https://api.biteship.com/v1/rates/couriers" + api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" headers = { 'Authorization': api_key, diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4522dac0..54da700b 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -526,46 +526,55 @@ class StockPicking(models.Model): if self.biteship_tracking_id: raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") - # Mencari data sale.order.line berdasarkan sale_id + # Fungsi bantu: menentukan apakah kurir perlu koordinat + def is_courier_need_coordinates(service_code): + return service_code in [ + "instant", "same_day", "instant_car", + "instant_bike", "motorcycle", "mpv", "van", "truck", + "cdd_bak", "cdd_box", "engkel_box", "engkel_bak" + ] + + # Ambil order line products = self.env['sale.order.line'].search([('order_id', '=', self.sale_id.id)]) - - # Fungsi untuk membangun items_data dari order lines + + # Bangun data items untuk standard def build_items_data(lines): return [{ "name": line.product_id.name, "description": line.name, "value": line.price_unit, "quantity": line.product_uom_qty, - "weight": line.weight + "weight": line.weight*1000 } for line in lines] - # Items untuk pengiriman standard items_data_standard = build_items_data(products) - # Items untuk pengiriman instant, mengambil product_id dari move_line_ids_without_package + # Bangun data items untuk pengiriman instant items_data_instant = [] for move_line in self.move_line_ids_without_package: - # Mencari baris di sale.order.line berdasarkan product_id dari move_line order_line = self.env['sale.order.line'].search([ ('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, "description": order_line.name, "value": order_line.price_unit, "quantity": move_line.qty_done, - "weight": order_line.weight + "weight": order_line.weight*1000 }) + _logger.info(f"Items data standard: {items_data_standard}") + _logger.info(f"Items data instant: {items_data_instant}") + # Bangun payload dasar payload = { - "origin_coordinate" :{ + "origin_coordinate": { "latitude": -6.3031123, - "longitude" : 106.7794934999 + "longitude": 106.7794934999 }, - "reference_id" : self.sale_id.name, + "reference_id": self.sale_id.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -581,30 +590,40 @@ class StockPicking(models.Model): "courier_type": self.sale_id.delivery_service_type or "reg", "courier_company": self.carrier_id.name.lower(), "delivery_type": "now", - "destination_postal_code": self.real_shipping_id.zip, "items": items_data_standard } - _logger.info(f"Payload untuk Biteship: {payload}") + _logger.info(f"Delivery service type: {self.sale_id.delivery_service_type}") + _logger.info(f"Carrier: {self.carrier_id.name}") + _logger.info(f"Payload awal: {payload}") + + # Tambahkan destination_coordinate jika diperlukan + if is_courier_need_coordinates(self.sale_id.delivery_service_type): + if not self.real_shipping_id.latitude or not self.real_shipping_id.longtitude: + raise UserError("Alamat tujuan tidak memiliki koordinat (latitude/longitude).") + + # items_to_use = items_data_instant if items_data_instant else items_data_standard + if not items_data_instant: + raise UserError("Pengiriman instant membutuhkan produk yang sudah diproses (qty_done > 0). Harap lakukan validasi picking terlebih dahulu.") - # Cek jika pengiriman instant atau same_day - if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): payload.update({ - "destination_coordinate" : { + "destination_coordinate": { "latitude": self.real_shipping_id.latitude, "longitude": self.real_shipping_id.longtitude, }, "items": items_data_instant }) - + + _logger.info(f"Payload untuk Biteship: {payload}") + + # Kirim ke Biteship api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } - # Kirim request ke Biteship - response = requests.post(_biteship_url+'/orders', headers=headers, json=payload) + response = requests.post(_biteship_url + '/orders', headers=headers, json=payload) _logger.info(f"Response dari Biteship: {response.text}") if response.status_code == 200: @@ -614,11 +633,11 @@ class StockPicking(models.Model): self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") - - waybill_id = data.get("courier", {}).get("waybill_id", "") + + waybill_id = self.biteship_waybill_id self.message_post( - body=f"📦 Biteship berhasil dilakukan.
" + body=f"Biteship berhasil dilakukan.
" f"Kurir: {self.carrier_id.name}
" f"Tracking ID: {self.biteship_tracking_id or '-'}
" f"Resi: {waybill_id or '-'}", @@ -629,16 +648,18 @@ class StockPicking(models.Model): return { 'effect': { - 'fadeout': 'slow', # Efek menghilang perlahan - 'message': message, # Pesan sukses - 'type': 'rainbow_man', # Efek animasi lucu Odoo + 'fadeout': 'slow', + 'message': message, + 'type': 'rainbow_man', } } + else: error_data = response.json() error_message = error_data.get("error", "Unknown error") error_code = error_data.get("code", "No code provided") raise UserError(f"Error saat mengirim ke Biteship: {error_message} (Code: {error_code})") + @api.constrains('driver_departure_date') def constrains_driver_departure_date(self): -- cgit v1.2.3 From b641951b811590231c060ac40ef633f59037bfbb Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 29 May 2025 20:26:55 +0700 Subject: (andri) revisi biteship terkait berat --- indoteknik_custom/models/sale_order.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a4166016..94cbfb84 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -652,8 +652,8 @@ class SaleOrder(models.Model): product_names = '
'.join(missing_weight_products) self.message_post(body=f"Produk berikut tidak memiliki berat:
{product_names}") - if total_weight == 0: - raise UserError("Tidak dapat mengestimasi ongkir tanpa karena berat produk = 0 kg.") + # if total_weight == 0: + # raise UserError("Tidak dapat mengestimasi ongkir tanpa karena berat produk = 0 kg.") # Validasi alamat pengiriman if not self.real_shipping_id: @@ -674,8 +674,8 @@ class SaleOrder(models.Model): total_weight = self._validate_for_shipping_estimate() weight_gram = int(total_weight * 1000) - # if weight_gram < 100: - # weight_gram = 100 + if weight_gram < 100: + weight_gram = 100 items = [{ "name": "Paket Pesanan", -- cgit v1.2.3 From 711885733186a090be447099f1b7979e89ada85d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 29 May 2025 21:58:53 +0700 Subject: (andri) pilihan shipping method akan terganti jika pilihan kurir tsb tidak ada pada hasil estimate shipping (otomatis akan keganti di opsi pertama) --- indoteknik_custom/models/sale_order.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 94cbfb84..e4564c7d 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -828,6 +828,19 @@ class SaleOrder(models.Model): 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}") + if selected_option: self.shipping_option_id = selected_option.id self.delivery_amt = selected_option.price -- cgit v1.2.3 From d6c59069035919e270d4940a39242fe5d5291982 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 29 May 2025 22:13:46 +0700 Subject: (andri) tambah validasi pada shipping method --- indoteknik_custom/models/sale_order.py | 83 +++++++++++++++++----------------- 1 file changed, 41 insertions(+), 42 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index e4564c7d..946761ce 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -306,64 +306,63 @@ class SaleOrder(models.Model): @api.onchange('carrier_id') def _onchange_carrier_id(self): - self.shipping_option_id = False - self.delivery_amt = 0 - if not self.carrier_id: + self.shipping_option_id = False + self.delivery_amt = 0 return {'domain': {'shipping_option_id': [('id', '=', -1)]}} - - # Cari provider dari carrier yang dipilih langsung dari rajaongkir_kurir + + # Ambil provider dari rajaongkir_kurir self.env.cr.execute(""" SELECT name FROM rajaongkir_kurir WHERE delivery_carrier_id = %s LIMIT 1 """, (self.carrier_id.id,)) - result = self.env.cr.fetchone() provider = result[0].lower() if result and result[0] else False - - # Fallback jika tidak ditemukan di rajaongkir_kurir + + # Fallback: pakai nama carrier if not provider: - # Gunakan nama carrier, ambil kata pertama provider = self.carrier_id.name.lower().split()[0] if self.carrier_id.name else False - - # Log untuk debugging + _logger.info(f"Carrier changed to {self.carrier_id.name}, provider: {provider}") - - # PENTING: self.id mungkin False atau NewId pada saat onchange - # Perlu memeriksa apakah ini adalah record baru atau yang sudah ada - sale_order_id = False - if hasattr(self, '_origin') and self._origin: - sale_order_id = self._origin.id - - # Cek jika ada shipping options dengan provider ini (tanpa filter sale_order_id dulu) + + sale_order_id = self._origin.id if self._origin else False + + # Cek jumlah shipping option dengan provider tersebut self.env.cr.execute(""" SELECT COUNT(*) FROM shipping_option - WHERE LOWER(provider) LIKE %s - """, (f'%{provider}%',)) - + 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}") - - # Buat domain untuk shipping_option_id - if count > 0: - # Jika ada options yang tersedia, buat domain yang lebih permisif - domain = [ - '|', - ('provider', 'ilike', f'%{provider}%'), - ('provider', '=', provider) - ] - - # Jika ini record yang sudah ada, tambahkan filter sale_order_id - if sale_order_id: - domain = [ - '|', - '&', ('sale_order_id', '=', sale_order_id), ('provider', 'ilike', f'%{provider}%'), - '&', ('sale_order_id', '=', False), ('provider', 'ilike', f'%{provider}%') - ] - else: - domain = [('id', '=', -1)] # Tidak ada opsi - + + # VALIDASI GAGAL + if count == 0: + previous_carrier = self._origin.carrier_id if self._origin else False + self.carrier_id = previous_carrier + + return { + 'warning': { + 'title': "Shipping Method Tidak Tersedia", + 'message': ( + f"Shipping method '{provider}' tidak tersedia pada pengiriman ini.\n" + f"Pilihan dikembalikan ke sebelumnya." + ) + }, + 'domain': {'shipping_option_id': [('id', '=', -1)]} + } + + # ✅ Valid, baru reset shipping_option dan delivery amount + self.shipping_option_id = False + self.delivery_amt = 0 + + 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}} -- cgit v1.2.3 From 2f89da69c9ec8d78f187be5afd254cdd594ee24a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 30 May 2025 09:19:22 +0700 Subject: (andri) fix bug shipping method custom --- indoteknik_custom/models/sale_order.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 946761ce..f1280b37 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -311,6 +311,15 @@ class SaleOrder(models.Model): 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 self.env.cr.execute(""" SELECT name FROM rajaongkir_kurir @@ -320,21 +329,27 @@ class SaleOrder(models.Model): result = self.env.cr.fetchone() provider = result[0].lower() if result and result[0] else False - # Fallback: pakai nama carrier + # 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}") - sale_order_id = self._origin.id if self._origin else False + sale_order_id = self._origin.id if self._origin and self._origin.id else None - # Cek jumlah shipping option dengan provider tersebut - 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] + # 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}%',)) + count = self.env.cr.fetchone()[0] _logger.info(f"Found {count} shipping options for provider {provider}") # VALIDASI GAGAL @@ -346,7 +361,7 @@ class SaleOrder(models.Model): 'warning': { 'title': "Shipping Method Tidak Tersedia", 'message': ( - f"Shipping method '{provider}' tidak tersedia pada pengiriman ini.\n" + f"Shipping method '{self.carrier_id.name}' tidak tersedia pada pengiriman ini.\n" f"Pilihan dikembalikan ke sebelumnya." ) }, @@ -366,6 +381,7 @@ class SaleOrder(models.Model): _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: -- cgit v1.2.3 From c652ee3e1f652d23e37833f01e3fdd7aa8e52021 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 31 May 2025 11:03:06 +0700 Subject: (andri) add tracking biteship pada BU OUT & fix delivery departure & arrival date --- indoteknik_custom/models/stock_picking.py | 55 ++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 54da700b..4d38e5b3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1551,7 +1551,60 @@ 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: + local_dt_str = picking._convert_to_local_time(dt_str) + dt = fields.Datetime.from_string(local_dt_str) + except Exception as e: + _logger.warning(f"[Biteship Sync] Gagal parse datetime: {e}") + continue + + # Update tanggal ke field + if status == "picked" and dt and not picking.driver_departure_date: + updated_fields["driver_departure_date"] = dt + + if status == "delivered" and dt and not picking.driver_arrival_date: + updated_fields["driver_arrival_date"] = dt + + # Buat log unik + if dt and desc: + desc_clean = ' '.join(desc.strip().split()) + log_line = f"[TRACKING] {status} - {dt.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) -- cgit v1.2.3 From b6928c9bffc486b471d4c335c2550cbc1bf7d841 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 31 May 2025 12:32:45 +0700 Subject: (andri) fix bug datetime departure&arrival --- indoteknik_custom/models/stock_picking.py | 34 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4d38e5b3..4517a941 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1570,23 +1570,23 @@ class StockPicking(models.Model): dt = False try: - local_dt_str = picking._convert_to_local_time(dt_str) - dt = fields.Datetime.from_string(local_dt_str) + 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 + # Update tanggal ke field (pastikan naive datetime UTC) if status == "picked" and dt and not picking.driver_departure_date: - updated_fields["driver_departure_date"] = dt + 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"] = dt + updated_fields["driver_arrival_date"] = fields.Datetime.to_string(dt) - # Buat log unik + # 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.strftime('%d %b %Y %H:%M')}: {desc_clean}" + 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) @@ -1607,15 +1607,21 @@ class StockPicking(models.Model): 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 = { -- cgit v1.2.3 From 03d52ddceacea1939aef8ee4c571cacdb8b2c055 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sun, 1 Jun 2025 13:12:27 +0700 Subject: (andri) fix bug selected shipping service --- indoteknik_custom/models/sale_order.py | 78 ++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 37 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index f1280b37..490e4581 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,20 +306,13 @@ class SaleOrder(models.Model): @api.onchange('carrier_id') def _onchange_carrier_id(self): + 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 self.env.cr.execute(""" SELECT name FROM rajaongkir_kurir @@ -329,56 +322,67 @@ 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 + # Fallback dari nama carrier 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}%',)) + # Ambil ID SO + sale_order_id = self._origin.id if self._origin else False + # Hitung jumlah 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}") + + # Jika tidak ditemukan shipping option 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 + + # Rehitung provider untuk domain fallback + 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 sale_order_id and 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 ditemukan, set domain normal 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}} -- cgit v1.2.3 From e5a3d37ca40127a6bd2a31f08d94704103a1ac11 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sun, 1 Jun 2025 17:23:56 +0700 Subject: (andri) fix bug shipping method --- indoteknik_custom/models/sale_order.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 490e4581..453406c4 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -306,6 +306,10 @@ 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 @@ -313,7 +317,7 @@ class SaleOrder(models.Model): if not self.carrier_id: 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 @@ -322,16 +326,14 @@ class SaleOrder(models.Model): result = self.env.cr.fetchone() provider = result[0].lower() if result and result[0] else False - # Fallback dari nama carrier if not provider: provider = self.carrier_id.name.lower().split()[0] if self.carrier_id.name else False _logger.info(f"[Carrier Changed] {self.carrier_id.name}, Detected Provider: {provider}") - # Ambil ID SO - sale_order_id = self._origin.id if self._origin else False + sale_order_id = self._origin.id - # Hitung jumlah shipping_option yang cocok + # 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 @@ -340,7 +342,6 @@ class SaleOrder(models.Model): _logger.info(f"[Shipping Option Count] Provider: {provider} | SO ID: {sale_order_id} | Count: {count}") - # Jika tidak ditemukan shipping option if count == 0: previous_carrier = self._origin.carrier_id if self._origin else False previous_provider = False @@ -348,7 +349,7 @@ class SaleOrder(models.Model): self.carrier_id = previous_carrier self.shipping_option_id = self._origin.shipping_option_id if self._origin else False - # Rehitung provider untuk domain fallback + # Ambil kembali domain provider sebelumnya if previous_carrier: self.env.cr.execute(""" SELECT name FROM rajaongkir_kurir @@ -358,7 +359,7 @@ class SaleOrder(models.Model): 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 sale_order_id and previous_provider: + if previous_provider: fallback_domain = [ '|', '&', ('sale_order_id', '=', sale_order_id), ('provider', 'ilike', f'%{previous_provider}%'), @@ -376,7 +377,7 @@ class SaleOrder(models.Model): 'domain': {'shipping_option_id': fallback_domain} } - # Jika ditemukan, set domain normal + # Jika data ada, kembalikan domain biasa domain = [ '|', '&', ('sale_order_id', '=', sale_order_id), ('provider', 'ilike', f'%{provider}%'), @@ -385,7 +386,6 @@ class SaleOrder(models.Model): return {'domain': {'shipping_option_id': domain}} - @api.onchange('shipping_option_id') def _onchange_shipping_option_id(self): if not self.shipping_option_id: -- cgit v1.2.3 From 1c0bec8dadc593348df9ca585dae13b8ff65c316 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sun, 1 Jun 2025 20:29:57 +0700 Subject: (andri) fix bug price estimate biteship --- indoteknik_custom/models/sale_order.py | 57 ++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 27 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 453406c4..ec4b55e7 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -696,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, }] @@ -811,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: @@ -822,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 -- cgit v1.2.3 From fe458043667bb7f1cde757659fefe0174252002d Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 2 Jun 2025 01:09:39 +0700 Subject: estimasi barang sampai fix> --- indoteknik_custom/models/sale_order.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index f1280b37..aa1b4b49 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1057,16 +1057,23 @@ class SaleOrder(models.Model): rec.compute_fullfillment = True - @api.depends('date_order', 'estimated_arrival_days', 'state', 'estimated_arrival_days_start') + @api.depends('expected_ready_to_ship', 'shipping_option_id.etd', 'state') def _compute_eta_date(self): - current_date = datetime.now() - for rec in self: - if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start: - rec.eta_date = current_date + timedelta(days=rec.estimated_arrival_days) - rec.eta_date_start = current_date + timedelta(days=rec.estimated_arrival_days_start) + for rec in self: + if rec.expected_ready_to_ship and rec.shipping_option_id and rec.shipping_option_id.etd and rec.state not in ['cancel']: + etd_text = rec.shipping_option_id.etd + match = re.match(r"(\d+)\s*-\s*(\d+)", etd_text) + if match: + start_days = int(match.group(1)) + end_days = int(match.group(2)) + rec.eta_date_start = rec.expected_ready_to_ship + timedelta(days=start_days) + rec.eta_date = rec.expected_ready_to_ship + timedelta(days=end_days) + else: + rec.eta_date_start = False + rec.eta_date = False else: - rec.eta_date = False rec.eta_date_start = False + rec.eta_date = False def get_days_until_next_business_day(self, start_date=None, *args, **kwargs): -- cgit v1.2.3 From 9e71d7ac3e018e9d4415e1341231671e62fbdb45 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 2 Jun 2025 11:19:21 +0700 Subject: fix hours shipping method --- indoteknik_custom/models/sale_order.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a76d8011..a86d43cb 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1068,13 +1068,33 @@ class SaleOrder(models.Model): def _compute_eta_date(self): for rec in self: if rec.expected_ready_to_ship and rec.shipping_option_id and rec.shipping_option_id.etd and rec.state not in ['cancel']: - etd_text = rec.shipping_option_id.etd - match = re.match(r"(\d+)\s*-\s*(\d+)", etd_text) + etd_text = rec.shipping_option_id.etd.strip().lower() + match = re.match(r"(\d+)\s*-\s*(\d+)\s*(days?|hours?)", etd_text) + single_match = re.match(r"(\d+)\s*(days?|hours?)", etd_text) + if match: - start_days = int(match.group(1)) - end_days = int(match.group(2)) - rec.eta_date_start = rec.expected_ready_to_ship + timedelta(days=start_days) - rec.eta_date = rec.expected_ready_to_ship + timedelta(days=end_days) + start_val = int(match.group(1)) + end_val = int(match.group(2)) + unit = match.group(3) + + if 'hour' in unit: + rec.eta_date_start = rec.expected_ready_to_ship + timedelta(hours=start_val) + rec.eta_date = rec.expected_ready_to_ship + timedelta(hours=end_val) + else: + rec.eta_date_start = rec.expected_ready_to_ship + timedelta(days=start_val) + rec.eta_date = rec.expected_ready_to_ship + timedelta(days=end_val) + + elif single_match: + val = int(single_match.group(1)) + unit = single_match.group(2) + + if 'hour' in unit: + rec.eta_date_start = rec.expected_ready_to_ship + timedelta(hours=val) + rec.eta_date = rec.expected_ready_to_ship + timedelta(hours=val) + else: + rec.eta_date_start = rec.expected_ready_to_ship + timedelta(days=val) + rec.eta_date = rec.expected_ready_to_ship + timedelta(days=val) + else: rec.eta_date_start = False rec.eta_date = False -- cgit v1.2.3 From 43d180117e90db9115f07ab4b5b2880c32594bea Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 2 Jun 2025 12:09:27 +0700 Subject: (andri) fix date tracking --- indoteknik_custom/models/stock_picking.py | 34 +++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4517a941..a2935a07 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1570,7 +1570,8 @@ class StockPicking(models.Model): dt = False try: - dt = picking._convert_to_local_time(dt_str) + dt = picking._convert_to_utc_datetime(dt_str) + _logger.info(f"[Biteship Sync] Berhasil parse datetime: {dt_str} -> {dt}") except Exception as e: _logger.warning(f"[Biteship Sync] Gagal parse datetime: {e}") continue @@ -1584,7 +1585,12 @@ class StockPicking(models.Model): # Buat log unik dengan waktu lokal Asia/Jakarta if dt and desc: - dt_local = pytz.utc.localize(dt).astimezone(pytz.timezone("Asia/Jakarta")) + try: + dt_local = parser.parse(dt_str).replace(tzinfo=None) + except Exception as e: + _logger.warning(f"[Biteship Sync] Gagal parse dt_str untuk log: {e}") + dt_local = dt # fallback + 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): @@ -1605,10 +1611,24 @@ class StockPicking(models.Model): """, (self._name, self.id, f"%{log_line}%")) return self.env.cr.fetchone() is not None + # Untuk internal Odoo (mengembalikan naive UTC datetime untuk disimpan ke DB) + def _convert_to_utc_datetime(self, iso_date): + try: + 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) + utc_dt = waktu.astimezone(pytz.utc).replace(tzinfo=None) + return utc_dt + except Exception as e: + _logger.warning(f"[Biteship] Gagal konversi waktu UTC: {e}") + return False + + # Untuk tampilan di API atau kebutuhan web (mengembalikan string waktu lokal) def _convert_to_local_time(self, iso_date): try: - from dateutil import parser - import pytz if isinstance(iso_date, str): waktu = parser.parse(iso_date) else: @@ -1617,11 +1637,9 @@ class StockPicking(models.Model): waktu = waktu.replace(tzinfo=pytz.utc) local_tz = pytz.timezone("Asia/Jakarta") local_dt = waktu.astimezone(local_tz) - utc_dt = local_dt.astimezone(pytz.utc).replace(tzinfo=None) - return utc_dt + return local_dt.strftime("%Y-%m-%d %H:%M:%S") except Exception as e: - _logger.warning(f"[Biteship] Gagal konversi waktu lokal: {e}") - return False + return str(e) def _map_status_biteship(self, status): status_mapping = { -- cgit v1.2.3 From d6f3060b46582ddd78f596ce3527871cae1b2b46 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 3 Jun 2025 14:08:30 +0700 Subject: (andri) ganti referensi dari no SO ke no BU OUT --- indoteknik_custom/models/stock_picking.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a2935a07..39872ecb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -568,13 +568,13 @@ class StockPicking(models.Model): _logger.info(f"Items data standard: {items_data_standard}") _logger.info(f"Items data instant: {items_data_instant}") - # Bangun payload dasar + payload = { "origin_coordinate": { "latitude": -6.3031123, "longitude": 106.7794934999 }, - "reference_id": self.sale_id.name, + "reference_id": self.name, # PERUBAHAN: Gunakan nomor BU/OUT "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -587,6 +587,7 @@ class StockPicking(models.Model): "destination_address": self.real_shipping_id.street, "destination_postal_code": self.real_shipping_id.zip, "origin_note": "BELAKANG INDOMARET", + "destination_note": f"SO: {self.sale_id.name}", # PERUBAHAN: Tambahkan SO ke note "courier_type": self.sale_id.delivery_service_type or "reg", "courier_company": self.carrier_id.name.lower(), "delivery_type": "now", @@ -640,7 +641,9 @@ class StockPicking(models.Model): body=f"Biteship berhasil dilakukan.
" f"Kurir: {self.carrier_id.name}
" f"Tracking ID: {self.biteship_tracking_id or '-'}
" - f"Resi: {waybill_id or '-'}", + f"Resi: {waybill_id or '-'}
" + f"Reference: {self.name}
" + f"SO: {self.sale_id.name}", message_type="comment" ) -- cgit v1.2.3 From b519e0fdac46a64ffef87d27ba824038147d831b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 3 Jun 2025 15:36:08 +0700 Subject: (andri) memastikan order sesuai dengan informasi barang yang ingin dikirim --- indoteknik_custom/models/stock_picking.py | 98 ++++++++++++------------------- 1 file changed, 39 insertions(+), 59 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 39872ecb..cdff6e32 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -526,7 +526,6 @@ class StockPicking(models.Model): if self.biteship_tracking_id: raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") - # Fungsi bantu: menentukan apakah kurir perlu koordinat def is_courier_need_coordinates(service_code): return service_code in [ "instant", "same_day", "instant_car", @@ -534,47 +533,42 @@ class StockPicking(models.Model): "cdd_bak", "cdd_box", "engkel_box", "engkel_bak" ] - # Ambil order line - products = self.env['sale.order.line'].search([('order_id', '=', self.sale_id.id)]) - - # Bangun data items untuk standard - def build_items_data(lines): - return [{ - "name": line.product_id.name, - "description": line.name, - "value": line.price_unit, - "quantity": line.product_uom_qty, - "weight": line.weight*1000 - } for line in lines] - - items_data_standard = build_items_data(products) + # ✅ Ambil item dari move_line_ids_with_package (qty_done > 0) + items = [] + for ml in self.move_line_ids_without_package: + if ml.qty_done <= 0: + continue - # Bangun data items untuk pengiriman instant - items_data_instant = [] - for move_line in self.move_line_ids_without_package: - order_line = self.env['sale.order.line'].search([ + product = ml.product_id + weight = product.weight or 0.1 # default minimal + line = ml.sale_line_id or self.env['sale.order.line'].search([ ('order_id', '=', self.sale_id.id), - ('product_id', '=', move_line.product_id.id) + ('product_id', '=', product.id) ], limit=1) - if order_line: - items_data_instant.append({ - "name": order_line.product_id.name, - "description": order_line.name, - "value": order_line.price_unit, - "quantity": move_line.qty_done, - "weight": order_line.weight*1000 - }) + value = line.price_unit if line else 0 + description = line.name if line else product.name + + items.append({ + "name": product.name, + "description": description, + "value": value, + "quantity": ml.qty_done, + "weight": int(weight * 1000), + }) - _logger.info(f"Items data standard: {items_data_standard}") - _logger.info(f"Items data instant: {items_data_instant}") + if not items: + raise UserError("Pengiriman tidak dapat dilakukan karena tidak ada barang yang divalidasi (qty_done = 0).") + + shipping_partner = self.real_shipping_id + courier_service_code = self.sale_id.delivery_service_type or "reg" payload = { "origin_coordinate": { "latitude": -6.3031123, "longitude": 106.7794934999 }, - "reference_id": self.name, # PERUBAHAN: Gunakan nomor BU/OUT + "reference_id": self.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -582,38 +576,26 @@ class StockPicking(models.Model): "origin_contact_phone": "081717181922", "origin_address": "Jl. Bandengan Utara Komp A & BRT. Penjaringan, Kec. Penjaringan, Jakarta (BELAKANG INDOMARET) KOTA JAKARTA UTARA PENJARINGAN", "origin_postal_code": 14440, - "destination_contact_name": self.real_shipping_id.name, - "destination_contact_phone": self.real_shipping_id.phone or self.real_shipping_id.mobile, - "destination_address": self.real_shipping_id.street, - "destination_postal_code": self.real_shipping_id.zip, + "destination_contact_name": shipping_partner.name, + "destination_contact_phone": shipping_partner.phone or shipping_partner.mobile, + "destination_address": shipping_partner.street, + "destination_postal_code": shipping_partner.zip, "origin_note": "BELAKANG INDOMARET", - "destination_note": f"SO: {self.sale_id.name}", # PERUBAHAN: Tambahkan SO ke note - "courier_type": self.sale_id.delivery_service_type or "reg", + "destination_note": f"SO: {self.sale_id.name}", + "courier_type": courier_service_code, "courier_company": self.carrier_id.name.lower(), "delivery_type": "now", - "items": items_data_standard + "items": items } - _logger.info(f"Delivery service type: {self.sale_id.delivery_service_type}") - _logger.info(f"Carrier: {self.carrier_id.name}") - _logger.info(f"Payload awal: {payload}") - - # Tambahkan destination_coordinate jika diperlukan - if is_courier_need_coordinates(self.sale_id.delivery_service_type): - if not self.real_shipping_id.latitude or not self.real_shipping_id.longtitude: + if is_courier_need_coordinates(courier_service_code): + if not shipping_partner.latitude or not shipping_partner.longtitude: raise UserError("Alamat tujuan tidak memiliki koordinat (latitude/longitude).") - # items_to_use = items_data_instant if items_data_instant else items_data_standard - if not items_data_instant: - raise UserError("Pengiriman instant membutuhkan produk yang sudah diproses (qty_done > 0). Harap lakukan validasi picking terlebih dahulu.") - - payload.update({ - "destination_coordinate": { - "latitude": self.real_shipping_id.latitude, - "longitude": self.real_shipping_id.longtitude, - }, - "items": items_data_instant - }) + payload["destination_coordinate"] = { + "latitude": shipping_partner.latitude, + "longitude": shipping_partner.longtitude, + } _logger.info(f"Payload untuk Biteship: {payload}") @@ -633,7 +615,7 @@ class StockPicking(models.Model): self.biteship_id = data.get("id", "") self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") - self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") + self.delivery_tracking_no = self.biteship_waybill_id waybill_id = self.biteship_waybill_id @@ -656,13 +638,11 @@ class StockPicking(models.Model): 'type': 'rainbow_man', } } - else: error_data = response.json() error_message = error_data.get("error", "Unknown error") error_code = error_data.get("code", "No code provided") raise UserError(f"Error saat mengirim ke Biteship: {error_message} (Code: {error_code})") - @api.constrains('driver_departure_date') def constrains_driver_departure_date(self): -- cgit v1.2.3 From 8a9c08a21fd7d2ac63ef849d9417b11563092f0d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 3 Jun 2025 21:10:12 +0700 Subject: (andri) perbaikan biteship pada stock picking --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cdff6e32..7e001299 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -541,9 +541,9 @@ class StockPicking(models.Model): product = ml.product_id weight = product.weight or 0.1 # default minimal - line = ml.sale_line_id or self.env['sale.order.line'].search([ + line = ml.move_id.sale_line_id or self.env['sale.order.line'].search([ ('order_id', '=', self.sale_id.id), - ('product_id', '=', product.id) + ('product_id', '=', ml.product_id.id) ], limit=1) value = line.price_unit if line else 0 -- cgit v1.2.3 From e43e3d0da19e5cbbcbadec40c252c24d2921149c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 4 Jun 2025 08:52:38 +0700 Subject: (andri) fix bug SLA pada quotation SO --- indoteknik_custom/models/sale_order.py | 172 ++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 66 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a86d43cb..3e340c60 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -306,78 +306,71 @@ class SaleOrder(models.Model): @api.onchange('carrier_id') def _onchange_carrier_id(self): - # Jika record masih baru (belum disimpan), jangan jalankan onchange + # ─────────────────────────────────────────────────────────────── + # 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 + # ─────────────────────────────────────────────────────────────── self.shipping_option_id = False - # self.delivery_amt = 0 - # self.delivery_service_type = False if not self.carrier_id: return {'domain': {'shipping_option_id': [('id', '=', -1)]}} - # Ambil provider dari mapping + # cari provider dari mapping rajaongkir_kurir self.env.cr.execute(""" - SELECT name FROM rajaongkir_kurir + SELECT name FROM rajaongkir_kurir WHERE delivery_carrier_id = %s LIMIT 1 """, (self.carrier_id.id,)) - result = self.env.cr.fetchone() - provider = result[0].lower() if result and result[0] else False - - if not provider: - provider = self.carrier_id.name.lower().split()[0] if self.carrier_id.name else False + row = self.env.cr.fetchone() + provider = row[0].lower() if row and row[0] else ( + self.carrier_id.name.lower().split()[0] if self.carrier_id.name else False + ) _logger.info(f"[Carrier Changed] {self.carrier_id.name}, Detected Provider: {provider}") - sale_order_id = self._origin.id - - # Cek apakah ada shipping_option yang cocok + # 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)) - count = self.env.cr.fetchone()[0] - - _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}%') - ] + matched = self.env.cr.fetchone()[0] or 0 + 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.shipping_option_id = self._origin.shipping_option_id or False return { 'warning': { 'title': "Shipping Option Tidak Ditemukan", 'message': ( - f"Layanan kurir tidak tersedia untuk pengiriman ini.\n" - f"Pilihan dikembalikan ke sebelumnya." + "Layanan kurir tidak tersedia untuk pengiriman ini.\n" + "Pilihan dikembalikan ke sebelumnya." ) }, - 'domain': {'shipping_option_id': fallback_domain} + 'domain': {'shipping_option_id': [('id', '=', -1)]} } - # Jika data ada, kembalikan domain biasa + # kalau match ada → set domain normal (hanya option dengan provider itu) domain = [ '|', '&', ('sale_order_id', '=', sale_order_id), ('provider', 'ilike', f'%{provider}%'), @@ -1230,30 +1223,77 @@ class SaleOrder(models.Model): self._calculate_etrts_date() + # def _validate_expected_ready_ship_date(self): + # for rec in self: + # if not rec.order_line: + # _logger.info("⏩ Lewati validasi ERTS karena belum ada produk.") + # return # Lewati validasi jika belum ada produk + + # now = fields.Datetime.now() + # expected_date = rec.expected_ready_to_ship and rec.expected_ready_to_ship.date() or None + # if not expected_date: + # return # Tidak validasi jika tidak ada input sama sekali + + # sla = rec.calculate_sla_by_vendor() + # offset_day, lewat_jam_3 = rec.get_days_until_next_business_day() + # eta_minimum = now + timedelta(days=sla + offset_day) + + # if expected_date < eta_minimum.date(): + # rec.expected_ready_to_ship = eta_minimum + # raise ValidationError( + # "Tanggal 'Expected Ready to Ship' tidak boleh lebih kecil dari {}. Mohon pilih tanggal minimal {}." + # .format(eta_minimum.strftime('%d-%m-%Y'), eta_minimum.strftime('%d-%m-%Y')) + # ) + def _validate_expected_ready_ship_date(self): + """ + Pastikan expected_ready_to_ship tidak lebih awal dari SLA minimum. + Dipanggil setiap onchange / simpan SO. + """ for rec in self: - if rec.expected_ready_to_ship and rec.commitment_date: - current_date = datetime.now() - # Hanya membandingkan tanggal saja, tanpa jam - expected_date = rec.expected_ready_to_ship.date() - - max_slatime = 1 # Default SLA jika tidak ada - slatime = self.calculate_sla_by_vendor(rec.order_line) - max_slatime = max(max_slatime, slatime['slatime']) - - offset , is3pm = self.get_days_until_next_business_day(current_date) - sum_days = max_slatime + offset - sum_days -= 1 - eta_minimum = current_date + timedelta(days=sum_days) - - if expected_date < eta_minimum.date(): - rec.expected_ready_to_ship = eta_minimum - raise ValidationError( - "Tanggal 'Expected Ready to Ship' tidak boleh lebih kecil dari {}. Mohon pilih tanggal minimal {}." - .format(eta_minimum.strftime('%d-%m-%Y'), eta_minimum.strftime('%d-%m-%Y')) - ) - else: - rec.commitment_date = rec.expected_ready_to_ship + # ───────────────────────────────────────────────────── + # 1. Hanya validasi kalau field sudah terisi + # (quotation baru / belum ada tanggal → abaikan) + # ───────────────────────────────────────────────────── + if not rec.expected_ready_to_ship: + continue + + current_date = datetime.now() + + # ───────────────────────────────────────────────────── + # 2. Hitung SLA berdasarkan product lines (jika ada) + # ───────────────────────────────────────────────────── + products = rec.order_line + if products: + sla_data = rec.calculate_sla_by_vendor(products) + max_sla_time = sla_data.get('slatime', 1) + else: + # belum ada item → gunakan default 1 hari + max_sla_time = 1 + + # offset hari libur / weekend + offset, is3pm = rec.get_days_until_next_business_day(current_date) + min_days = max_sla_time + offset - 1 + eta_minimum = current_date + timedelta(days=min_days) + + # ───────────────────────────────────────────────────── + # 3. Validasi - raise error bila terlalu cepat + # ───────────────────────────────────────────────────── + if rec.expected_ready_to_ship.date() < eta_minimum.date(): + # set otomatis ke tanggal minimum supaya user tidak perlu + # menekan Save dua kali + rec.expected_ready_to_ship = eta_minimum + + raise ValidationError( + _("Tanggal 'Expected Ready to Ship' tidak boleh " + "lebih kecil dari %(tgl)s. Mohon pilih minimal %(tgl)s.") + % {'tgl': eta_minimum.strftime('%d-%m-%Y')} + ) + else: + # sinkronkan ke field commitment_date + rec.commitment_date = rec.expected_ready_to_ship + + @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship -- cgit v1.2.3 From b892a11ae2e28d1ad2d42c1fba0fec875fdbf163 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 7 Jun 2025 08:34:54 +0700 Subject: (andri) fix bug shipping method --- indoteknik_custom/models/sale_order.py | 53 ++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a86d43cb..9faafb11 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -256,6 +256,20 @@ class SaleOrder(models.Model): ('custom', 'Custom'), ], string='Shipping Option', help="Select shipping option for delivery", tracking=True) + @api.onchange('shipping_cost_covered') + def _onchange_shipping_cost_covered(self): + if self.shipping_cost_covered == 'indoteknik' and self.select_shipping_option == 'biteship': + self.shipping_cost_covered = 'customer' + return { + 'warning': { + 'title': "Biteship Tidak Diizinkan", + 'message': ( + "Biaya pengiriman ditanggung Indoteknik, sehingga tidak diizinkan menggunakan metode Biteship. " + "Pilihan penanggung biaya akan dikembalikan sebelumnya" + ) + } + } + def get_biteship_carrier_ids(self): courier_codes = tuple(self._get_biteship_courier_codes() or []) if not courier_codes: @@ -277,20 +291,29 @@ class SaleOrder(models.Model): if view_type == 'form': doc = etree.XML(res['arch']) - carrier_ids = self.get_biteship_carrier_ids() - if carrier_ids: - carrier_ids_str = '(' + ','.join(str(x) for x in carrier_ids) + ')' - else: - carrier_ids_str = '(-1,)' # aman kalau kosong + # Ambil semua delivery_carrier_id dari mapping rajaongkir_kurir + biteship_ids = self.env['rajaongkir.kurir'].search([]).mapped('delivery_carrier_id.id') + biteship_ids = list(set(filter(None, biteship_ids))) # pastikan unik dan bukan None + + all_ids = self.env['delivery.carrier'].search([]).ids + custom_ids = list(set(all_ids) - set(biteship_ids)) - # ✅ Tambahkan log di sini - _logger.info("🛰️ Biteship Carrier IDs: %s", carrier_ids) - _logger.info("📦 Domain string to apply: [('id', 'in', %s)]", carrier_ids_str) + # Format sebagai string Python list + biteship_ids_str = ','.join(str(i) for i in biteship_ids) or '-1' + custom_ids_str = ','.join(str(i) for i in custom_ids) or '-1' + # Terapkan domain ke field carrier_id for node in doc.xpath("//field[@name='carrier_id']"): - node.set('domain', "[('id', 'in', %s)]" % carrier_ids_str) + # Domain tergantung select_shipping_option + node.set( + 'domain', + "[('id', 'in', [%s]) if select_shipping_option == 'biteship' else ('id', 'in', [%s])]" % + (biteship_ids_str, custom_ids_str) + ) + # Simpan kembali hasil XML ke arsitektur form res['arch'] = etree.tostring(doc, encoding='unicode') + return res # @api.onchange('shipping_option_id') @@ -477,6 +500,18 @@ class SaleOrder(models.Model): @api.onchange('select_shipping_option') def _onchange_select_shipping_option(self): + if self.select_shipping_option == 'biteship' and self.shipping_cost_covered == 'indoteknik': + self.select_shipping_option = self._origin.select_shipping_option if self._origin else 'custom' + return { + 'warning': { + 'title': "Biteship Tidak Diizinkan", + 'message': ( + "Biaya pengiriman ditanggung Indoteknik. Tidak diizinkan memilih metode Biteship. " + "Opsi pengiriman dikembalikan ke sebelumnya." + ) + } + } + self.shipping_option_id = False self.carrier_id = False self.delivery_amt = 0 -- cgit v1.2.3 From 288a7f333c1d37574dba76f3fb3f7216da259cae Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 7 Jun 2025 09:41:27 +0700 Subject: (andri) add field shipping option pada stock picking sebagai tambahan info --- indoteknik_custom/models/stock_picking.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 7e001299..d6413b87 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -244,6 +244,11 @@ class StockPicking(models.Model): biteship_waybill_id = fields.Char(string="Biteship Waybill ID") final_seq = fields.Float(string='Remaining Time') shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') + shipping_option_so_id = fields.Many2one('shipping.option', string='Shipping Option SO', related='sale_id.shipping_option_id') + select_shipping_option_so = fields.Selection([ + ('biteship', 'Biteship'), + ('custom', 'Custom'), + ], string='Shipping Type SO', related='sale_id.select_shipping_option') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') -- cgit v1.2.3 From de747091235c844d33bcf62b4d98af0d03251826 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 9 Jun 2025 11:37:19 +0700 Subject: (andri) fix note order biteship --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d6413b87..d04b3d27 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -587,6 +587,7 @@ class StockPicking(models.Model): "destination_postal_code": shipping_partner.zip, "origin_note": "BELAKANG INDOMARET", "destination_note": f"SO: {self.sale_id.name}", + "order_note": f"SO: {self.sale_id.name}", "courier_type": courier_service_code, "courier_company": self.carrier_id.name.lower(), "delivery_type": "now", -- cgit v1.2.3 From 2760b81f8a650ea95d36c125d1ab4e2feb011e44 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 9 Jun 2025 17:12:48 +0700 Subject: (andri) fix berat produk jika transaksi dari website --- indoteknik_custom/models/sale_order.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a0576ad8..f53d375b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -844,9 +844,10 @@ class SaleOrder(models.Model): 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 + product_weight = line.product_id.weight or 0 + if product_weight > 0: + total_weight += product_weight * line.product_uom_qty + line.weight = product_weight else: missing_weight_products.append(line.product_id.name) @@ -2795,8 +2796,8 @@ class SaleOrder(models.Model): def create(self, vals): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) - _logger.info(f"[CREATE CONTEXT] {self.env.context}") - order._auto_set_shipping_from_website() + # _logger.info(f"[CREATE CONTEXT] {self.env.context}") + # order._auto_set_shipping_from_website() order._compute_etrts_date() order._validate_expected_ready_ship_date() # order._validate_delivery_amt() @@ -2893,7 +2894,7 @@ class SaleOrder(models.Model): "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") order._update_delivery_service_type_from_shipping_option(vals) - + if 'carrier_id' in vals: for order in self: for picking in order.picking_ids: -- cgit v1.2.3 From 1a63fac5f7f4dbb2990e5b1eeb9d7f381f39e908 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 9 Jun 2025 20:35:35 +0700 Subject: add products in manifest --- indoteknik_custom/models/stock_picking.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 71eca020..ae3c8f1d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1646,6 +1646,14 @@ class StockPicking(models.Model): sale_order_delay = self.env['sale.order.delay'].search([('so_number', '=', order.name)], limit=1) + product_shipped = [] + for move_line in self.move_line_ids_without_package: + if move_line.qty_done > 0: + product_shipped.append({ + 'name': move_line.product_id.name, + 'qty': move_line.qty_done + }) + response = { 'delivery_order': { 'name': self.name, @@ -1662,7 +1670,8 @@ class StockPicking(models.Model): 'eta': self.generate_eta_delivery(), 'is_biteship': True if self.biteship_id else False, 'manifests': self.get_manifests(), - 'is_delay': True if sale_order_delay and sale_order_delay.status == 'delayed' else False + 'is_delay': True if sale_order_delay and sale_order_delay.status == 'delayed' else False, + 'products': product_shipped } if self.biteship_id: -- cgit v1.2.3 From 5ca33915f1e3d052cfa989163d43a15dbc9ddec9 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 10 Jun 2025 11:17:57 +0700 Subject: (andri) add button get koordinat pada contact --- indoteknik_custom/models/res_partner.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index f1e362e6..0f1edac2 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -3,6 +3,7 @@ from odoo.exceptions import UserError, ValidationError from datetime import datetime from odoo.http import request import re +import requests class GroupPartner(models.Model): _name = 'group.partner' @@ -521,4 +522,28 @@ class ResPartner(models.Model): @api.onchange('name') def _onchange_name(self): if self.company_type == 'person': - self.nama_wajib_pajak = self.name \ No newline at end of file + self.nama_wajib_pajak = self.name + + def geocode_address(self): + for rec in self: + address = ', '.join(filter(None, [ + rec.street, + rec.city, + rec.state_id.name if rec.state_id else '', + rec.zip, + rec.country_id.name if rec.country_id else '' + ])) + + if not address: + continue + + api_key = self.env['ir.config_parameter'].sudo().get_param('google.maps.api_key') + url = f'https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={api_key}' + response = requests.get(url) + + if response.ok: + result = response.json() + if result.get('results'): + location = result['results'][0]['geometry']['location'] + rec.latitude = location['lat'] + rec.longtitude = location['lng'] \ No newline at end of file -- cgit v1.2.3 From df0467f8e493840f3013bc58ca26fc6d98793c95 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 10 Jun 2025 15:47:32 +0700 Subject: (andri) add openstreetmaps pada contact --- indoteknik_custom/models/res_partner.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 0f1edac2..fee0e73b 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -146,6 +146,7 @@ class ResPartner(models.Model): date_payment_terms_purchase = fields.Datetime(string='Date Update Payment Terms') longtitude = fields.Char(string='Longtitude') latitude = fields.Char(string='Latitude') + map_view = fields.Char(string='Map') address_map = fields.Char(string='Address Map') company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], @@ -526,12 +527,24 @@ class ResPartner(models.Model): def geocode_address(self): for rec in self: + # Ambil nama dari relasi (Many2one) atau gunakan nilai default + kelurahan = rec.kelurahan_id.name if rec.kelurahan_id else '' + kecamatan = rec.kecamatan_id.name if rec.kecamatan_id else '' + kota = rec.kota_id.name if rec.kota_id else '' + zip_code = rec.zip or '' + state = rec.state_id.name if rec.state_id else '' + country = rec.country_id.name if rec.country_id else '' + street = rec.street or '' + + # Susun alamat lengkap sesuai urutan lokal address = ', '.join(filter(None, [ - rec.street, - rec.city, - rec.state_id.name if rec.state_id else '', - rec.zip, - rec.country_id.name if rec.country_id else '' + street, + kelurahan, + kecamatan, + kota, + zip_code, + state, + country, ])) if not address: @@ -539,6 +552,7 @@ class ResPartner(models.Model): api_key = self.env['ir.config_parameter'].sudo().get_param('google.maps.api_key') url = f'https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={api_key}' + response = requests.get(url) if response.ok: @@ -546,4 +560,4 @@ class ResPartner(models.Model): if result.get('results'): location = result['results'][0]['geometry']['location'] rec.latitude = location['lat'] - rec.longtitude = location['lng'] \ No newline at end of file + rec.longtitude = location['lng'] -- cgit v1.2.3 From a6629db53b6080bd2217e426b434c2ecc72588ab Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 11 Jun 2025 09:14:42 +0700 Subject: (andri) add button INFORMATION DETAIL pada popup detail contact & addresses --- indoteknik_custom/models/res_partner.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index fee0e73b..b8bdfe22 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -525,6 +525,16 @@ class ResPartner(models.Model): if self.company_type == 'person': self.nama_wajib_pajak = self.name + def action_open_full_form(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'Partner', + 'res_model': 'res.partner', + 'res_id': self.id, + 'view_mode': 'form', + 'target': 'current', + } + def geocode_address(self): for rec in self: # Ambil nama dari relasi (Many2one) atau gunakan nilai default -- cgit v1.2.3 From 771cd3b9f5c0a6594b6e276bc47e3599d6c751e4 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 11 Jun 2025 09:50:05 +0700 Subject: (andri) add validasi tidak bisa pinpoint ketika alamat yang diisikan belum lengkap --- indoteknik_custom/models/res_partner.py | 62 ++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 24 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index b8bdfe22..a15398c7 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -537,32 +537,42 @@ class ResPartner(models.Model): def geocode_address(self): for rec in self: - # Ambil nama dari relasi (Many2one) atau gunakan nilai default - kelurahan = rec.kelurahan_id.name if rec.kelurahan_id else '' - kecamatan = rec.kecamatan_id.name if rec.kecamatan_id else '' - kota = rec.kota_id.name if rec.kota_id else '' - zip_code = rec.zip or '' - state = rec.state_id.name if rec.state_id else '' - country = rec.country_id.name if rec.country_id else '' - street = rec.street or '' - - # Susun alamat lengkap sesuai urutan lokal - address = ', '.join(filter(None, [ - street, - kelurahan, - kecamatan, - kota, - zip_code, - state, - country, - ])) - - if not address: - continue - + # Daftar field penting + required_fields = { + 'Alamat Jalan (street)': rec.street, + 'Kelurahan': rec.kelurahan_id.name if rec.kelurahan_id else '', + 'Kecamatan': rec.kecamatan_id.name if rec.kecamatan_id else '', + 'Kota': rec.kota_id.name if rec.kota_id else '', + 'Kode Pos': rec.zip, + 'Provinsi': rec.state_id.name if rec.state_id else '', + 'Negara': rec.country_id.name if rec.country_id else '', + } + + # Cek jika ada yang kosong + missing = [label for label, val in required_fields.items() if not val] + if missing: + raise UserError( + "Alamat tidak lengkap. Mohon lengkapi field berikut:\n- " + "\n- ".join(missing) + ) + + # Susun alamat lengkap + address = ', '.join([ + required_fields['Alamat Jalan (street)'], + required_fields['Kelurahan'], + required_fields['Kecamatan'], + required_fields['Kota'], + required_fields['Kode Pos'], + required_fields['Provinsi'], + required_fields['Negara'], + ]) + + # Ambil API Key api_key = self.env['ir.config_parameter'].sudo().get_param('google.maps.api_key') - url = f'https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={api_key}' + if not api_key: + raise UserError("API Key Google Maps belum dikonfigurasi. Silakan isi melalui Settings.") + # Request ke Google Maps + url = f'https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={api_key}' response = requests.get(url) if response.ok: @@ -571,3 +581,7 @@ class ResPartner(models.Model): location = result['results'][0]['geometry']['location'] rec.latitude = location['lat'] rec.longtitude = location['lng'] + else: + raise UserError("Tidak ditemukan hasil geocode untuk alamat tersebut.") + else: + raise UserError("Permintaan ke Google Maps gagal. Periksa koneksi internet atau API Key.") -- cgit v1.2.3 From be7c601f44c3fab282dc91559a62a024d09e3f73 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 12 Jun 2025 15:07:21 +0700 Subject: (andri) hapus validasi negara pada pinpoint & hapus autofokus page pinpoin --- indoteknik_custom/models/res_partner.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index a15398c7..eeb8b67d 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -545,7 +545,6 @@ class ResPartner(models.Model): 'Kota': rec.kota_id.name if rec.kota_id else '', 'Kode Pos': rec.zip, 'Provinsi': rec.state_id.name if rec.state_id else '', - 'Negara': rec.country_id.name if rec.country_id else '', } # Cek jika ada yang kosong @@ -563,7 +562,6 @@ class ResPartner(models.Model): required_fields['Kota'], required_fields['Kode Pos'], required_fields['Provinsi'], - required_fields['Negara'], ]) # Ambil API Key -- cgit v1.2.3 From 8ac5d556a6686c6b81d5e9178bff5d308e8f176f Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 12 Jun 2025 15:11:06 +0700 Subject: (andri) add validasi tidak bisa kirim biteship (stock picking) jika shipping option SO adalah custom --- indoteknik_custom/models/stock_picking.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ae3c8f1d..8b03e18d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -690,6 +690,9 @@ class StockPicking(models.Model): def action_send_to_biteship(self): if self.biteship_tracking_id: raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") + + if self.sale_id.select_shipping_option == 'custom': + raise UserError("Shipping Option pada Sales Order ini adalah *Custom*. Tidak dapat dikirim melalui Biteship.") def is_courier_need_coordinates(service_code): return service_code in [ -- cgit v1.2.3 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 From 3cb22a5059bfddc5f1d234e8c34e726debe9643d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 13 Jun 2025 17:05:11 +0700 Subject: (andri) perapihan log note estimate shipping --- indoteknik_custom/models/sale_order.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index e54053ff..b9ca4a09 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -890,7 +890,7 @@ class SaleOrder(models.Model): total_weight = self._validate_for_shipping_estimate() weight_gram = int(total_weight * 1000) - if weight_gram < 100: + if weight_gram Estimasi Ongkos Kirim Biteship ({origin_info} → {destination_info}):
"] + message_lines = [f"Estimasi Ongkos Kirim Biteship:
"] for courier, options in courier_options.items(): message_lines.append(f"{courier}:
") @@ -1081,6 +1073,19 @@ class SaleOrder(models.Model): if courier != list(courier_options.keys())[-1]: message_lines.append("
") + origin_address = "Jl. Bandengan Utara Komp A & BRT. Penjaringan, Kec. Penjaringan, Jakarta (BELAKANG INDOMARET) KOTA JAKARTA UTARA PENJARINGAN" + destination_address = shipping_address.alamat_lengkap_text or shipping_address.street or shipping_address.name or '' + if use_coordinate: + origin_suffix = f"(Koordinat: {origin_data.get('origin_latitude')}, {origin_data.get('origin_longitude')})" + destination_suffix = f"(Koordinat: {destination_data.get('destination_latitude')}, {destination_data.get('destination_longitude')})" + else: + origin_suffix = f"(Kode Pos: {origin_data.get('origin_postal_code')})" + destination_suffix = f"(Kode Pos: {destination_data.get('destination_postal_code')})" + + message_lines.append("


Info Lokasi:
") + message_lines.append(f"Asal: {origin_address} {origin_suffix}
") + message_lines.append(f"Tujuan: {destination_address} {destination_suffix}
") + message_body = "".join(message_lines) self.message_post( -- cgit v1.2.3 From 1cecf60302ea02e250a97ca2dbc6679332988bc0 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 14 Jun 2025 10:26:28 +0700 Subject: (andri) add search pada gmaps contact --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b9ca4a09..492153c0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -890,7 +890,7 @@ class SaleOrder(models.Model): total_weight = self._validate_for_shipping_estimate() weight_gram = int(total_weight * 1000) - if weight_gram Date: Sat, 14 Jun 2025 11:49:33 +0700 Subject: (andri) fix peletakan search autocomplete & tambahan info mengenai address map --- indoteknik_custom/models/res_partner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 6ef5698c..380761b4 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -149,7 +149,7 @@ class ResPartner(models.Model): longtitude = fields.Char(string='Longtitude') latitude = fields.Char(string='Latitude') map_view = fields.Char(string='Map') - address_map = fields.Char(string='Address Map') + address_map = fields.Char(string='Address Map', help='Ini adalah alamat yang didapatkan dari pin point pada peta') company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], compute='_compute_company_type', inverse='_write_company_type', tracking=3) -- cgit v1.2.3 From 819f7c2f0dfe0eaf857c44e80062dcc7e94c9828 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 14 Jun 2025 13:06:43 +0700 Subject: (andri) memindahkan key biteship (test&live) ke system parameter --- indoteknik_custom/models/sale_order.py | 4 ++-- indoteknik_custom/models/stock_picking.py | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 492153c0..8c8a3cc3 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1101,8 +1101,8 @@ class SaleOrder(models.Model): def _call_biteship_api(self, origin_data, destination_data, items, couriers=None): url = "https://api.biteship.com/v1/rates/couriers" - api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" - + # api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') + api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') headers = { 'Authorization': api_key, 'Content-Type': 'application/json' diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8b03e18d..89e7fecc 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -19,11 +19,6 @@ import re _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" -_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" - - -# _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" - class StockPicking(models.Model): _inherit = 'stock.picking' @@ -170,6 +165,10 @@ class StockPicking(models.Model): area_name = fields.Char(string="Area", compute="_compute_area_name") + def _get_biteship_api_key(self): + return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') + # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') + @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: @@ -769,7 +768,7 @@ class StockPicking(models.Model): _logger.info(f"Payload untuk Biteship: {payload}") # Kirim ke Biteship - api_key = _biteship_api_key + api_key = self._get_biteship_api_key() headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" @@ -1712,7 +1711,7 @@ class StockPicking(models.Model): return response def get_manifest_biteship(self): - api_key = _biteship_api_key + api_key = self._get_biteship_api_key() headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" -- cgit v1.2.3 From 8718c383e39440f83d6579ef536478053e623bc3 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 14 Jun 2025 13:14:24 +0700 Subject: (andri) live biteship --- indoteknik_custom/models/sale_order.py | 4 ++-- indoteknik_custom/models/stock_picking.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8c8a3cc3..bfc4e5fd 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1101,8 +1101,8 @@ class SaleOrder(models.Model): def _call_biteship_api(self, origin_data, destination_data, items, couriers=None): url = "https://api.biteship.com/v1/rates/couriers" - # api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') - api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') + api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') + # api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') headers = { 'Authorization': api_key, 'Content-Type': 'application/json' diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 89e7fecc..7775e991 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -166,8 +166,8 @@ class StockPicking(models.Model): area_name = fields.Char(string="Area", compute="_compute_area_name") def _get_biteship_api_key(self): - return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') - # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') + # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') + return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): -- cgit v1.2.3 From b20b6ada9b13ea40732de14e7ce356f28df40a6b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 16 Jun 2025 08:07:03 +0700 Subject: (andri) revisi pesan help pada address_map & nonaktifkan button get pin point location sebab GMAPS sudah bisa --- indoteknik_custom/models/res_partner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 380761b4..986ff786 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -149,7 +149,7 @@ class ResPartner(models.Model): longtitude = fields.Char(string='Longtitude') latitude = fields.Char(string='Latitude') map_view = fields.Char(string='Map') - address_map = fields.Char(string='Address Map', help='Ini adalah alamat yang didapatkan dari pin point pada peta') + address_map = fields.Char(string='Address Map', help='Ini adalah alamat yang didapatkan dari pin point pada peta, bila hasil alamat tidak sesuai, silahkan ubah alamat pada field Alamat Lengkap', tracking=3) company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], compute='_compute_company_type', inverse='_write_company_type', tracking=3) -- cgit v1.2.3 From 1d26fc49f0e01cb972a8dbb60db600222389423d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 16 Jun 2025 08:11:53 +0700 Subject: (andri) rev help address map --- indoteknik_custom/models/res_partner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 986ff786..9668d79b 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -149,7 +149,7 @@ class ResPartner(models.Model): longtitude = fields.Char(string='Longtitude') latitude = fields.Char(string='Latitude') map_view = fields.Char(string='Map') - address_map = fields.Char(string='Address Map', help='Ini adalah alamat yang didapatkan dari pin point pada peta, bila hasil alamat tidak sesuai, silahkan ubah alamat pada field Alamat Lengkap', tracking=3) + address_map = fields.Char(string='Address Map', help='Alamat ini diisi otomatis berdasarkan koordinat pin pada peta. Silakan koreksi jika terdapat ketidaksesuaian', tracking=3) company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], compute='_compute_company_type', inverse='_write_company_type', tracking=3) -- cgit v1.2.3 From df02e9c6f0db21b43ae25d77c7072a5dd15f9848 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 16 Jun 2025 08:12:35 +0700 Subject: (andri) rev help address map --- indoteknik_custom/models/res_partner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 9668d79b..a8ce95d1 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -149,7 +149,7 @@ class ResPartner(models.Model): longtitude = fields.Char(string='Longtitude') latitude = fields.Char(string='Latitude') map_view = fields.Char(string='Map') - address_map = fields.Char(string='Address Map', help='Alamat ini diisi otomatis berdasarkan koordinat pin pada peta. Silakan koreksi jika terdapat ketidaksesuaian', tracking=3) + address_map = fields.Char(string='Address Map', help='Alamat ini diisi otomatis berdasarkan koordinat pin pada peta. Silakan koreksi dan ubah jika terdapat ketidaksesuaian', tracking=3) company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], compute='_compute_company_type', inverse='_write_company_type', tracking=3) -- cgit v1.2.3 From 2ff882e9f591a25b6b9f5adbd4dd90e7402017a9 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 16 Jun 2025 08:48:39 +0700 Subject: (andri) comment map view --- indoteknik_custom/models/res_partner.py | 254 ++++++++++++++++---------------- indoteknik_custom/models/sale_order.py | 1 + 2 files changed, 128 insertions(+), 127 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index a8ce95d1..1786efa3 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -148,7 +148,7 @@ class ResPartner(models.Model): date_payment_terms_purchase = fields.Datetime(string='Date Update Payment Terms') longtitude = fields.Char(string='Longtitude') latitude = fields.Char(string='Latitude') - map_view = fields.Char(string='Map') + # map_view = fields.Char(string='Map') address_map = fields.Char(string='Address Map', help='Alamat ini diisi otomatis berdasarkan koordinat pin pada peta. Silakan koreksi dan ubah jika terdapat ketidaksesuaian', tracking=3) company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], @@ -549,130 +549,130 @@ class ResPartner(models.Model): 'target': 'current', } - def geocode_address(self): - for rec in self: - # Daftar field penting - required_fields = { - 'Alamat Jalan (street)': rec.street, - 'Kelurahan': rec.kelurahan_id.name if rec.kelurahan_id else '', - 'Kecamatan': rec.kecamatan_id.name if rec.kecamatan_id else '', - 'Kota': rec.kota_id.name if rec.kota_id else '', - 'Kode Pos': rec.zip, - 'Provinsi': rec.state_id.name if rec.state_id else '', - } - - # Cek jika ada yang kosong - missing = [label for label, val in required_fields.items() if not val] - if missing: - raise UserError( - "Alamat tidak lengkap. Mohon lengkapi field berikut:\n- " + "\n- ".join(missing) - ) - - # Susun alamat lengkap - address = ', '.join([ - required_fields['Alamat Jalan (street)'], - required_fields['Kelurahan'], - required_fields['Kecamatan'], - required_fields['Kota'], - required_fields['Kode Pos'], - required_fields['Provinsi'], - ]) - - # Ambil API Key - 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. Silakan isi melalui Settings.") - - # Request ke Google Maps - url = f'https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={api_key}' - response = requests.get(url) - - if response.ok: - 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: - 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])) + # def geocode_address(self): + # for rec in self: + # # Daftar field penting + # required_fields = { + # 'Alamat Jalan (street)': rec.street, + # 'Kelurahan': rec.kelurahan_id.name if rec.kelurahan_id else '', + # 'Kecamatan': rec.kecamatan_id.name if rec.kecamatan_id else '', + # 'Kota': rec.kota_id.name if rec.kota_id else '', + # 'Kode Pos': rec.zip, + # 'Provinsi': rec.state_id.name if rec.state_id else '', + # } - 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'), - } + # # Cek jika ada yang kosong + # missing = [label for label, val in required_fields.items() if not val] + # if missing: + # raise UserError( + # "Alamat tidak lengkap. Mohon lengkapi field berikut:\n- " + "\n- ".join(missing) + # ) + + # # Susun alamat lengkap + # address = ', '.join([ + # required_fields['Alamat Jalan (street)'], + # required_fields['Kelurahan'], + # required_fields['Kecamatan'], + # required_fields['Kota'], + # required_fields['Kode Pos'], + # required_fields['Provinsi'], + # ]) + + # # Ambil API Key + # 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. Silakan isi melalui Settings.") + + # # Request ke Google Maps + # url = f'https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={api_key}' + # response = requests.get(url) + + # if response.ok: + # 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: + # 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'), + # } diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bfc4e5fd..d0017115 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -75,6 +75,7 @@ class SaleOrderLine(models.Model): _inherit = 'sale.order.line' def unlink(self): + lines_to_reject = [] for line in self: if line.order_id: -- cgit v1.2.3 From 6e2419c226eb74ea5c14d21f441d570829a20021 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 16 Jun 2025 08:54:56 +0700 Subject: (andri) on map view --- indoteknik_custom/models/res_partner.py | 254 ++++++++++++++++---------------- 1 file changed, 127 insertions(+), 127 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 1786efa3..a8ce95d1 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -148,7 +148,7 @@ class ResPartner(models.Model): date_payment_terms_purchase = fields.Datetime(string='Date Update Payment Terms') longtitude = fields.Char(string='Longtitude') latitude = fields.Char(string='Latitude') - # map_view = fields.Char(string='Map') + map_view = fields.Char(string='Map') address_map = fields.Char(string='Address Map', help='Alamat ini diisi otomatis berdasarkan koordinat pin pada peta. Silakan koreksi dan ubah jika terdapat ketidaksesuaian', tracking=3) company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], @@ -549,130 +549,130 @@ class ResPartner(models.Model): 'target': 'current', } - # def geocode_address(self): - # for rec in self: - # # Daftar field penting - # required_fields = { - # 'Alamat Jalan (street)': rec.street, - # 'Kelurahan': rec.kelurahan_id.name if rec.kelurahan_id else '', - # 'Kecamatan': rec.kecamatan_id.name if rec.kecamatan_id else '', - # 'Kota': rec.kota_id.name if rec.kota_id else '', - # 'Kode Pos': rec.zip, - # 'Provinsi': rec.state_id.name if rec.state_id else '', - # } + def geocode_address(self): + for rec in self: + # Daftar field penting + required_fields = { + 'Alamat Jalan (street)': rec.street, + 'Kelurahan': rec.kelurahan_id.name if rec.kelurahan_id else '', + 'Kecamatan': rec.kecamatan_id.name if rec.kecamatan_id else '', + 'Kota': rec.kota_id.name if rec.kota_id else '', + 'Kode Pos': rec.zip, + 'Provinsi': rec.state_id.name if rec.state_id else '', + } + + # Cek jika ada yang kosong + missing = [label for label, val in required_fields.items() if not val] + if missing: + raise UserError( + "Alamat tidak lengkap. Mohon lengkapi field berikut:\n- " + "\n- ".join(missing) + ) + + # Susun alamat lengkap + address = ', '.join([ + required_fields['Alamat Jalan (street)'], + required_fields['Kelurahan'], + required_fields['Kecamatan'], + required_fields['Kota'], + required_fields['Kode Pos'], + required_fields['Provinsi'], + ]) + + # Ambil API Key + 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. Silakan isi melalui Settings.") + + # Request ke Google Maps + url = f'https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={api_key}' + response = requests.get(url) + + if response.ok: + 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.") - # # Cek jika ada yang kosong - # missing = [label for label, val in required_fields.items() if not val] - # if missing: - # raise UserError( - # "Alamat tidak lengkap. Mohon lengkapi field berikut:\n- " + "\n- ".join(missing) - # ) - - # # Susun alamat lengkap - # address = ', '.join([ - # required_fields['Alamat Jalan (street)'], - # required_fields['Kelurahan'], - # required_fields['Kecamatan'], - # required_fields['Kota'], - # required_fields['Kode Pos'], - # required_fields['Provinsi'], - # ]) - - # # Ambil API Key - # 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. Silakan isi melalui Settings.") - - # # Request ke Google Maps - # url = f'https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={api_key}' - # response = requests.get(url) - - # if response.ok: - # 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: - # 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'), - # } + 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 ac3e99d4d7e1fd21aa146d621a06b42df86e3f7b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 16 Jun 2025 09:20:13 +0700 Subject: (andri) set default shipping option ke custom --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 7c3f8952..39b74069 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -333,7 +333,7 @@ class SaleOrder(models.Model): select_shipping_option = fields.Selection([ ('biteship', 'Biteship'), ('custom', 'Custom'), - ], string='Shipping Option', help="Select shipping option for delivery", tracking=True) + ], string='Shipping Option', help="Select shipping option for delivery", tracking=True, default='custom') hold_outgoing = fields.Boolean('Hold Outgoing SO', tracking=3) state_ask_cancel = fields.Selection([ -- cgit v1.2.3 From 9b6aa67f911a0db8d466d2f63cf1d8ce43ab8e14 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 16 Jun 2025 10:59:35 +0700 Subject: (andri) fix autoset checkout SO custom --- indoteknik_custom/models/sale_order.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 39b74069..0662522f 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2755,7 +2755,8 @@ class SaleOrder(models.Model): continue # Skip jika Self Pick Up - if order.carrier_id and order.carrier_id.id == 32: + if int(order.carrier_id.id or 0) == 32: + _logger.info(f"[Checkout] Skip estimasi: Self Pickup untuk SO {order.name}") order.select_shipping_option = 'custom' continue -- cgit v1.2.3 From dba82e88291ac63473b8f45dd7a4da5b4d147973 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 16 Jun 2025 14:00:14 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6eac857c..92317334 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1362,7 +1362,7 @@ class StockPicking(models.Model): picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) - if picking_date != invoice_date and picking.update_date_doc_kirim_add: + if picking_date != invoice_date and picking.update_date_doc_kirim_add and not picking.so_lama: raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( picking_date.strftime('%d-%m-%Y'), invoice_date.strftime('%d-%m-%Y') -- cgit v1.2.3 From 3c8ff8cb6a24dd1ddec7d34313722d7dee6f23a3 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 16 Jun 2025 16:15:57 +0700 Subject: (andri) taruh key hardcode --- indoteknik_custom/models/sale_order.py | 4 +++- indoteknik_custom/models/stock_picking.py | 13 ++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0662522f..1771f210 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1102,7 +1102,9 @@ class SaleOrder(models.Model): def _call_biteship_api(self, origin_data, destination_data, items, couriers=None): url = "https://api.biteship.com/v1/rates/couriers" - api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') + api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" + # api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" + # api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') # api_key = self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') headers = { 'Authorization': api_key, diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6eac857c..f552ff3f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -19,6 +19,9 @@ import re _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" +biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" +# biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" + class StockPicking(models.Model): _inherit = 'stock.picking' @@ -165,9 +168,9 @@ class StockPicking(models.Model): area_name = fields.Char(string="Area", compute="_compute_area_name") - def _get_biteship_api_key(self): - # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') - return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') + # def _get_biteship_api_key(self): + # # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') + # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_live') @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): @@ -762,7 +765,7 @@ class StockPicking(models.Model): _logger.info(f"Payload untuk Biteship: {payload}") # Kirim ke Biteship - api_key = self._get_biteship_api_key() + api_key = biteship_api_key headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" @@ -1705,7 +1708,7 @@ class StockPicking(models.Model): return response def get_manifest_biteship(self): - api_key = self._get_biteship_api_key() + api_key = biteship_api_key headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" -- cgit v1.2.3