1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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
|