diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2025-09-17 18:05:45 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2025-09-17 18:05:45 +0700 |
| commit | ade0fe34d2d77fa9ee8e275cb60301b325710bd1 (patch) | |
| tree | eeeb88d8c7593a410244cfea20c379e39f2713aa | |
| parent | 776d2eefc8d43462eee3992992088e066fecfe21 (diff) | |
coretax
| -rwxr-xr-x | fixco_custom/models/__init__.py | 3 | ||||
| -rw-r--r-- | fixco_custom/models/account_move.py | 16 | ||||
| -rw-r--r-- | fixco_custom/models/coretax_faktur.py | 176 | ||||
| -rwxr-xr-x | fixco_custom/models/partner.py | 5 | ||||
| -rwxr-xr-x | fixco_custom/security/ir.model.access.csv | 1 | ||||
| -rw-r--r-- | fixco_custom/views/account_move.xml | 8 |
6 files changed, 207 insertions, 2 deletions
diff --git a/fixco_custom/models/__init__.py b/fixco_custom/models/__init__.py index 23f28a9..27ca4e1 100755 --- a/fixco_custom/models/__init__.py +++ b/fixco_custom/models/__init__.py @@ -29,4 +29,5 @@ from . import product_public_category from . import reordering_rule from . import update_depreciation_move_wizard from . import invoice_reklas -from . import uangmuka_pembelian
\ No newline at end of file +from . import uangmuka_pembelian +from . import coretax_faktur
\ No newline at end of file diff --git a/fixco_custom/models/account_move.py b/fixco_custom/models/account_move.py index 836b878..bb84573 100644 --- a/fixco_custom/models/account_move.py +++ b/fixco_custom/models/account_move.py @@ -42,6 +42,22 @@ class AccountMove(models.Model): reklas_used = fields.Boolean('Reklas Used?', compute='_compute_reklas_used') reklas_used_by = fields.Many2one('account.move', string='Reklas Used By', compute='_compute_reklas_used') + def export_faktur_to_xml(self): + valid_invoices = self + + coretax_faktur = self.env['coretax.faktur'].create({}) + + response = coretax_faktur.export_to_download( + invoices=valid_invoices + ) + + valid_invoices.write({ + 'is_efaktur_exported': True, + 'date_efaktur_exported': datetime.utcnow(), + }) + + return response + @api.depends('line_ids.reconciled', 'line_ids.matching_number') def _compute_reklas_used(self): for move in self: diff --git a/fixco_custom/models/coretax_faktur.py b/fixco_custom/models/coretax_faktur.py new file mode 100644 index 0000000..7790904 --- /dev/null +++ b/fixco_custom/models/coretax_faktur.py @@ -0,0 +1,176 @@ +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 diff --git a/fixco_custom/models/partner.py b/fixco_custom/models/partner.py index bd17ded..c8e95b0 100755 --- a/fixco_custom/models/partner.py +++ b/fixco_custom/models/partner.py @@ -15,4 +15,7 @@ class Partner(models.Model): [('online', 'Online'), ('offline', 'Offline')], string='Customer Type' - )
\ No newline at end of file + ) + sppkp = fields.Char(string="SPPKP", tracking=True) + nitku = fields.Char(string="NITKU", tracking=True) + npwp = fields.Char(string="NPWP", tracking=True)
\ No newline at end of file diff --git a/fixco_custom/security/ir.model.access.csv b/fixco_custom/security/ir.model.access.csv index d1cd618..8103ca2 100755 --- a/fixco_custom/security/ir.model.access.csv +++ b/fixco_custom/security/ir.model.access.csv @@ -36,4 +36,5 @@ access_purchasing_job_note_wizard,access.purchasing.job.note.wizard,model_purcha access_update_depreciation_move_wizard,access.update.depreciation.move.wizard,model_update_depreciation_move_wizard,,1,1,1,1 access_invoice_reklas,access.invoice.reklas,model_invoice_reklas,,1,1,1,1 access_uangmuka_pembelian,access.uangmuka.pembelian,model_uangmuka_pembelian,,1,1,1,1 +access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1 access_report.fixco_custom.report_picking_list_custom,access.report.fixco_custom.report_picking_list_custom,model_report_fixco_custom_report_picking_list_custom,,1,1,1,1 diff --git a/fixco_custom/views/account_move.xml b/fixco_custom/views/account_move.xml index 88d3b46..3b122dd 100644 --- a/fixco_custom/views/account_move.xml +++ b/fixco_custom/views/account_move.xml @@ -115,5 +115,13 @@ </field> </field> </record> + + <record id="action_export_faktur" model="ir.actions.server"> + <field name="name">Export Faktur ke XML</field> + <field name="model_id" ref="account.model_account_move" /> + <field name="binding_model_id" ref="account.model_account_move" /> + <field name="state">code</field> + <field name="code">action = records.export_faktur_to_xml()</field> + </record> </data> </odoo>
\ No newline at end of file |
