From 9677d61248b4399239c6e0eccac57a6f945ec58c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 1 Jul 2025 16:32:45 +0700 Subject: (andri) rajaongkir V2 --- indoteknik_custom/models/sale_order.py | 138 +++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 48 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 109771e9..bc830e2b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -845,25 +845,32 @@ class SaleOrder(models.Model): 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 + kecamatan_name = self.real_shipping_id.kecamatan_id.name + kota_name = self.real_shipping_id.kota_id.name + + destination_subsdistrict_id = self._get_subdistrict_id_from_komerce(kecamatan_name, kota_name) + + # 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'])) - + + for cost in result.get('data', []): + service = cost.get('service') + description = cost.get('description') + etd = cost.get('etd', '') + value = cost.get('cost', 0) + provider = cost.get('code') + + shipping_options.append((service, description, etd, value, provider)) + 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, @@ -873,19 +880,15 @@ class SaleOrder(models.Model): "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])}", + f"{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]}, 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.") @@ -1191,25 +1194,30 @@ class SaleOrder(models.Model): def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): - url = 'https://pro.rajaongkir.com/api/cost' + url = 'https://rajaongkir.komerce.id/api/v1/calculate/domestic-cost' headers = { 'key': '9b1310f644056d84d60b0af6bb21611a', } courier = self.carrier_id.name.lower() data = { - 'origin': 2127, - 'originType': 'subdistrict', + 'origin': 17656, + # 'originType': 'subdistrict', 'destination': int(destination_subsdistrict_id), - 'destinationType': 'subdistrict', + # 'destinationType': 'subdistrict', 'weight': int(total_weight * 1000), 'courier': courier, } - response = requests.post(url, headers=headers, data=data) - if response.status_code == 200: - return response.json() - return None + try: + _logger.info(f"Calling RajaOngkir API with data: {data}") + response = requests.post(url, headers=headers, data=data) + _logger.info(f"RajaOngkir response: {response.status_code} - {response.text}") + + if response.status_code == 200: + return response.json() + except Exception as e: + _logger.error(f"Exception while calling RajaOngkir: {str(e)}") def _normalize_city_name(self, city_name): city_name = city_name.lower() @@ -1223,37 +1231,71 @@ class SaleOrder(models.Model): return city_name - def _get_city_id_by_name(self, city_name): - url = 'https://pro.rajaongkir.com/api/city' + # def _get_city_id_by_name(self, city_name): + # url = 'https://pro.rajaongkir.com/api/city' + # headers = { + # 'key': '9b1310f644056d84d60b0af6bb21611a', + # } + + # normalized_city_name = self._normalize_city_name(city_name) + + # response = requests.get(url, headers=headers) + # if response.status_code == 200: + # city_data = response.json() + # for city in city_data['rajaongkir']['results']: + # if city['city_name'].lower() == normalized_city_name: + # return city['city_id'] + # return None + + # def _get_subdistrict_id_by_name(self, city_id, subdistrict_name): + # url = f'https://pro.rajaongkir.com/api/subdistrict?city={city_id}' + # headers = { + # 'key': '9b1310f644056d84d60b0af6bb21611a', + # } + + # response = requests.get(url, headers=headers) + # if response.status_code == 200: + # subdistrict_data = response.json() + # for subdistrict in subdistrict_data['rajaongkir']['results']: + # subsdistrict_1 = subdistrict['subdistrict_name'].lower() + # subsdistrict_2 = subdistrict_name.lower() + + # if subsdistrict_1 == subsdistrict_2: + # return subdistrict['subdistrict_id'] + # return None + + def _get_subdistrict_id_from_komerce(self, kecamatan_name, kota_name): + url = 'https://rajaongkir.komerce.id/api/v1/destination/domestic-destination' headers = { 'key': '9b1310f644056d84d60b0af6bb21611a', } - - normalized_city_name = self._normalize_city_name(city_name) - - response = requests.get(url, headers=headers) - if response.status_code == 200: - city_data = response.json() - for city in city_data['rajaongkir']['results']: - if city['city_name'].lower() == normalized_city_name: - return city['city_id'] - return None - - def _get_subdistrict_id_by_name(self, city_id, subdistrict_name): - url = f'https://pro.rajaongkir.com/api/subdistrict?city={city_id}' - headers = { - 'key': '9b1310f644056d84d60b0af6bb21611a', + search = f"{kecamatan_name} {kota_name}" + params = { + 'search': search, + 'limit': 5 } - response = requests.get(url, headers=headers) - if response.status_code == 200: - subdistrict_data = response.json() - for subdistrict in subdistrict_data['rajaongkir']['results']: - subsdistrict_1 = subdistrict['subdistrict_name'].lower() - subsdistrict_2 = subdistrict_name.lower() + try: + response = requests.get(url, headers=headers, params=params, timeout=10) + if response.status_code == 200: + data = response.json().get('data', []) + _logger.info(f"[Komerce] Fetched {len(data)} subdistricts for search '{search}'") + _logger.info(f"[Komerce] Response: {data}") + + normalized_kota = self._normalize_city_name(kota_name) + for item in data: + if ( + item.get('subdistrict_name', '').lower() == kecamatan_name.lower() and + item.get('city_name', '').lower() == normalized_kota + ): + return item.get('id') + + _logger.warning(f"[Komerce] No match for '{kecamatan_name}' in city '{kota_name}'") + else: + _logger.error(f"[Komerce] HTTP Error {response.status_code}: {response.text}") + except Exception as e: + _logger.error(f"[Komerce] Exception: {e}") - if subsdistrict_1 == subsdistrict_2: - return subdistrict['subdistrict_id'] return None def _compute_type_promotion(self): -- cgit v1.2.3 From 84a038cff26c28bd714fd6744f48c2b0e91cf347 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 1 Jul 2025 17:32:02 +0700 Subject: (andri) fix API destination --- indoteknik_custom/models/sale_order.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bc830e2b..4b4e06cd 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -847,8 +847,9 @@ class SaleOrder(models.Model): kecamatan_name = self.real_shipping_id.kecamatan_id.name kota_name = self.real_shipping_id.kota_id.name + kelurahan_name = self.real_shipping_id.kelurahan_id.name - destination_subsdistrict_id = self._get_subdistrict_id_from_komerce(kecamatan_name, kota_name) + destination_subsdistrict_id = self._get_subdistrict_id_from_komerce(kecamatan_name, kota_name, kelurahan_name) # destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id if not destination_subsdistrict_id: @@ -1264,15 +1265,20 @@ class SaleOrder(models.Model): # return subdistrict['subdistrict_id'] # return None - def _get_subdistrict_id_from_komerce(self, kecamatan_name, kota_name): + def _get_subdistrict_id_from_komerce(self, kecamatan_name, kota_name, kelurahan_name=None): url = 'https://rajaongkir.komerce.id/api/v1/destination/domestic-destination' headers = { 'key': '9b1310f644056d84d60b0af6bb21611a', } - search = f"{kecamatan_name} {kota_name}" + + if kelurahan_name: + search = f"{kelurahan_name} {kecamatan_name} {kota_name}" + else: + search = f"{kecamatan_name} {kota_name}" + params = { 'search': search, - 'limit': 5 + 'limit': 10 } try: @@ -1283,14 +1289,20 @@ class SaleOrder(models.Model): _logger.info(f"[Komerce] Response: {data}") normalized_kota = self._normalize_city_name(kota_name) + for item in data: + match_kelurahan = ( + not kelurahan_name or + item.get('subdistrict_name', '').lower() == kelurahan_name.lower() + ) if ( - item.get('subdistrict_name', '').lower() == kecamatan_name.lower() and + match_kelurahan and + item.get('district_name', '').lower() == kecamatan_name.lower() and item.get('city_name', '').lower() == normalized_kota ): return item.get('id') - _logger.warning(f"[Komerce] No match for '{kecamatan_name}' in city '{kota_name}'") + _logger.warning(f"[Komerce] No match for '{kecamatan_name}' in city '{kota_name}' with kelurahan '{kelurahan_name}'") else: _logger.error(f"[Komerce] HTTP Error {response.status_code}: {response.text}") except Exception as e: -- cgit v1.2.3 From f1f2b012ed156213a623858cb3fb816e6a795f3c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 2 Jul 2025 10:20:27 +0700 Subject: (andri) fix pay diff --- indoteknik_custom/models/res_partner.py | 26 +++++++++++++------------- indoteknik_custom/views/res_partner.xml | 11 +++++++++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index f5347bea..1e5cfd62 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -165,21 +165,21 @@ class ResPartner(models.Model): "this feature", tracking=3) telegram_id = fields.Char(string="Telegram") avg_aging= fields.Float(string='Average Aging') - payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', compute="_compute_payment_difficulty", inverse = "_inverse_payment_difficulty", tracking=3) + payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', compute="", inverse = "", tracking=3) payment_history_url = fields.Text(string='Payment History URL') - @api.depends('parent_id.payment_difficulty') - def _compute_payment_difficulty(self): - for partner in self: - if partner.parent_id: - partner.payment_difficulty = partner.parent_id.payment_difficulty - - def _inverse_payment_difficulty(self): - for partner in self: - if not partner.parent_id: - partner.child_ids.write({ - 'payment_difficulty': partner.payment_difficulty - }) + # @api.depends('parent_id.payment_difficulty') + # def _compute_payment_difficulty(self): + # for partner in self: + # if partner.parent_id: + # partner.payment_difficulty = partner.parent_id.payment_difficulty + + # def _inverse_payment_difficulty(self): + # for partner in self: + # if not partner.parent_id: + # partner.child_ids.write({ + # 'payment_difficulty': partner.payment_difficulty + # }) @api.model def _default_payment_term(self): diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index 6115587b..ac4d0364 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -108,6 +108,13 @@ 1 + + + + + + + @@ -181,11 +188,11 @@ - + -- cgit v1.2.3 From b74109805a2ec65cb4a4b7811fdc34403d2505b2 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 2 Jul 2025 11:38:18 +0700 Subject: (andri) tambah validasi jika kurir tidak mendukung --- indoteknik_custom/models/sale_order.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4b4e06cd..74d96314 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -856,6 +856,9 @@ class SaleOrder(models.Model): raise UserError("Gagal mendapatkan ID kota tujuan.") result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id) + if not result or not result.get('data'): + raise UserError(_("Kurir %s tidak tersedia untuk tujuan ini. Silakan pilih kurir lain.") % self.carrier_id.name) + if result: shipping_options = [] @@ -1278,7 +1281,7 @@ class SaleOrder(models.Model): params = { 'search': search, - 'limit': 10 + 'limit': 5 } try: -- cgit v1.2.3 From 5a3c3d327dd04b3ec4f3c272e4bc50ab9c594058 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 3 Jul 2025 09:20:44 +0700 Subject: (andri) fix --- indoteknik_custom/models/res_partner.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 1e5cfd62..f5347bea 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -165,21 +165,21 @@ class ResPartner(models.Model): "this feature", tracking=3) telegram_id = fields.Char(string="Telegram") avg_aging= fields.Float(string='Average Aging') - payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', compute="", inverse = "", tracking=3) + payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', compute="_compute_payment_difficulty", inverse = "_inverse_payment_difficulty", tracking=3) payment_history_url = fields.Text(string='Payment History URL') - # @api.depends('parent_id.payment_difficulty') - # def _compute_payment_difficulty(self): - # for partner in self: - # if partner.parent_id: - # partner.payment_difficulty = partner.parent_id.payment_difficulty - - # def _inverse_payment_difficulty(self): - # for partner in self: - # if not partner.parent_id: - # partner.child_ids.write({ - # 'payment_difficulty': partner.payment_difficulty - # }) + @api.depends('parent_id.payment_difficulty') + def _compute_payment_difficulty(self): + for partner in self: + if partner.parent_id: + partner.payment_difficulty = partner.parent_id.payment_difficulty + + def _inverse_payment_difficulty(self): + for partner in self: + if not partner.parent_id: + partner.child_ids.write({ + 'payment_difficulty': partner.payment_difficulty + }) @api.model def _default_payment_term(self): -- cgit v1.2.3 From eac293a01a1cdf5e7d2be15575f96e17ebe33a4d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 3 Jul 2025 09:49:37 +0700 Subject: (Andri) fix related BU pada PO --- indoteknik_custom/models/purchase_order.py | 60 ++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index a3941b3b..4dc26d74 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -101,43 +101,65 @@ class PurchaseOrder(models.Model): @api.depends('name') def _compute_bu_related_count(self): + StockPicking = self.env['stock.picking'] for order in self: if not order.name: order.bu_related_count = 0 continue - # BU langsung dari PO - base_bu = self.env['stock.picking'].search([ + # Ambil semua BU awal dari PO + base_bu = StockPicking.search([ ('name', 'ilike', 'BU/'), ('origin', 'ilike', order.name) ]) - base_names = base_bu.mapped('name') - # Return dari BU di atas - return_bu = self.env['stock.picking'].search([ - ('origin', 'in', [f"Return of {name}" for name in base_names]) - ]) + all_bu = base_bu + seen_names = set(base_bu.mapped('name')) + + # Loop rekursif untuk mencari seluruh return BU + while True: + next_bu = StockPicking.search([ + ('name', 'ilike', 'BU/'), + ('origin', 'in', ['Return of %s' % name for name in seen_names]) + ]) + next_names = set(next_bu.mapped('name')) + + if not next_names - seen_names: + break + + all_bu |= next_bu + seen_names |= next_names + + order.bu_related_count = len(all_bu) - order.bu_related_count = len(base_bu) + len(return_bu) def action_view_related_bu(self): self.ensure_one() + StockPicking = self.env['stock.picking'] + # Step 1: cari semua BU pertama (PUT, INT) yang berasal dari PO ini - base_bu = self.env['stock.picking'].search([ + base_bu = StockPicking.search([ ('name', 'ilike', 'BU/'), ('origin', 'ilike', self.name) ]) - base_bu_names = base_bu.mapped('name') - # Step 2: cari BU turunan (seperti BU/VRT) yang origin-nya mengandung nama BU tersebut - domain = [ - '|', - '&', - ('name', 'ilike', 'BU/'), - ('origin', 'ilike', self.name), - ('origin', 'in', [f"Return of {name}" for name in base_bu_names]) - ] + all_bu = base_bu + seen_names = set(base_bu.mapped('name')) + + # Step 2: Loop rekursif cari BU dengan origin 'Return of {name}' + while True: + next_bu = StockPicking.search([ + ('name', 'ilike', 'BU/'), + ('origin', 'in', ['Return of %s' % name for name in seen_names]) + ]) + next_names = set(next_bu.mapped('name')) + + if not next_names - seen_names: + break + + all_bu |= next_bu + seen_names |= next_names return { 'name': 'Related BU (INT/PRT/PUT/VRT)', @@ -145,7 +167,7 @@ class PurchaseOrder(models.Model): 'res_model': 'stock.picking', 'view_mode': 'tree,form', 'target': 'current', - 'domain': domain, + 'domain': [('id', 'in', list(all_bu.ids))], } -- cgit v1.2.3 From db60e29b2f599ac21e96ffdfb5be94e3c0ba6a2f Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 3 Jul 2025 16:16:32 +0700 Subject: (andri) fix eta date reserved computed --- indoteknik_custom/models/sale_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 74d96314..591951ca 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -388,7 +388,9 @@ class SaleOrder(models.Model): pickings = order.picking_ids.filtered( lambda p: p.state in ('assigned', 'done') and p.date_reserved and 'BU/PICK/' in (p.name or '') ) - order.eta_date_reserved = min(pickings.mapped('date_done')) if pickings else False + done_dates = [d for d in pickings.mapped('date_done') if d] + order.eta_date_reserved = min(done_dates) if done_dates else False + # order.eta_date_reserved = min(pickings.mapped('date_done')) if pickings else False @api.onchange('shipping_cost_covered') def _onchange_shipping_cost_covered(self): -- cgit v1.2.3