summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models/coretax_fatur.py
blob: cabcd5d6d6223eb752e2ac7186fbd8519e28a63a (plain)
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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 = 463

    def validate_and_format_number(slef, 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

            _logger.info(" invoice down_payments: %s", invoice.down_payment)
            # Handle product lines based on down_payments flag
            if invoice.down_payment 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)
                )
            )

            # 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)
                coretax_id = line.product_uom_id.coretax_id
                # 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 = coretax_id
                # 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, down_payments):
        # Generate XML content
        xml_content = self.generate_xml(invoices, down_payments)

        # 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