From d75cb8d198eeca1296aa467b0d8e3fd9db9c571f Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 15 May 2025 15:09:09 +0700 Subject: Add Sales Discount in XML Faktur Pajak --- indoteknik_custom/models/coretax_fatur.py | 82 +++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index b4bffbd2..ffc2813f 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -4,19 +4,20 @@ from xml.dom import minidom import base64 import re + class CoretaxFaktur(models.Model): _name = 'coretax.faktur' _description = 'Export Faktur ke XML' - - export_file = fields.Binary(string="Export File", ) - export_filename = fields.Char(string="Export File", ) - - def validate_and_format_number(slef, input_number): + + export_file = fields.Binary(string="Export File", ) + export_filename = fields.Char(string="Export File", ) + + def validate_and_format_number(self, input_number): # Hapus semua karakter non-digit cleaned_number = re.sub(r'\D', '', input_number) - + total_sum = sum(int(char) for char in cleaned_number) - if total_sum == 0 : + if total_sum == 0: return '0000000000000000' # Hitung jumlah digit @@ -39,22 +40,16 @@ class CoretaxFaktur(models.Model): # Tambahkan elemen ListOfTaxInvoice list_of_tax_invoice = ET.SubElement(root, 'ListOfTaxInvoice') - # Dapatkan data faktur - # inv_obj = self.env['account.move'] - # invoices = inv_obj.search([('is_efaktur_exported','=',True), - # ('state','=','posted'), - # ('efaktur_id','!=', False), - # ('move_type','=','out_invoice')], limit = 5) - for invoice in invoices: tax_invoice = ET.SubElement(list_of_tax_invoice, 'TaxInvoice') buyerTIN = self.validate_and_format_number(invoice.partner_id.npwp) nitku = invoice.partner_id.nitku formula = nitku if nitku else buyerTIN.ljust(len(buyerTIN) + 6, '0') - buyerIDTKU = formula if sum(int(char) for char in buyerTIN) > 0 else '000000' + 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') @@ -64,30 +59,67 @@ 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 + # Find all product lines (exclude discounts and other adjustments) + product_lines = invoice.invoice_line_ids.filtered( + lambda l: not l.display_type and l.product_id and 'Diskon' not in l.account_id.name + ) + + # Find all discount lines + discount_lines = invoice.invoice_line_ids.filtered( + lambda l: not l.display_type and ('Diskon' in l.account_id.name or l.name and 'Diskon' in l.name) + ) + + # Calculate total product amount (before discount) + total_product_amount = sum(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 invoice.invoice_line_ids: - otherTaxBase = round(line.price_subtotal * (11/12)) if line.price_subtotal else 0 + + for line in product_lines: + # Calculate prorated discount + 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 + total_discount = round(line_discount, 2) + + # Calculate tax base after discount + price_after_discount = line.price_subtotal - line_discount + + # Calculate other tax values + otherTaxBase = round(price_after_discount * (11 / 12), 2) if price_after_discount 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_subtotal/line.quantity, 2)) if line.price_subtotal 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(round(line.price_subtotal)) if line.price_subtotal else '0' + ET.SubElement(good_service, 'Price').text = str(round(subtotal / quantity, 2)) if subtotal 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(price_after_discount)) ET.SubElement(good_service, 'OtherTaxBase').text = str(otherTaxBase) ET.SubElement(good_service, 'VATRate').text = '12' - ET.SubElement(good_service, 'VAT').text = str(round(otherTaxBase * 0.12, 2)) + ET.SubElement(good_service, 'VAT').text = str(vat_amount) ET.SubElement(good_service, 'STLGRate').text = '0' ET.SubElement(good_service, 'STLG').text = '0' -- cgit v1.2.3 From 3ecfd3cbf9e3257644c388801f18870960ef3ac0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 15 May 2025 21:58:34 +0700 Subject: prevent export error when state is in draft and add filter based on account_id. --- indoteknik_custom/models/coretax_fatur.py | 47 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index ffc2813f..c0558e5b 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -12,11 +12,20 @@ class CoretaxFaktur(models.Model): export_file = fields.Binary(string="Export File", ) export_filename = fields.Char(string="Export File", ) - def validate_and_format_number(self, input_number): + def validate_and_format_number(slef, input_number): + # mencegah error ketika mau cetak xml ketika masih di draft + if input_number is None: + return '0000000000000000' + + # ubah ke str kalau blm + if not isinstance(input_number, str): + input_number = str(input_number) + # Hapus semua karakter non-digit cleaned_number = re.sub(r'\D', '', input_number) total_sum = sum(int(char) for char in cleaned_number) + if total_sum == 0: return '0000000000000000' @@ -69,18 +78,36 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # Find all product lines (exclude discounts and other adjustments) + # initiate diskon id + # ACCOUNT_DISCOUNT_IDS = [463, 464, 467] + # product_lines = invoice.invoice_line_ids.filtered( + # lambda l: not l.display_type and l.product_id and + # hasattr(l, 'account_id') and l.account_id and + # l.account_id.id not in self.ACCOUNT_DISCOUNT_IDS and + # l.quantity != -1 + # ) + # 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 in self.ACCOUNT_DISCOUNT_IDS) or + # (l.quantity == -1) + # ) + # ) + # discount_id = self.env['account.account'].search([('name', '=', 'Diskon')]) + + # cari product dari inovoice line product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and l.product_id and 'Diskon' not in l.account_id.name + lambda l: not l.display_type and l.product_id and 'Diskon' not in l.account_id.id ) - # Find all discount lines + # cari diskon/potongan discount_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and ('Diskon' in l.account_id.name or l.name and 'Diskon' in l.name) - ) + lambda l: not l.display_type and ('Diskon' in l.account_id.id or l.name and 'Diskon' in l.name) + ) # ini ke account.id # Calculate total product amount (before discount) total_product_amount = sum(line.price_subtotal for line in product_lines) + if total_product_amount == 0: total_product_amount = 1 # Avoid division by zero @@ -100,11 +127,8 @@ class CoretaxFaktur(models.Model): quantity = line.quantity total_discount = round(line_discount, 2) - # Calculate tax base after discount - price_after_discount = line.price_subtotal - line_discount - # Calculate other tax values - otherTaxBase = round(price_after_discount * (11 / 12), 2) if price_after_discount else 0 + otherTaxBase = round(subtotal * (11 / 12), 2) if subtotal else 0 vat_amount = round(otherTaxBase * 0.12, 2) # Create the line in XML @@ -116,7 +140,8 @@ class CoretaxFaktur(models.Model): ET.SubElement(good_service, 'Price').text = str(round(subtotal / quantity, 2)) if subtotal 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(price_after_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) -- cgit v1.2.3 From a6a26665953d36578c62b6f4d5608b716d9fac88 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 15 May 2025 23:42:20 +0700 Subject: fix not iterable account_id --- indoteknik_custom/models/coretax_fatur.py | 40 ++++++++----------------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index c0558e5b..f2a1f793 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -13,14 +13,6 @@ class CoretaxFaktur(models.Model): export_filename = fields.Char(string="Export File", ) def validate_and_format_number(slef, input_number): - # mencegah error ketika mau cetak xml ketika masih di draft - if input_number is None: - return '0000000000000000' - - # ubah ke str kalau blm - if not isinstance(input_number, str): - input_number = str(input_number) - # Hapus semua karakter non-digit cleaned_number = re.sub(r'\D', '', input_number) @@ -78,36 +70,24 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # initiate diskon id - # ACCOUNT_DISCOUNT_IDS = [463, 464, 467] - # product_lines = invoice.invoice_line_ids.filtered( - # lambda l: not l.display_type and l.product_id and - # hasattr(l, 'account_id') and l.account_id and - # l.account_id.id not in self.ACCOUNT_DISCOUNT_IDS and - # l.quantity != -1 - # ) - # 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 in self.ACCOUNT_DISCOUNT_IDS) or - # (l.quantity == -1) - # ) - # ) - # discount_id = self.env['account.account'].search([('name', '=', 'Diskon')]) - - # cari product dari inovoice line + # cari product product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and l.product_id and 'Diskon' not in l.account_id.id + lambda l: not l.display_type and l.product_id and + hasattr(l, 'account_id') and l.account_id and + 'Diskon' not in (l.account_id.name or '') ) # cari diskon/potongan discount_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and ('Diskon' in l.account_id.id or l.name and 'Diskon' in l.name) - ) # ini ke account.id + lambda l: not l.display_type and ( + (hasattr(l, 'account_id') and l.account_id and 'Diskon' in (l.account_id.name or '')) or + (l.name and isinstance(l.name, str) and 'Diskon' in l.name) or + (l.quantity == -1) + ) + ) # Calculate total product amount (before discount) total_product_amount = sum(line.price_subtotal for line in product_lines) - if total_product_amount == 0: total_product_amount = 1 # Avoid division by zero -- cgit v1.2.3 From f8811bdf897fe0176921cc01b0ee8c9b98c883d3 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 16 May 2025 09:35:23 +0700 Subject: filter using account_id --- indoteknik_custom/models/coretax_fatur.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index f2a1f793..92ff1a72 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -12,6 +12,8 @@ class CoretaxFaktur(models.Model): export_file = fields.Binary(string="Export File", ) export_filename = fields.Char(string="Export File", ) + DISCOUNT_ACCOUNT_ID = 463 + def validate_and_format_number(slef, input_number): # Hapus semua karakter non-digit cleaned_number = re.sub(r'\D', '', input_number) @@ -70,18 +72,19 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # cari product + # Filter product product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and l.product_id and - hasattr(l, 'account_id') and l.account_id and - 'Diskon' not in (l.account_id.name or '') + 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 ) - # cari diskon/potongan + # Filter discount discount_lines = invoice.invoice_line_ids.filtered( lambda l: not l.display_type and ( - (hasattr(l, 'account_id') and l.account_id and 'Diskon' in (l.account_id.name or '')) or - (l.name and isinstance(l.name, str) and 'Diskon' in l.name) or + (hasattr(l, 'account_id') and l.account_id and + l.account_id.id == self.DISCOUNT_ACCOUNT_ID) or (l.quantity == -1) ) ) -- cgit v1.2.3