from odoo import models, fields 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): _name = 'coretax.faktur' _description = 'Export Faktur ke XML' export_file = fields.Binary(string="Export File", ) export_filename = fields.Char(string="Export File", ) DISCOUNT_ACCOUNT_ID = 426 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: return '0000000000000000' # Hitung jumlah digit digit_count = len(cleaned_number) # Jika jumlah digit kurang dari 15, tambahkan nol di depan if digit_count < 16: cleaned_number = cleaned_number.zfill(16) return cleaned_number 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", 'xsi:noNamespaceSchemaLocation': "TaxInvoice.xsd" }) ET.SubElement(root, 'TIN').text = '0742260227086000' # Tambahkan elemen ListOfTaxInvoice list_of_tax_invoice = ET.SubElement(root, 'ListOfTaxInvoice') 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' # 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, 'TaxInvoiceOpt').text = 'Normal' ET.SubElement(tax_invoice, 'TrxCode').text = '04' ET.SubElement(tax_invoice, 'AddInfo') ET.SubElement(tax_invoice, 'CustomDoc') ET.SubElement(tax_invoice, 'CustomDocMonthYear') ET.SubElement(tax_invoice, 'RefDesc').text = invoice.name 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, '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, '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 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 ) 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) ) ) # 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: # 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) 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(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' 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' ET.SubElement(good_service, 'STLG').text = '0' # Pretty print XML xml_str = ET.tostring(root, encoding='utf-8') xml_pretty = minidom.parseString(xml_str).toprettyxml(indent=" ") return xml_pretty def export_to_download(self, invoices): # Generate XML content xml_content = self.generate_xml(invoices) # Encode content to Base64 xml_encoded = base64.b64encode(xml_content.encode('utf-8')) # Buat attachment untuk XML attachment = self.env['ir.attachment'].create({ 'name': 'Faktur_XML.xml', 'type': 'binary', 'datas': xml_encoded, 'mimetype': 'application/xml', 'store_fname': 'faktur_xml.xml', }) # Kembalikan URL untuk download dengan header 'Content-Disposition' response = { 'type': 'ir.actions.act_url', 'url': '/web/content/{}?download=true'.format(attachment.id), 'target': 'self', } return response