From 06afce2162894ef23163062092daf00882de8a85 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 20 Jun 2025 09:04:18 +0700 Subject: (andri) revisi penamaan field pada jika commision type adalah cashback --- indoteknik_custom/models/commision.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 03d32d2d..215e2ded 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -175,10 +175,19 @@ class CustomerCommision(models.Model): ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status') - commision_percent = fields.Float(string='Commision %', tracking=3) - commision_amt = fields.Float(string='Commision Amount', tracking=3) - cashback = fields.Float(string='Cashback', compute="compute_cashback") - total_commision = fields.Float(string='Total Commision', compute="compute_cashback") + + # commision_percent = fields.Float(string='Commision %', tracking=3) + commision_percent = fields.Float(string='Cashback %', tracking=3) + + # commision_amt = fields.Float(string='Commision Amount', tracking=3) + commision_amt = fields.Float(string='Cashback', tracking=3) + + # cashback = fields.Float(string='Cashback', compute="compute_cashback") + cashback = fields.Float(string='PPh Cashback', compute="compute_cashback") + + # total_commision = fields.Float(string='Total Commision', compute="compute_cashback") + total_commision = fields.Float(string='Cashback yang dibayarkan', compute="compute_cashback") + total_cashback = fields.Float(string='Total Cashback') commision_amt_text = fields.Char(string='Commision Amount Text', compute='compute_delivery_amt_text') total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp') -- cgit v1.2.3 From 8a3717c34e0968f3f6ddd7d0cc4fb18aeb218bfe Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 20 Jun 2025 10:04:01 +0700 Subject: (andri) ganti no dokumen yang lama (CC) sesuai dengan commision type yang dipilih (RB/FE/CB) --- indoteknik_custom/models/commision.py | 35 ++++++++++++++++++++++++--------- indoteknik_custom/views/ir_sequence.xml | 32 +++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 215e2ded..46718397 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -175,7 +175,7 @@ class CustomerCommision(models.Model): ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status') - + # commision_percent = fields.Float(string='Commision %', tracking=3) commision_percent = fields.Float(string='Cashback %', tracking=3) @@ -357,14 +357,31 @@ class CustomerCommision(models.Model): @api.model def create(self, vals): - vals['number'] = self.env['ir.sequence'].next_by_code('customer.commision') or '0' - # if vals['commision_amt'] > 0: - # commision_amt = vals['commision_amt'] - # total_dpp = vals['total_dpp'] - # commision_percent = commision_amt / total_dpp * 100 - # vals['commision_percent'] = commision_percent - result = super(CustomerCommision, self).create(vals) - return result + commision_type = vals.get('commision_type') + + if commision_type == 'cashback': + sequence_code = 'customer.commision.cashback' + elif commision_type == 'fee': + sequence_code = 'customer.commision.fee' + elif commision_type == 'rebate': + sequence_code = 'customer.commision.rebate' + else: + raise UserError('Tipe komisi tidak dikenal!') + + vals['number'] = self.env['ir.sequence'].next_by_code(sequence_code) or '0' + + return super(CustomerCommision, self).create(vals) + + # @api.model + # def create(self, vals): + # vals['number'] = self.env['ir.sequence'].next_by_code('customer.commision') or '0' + # # if vals['commision_amt'] > 0: + # # commision_amt = vals['commision_amt'] + # # total_dpp = vals['total_dpp'] + # # commision_percent = commision_amt / total_dpp * 100 + # # vals['commision_percent'] = commision_percent + # result = super(CustomerCommision, self).create(vals) + # return result def action_confirm_customer_commision(self): jakarta_tz = pytz.timezone('Asia/Jakarta') diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 97bf40bb..bb8848c4 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -131,7 +131,7 @@ 1 - + + + + Customer Commision Cashback + customer.commision.cashback + CB/%(year)s/ + 5 + 1 + 1 + True + + + + Customer Commision Fee + customer.commision.fee + FE/%(year)s/ + 5 + 1 + 1 + True + + + + Customer Commision Rebate + customer.commision.rebate + RB/%(year)s/ + 5 + 1 + 1 + True -- cgit v1.2.3 From f3cb0f0114b0e43bb6276e93e3636f0bf55c66b8 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 21 Jun 2025 14:20:24 +0700 Subject: (andri) Mark as Completed pada SO akan terekam di chatter --- indoteknik_custom/models/sale_orders_multi_update.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/indoteknik_custom/models/sale_orders_multi_update.py b/indoteknik_custom/models/sale_orders_multi_update.py index 95cfde21..962f60b5 100644 --- a/indoteknik_custom/models/sale_orders_multi_update.py +++ b/indoteknik_custom/models/sale_orders_multi_update.py @@ -11,6 +11,13 @@ class SaleOrdersMultiUpdate(models.TransientModel): sale_ids = self._context['sale_ids'] sales = self.env['sale.order'].browse(sale_ids) sales.action_multi_update_invoice_status() + + for sale in sales: + sale.message_post( + body="Sales Order has been marked as Completed", + message_type="comment" + ) + return { 'type': 'ir.actions.client', 'tag': 'display_notification', -- cgit v1.2.3 From eeb72c4ed24c33403bb733a51198b9cc0f356e6a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 21 Jun 2025 14:57:10 +0700 Subject: (andri) rev log --- indoteknik_custom/models/sale_order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 6da46398..c8d4a712 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -591,11 +591,11 @@ class SaleOrder(models.Model): 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) + _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) + _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.") + _logger.info("shipping_option_id not found in vals or record.") # @api.model # def fields_get(self, allfields=None, attributes=None): -- cgit v1.2.3 From 6ae8ac2d0a560d850e7e8dc2ce87157f3e5a9669 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 23 Jun 2025 10:10:51 +0700 Subject: (andri) menu commision customer menjadi commision benefits --- indoteknik_custom/models/commision.py | 1 + indoteknik_custom/views/customer_commision.xml | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 46718397..199aa106 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -148,6 +148,7 @@ class CustomerCommision(models.Model): _order = 'id desc' _inherit = ['mail.thread'] _rec_name = 'number' + _description = 'Customer Benefits' number = fields.Char(string='Document No', index=True, copy=False, readonly=True) date_from = fields.Date(string='Date From', required=True) diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index 4be0840f..d5fb1d70 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -70,7 +70,7 @@ statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,pengajuan4,approved" statusbar_colors='{"reject":"red"}'/> - +
@@ -173,7 +173,7 @@ - Customer Commision + Customer Benefits ir.actions.act_window customer.commision @@ -181,14 +181,14 @@ Date: Fri, 20 Jun 2025 09:57:29 +0700 Subject: bu pick in purchase order sales match --- indoteknik_custom/models/purchase_order_sales_match.py | 14 ++++++++++++++ indoteknik_custom/models/stock_picking.py | 4 ++++ indoteknik_custom/views/purchase_order.xml | 1 + 3 files changed, 19 insertions(+) diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py index 0bd0092b..2ea89dab 100644 --- a/indoteknik_custom/models/purchase_order_sales_match.py +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -28,6 +28,20 @@ class PurchaseOrderSalesMatch(models.Model): purchase_price_po = fields.Float('Purchase Price PO', compute='_compute_purchase_price_po') purchase_line_id = fields.Many2one('purchase.order.line', string='Purchase Line', compute='_compute_purchase_line_id') hold_outgoing_so = fields.Boolean(string='Hold Outgoing SO', related='sale_id.hold_outgoing') + bu_pick = fields.Many2one('stock.picking', string='BU Pick', compute='compute_bu_pick') + + def compute_bu_pick(self): + for rec in self: + stock_move = self.env['stock.move'].search([ + ('reference', 'ilike', 'BU/PICK'), + ('state', 'in', ['confirmed','waiting','partially_available']), + ('product_id', '=', rec.product_id.id), + ('sale_line_id', '=', rec.sale_line_id.id), + ]) + if stock_move: + rec.bu_pick = stock_move.picking_id.id + else: + rec.bu_pick = '' def _compute_purchase_line_id(self): for line in self: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index eabef37c..7c4e6bc0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1309,6 +1309,7 @@ class StockPicking(models.Model): self.final_seq = 0 self.set_picking_code_out() self.send_koli_to_so() + self.automatic_reserve_product() if (self.state_reserve == 'done' and self.picking_type_code == 'internal' and 'BU/PICK/' in self.name and self.linked_manual_bu_out): @@ -1347,6 +1348,9 @@ class StockPicking(models.Model): } self.send_mail_bills() return res + + # def automatic_reserve_product(self): + # if self.name def check_invoice_date(self): for picking in self: diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index 7b568d07..530fd115 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -332,6 +332,7 @@ + -- cgit v1.2.3 From a6aa700b5016c98d579a52125e3686acc615ce88 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 23 Jun 2025 16:29:02 +0700 Subject: trial automatic reserve and change qty purchase stock --- indoteknik_custom/models/automatic_purchase.py | 2 +- indoteknik_custom/models/stock_picking.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index c9edf07c..83a7cb3c 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -486,7 +486,7 @@ class AutomaticPurchase(models.Model): # _logger.info('test %s' % point.product_id.name) if point.product_id.qty_available_bandengan > point.product_min_qty: continue - qty_purchase = point.product_max_qty - point.product_id.qty_incoming_bandengan - point.product_id.qty_onhand_bandengan + qty_purchase = point.product_max_qty - point.product_id.qty_incoming_bandengan - point.product_id.qty_available_bandengan po_line = self.env['purchase.order.line'].search([('product_id', '=', point.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) if self.vendor_id: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 7c4e6bc0..27046063 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1309,8 +1309,6 @@ class StockPicking(models.Model): self.final_seq = 0 self.set_picking_code_out() self.send_koli_to_so() - self.automatic_reserve_product() - if (self.state_reserve == 'done' and self.picking_type_code == 'internal' and 'BU/PICK/' in self.name and self.linked_manual_bu_out): if not self.linked_manual_bu_out.date_reserved: @@ -1347,10 +1345,18 @@ class StockPicking(models.Model): 'target': 'new', } self.send_mail_bills() + if 'BU/PUT' in self.name: + self.automatic_reserve_product() return res - # def automatic_reserve_product(self): - # if self.name + def automatic_reserve_product(self): + if self.state == 'done': + po = self.env['purchase.order'].search([ + ('name', '=', self.group_id.name) + ]) + + for line in po.order_sales_match_line: + line.bu_pick.action_assign() def check_invoice_date(self): for picking in self: -- cgit v1.2.3 From 05283d3ec0c49449e2ed7b14b2d824739db19174 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 24 Jun 2025 09:43:15 +0700 Subject: (andri) fix date reserved di SO --- indoteknik_custom/models/sale_order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c8d4a712..c1cdf2ed 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -382,13 +382,13 @@ class SaleOrder(models.Model): # Simpan ke field sebagai UTC-naive datetime (standar Odoo) order.et_products = eta_datetime.astimezone(pytz.utc).replace(tzinfo=None) - @api.depends('picking_ids.state', 'picking_ids.date_reserved') + @api.depends('picking_ids.state', 'picking_ids.date_done') def _compute_eta_date_reserved(self): for order in self: pickings = order.picking_ids.filtered( - lambda p: p.state == 'assigned' and p.date_reserved and 'BU/PICK/' in (p.name or '') + 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_reserved')) if pickings 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 From 6a913c0025c64903536fa6c9aeda526b609d27e6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 24 Jun 2025 11:23:36 +0700 Subject: change request coretax down payment --- indoteknik_custom/models/account_move.py | 3 +- indoteknik_custom/models/coretax_fatur.py | 83 +++++++++++++++++++++---------- indoteknik_custom/models/sale_order.py | 1 + indoteknik_custom/views/account_move.xml | 1 + 4 files changed, 62 insertions(+), 26 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 66020a69..ece47236 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -70,6 +70,7 @@ class AccountMove(models.Model): reklas_misc_id = fields.Many2one('account.move', string='Journal Entries Reklas') # Di model account.move bill_id = fields.Many2one('account.move', string='Vendor Bill', domain=[('move_type', '=', 'in_invoice')], help='Bill asal dari proses reklas ini') + down_payment = fields.Boolean('Down Payments?') # def name_get(self): @@ -441,7 +442,7 @@ class AccountMove(models.Model): # Panggil model coretax.faktur untuk menghasilkan XML coretax_faktur = self.env['coretax.faktur'].create({}) - response = coretax_faktur.export_to_download(invoices=valid_invoices) + response = coretax_faktur.export_to_download(invoices=valid_invoices, down_payments=valid_invoices.down_payment) current_time = datetime.utcnow() # Tandai faktur sebagai sudah diekspor diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index 92ff1a72..54eb0f8e 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -32,7 +32,7 @@ class CoretaxFaktur(models.Model): return cleaned_number - def generate_xml(self, invoices=None): + def generate_xml(self, invoices=None, down_payments=False): # Buat root XML root = ET.Element('TaxInvoiceBulk', { 'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance", @@ -72,42 +72,76 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # Filter product - product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and hasattr(l, 'account_id') and - l.account_id and l.product_id and - l.account_id.id != self.DISCOUNT_ACCOUNT_ID and - l.quantity != -1 - ) + # Handle product lines based on down_payments flag + if down_payments and invoice.invoice_origin: + # Get from sale.order.line for down payment + sale_order = invoice.sale_id + if sale_order: + product_lines = sale_order.order_line.filtered( + lambda l: l.product_id and not l.is_downpayment and not l.display_type and not l.product_id.id == 229625 + ) + # Convert sale order lines to invoice-like format + converted_lines = [] + for line in product_lines: + converted_lines.append({ + 'name': line.name, + 'product_id': line.product_id, + 'price_subtotal': line.price_subtotal, + 'quantity': line.product_uom_qty, + 'price_unit': line.price_unit, + 'account_id': line.order_id.analytic_account_id or False, + }) + product_lines = converted_lines + else: + product_lines = [] + else: + # Normal case - get from invoice lines + product_lines = invoice.invoice_line_ids.filtered( + lambda l: not l.display_type and hasattr(l, 'account_id') and + l.account_id and l.product_id and + l.account_id.id != self.DISCOUNT_ACCOUNT_ID and + l.quantity != -1 + ) - # Filter discount + # Filter discount (always from invoice) discount_lines = invoice.invoice_line_ids.filtered( lambda l: not l.display_type and ( - (hasattr(l, 'account_id') and l.account_id and - l.account_id.id == self.DISCOUNT_ACCOUNT_ID) or - (l.quantity == -1) + (hasattr(l, 'account_id') and l.account_id and + l.account_id.id == self.DISCOUNT_ACCOUNT_ID) or + (l.quantity == -1) ) ) - # Calculate total product amount (before discount) - total_product_amount = sum(line.price_subtotal for line in product_lines) + # Calculate totals + total_product_amount = sum(line.get('price_subtotal', 0) if isinstance(line, dict) + else line.price_subtotal for line in product_lines) if total_product_amount == 0: total_product_amount = 1 # Avoid division by zero - # Calculate total discount amount total_discount_amount = abs(sum(line.price_subtotal for line in discount_lines)) # Tambahkan elemen ListOfGoodService list_of_good_service = ET.SubElement(tax_invoice, 'ListOfGoodService') for line in product_lines: + # Handle both dict (converted sale lines) and normal invoice lines + if isinstance(line, dict): + line_price_subtotal = line['price_subtotal'] + line_quantity = line['quantity'] + line_name = line['name'] + line_price_unit = line['price_unit'] + else: + line_price_subtotal = line.price_subtotal + line_quantity = line.quantity + line_name = line.name + line_price_unit = line.price_unit + # Calculate prorated discount - line_proportion = line.price_subtotal / total_product_amount + line_proportion = line_price_subtotal / total_product_amount line_discount = total_discount_amount * line_proportion - # unit_price = line.price_unit - subtotal = line.price_subtotal - quantity = line.quantity + subtotal = line_price_subtotal + quantity = line_quantity total_discount = round(line_discount, 2) # Calculate other tax values @@ -118,13 +152,12 @@ class CoretaxFaktur(models.Model): good_service = ET.SubElement(list_of_good_service, 'GoodService') ET.SubElement(good_service, 'Opt').text = 'A' ET.SubElement(good_service, 'Code').text = '000000' - ET.SubElement(good_service, 'Name').text = line.name + ET.SubElement(good_service, 'Name').text = line_name ET.SubElement(good_service, 'Unit').text = 'UM.0018' - ET.SubElement(good_service, 'Price').text = str(round(subtotal / quantity, 2)) if subtotal else '0' + ET.SubElement(good_service, 'Price').text = str(round(line_price_unit, 2)) if line_price_unit else '0' ET.SubElement(good_service, 'Qty').text = str(quantity) ET.SubElement(good_service, 'TotalDiscount').text = str(total_discount) - ET.SubElement(good_service, 'TaxBase').text = str( - round(subtotal)) if subtotal else '0' + ET.SubElement(good_service, 'TaxBase').text = str(round(subtotal)) if subtotal else '0' ET.SubElement(good_service, 'OtherTaxBase').text = str(otherTaxBase) ET.SubElement(good_service, 'VATRate').text = '12' ET.SubElement(good_service, 'VAT').text = str(vat_amount) @@ -136,9 +169,9 @@ class CoretaxFaktur(models.Model): xml_pretty = minidom.parseString(xml_str).toprettyxml(indent=" ") return xml_pretty - def export_to_download(self, invoices): + def export_to_download(self, invoices, down_payments): # Generate XML content - xml_content = self.generate_xml(invoices) + xml_content = self.generate_xml(invoices, down_payments) # Encode content to Base64 xml_encoded = base64.b64encode(xml_content.encode('utf-8')) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c1cdf2ed..85228901 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1592,6 +1592,7 @@ class SaleOrder(models.Model): 'campaign_id': self.campaign_id.id, 'medium_id': self.medium_id.id, 'source_id': self.source_id.id, + 'down_payment': 229625 in [line.product_id.id for line in self.order_line], 'user_id': self.user_id.id, 'sale_id': self.id, 'invoice_user_id': self.user_id.id, diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index 0fc62293..e8061862 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -60,6 +60,7 @@ + -- cgit v1.2.3 From e1e281f6f43b9ba22443845484422cd0c5b1fb30 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 24 Jun 2025 12:08:57 +0700 Subject: (andri) penambahan field pada respartner --- indoteknik_custom/models/res_partner.py | 17 +++++++++++++++++ indoteknik_custom/views/res_partner.xml | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 9986b9c0..98aac7eb 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -165,6 +165,23 @@ 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', tracking=3, compute='_compute_payment_difficulty', inverse='_inverse_payment_difficulty', store=True) + 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.model def _default_payment_term(self): return self.env.ref('__export__.account_payment_term_26_484409e2').id diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index 30b5ca36..6115587b 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -181,6 +181,11 @@ + + + + + -- cgit v1.2.3 From 01c70504c6359d305327921a0329e59a269c868c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 24 Jun 2025 13:32:02 +0700 Subject: (andri) add field di respartner --- indoteknik_custom/views/res_partner.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index 6115587b..ca48acf3 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -181,11 +181,11 @@ - + -- cgit v1.2.3 From c60fa8f74b019b677d8e9ce513bcd7875a9b00de Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 24 Jun 2025 13:34:37 +0700 Subject: (andri) add field pada view --- indoteknik_custom/views/res_partner.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index ca48acf3..6115587b 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -181,11 +181,11 @@ - + -- cgit v1.2.3 From e921762879216dbe8df4e36dfa294ae4ccf293ee Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 24 Jun 2025 13:39:12 +0700 Subject: (andri) fix --- indoteknik_custom/models/res_partner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 98aac7eb..d23ab824 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -166,7 +166,7 @@ class ResPartner(models.Model): 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', tracking=3, compute='_compute_payment_difficulty', inverse='_inverse_payment_difficulty', store=True) + payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', tracking=3, compute='_compute_payment_difficulty', inverse='_inverse_payment_difficulty') payment_history_url = fields.Text(string='Payment History URL') @api.depends('parent_id.payment_difficulty') -- cgit v1.2.3 From a7139584557cc8c0d881bafc25b4cbbdb1c93ffc Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 24 Jun 2025 13:59:07 +0700 Subject: (andri) ganti compute field menjadi field biasa --- indoteknik_custom/models/res_partner.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index d23ab824..d439c7a8 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -164,23 +164,22 @@ class ResPartner(models.Model): "Set its value to 0.00 to disable " "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', tracking=3, compute='_compute_payment_difficulty', inverse='_inverse_payment_difficulty') + payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='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 + # @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 - }) + # 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): @@ -209,6 +208,10 @@ class ResPartner(models.Model): for rec in self: if 'latitude' in vals or 'longtitude' in vals: rec._update_address_from_coords() + + # Sinkronisasi payment_difficulty ke semua anak jika partner ini adalah parent + if not rec.parent_id and 'payment_difficulty' in vals: + rec.child_ids.write({'payment_difficulty': vals['payment_difficulty']}) # # # if 'property_payment_term_id' in vals: # # if not self.env.user.is_accounting and vals['property_payment_term_id'] != 26: @@ -227,6 +230,8 @@ class ResPartner(models.Model): for rec in records: if vals.get('latitude') and vals.get('longtitude'): rec._update_address_from_coords() + if rec.parent_id and not vals.get('payment_difficulty'): + rec.payment_difficulty = rec.parent_id.payment_difficulty return records @api.constrains('name') -- cgit v1.2.3 From 7e42b0fdd1735df03e249f1362e58c169236465d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 24 Jun 2025 15:08:37 +0700 Subject: (andri) fix download xml invoice --- indoteknik_custom/models/account_move.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index ece47236..af24f93e 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -437,18 +437,18 @@ class AccountMove(models.Model): return invoices def export_faktur_to_xml(self): - valid_invoices = self - # Panggil model coretax.faktur untuk menghasilkan XML coretax_faktur = self.env['coretax.faktur'].create({}) - response = coretax_faktur.export_to_download(invoices=valid_invoices, down_payments=valid_invoices.down_payment) - current_time = datetime.utcnow() - # Tandai faktur sebagai sudah diekspor + response = coretax_faktur.export_to_download( + invoices=valid_invoices, + down_payments=[inv.down_payment for inv in valid_invoices], + ) + valid_invoices.write({ 'is_efaktur_exported': True, - 'date_efaktur_exported': current_time, # Set tanggal ekspor + 'date_efaktur_exported': datetime.utcnow(), }) - return response + return response \ No newline at end of file -- cgit v1.2.3 From 9bc5f5c347b3e8f4d739d615e6a858b25c98e78c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 25 Jun 2025 08:32:05 +0700 Subject: (andri) fix --- indoteknik_custom/views/res_partner.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index 6115587b..2ed37caf 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -182,10 +182,10 @@ - + -- cgit v1.2.3 From 01a2063093951e1125d86dc9b8794e591df07a06 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 25 Jun 2025 08:32:25 +0700 Subject: (andri) fix --- indoteknik_custom/views/res_partner.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index 2ed37caf..a851385f 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -181,7 +181,7 @@ - + - + -- cgit v1.2.3 From 92bf91488eca67adabb6bfe8394385a9cee1032d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 25 Jun 2025 09:58:26 +0700 Subject: fix bu pick automatic reserve --- indoteknik_custom/models/stock_picking.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 27046063..c884f97e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1356,6 +1356,8 @@ class StockPicking(models.Model): ]) for line in po.order_sales_match_line: + if not line.bu_pick: + continue line.bu_pick.action_assign() def check_invoice_date(self): -- cgit v1.2.3 From 74b49469064387219474ddf8e8f38e5d676079c2 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 25 Jun 2025 10:03:06 +0700 Subject: push --- indoteknik_custom/models/purchase_order_sales_match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py index 2ea89dab..b18864f3 100644 --- a/indoteknik_custom/models/purchase_order_sales_match.py +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -41,7 +41,7 @@ class PurchaseOrderSalesMatch(models.Model): if stock_move: rec.bu_pick = stock_move.picking_id.id else: - rec.bu_pick = '' + rec.bu_pick = None def _compute_purchase_line_id(self): for line in self: -- cgit v1.2.3 From 3c43b91173cdaf5762e2f39197a89a2b24090458 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 25 Jun 2025 10:47:17 +0700 Subject: (andri) fix peletakan field pada view billreklas --- indoteknik_custom/views/account_move.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index e8061862..ad52a74a 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -37,6 +37,8 @@ + + -- cgit v1.2.3 From 8ca3394604d088a683fc4a3c69321ad65f093b7f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 25 Jun 2025 11:18:33 +0700 Subject: add terbilang total cashback customer commision --- indoteknik_custom/models/commision.py | 15 +++++++++++++++ indoteknik_custom/views/customer_commision.xml | 1 + 2 files changed, 16 insertions(+) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 199aa106..997d4470 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -191,6 +191,7 @@ class CustomerCommision(models.Model): total_cashback = fields.Float(string='Total Cashback') commision_amt_text = fields.Char(string='Commision Amount Text', compute='compute_delivery_amt_text') + total_cashback_text = fields.Char(string='Commision Amount Text', compute='compute_total_cashback_text') total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp') commision_type = fields.Selection([ ('fee', 'Fee'), @@ -281,6 +282,20 @@ class CustomerCommision(models.Model): except: record.commision_amt_text = res + def compute_total_cashback_text(self): + tb = Terbilang() + + for record in self: + res = '' + + try: + if record.total_commision > 0: + tb.parse(int(record.total_commision)) + res = tb.getresult().title() + record.commision_amt_text = res + ' Rupiah' + except: + record.commision_amt_text = res + def _compute_grouped_numbers(self): for rec in self: so_numbers = set() diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index d5fb1d70..1c17bf63 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -83,6 +83,7 @@ + -- cgit v1.2.3 From f87442f721ea925bd2763492f990bbe68e1627ac Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 25 Jun 2025 13:09:24 +0700 Subject: fix terbilang cashback customer benefits --- indoteknik_custom/models/commision.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 997d4470..b685f6e1 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -191,7 +191,7 @@ class CustomerCommision(models.Model): total_cashback = fields.Float(string='Total Cashback') commision_amt_text = fields.Char(string='Commision Amount Text', compute='compute_delivery_amt_text') - total_cashback_text = fields.Char(string='Commision Amount Text', compute='compute_total_cashback_text') + total_cashback_text = fields.Char(string='Cashback Text', compute='compute_total_cashback_text') total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp') commision_type = fields.Selection([ ('fee', 'Fee'), @@ -287,14 +287,14 @@ class CustomerCommision(models.Model): for record in self: res = '' - try: if record.total_commision > 0: tb.parse(int(record.total_commision)) res = tb.getresult().title() - record.commision_amt_text = res + ' Rupiah' - except: - record.commision_amt_text = res + record.total_cashback_text = f"{res} Rupiah" if res else "" + except Exception as e: + record.total_cashback_text = "" + _logger.error("Error computing cashback text: %s", str(e)) def _compute_grouped_numbers(self): for rec in self: -- cgit v1.2.3 From 0e02e21f5b8943a25e21c377303f6552494e6cc7 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 25 Jun 2025 14:45:32 +0700 Subject: (andri) fix xml coretax --- indoteknik_custom/models/coretax_fatur.py | 105 +++++++----------------------- 1 file changed, 25 insertions(+), 80 deletions(-) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index 54eb0f8e..4397d7a1 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -51,8 +51,7 @@ class CoretaxFaktur(models.Model): buyerIDTKU = formula if sum(int(char) for char in buyerTIN) > 0 else '000000' # Tambahkan elemen faktur - ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime( - '%Y-%m-%d') if invoice.invoice_date else '' + ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime('%Y-%m-%d') if invoice.invoice_date else '' ET.SubElement(tax_invoice, 'TaxInvoiceOpt').text = 'Normal' ET.SubElement(tax_invoice, 'TrxCode').text = '04' ET.SubElement(tax_invoice, 'AddInfo') @@ -62,103 +61,49 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'FacilityStamp') ET.SubElement(tax_invoice, 'SellerIDTKU').text = '0742260227086000000000' ET.SubElement(tax_invoice, 'BuyerTin').text = buyerTIN - ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum( - int(char) for char in buyerTIN) > 0 else 'Other ID' + ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum(int(char) for char in buyerTIN) > 0 else 'Other ID' ET.SubElement(tax_invoice, 'BuyerCountry').text = 'IDN' - ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = '-' if sum( - int(char) for char in buyerTIN) > 0 else str(invoice.partner_id.id) + ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = '-' if sum(int(char) for char in buyerTIN) > 0 else str(invoice.partner_id.id) ET.SubElement(tax_invoice, 'BuyerName').text = invoice.partner_id.nama_wajib_pajak or '' ET.SubElement(tax_invoice, 'BuyerAdress').text = invoice.partner_id.alamat_lengkap_text or '' ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # Handle product lines based on down_payments flag - if down_payments and invoice.invoice_origin: - # Get from sale.order.line for down payment - sale_order = invoice.sale_id - if sale_order: - product_lines = sale_order.order_line.filtered( - lambda l: l.product_id and not l.is_downpayment and not l.display_type and not l.product_id.id == 229625 - ) - # Convert sale order lines to invoice-like format - converted_lines = [] - for line in product_lines: - converted_lines.append({ - 'name': line.name, - 'product_id': line.product_id, - 'price_subtotal': line.price_subtotal, - 'quantity': line.product_uom_qty, - 'price_unit': line.price_unit, - 'account_id': line.order_id.analytic_account_id or False, - }) - product_lines = converted_lines - else: - product_lines = [] - else: - # Normal case - get from invoice lines - product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and hasattr(l, 'account_id') and - l.account_id and l.product_id and - l.account_id.id != self.DISCOUNT_ACCOUNT_ID and - l.quantity != -1 - ) - - # Filter discount (always from invoice) - discount_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and ( - (hasattr(l, 'account_id') and l.account_id and - l.account_id.id == self.DISCOUNT_ACCOUNT_ID) or - (l.quantity == -1) - ) + # Ambil lines + product_lines = invoice.invoice_line_ids.filtered( + lambda l: not l.display_type and l.product_id and l.account_id and l.account_id.id != self.DISCOUNT_ACCOUNT_ID and l.quantity != -1 ) - # Calculate totals - total_product_amount = sum(line.get('price_subtotal', 0) if isinstance(line, dict) - else line.price_subtotal for line in product_lines) - if total_product_amount == 0: - total_product_amount = 1 # Avoid division by zero - - total_discount_amount = abs(sum(line.price_subtotal for line in discount_lines)) + invoice_untaxed = invoice.amount_untaxed + if invoice_untaxed == 0: + invoice_untaxed = 1 # Hindari div zero # Tambahkan elemen ListOfGoodService list_of_good_service = ET.SubElement(tax_invoice, 'ListOfGoodService') for line in product_lines: - # Handle both dict (converted sale lines) and normal invoice lines - if isinstance(line, dict): - line_price_subtotal = line['price_subtotal'] - line_quantity = line['quantity'] - line_name = line['name'] - line_price_unit = line['price_unit'] - else: - line_price_subtotal = line.price_subtotal - line_quantity = line.quantity - line_name = line.name - line_price_unit = line.price_unit - - # Calculate prorated discount - line_proportion = line_price_subtotal / total_product_amount - line_discount = total_discount_amount * line_proportion - - subtotal = line_price_subtotal - quantity = line_quantity - total_discount = round(line_discount, 2) - - # Calculate other tax values - otherTaxBase = round(subtotal * (11 / 12), 2) if subtotal else 0 - vat_amount = round(otherTaxBase * 0.12, 2) - - # Create the line in XML + line_price_subtotal = line.price_subtotal + line_quantity = line.quantity + line_name = line.name + line_price_unit = line.price_unit + + # Hitung proporsi & tax base per line + line_proportion = line_price_subtotal / invoice_untaxed + line_tax_base = round(invoice.amount_untaxed * line_proportion, 2) + other_tax_base = round(line_tax_base * (11 / 12), 2) + vat_amount = round(other_tax_base * 0.12, 2) + + # Isi XML good_service = ET.SubElement(list_of_good_service, 'GoodService') ET.SubElement(good_service, 'Opt').text = 'A' ET.SubElement(good_service, 'Code').text = '000000' ET.SubElement(good_service, 'Name').text = line_name ET.SubElement(good_service, 'Unit').text = 'UM.0018' ET.SubElement(good_service, 'Price').text = str(round(line_price_unit, 2)) if line_price_unit else '0' - ET.SubElement(good_service, 'Qty').text = str(quantity) - ET.SubElement(good_service, 'TotalDiscount').text = str(total_discount) - ET.SubElement(good_service, 'TaxBase').text = str(round(subtotal)) if subtotal else '0' - ET.SubElement(good_service, 'OtherTaxBase').text = str(otherTaxBase) + ET.SubElement(good_service, 'Qty').text = str(line_quantity) + ET.SubElement(good_service, 'TotalDiscount').text = '0' + ET.SubElement(good_service, 'TaxBase').text = str(line_tax_base) + ET.SubElement(good_service, 'OtherTaxBase').text = str(other_tax_base) ET.SubElement(good_service, 'VATRate').text = '12' ET.SubElement(good_service, 'VAT').text = str(vat_amount) ET.SubElement(good_service, 'STLGRate').text = '0' -- cgit v1.2.3 From e483cf1634b16df7c9ecc4a89007dbc253b68da1 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 25 Jun 2025 14:50:32 +0700 Subject: (andri) ganti penamaan field commision --- indoteknik_custom/models/commision.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index b685f6e1..4db6c009 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -178,10 +178,10 @@ class CustomerCommision(models.Model): ], string='Status') # commision_percent = fields.Float(string='Commision %', tracking=3) - commision_percent = fields.Float(string='Cashback %', tracking=3) + commision_percent = fields.Float(string='Persentase', tracking=3) # commision_amt = fields.Float(string='Commision Amount', tracking=3) - commision_amt = fields.Float(string='Cashback', tracking=3) + commision_amt = fields.Float(string='Amount', tracking=3) # cashback = fields.Float(string='Cashback', compute="compute_cashback") cashback = fields.Float(string='PPh Cashback', compute="compute_cashback") @@ -190,7 +190,7 @@ class CustomerCommision(models.Model): total_commision = fields.Float(string='Cashback yang dibayarkan', compute="compute_cashback") total_cashback = fields.Float(string='Total Cashback') - commision_amt_text = fields.Char(string='Commision Amount Text', compute='compute_delivery_amt_text') + commision_amt_text = fields.Char(string='Amount Text', compute='compute_delivery_amt_text') total_cashback_text = fields.Char(string='Cashback Text', compute='compute_total_cashback_text') total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp') commision_type = fields.Selection([ -- cgit v1.2.3 From e30c678ed5eb53f67d95d02645f6662462ec07d0 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 25 Jun 2025 14:54:16 +0700 Subject: (andri) kembalikan perhitungan coretax --- indoteknik_custom/models/coretax_fatur.py | 105 +++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 25 deletions(-) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index 4397d7a1..54eb0f8e 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -51,7 +51,8 @@ class CoretaxFaktur(models.Model): buyerIDTKU = formula if sum(int(char) for char in buyerTIN) > 0 else '000000' # Tambahkan elemen faktur - ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime('%Y-%m-%d') if invoice.invoice_date else '' + ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime( + '%Y-%m-%d') if invoice.invoice_date else '' ET.SubElement(tax_invoice, 'TaxInvoiceOpt').text = 'Normal' ET.SubElement(tax_invoice, 'TrxCode').text = '04' ET.SubElement(tax_invoice, 'AddInfo') @@ -61,49 +62,103 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'FacilityStamp') ET.SubElement(tax_invoice, 'SellerIDTKU').text = '0742260227086000000000' ET.SubElement(tax_invoice, 'BuyerTin').text = buyerTIN - ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum(int(char) for char in buyerTIN) > 0 else 'Other ID' + ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum( + int(char) for char in buyerTIN) > 0 else 'Other ID' ET.SubElement(tax_invoice, 'BuyerCountry').text = 'IDN' - ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = '-' if sum(int(char) for char in buyerTIN) > 0 else str(invoice.partner_id.id) + ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = '-' if sum( + int(char) for char in buyerTIN) > 0 else str(invoice.partner_id.id) ET.SubElement(tax_invoice, 'BuyerName').text = invoice.partner_id.nama_wajib_pajak or '' ET.SubElement(tax_invoice, 'BuyerAdress').text = invoice.partner_id.alamat_lengkap_text or '' ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # Ambil lines - product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and l.product_id and l.account_id and l.account_id.id != self.DISCOUNT_ACCOUNT_ID and l.quantity != -1 + # Handle product lines based on down_payments flag + if down_payments and invoice.invoice_origin: + # Get from sale.order.line for down payment + sale_order = invoice.sale_id + if sale_order: + product_lines = sale_order.order_line.filtered( + lambda l: l.product_id and not l.is_downpayment and not l.display_type and not l.product_id.id == 229625 + ) + # Convert sale order lines to invoice-like format + converted_lines = [] + for line in product_lines: + converted_lines.append({ + 'name': line.name, + 'product_id': line.product_id, + 'price_subtotal': line.price_subtotal, + 'quantity': line.product_uom_qty, + 'price_unit': line.price_unit, + 'account_id': line.order_id.analytic_account_id or False, + }) + product_lines = converted_lines + else: + product_lines = [] + else: + # Normal case - get from invoice lines + product_lines = invoice.invoice_line_ids.filtered( + lambda l: not l.display_type and hasattr(l, 'account_id') and + l.account_id and l.product_id and + l.account_id.id != self.DISCOUNT_ACCOUNT_ID and + l.quantity != -1 + ) + + # Filter discount (always from invoice) + discount_lines = invoice.invoice_line_ids.filtered( + lambda l: not l.display_type and ( + (hasattr(l, 'account_id') and l.account_id and + l.account_id.id == self.DISCOUNT_ACCOUNT_ID) or + (l.quantity == -1) + ) ) - invoice_untaxed = invoice.amount_untaxed - if invoice_untaxed == 0: - invoice_untaxed = 1 # Hindari div zero + # Calculate totals + total_product_amount = sum(line.get('price_subtotal', 0) if isinstance(line, dict) + else line.price_subtotal for line in product_lines) + if total_product_amount == 0: + total_product_amount = 1 # Avoid division by zero + + total_discount_amount = abs(sum(line.price_subtotal for line in discount_lines)) # Tambahkan elemen ListOfGoodService list_of_good_service = ET.SubElement(tax_invoice, 'ListOfGoodService') for line in product_lines: - line_price_subtotal = line.price_subtotal - line_quantity = line.quantity - line_name = line.name - line_price_unit = line.price_unit - - # Hitung proporsi & tax base per line - line_proportion = line_price_subtotal / invoice_untaxed - line_tax_base = round(invoice.amount_untaxed * line_proportion, 2) - other_tax_base = round(line_tax_base * (11 / 12), 2) - vat_amount = round(other_tax_base * 0.12, 2) - - # Isi XML + # Handle both dict (converted sale lines) and normal invoice lines + if isinstance(line, dict): + line_price_subtotal = line['price_subtotal'] + line_quantity = line['quantity'] + line_name = line['name'] + line_price_unit = line['price_unit'] + else: + line_price_subtotal = line.price_subtotal + line_quantity = line.quantity + line_name = line.name + line_price_unit = line.price_unit + + # Calculate prorated discount + line_proportion = line_price_subtotal / total_product_amount + line_discount = total_discount_amount * line_proportion + + subtotal = line_price_subtotal + quantity = line_quantity + total_discount = round(line_discount, 2) + + # Calculate other tax values + otherTaxBase = round(subtotal * (11 / 12), 2) if subtotal else 0 + vat_amount = round(otherTaxBase * 0.12, 2) + + # Create the line in XML good_service = ET.SubElement(list_of_good_service, 'GoodService') ET.SubElement(good_service, 'Opt').text = 'A' ET.SubElement(good_service, 'Code').text = '000000' ET.SubElement(good_service, 'Name').text = line_name ET.SubElement(good_service, 'Unit').text = 'UM.0018' ET.SubElement(good_service, 'Price').text = str(round(line_price_unit, 2)) if line_price_unit else '0' - ET.SubElement(good_service, 'Qty').text = str(line_quantity) - ET.SubElement(good_service, 'TotalDiscount').text = '0' - ET.SubElement(good_service, 'TaxBase').text = str(line_tax_base) - ET.SubElement(good_service, 'OtherTaxBase').text = str(other_tax_base) + ET.SubElement(good_service, 'Qty').text = str(quantity) + ET.SubElement(good_service, 'TotalDiscount').text = str(total_discount) + ET.SubElement(good_service, 'TaxBase').text = str(round(subtotal)) if subtotal else '0' + ET.SubElement(good_service, 'OtherTaxBase').text = str(otherTaxBase) ET.SubElement(good_service, 'VATRate').text = '12' ET.SubElement(good_service, 'VAT').text = str(vat_amount) ET.SubElement(good_service, 'STLGRate').text = '0' -- cgit v1.2.3 From 27926566ef6fee5a5a4be9c4cfcaddc6edfa23a9 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 26 Jun 2025 08:44:24 +0700 Subject: (andri) fix price coretax & penamaan field cust benefits --- indoteknik_custom/models/commision.py | 2 +- indoteknik_custom/models/coretax_fatur.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 4db6c009..97184cdb 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -178,7 +178,7 @@ class CustomerCommision(models.Model): ], string='Status') # commision_percent = fields.Float(string='Commision %', tracking=3) - commision_percent = fields.Float(string='Persentase', tracking=3) + commision_percent = fields.Float(string='Persentase (%)', tracking=3) # commision_amt = fields.Float(string='Commision Amount', tracking=3) commision_amt = fields.Float(string='Amount', tracking=3) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index 54eb0f8e..9b1544d3 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -148,13 +148,16 @@ class CoretaxFaktur(models.Model): otherTaxBase = round(subtotal * (11 / 12), 2) if subtotal else 0 vat_amount = round(otherTaxBase * 0.12, 2) + price_per_unit = round(subtotal / quantity, 2) if quantity else 0 + # Create the line in XML good_service = ET.SubElement(list_of_good_service, 'GoodService') ET.SubElement(good_service, 'Opt').text = 'A' ET.SubElement(good_service, 'Code').text = '000000' ET.SubElement(good_service, 'Name').text = line_name ET.SubElement(good_service, 'Unit').text = 'UM.0018' - ET.SubElement(good_service, 'Price').text = str(round(line_price_unit, 2)) if line_price_unit else '0' + # ET.SubElement(good_service, 'Price').text = str(round(line_price_unit, 2)) if line_price_unit else '0' + ET.SubElement(good_service, 'Price').text = str(price_per_unit) ET.SubElement(good_service, 'Qty').text = str(quantity) ET.SubElement(good_service, 'TotalDiscount').text = str(total_discount) ET.SubElement(good_service, 'TaxBase').text = str(round(subtotal)) if subtotal else '0' -- cgit v1.2.3 From 4a628d1644da21870706951c4601a5b007993cd8 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 26 Jun 2025 08:44:01 +0700 Subject: change command error --- indoteknik_custom/models/sale_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 85228901..109771e9 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1994,9 +1994,9 @@ class SaleOrder(models.Model): confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed' or x.state == 'done') if not confirmed_bom: raise UserError( - "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") + "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi Purchasing.") else: - raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") + raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi Purchasing") def check_duplicate_product(self): for order in self: -- cgit v1.2.3 From 8767ca7aed495a70114fdbaaa61b0772c497b4d0 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 26 Jun 2025 13:56:20 +0700 Subject: (andri) fix downpayment --- indoteknik_custom/models/coretax_fatur.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index 9b1544d3..ce94306f 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -3,6 +3,9 @@ import xml.etree.ElementTree as ET from xml.dom import minidom import base64 import re +import logging + +_logger = logging.getLogger(__name__) class CoretaxFaktur(models.Model): @@ -72,8 +75,9 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU + _logger.info(" invoice down_payments: %s", invoice.down_payment) # Handle product lines based on down_payments flag - if down_payments and invoice.invoice_origin: + if invoice.down_payment and invoice.invoice_origin: # Get from sale.order.line for down payment sale_order = invoice.sale_id if sale_order: -- cgit v1.2.3 From 498b7f73857189d1b22204c3f71f35d03ec4afb7 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 1 Jul 2025 09:36:21 +0700 Subject: add sale order on po bom and add bom on po bom --- indoteknik_custom/models/mrp_production.py | 5 +++-- indoteknik_custom/models/purchase_order.py | 1 + indoteknik_custom/views/mrp_production.xml | 2 +- indoteknik_custom/views/purchase_order.xml | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 14821f27..85b8405f 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -110,8 +110,9 @@ class MrpProduction(models.Model): 'picking_type_id': 28, # indoteknik bandengan receipts 'date_order': current_time, 'product_bom_id': self.product_id.id, - # 'sale_order_id': self.sale_order_id.id, - 'note_description': 'from Manufacturing Order' + 'sale_order_id': self.sale_order.id, + 'manufacturing_id': self.id, + 'note_description': 'from Manufacturing Order', } domain = [ diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 1a7e50f8..a3941b3b 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -97,6 +97,7 @@ class PurchaseOrder(models.Model): string="BU Related Count", compute='_compute_bu_related_count' ) + manufacturing_id = fields.Many2one('mrp.production', string='Manufacturing Orders') @api.depends('name') def _compute_bu_related_count(self): diff --git a/indoteknik_custom/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml index 3de52a08..5057415f 100644 --- a/indoteknik_custom/views/mrp_production.xml +++ b/indoteknik_custom/views/mrp_production.xml @@ -11,7 +11,7 @@ - + diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index 530fd115..dae23eed 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -87,7 +87,8 @@ - + + -- cgit v1.2.3 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 From 38ba3d7f5b59a4444d9eb953a6c83e4ab6015ba6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 4 Jul 2025 09:18:50 +0700 Subject: approval payment term --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/approval_payment_term.py | 82 +++++++++++++++++++++++ indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/account_move.xml | 1 + indoteknik_custom/views/approval_payment_term.xml | 74 ++++++++++++++++++++ indoteknik_custom/views/ir_sequence.xml | 10 +++ 7 files changed, 170 insertions(+) create mode 100644 indoteknik_custom/models/approval_payment_term.py create mode 100644 indoteknik_custom/views/approval_payment_term.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index ad019d4b..17cec7b6 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -156,6 +156,7 @@ 'views/stock_backorder_confirmation_views.xml', 'views/barcoding_product.xml', 'views/project_views.xml', + 'views/approval_payment_term.xml', 'report/report.xml', 'report/report_banner_banner.xml', 'report/report_banner_banner2.xml', diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 605d1016..83392d42 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -151,3 +151,4 @@ from . import account_payment_register from . import stock_inventory from . import sale_order_delay from . import approval_invoice_date +from . import approval_payment_term diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py new file mode 100644 index 00000000..81eb1908 --- /dev/null +++ b/indoteknik_custom/models/approval_payment_term.py @@ -0,0 +1,82 @@ +from odoo import models, api, fields +from odoo.exceptions import AccessError, UserError, ValidationError +from datetime import timedelta, date, datetime +import logging + +_logger = logging.getLogger(__name__) + +class ApprovalPaymentTerm(models.Model): + _name = "approval.payment.term" + _description = "Approval Payment Term" + _inherit = ['mail.thread'] + _rec_name = 'number' + + number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True) + partner_id = fields.Many2one('res.partner', string='Partner', copy=False) + property_payment_term_id = fields.Many2one('account.payment.term', string='Payment Term', copy=False, tracking=True) + parent_id = fields.Many2one('res.partner', string='Related Company', copy=False) + blocking_stage = fields.Float(string='Blocking Amount', + help="Cannot make sales once the selected " + "customer is crossed blocking amount." + "Set its value to 0.00 to disable " + "this feature", tracking=True, copy=False) + warning_stage = fields.Float(string='Warning Amount', + help="A warning message will appear once the " + "selected customer is crossed warning " + "amount. Set its value to 0.00 to" + " disable this feature", tracking=True, copy=False) + active_limit = fields.Boolean('Active Credit Limit', copy=False, tracking=True) + approve_sales_manager = fields.Boolean('Approve Sales Manager', tracking=True, copy=False) + approve_finance = fields.Boolean('Approve Finance', tracking=True, copy=False) + approve_leader = fields.Boolean('Approve Pimpinan', tracking=True, copy=False) + reason = fields.Text('Reason', tracking=True) + approve_date = fields.Datetime('Approve Date') + + + @api.constrains('partner_id') + def constrains_partner_id(self): + if self.partner_id: + self.parent_id = self.partner_id.parent_id.id if self.partner_id.parent_id else None + self.blocking_stage = self.partner_id.blocking_stage + self.warning_stage = self.partner_id.warning_stage + self.active_limit = self.partner_id.active_limit + self.property_payment_term_id = self.partner_id.property_payment_term_id.id + + def button_approve(self): + user = self.env.user + is_it = user.has_group('indoteknik_custom.group_role_it') + + if is_it or user.id == 19: + self.approve_sales_manager = True + return + + if is_it or user.id == 688 and self.approve_sales_manager: + self.approve_finance = True + return + + if is_it or user.id == 7 and self.approve_sales_manager and self.approve_finance: + self.approve_leader = True + + if not is_it or not self.approve_finance: + raise UserError('Harus Approval Finance!!') + if not is_it or not self.approve_leader: + raise UserError('Harus Approval Pimpinan!!') + + if user.id == 7: + if not self.approve_finance: + raise UserError('Belum Di Approve Oleh Finance') + + if self.approve_leader == True: + self.partner_id.write({ + 'blocking_stage': self.blocking_stage, + 'warning_stage': self.warning_stage, + 'active_limit': self.active_limit, + 'property_payment_term_id': self.property_payment_term_id.id + }) + self.approve_date = datetime.utcnow() + + @api.model + def create(self, vals): + vals['number'] = self.env['ir.sequence'].next_by_code('approval.payment.term') or '0' + result = super(ApprovalPaymentTerm, self).create(vals) + return result diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 3dabae6d..2b970cfd 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -182,3 +182,4 @@ access_sale_order_delay,sale.order.delay,model_sale_order_delay,,1,1,1,1 access_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1 access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1 access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1 +access_approval_payment_term,access.approval.payment.term,model_approval_payment_term,,1,1,1,1 diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index ad52a74a..2f52b3d9 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -70,6 +70,7 @@ + diff --git a/indoteknik_custom/views/approval_payment_term.xml b/indoteknik_custom/views/approval_payment_term.xml new file mode 100644 index 00000000..87c77385 --- /dev/null +++ b/indoteknik_custom/views/approval_payment_term.xml @@ -0,0 +1,74 @@ + + + + approval.payment.term.tree + approval.payment.term + + + + + + + + + + + + + + + + + approval.payment.term.form + approval.payment.term + +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+ + + Approval Payment Term + ir.actions.act_window + approval.payment.term + tree,form + + + + +
\ No newline at end of file diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index bb8848c4..a0f5fc6b 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -150,6 +150,16 @@ 1 True + + + Approval Payment Term + approval.payment.term + APP/%(year)s/ + 5 + 1 + 1 + True + Customer Commision Fee -- cgit v1.2.3 From e5b1c4117bd887b1e77c0aa8117b79646397855b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 4 Jul 2025 10:02:31 +0700 Subject: (andri) fix open jurnal entries --- indoteknik_custom/models/account_move.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index af24f93e..b6627867 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -99,12 +99,27 @@ class AccountMove(models.Model): self.invoice_date = self.date + # def compute_length_of_payment(self): + # for rec in self: + # payment_term = rec.invoice_payment_term_id.line_ids[0].days + # terima_faktur = rec.date_terima_tukar_faktur + # payment = self.search([('ref', '=', rec.name), ('move_type', '=', 'entry')], limit=1) + + # if payment and terima_faktur: + # date_diff = terima_faktur - payment.date + # rec.length_of_payment = date_diff.days + payment_term + # else: + # rec.length_of_payment = 0 + def compute_length_of_payment(self): for rec in self: - payment_term = rec.invoice_payment_term_id.line_ids[0].days + payment_term = 0 + if rec.invoice_payment_term_id and rec.invoice_payment_term_id.line_ids: + payment_term = rec.invoice_payment_term_id.line_ids[0].days + terima_faktur = rec.date_terima_tukar_faktur payment = self.search([('ref', '=', rec.name), ('move_type', '=', 'entry')], limit=1) - + if payment and terima_faktur: date_diff = terima_faktur - payment.date rec.length_of_payment = date_diff.days + payment_term -- cgit v1.2.3 From d9ddc88ea00a5c86c7cf82552970ab0c917d8544 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 5 Jul 2025 10:36:28 +0700 Subject: (andri) add patch untuk webhook biteship --- indoteknik_custom/models/__init__.py | 1 + .../models/patch/__pycache__/__init__.py | 1 + .../models/patch/__pycache__/http_override.py | 46 ++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 indoteknik_custom/models/patch/__pycache__/__init__.py create mode 100644 indoteknik_custom/models/patch/__pycache__/http_override.py diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 83392d42..cc406c13 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -152,3 +152,4 @@ from . import stock_inventory from . import sale_order_delay from . import approval_invoice_date from . import approval_payment_term +from . import patch diff --git a/indoteknik_custom/models/patch/__pycache__/__init__.py b/indoteknik_custom/models/patch/__pycache__/__init__.py new file mode 100644 index 00000000..051b6537 --- /dev/null +++ b/indoteknik_custom/models/patch/__pycache__/__init__.py @@ -0,0 +1 @@ +from . import http_override \ No newline at end of file diff --git a/indoteknik_custom/models/patch/__pycache__/http_override.py b/indoteknik_custom/models/patch/__pycache__/http_override.py new file mode 100644 index 00000000..e1978edb --- /dev/null +++ b/indoteknik_custom/models/patch/__pycache__/http_override.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +import odoo.http +import json +import logging +from werkzeug.exceptions import BadRequest +import functools + +_logger = logging.getLogger(__name__) + +class CustomJsonRequest(odoo.http.JsonRequest): + def __init__(self, httprequest): + super(odoo.http.JsonRequest, self).__init__(httprequest) + + self.params = {} + request_data_raw = self.httprequest.get_data().decode(self.httprequest.charset) + + self.jsonrequest = {} + if request_data_raw.strip(): + try: + self.jsonrequest = json.loads(request_data_raw) + except ValueError: + msg = 'Invalid JSON data: %r' % (request_data_raw,) + _logger.info('%s: %s (Handled by CustomJsonRequest)', self.httprequest.path, msg) + raise BadRequest(msg) + else: + _logger.info("CustomJsonRequest: Received empty or whitespace-only JSON body. Treating as empty JSON for webhook.") + + self.params = dict(self.jsonrequest.get("params", {})) + self.context = self.params.pop('context', dict(self.session.context)) + + +_original_get_request = odoo.http.Root.get_request + +@functools.wraps(_original_get_request) +def _get_request_override(self, httprequest): + _logger.info("--- DEBUG: !!! _get_request_override IS CALLED !!! ---") + _logger.info(f"--- DEBUG: Request Mimetype: {httprequest.mimetype}, Path: {httprequest.path} ---") + + if httprequest.mimetype in ("application/json", "application/json-rpc"): + _logger.debug("Odoo HTTP: Using CustomJsonRequest for mimetype: %s", httprequest.mimetype) + return CustomJsonRequest(httprequest) + else: + _logger.debug("Odoo HTTP: Using original get_request for mimetype: %s", httprequest.mimetype) + return _original_get_request(self, httprequest) + +odoo.http.Root.get_request = _get_request_override \ No newline at end of file -- cgit v1.2.3 From fc6d38599b405820b3c266a31ef21a3a0f3f0a73 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 5 Jul 2025 10:51:52 +0700 Subject: (andri) fix --- indoteknik_api/controllers/api_v1/stock_picking.py | 60 +++++++++++++--------- indoteknik_custom/models/patch/__init__.py | 1 + .../models/patch/__pycache__/__init__.py | 1 - .../models/patch/__pycache__/http_override.py | 46 ----------------- indoteknik_custom/models/patch/http_override.py | 45 ++++++++++++++++ 5 files changed, 82 insertions(+), 71 deletions(-) create mode 100644 indoteknik_custom/models/patch/__init__.py delete mode 100644 indoteknik_custom/models/patch/__pycache__/__init__.py delete mode 100644 indoteknik_custom/models/patch/__pycache__/http_override.py create mode 100644 indoteknik_custom/models/patch/http_override.py diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index c5a4f7ed..1b247c8a 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -4,6 +4,9 @@ from odoo.http import request from pytz import timezone from datetime import datetime import json +import logging + +_logger = logging.getLogger(__name__) class StockPicking(controller.Controller): @@ -143,30 +146,39 @@ class StockPicking(controller.Controller): 'name': picking_data.name }) - # @http.route(prefix + 'webhook/biteship', type='json', auth='public', methods=['POST'], csrf=False) - # def udpate_status_from_bitehsip(self, **kw): - # try: - # if not request.jsonrequest: - # return "ok" - - # data = request.jsonrequest # Ambil data JSON dari request - # event = data.get('event') - - # # Handle Event Berdasarkan Jenisnya - # if event == "order.status": - # self.process_order_status(data) - # elif event == "order.price": - # self.process_order_price(data) - # elif event == "order.waybill_id": - # self.process_order_waybill(data) - - # return {'success': True, 'message': f'Webhook {event} received'} - # except Exception as e: - # return {'success': False, 'message': str(e)} - - # @http.route(prefix + 'webhook/biteship', auth='public', methods=['POST'], csrf=False) - # def udpate_status_from_bitehsip(self, **kw): - # return "ok" + @http.route(prefix + 'webhook/biteship', type='json', auth='public', methods=['POST'], csrf=False) + def update_status_from_biteship(self, **kw): + _logger.info("Biteship Webhook: Request received at controller start (type='json').") + + try: + # Karena type='json', Odoo secara otomatis akan mem-parsing JSON untuk Anda. + # 'data' akan berisi dictionary Python dari payload JSON Biteship. + data = request.jsonrequest + + # Log ini akan menunjukkan payload yang diterima (sudah dalam bentuk dict) + _logger.info(f"Biteship Webhook: Parsed JSON data from request.jsonrequest: {json.dumps(data)}") + + event = data.get('event') + if event: + _logger.info(f"Biteship Webhook: Processing event: {event}") + if event == "order.status": + self.process_order_status(data) + elif event == "order.price": + self.process_order_price(data) + elif event == "order.waybill_id": + self.process_order_waybill(data) + # Tambahkan logika untuk event lain jika ada + else: + _logger.info("Biteship Webhook: No specific event in payload. Likely an installation/verification ping or unknown event type.") + + # Untuk route type='json', Anda cukup mengembalikan dictionary Python. + # Odoo akan secara otomatis mengonversinya menjadi respons JSON yang valid. + return {'status': 'ok'} + + except Exception as e: + _logger.error(f"Biteship Webhook: Unhandled error during processing: {e}", exc_info=True) + # Untuk error, kembalikan dictionary error juga, Odoo akan mengonversinya ke JSON + return {'status': 'error', 'message': str(e)} def process_order_status(self, data): picking_model = request.env['stock.picking'].sudo().search([('biteship_id', '=', data.get('order_id'))], diff --git a/indoteknik_custom/models/patch/__init__.py b/indoteknik_custom/models/patch/__init__.py new file mode 100644 index 00000000..051b6537 --- /dev/null +++ b/indoteknik_custom/models/patch/__init__.py @@ -0,0 +1 @@ +from . import http_override \ No newline at end of file diff --git a/indoteknik_custom/models/patch/__pycache__/__init__.py b/indoteknik_custom/models/patch/__pycache__/__init__.py deleted file mode 100644 index 051b6537..00000000 --- a/indoteknik_custom/models/patch/__pycache__/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import http_override \ No newline at end of file diff --git a/indoteknik_custom/models/patch/__pycache__/http_override.py b/indoteknik_custom/models/patch/__pycache__/http_override.py deleted file mode 100644 index e1978edb..00000000 --- a/indoteknik_custom/models/patch/__pycache__/http_override.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -import odoo.http -import json -import logging -from werkzeug.exceptions import BadRequest -import functools - -_logger = logging.getLogger(__name__) - -class CustomJsonRequest(odoo.http.JsonRequest): - def __init__(self, httprequest): - super(odoo.http.JsonRequest, self).__init__(httprequest) - - self.params = {} - request_data_raw = self.httprequest.get_data().decode(self.httprequest.charset) - - self.jsonrequest = {} - if request_data_raw.strip(): - try: - self.jsonrequest = json.loads(request_data_raw) - except ValueError: - msg = 'Invalid JSON data: %r' % (request_data_raw,) - _logger.info('%s: %s (Handled by CustomJsonRequest)', self.httprequest.path, msg) - raise BadRequest(msg) - else: - _logger.info("CustomJsonRequest: Received empty or whitespace-only JSON body. Treating as empty JSON for webhook.") - - self.params = dict(self.jsonrequest.get("params", {})) - self.context = self.params.pop('context', dict(self.session.context)) - - -_original_get_request = odoo.http.Root.get_request - -@functools.wraps(_original_get_request) -def _get_request_override(self, httprequest): - _logger.info("--- DEBUG: !!! _get_request_override IS CALLED !!! ---") - _logger.info(f"--- DEBUG: Request Mimetype: {httprequest.mimetype}, Path: {httprequest.path} ---") - - if httprequest.mimetype in ("application/json", "application/json-rpc"): - _logger.debug("Odoo HTTP: Using CustomJsonRequest for mimetype: %s", httprequest.mimetype) - return CustomJsonRequest(httprequest) - else: - _logger.debug("Odoo HTTP: Using original get_request for mimetype: %s", httprequest.mimetype) - return _original_get_request(self, httprequest) - -odoo.http.Root.get_request = _get_request_override \ No newline at end of file diff --git a/indoteknik_custom/models/patch/http_override.py b/indoteknik_custom/models/patch/http_override.py new file mode 100644 index 00000000..6bec1343 --- /dev/null +++ b/indoteknik_custom/models/patch/http_override.py @@ -0,0 +1,45 @@ +import odoo.http +import json +import logging +from werkzeug.exceptions import BadRequest +import functools + +_logger = logging.getLogger(__name__) + +class CustomJsonRequest(odoo.http.JsonRequest): + def __init__(self, httprequest): + super(odoo.http.JsonRequest, self).__init__(httprequest) + + self.params = {} + request_data_raw = self.httprequest.get_data().decode(self.httprequest.charset) + + self.jsonrequest = {} + if request_data_raw.strip(): + try: + self.jsonrequest = json.loads(request_data_raw) + except ValueError: + msg = 'Invalid JSON data: %r' % (request_data_raw,) + _logger.info('%s: %s (Handled by CustomJsonRequest)', self.httprequest.path, msg) + raise BadRequest(msg) + else: + _logger.info("CustomJsonRequest: Received empty or whitespace-only JSON body. Treating as empty JSON for webhook.") + + self.params = dict(self.jsonrequest.get("params", {})) + self.context = self.params.pop('context', dict(self.session.context)) + + +_original_get_request = odoo.http.Root.get_request + +@functools.wraps(_original_get_request) +def _get_request_override(self, httprequest): + _logger.info("--- DEBUG: !!! _get_request_override IS CALLED !!! ---") + _logger.info(f"--- DEBUG: Request Mimetype: {httprequest.mimetype}, Path: {httprequest.path} ---") + + if httprequest.mimetype in ("application/json", "application/json-rpc"): + _logger.debug("Odoo HTTP: Using CustomJsonRequest for mimetype: %s", httprequest.mimetype) + return CustomJsonRequest(httprequest) + else: + _logger.debug("Odoo HTTP: Using original get_request for mimetype: %s", httprequest.mimetype) + return _original_get_request(self, httprequest) + +odoo.http.Root.get_request = _get_request_override \ No newline at end of file -- cgit v1.2.3 From 5b1b45d46e34c6724572b9b3182813e0bfdea0a3 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 5 Jul 2025 13:41:38 +0700 Subject: (andri) off patch karena webhook berhasil didaftarkan --- indoteknik_custom/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index cc406c13..b815b472 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -152,4 +152,4 @@ from . import stock_inventory from . import sale_order_delay from . import approval_invoice_date from . import approval_payment_term -from . import patch +# from . import patch -- cgit v1.2.3