From 7b4624b07835d84ca48e335ff0e8ba265ee2610e Mon Sep 17 00:00:00 2001 From: "Nicolas Viseur (vin)" Date: Mon, 4 Mar 2024 13:35:09 +0800 Subject: [PATCH] [ADD] l10n_my_ubl_pint: Malaysian Peppol PINT specialization Add support for the Malaysian Peppol PINT specialization, allowing to generate the related XML file. This also adds a few additional features that are in the specifications: - SST registration number, needed in the xml and on the invoice. - TTx registration number, needed on the invoice. Both numbers are optional, but if the business is registered in any of them they are required to display these on the invoices and they are required to enforce some rules. (cannot send taxes that are not 0% "Out of scope" if not registered with SST, ..) Task id # 3647922 closes odoo/odoo#159407 Signed-off-by: Julien Van Roy (juvr) --- addons/l10n_my_ubl_pint/__init__.py | 2 + addons/l10n_my_ubl_pint/__manifest__.py | 19 ++++ .../i18n/l10n_my_ubl_pint.pot | 100 ++++++++++++++++ addons/l10n_my_ubl_pint/i18n/ms_MY.po | 103 +++++++++++++++++ addons/l10n_my_ubl_pint/models/__init__.py | 4 + .../models/account_edi_xml_pint_my.py | 107 ++++++++++++++++++ addons/l10n_my_ubl_pint/models/res_company.py | 17 +++ addons/l10n_my_ubl_pint/models/res_partner.py | 23 ++++ .../l10n_my_ubl_pint/views/report_invoice.xml | 68 +++++++++++ .../views/res_company_view.xml | 14 +++ .../views/res_partner_view.xml | 14 +++ 11 files changed, 471 insertions(+) create mode 100644 addons/l10n_my_ubl_pint/__init__.py create mode 100644 addons/l10n_my_ubl_pint/__manifest__.py create mode 100644 addons/l10n_my_ubl_pint/i18n/l10n_my_ubl_pint.pot create mode 100644 addons/l10n_my_ubl_pint/i18n/ms_MY.po create mode 100644 addons/l10n_my_ubl_pint/models/__init__.py create mode 100644 addons/l10n_my_ubl_pint/models/account_edi_xml_pint_my.py create mode 100644 addons/l10n_my_ubl_pint/models/res_company.py create mode 100644 addons/l10n_my_ubl_pint/models/res_partner.py create mode 100644 addons/l10n_my_ubl_pint/views/report_invoice.xml create mode 100644 addons/l10n_my_ubl_pint/views/res_company_view.xml create mode 100644 addons/l10n_my_ubl_pint/views/res_partner_view.xml diff --git a/addons/l10n_my_ubl_pint/__init__.py b/addons/l10n_my_ubl_pint/__init__.py new file mode 100644 index 00000000000..d581b39cea7 --- /dev/null +++ b/addons/l10n_my_ubl_pint/__init__.py @@ -0,0 +1,2 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import models diff --git a/addons/l10n_my_ubl_pint/__manifest__.py b/addons/l10n_my_ubl_pint/__manifest__.py new file mode 100644 index 00000000000..ddf859b5477 --- /dev/null +++ b/addons/l10n_my_ubl_pint/__manifest__.py @@ -0,0 +1,19 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': 'Malaysia - UBL PINT', + 'countries': ['my'], + 'version': '1.0', + 'category': 'Accounting/Localizations/EDI', + 'icon': '/account/static/description/l10n.png', + 'description': """ + The UBL PINT e-invoicing format for Malaysia is based on the Peppol International (PINT) model for Billing. + """, + 'depends': ['account_edi_ubl_cii'], + 'data': [ + 'views/report_invoice.xml', + 'views/res_company_view.xml', + 'views/res_partner_view.xml', + ], + 'installable': True, + 'license': 'LGPL-3' +} diff --git a/addons/l10n_my_ubl_pint/i18n/l10n_my_ubl_pint.pot b/addons/l10n_my_ubl_pint/i18n/l10n_my_ubl_pint.pot new file mode 100644 index 00000000000..285394a7443 --- /dev/null +++ b/addons/l10n_my_ubl_pint/i18n/l10n_my_ubl_pint.pot @@ -0,0 +1,100 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_my_ubl_pint +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-09 02:43+0000\n" +"PO-Revision-Date: 2024-04-09 02:43+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_my_ubl_pint +#: model:ir.model,name:l10n_my_ubl_pint.model_res_company +msgid "Companies" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model,name:l10n_my_ubl_pint.model_base_document_layout +msgid "Company Document Layout" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model,name:l10n_my_ubl_pint.model_res_partner +msgid "Contact" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_base_document_layout__account_fiscal_country_id +msgid "Fiscal Country" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_partner__ubl_cii_format +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_users__ubl_cii_format +msgid "Format" +msgstr "" + +#. module: l10n_my_ubl_pint +#. odoo-python +#: code:addons/l10n_my_ubl_pint/models/account_edi_xml_pint_my.py:0 +#, python-format +msgid "" +"If your business is registered for SST, please provide your registration number in your company details.\n" +"Otherwise, you are not allowed to charge sales or services taxes in the e-Invoice." +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model,name:l10n_my_ubl_pint.model_account_edi_xml_pint_my +msgid "" +"Malaysian implementation of Peppol International (PINT) model for Billing" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields.selection,name:l10n_my_ubl_pint.selection__res_partner__ubl_cii_format__pint_my +msgid "PINT Malaysia" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_base_document_layout__sst_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_company__sst_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_partner__sst_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_users__sst_registration_number +msgid "SST" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_report_invoice_document +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_bold +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_boxed +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_standard +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_striped +msgid "SST:" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_base_document_layout__ttx_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_company__ttx_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_partner__ttx_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_users__ttx_registration_number +msgid "TTx" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_bold +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_boxed +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_standard +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_striped +msgid "TTx:" +msgstr "" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,help:l10n_my_ubl_pint.field_base_document_layout__account_fiscal_country_id +msgid "The country to use the tax reports from for this company" +msgstr "" diff --git a/addons/l10n_my_ubl_pint/i18n/ms_MY.po b/addons/l10n_my_ubl_pint/i18n/ms_MY.po new file mode 100644 index 00000000000..eaf66d44d66 --- /dev/null +++ b/addons/l10n_my_ubl_pint/i18n/ms_MY.po @@ -0,0 +1,103 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_my_ubl_pint +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-09 02:44+0000\n" +"PO-Revision-Date: 2024-04-09 02:44+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_my_ubl_pint +#: model:ir.model,name:l10n_my_ubl_pint.model_res_company +msgid "Companies" +msgstr "Syarikat" + +#. module: l10n_my_ubl_pint +#: model:ir.model,name:l10n_my_ubl_pint.model_base_document_layout +msgid "Company Document Layout" +msgstr "Susun Atur Dokumen Syarikat" + +#. module: l10n_my_ubl_pint +#: model:ir.model,name:l10n_my_ubl_pint.model_res_partner +msgid "Contact" +msgstr "Kontak" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_base_document_layout__account_fiscal_country_id +msgid "Fiscal Country" +msgstr "Negara Fiskal" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_partner__ubl_cii_format +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_users__ubl_cii_format +msgid "Format" +msgstr "Format" + +#. module: l10n_my_ubl_pint +#. odoo-python +#: code:addons/l10n_my_ubl_pint/models/account_edi_xml_pint_my.py:0 +#, python-format +msgid "" +"If your business is registered for SST, please provide your registration number in your company details.\n" +"Otherwise, you are not allowed to charge sales or services taxes in the e-Invoice." +msgstr "" +"Jika perniagaan anda didaftarkan untuk SST, sila berikan nombor pendaftaran anda dalam butiran syarikat anda.\n" +"Jika tidak, anda tidak dibenarkan mengenakan cukai jualan atau perkhidmatan dalam e-Invois." + +#. module: l10n_my_ubl_pint +#: model:ir.model,name:l10n_my_ubl_pint.model_account_edi_xml_pint_my +msgid "" +"Malaysian implementation of Peppol International (PINT) model for Billing" +msgstr "" +"Pelaksanaan model Peppol International (PINT) Malaysia untuk Pengebilan" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields.selection,name:l10n_my_ubl_pint.selection__res_partner__ubl_cii_format__pint_my +msgid "PINT Malaysia" +msgstr "PINT Malaysia" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_base_document_layout__sst_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_company__sst_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_partner__sst_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_users__sst_registration_number +msgid "SST" +msgstr "SST" + +#. module: l10n_my_ubl_pint +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_report_invoice_document +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_bold +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_boxed +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_standard +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_striped +msgid "SST:" +msgstr "SST:" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_base_document_layout__ttx_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_company__ttx_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_partner__ttx_registration_number +#: model:ir.model.fields,field_description:l10n_my_ubl_pint.field_res_users__ttx_registration_number +msgid "TTx" +msgstr "TTx" + +#. module: l10n_my_ubl_pint +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_bold +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_boxed +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_standard +#: model_terms:ir.ui.view,arch_db:l10n_my_ubl_pint.l10n_my_ubl_pint_external_layout_striped +msgid "TTx:" +msgstr "TTx:" + +#. module: l10n_my_ubl_pint +#: model:ir.model.fields,help:l10n_my_ubl_pint.field_base_document_layout__account_fiscal_country_id +msgid "The country to use the tax reports from for this company" +msgstr "Negara untuk menggunakan laporan cukai daripada syarikat ini" diff --git a/addons/l10n_my_ubl_pint/models/__init__.py b/addons/l10n_my_ubl_pint/models/__init__.py new file mode 100644 index 00000000000..e5f8909d233 --- /dev/null +++ b/addons/l10n_my_ubl_pint/models/__init__.py @@ -0,0 +1,4 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import account_edi_xml_pint_my +from . import res_company +from . import res_partner diff --git a/addons/l10n_my_ubl_pint/models/account_edi_xml_pint_my.py b/addons/l10n_my_ubl_pint/models/account_edi_xml_pint_my.py new file mode 100644 index 00000000000..ee7f5fae9cf --- /dev/null +++ b/addons/l10n_my_ubl_pint/models/account_edi_xml_pint_my.py @@ -0,0 +1,107 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, _ + + +class AccountEdiXmlUBLPINTMY(models.AbstractModel): + _inherit = "account.edi.xml.ubl_bis3" + _name = 'account.edi.xml.pint_my' + _description = "Malaysian implementation of Peppol International (PINT) model for Billing" + """ + * PINT Official documentation: https://docs.peppol.eu/poac/pint/pint/ + * PINT MY Official documentation: https://docs.peppol.eu/poac/my/pint-my + """ + + def _export_invoice_filename(self, invoice): + # EXTENDS account_edi_ubl_cii + return f"{invoice.name.replace('/', '_')}_pint_my.xml" + + def _export_invoice_vals(self, invoice): + # EXTENDS account_edi_ubl_cii + vals = super()._export_invoice_vals(invoice) + vals['vals'].update({ + # see https://docs.peppol.eu/poac/my/pint-my/bis/#profiles + 'customization_id': 'urn:peppol:pint:billing-1@my-1', + 'profile_id': 'urn:peppol:bis:billing', + }) + if invoice.currency_id != invoice.company_id.currency_id: + # see https://docs.peppol.eu/poac/my/pint-my/bis/#_tax_in_accounting_currency + vals['vals']['tax_currency_code'] = invoice.company_id.currency_id.name # accounting currency + return vals + + def _get_invoice_tax_totals_vals_list(self, invoice, taxes_vals): + # EXTENDS account_edi_ubl_cii + vals_list = super()._get_invoice_tax_totals_vals_list(invoice, taxes_vals) + company_currency = invoice.company_id.currency_id + if invoice.currency_id != company_currency: + # see https://docs.peppol.eu/poac/my/pint-my/bis/#_tax_in_accounting_currency + vals_list.append({ + 'currency': company_currency, + 'currency_dp': company_currency.decimal_places, + 'tax_amount': taxes_vals['tax_amount'], + 'tax_subtotal_vals': [], + }) + return vals_list + + def _get_partner_party_tax_scheme_vals_list(self, partner, role): + """ [aligned-ibrp-cl-01-my]-Malaysian invoice tax categories MUST be coded using Malaysian codes. """ + # EXTENDS account_edi_ubl_cii + tax_scheme_vals_list = super()._get_partner_party_tax_scheme_vals_list(partner, role) + # See https://docs.peppol.eu/poac/my/pint-my/bis/#_seller_tax_identifier + tax_scheme_vals_list[0]['company_id'] = partner.sst_registration_number or 'NA' + if role == 'supplier': + # TIN + gst_tax_scheme = tax_scheme_vals_list[0].copy() + gst_tax_scheme.update({ + 'company_id': partner.vat, + 'tax_scheme_vals': {'id': 'GST'}, + }) + tax_scheme_vals_list.append(gst_tax_scheme) + + return tax_scheme_vals_list + + def _get_tax_unece_codes(self, invoice, tax): + """ + In malaysia, only the following codes can be used: T, E, O + https://docs.peppol.eu/poac/my/pint-my/bis/#_tax_category_code + """ + # OVERRIDE account_edi_ubl_cii + codes = { + 'tax_category_code': False, + 'tax_exemption_reason_code': False, + 'tax_exemption_reason': False, + } + # If a business is not registered for SST and/or TTx, the business is not allowed to charge sales tax, + # service tax or tourism tax in the e-Invoice. + # In this case, the tax category code should be 'O' (Outside scope of tax). + # For now, we do not properly support Tourism tax (TTx) due to a lack of clarity on the subject. + supplier = invoice.company_id.partner_id.commercial_partner_id + if not supplier.sst_registration_number: + codes['tax_category_code'] = 'O' + elif tax.amount != 0: + codes['tax_category_code'] = 'T' + else: + codes['tax_category_code'] = 'E' + return codes + + def _export_invoice_constraints(self, invoice, vals): + # EXTENDS account_edi_ubl_cii + constraints = super()._export_invoice_constraints(invoice, vals) + + # A tax category "Outside of tax cope" can only have an amount of 0. + for tax_total_val in vals['vals']['tax_total_vals']: + for tax_subtotal_val in tax_total_val['tax_subtotal_vals']: + tax_category_vals = tax_subtotal_val['tax_category_vals'] + if tax_category_vals['tax_category_code'] == 'O' and tax_category_vals['percent'] != 0: + constraints['peppol_my_sst_registration'] = _( + "If your business is registered for SST, please provide your registration number in your company details.\n" + "Otherwise, you are not allowed to charge sales or services taxes in the e-Invoice." + ) + break + + # In malaysia, tax on good is paid at the manufacturer level. It is thus common to invoice without taxes, + # unless invoicing for a service. + constraints.pop('tax_on_line', '') + constraints.pop('cen_en16931_tax_line', '') + + return constraints diff --git a/addons/l10n_my_ubl_pint/models/res_company.py b/addons/l10n_my_ubl_pint/models/res_company.py new file mode 100644 index 00000000000..22cfa53db5d --- /dev/null +++ b/addons/l10n_my_ubl_pint/models/res_company.py @@ -0,0 +1,17 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import models, fields + + +class ResCompany(models.Model): + _inherit = 'res.company' + + sst_registration_number = fields.Char(related='partner_id.sst_registration_number', readonly=False) + ttx_registration_number = fields.Char(related='partner_id.ttx_registration_number', readonly=False) + + +class BaseDocumentLayout(models.TransientModel): + _inherit = 'base.document.layout' + + account_fiscal_country_id = fields.Many2one(related="company_id.account_fiscal_country_id") + sst_registration_number = fields.Char(related='company_id.sst_registration_number') + ttx_registration_number = fields.Char(related='company_id.ttx_registration_number') diff --git a/addons/l10n_my_ubl_pint/models/res_partner.py b/addons/l10n_my_ubl_pint/models/res_partner.py new file mode 100644 index 00000000000..e764a536fca --- /dev/null +++ b/addons/l10n_my_ubl_pint/models/res_partner.py @@ -0,0 +1,23 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import models, fields + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + ubl_cii_format = fields.Selection(selection_add=[('pint_my', "PINT Malaysia")]) + sst_registration_number = fields.Char(string="SST") + ttx_registration_number = fields.Char(string="TTx") + + def _get_edi_builder(self): + # EXTENDS 'account_edi_ubl_cii' + if self.ubl_cii_format == 'pint_my': + return self.env['account.edi.xml.pint_my'] + return super()._get_edi_builder() + + def _compute_ubl_cii_format(self): + # EXTENDS 'account_edi_ubl_cii' + super()._compute_ubl_cii_format() + for partner in self: + if partner.country_code == 'MY': + partner.ubl_cii_format = 'pint_my' diff --git a/addons/l10n_my_ubl_pint/views/report_invoice.xml b/addons/l10n_my_ubl_pint/views/report_invoice.xml new file mode 100644 index 00000000000..ae94060aba1 --- /dev/null +++ b/addons/l10n_my_ubl_pint/views/report_invoice.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + diff --git a/addons/l10n_my_ubl_pint/views/res_company_view.xml b/addons/l10n_my_ubl_pint/views/res_company_view.xml new file mode 100644 index 00000000000..690e7f23928 --- /dev/null +++ b/addons/l10n_my_ubl_pint/views/res_company_view.xml @@ -0,0 +1,14 @@ + + + + res.company.form.inherit.l10n_my_ubl_pint + res.company + + + + + + + + + diff --git a/addons/l10n_my_ubl_pint/views/res_partner_view.xml b/addons/l10n_my_ubl_pint/views/res_partner_view.xml new file mode 100644 index 00000000000..d5633ec3018 --- /dev/null +++ b/addons/l10n_my_ubl_pint/views/res_partner_view.xml @@ -0,0 +1,14 @@ + + + + res.partner.form.inherit.l10n_my_ubl_pint + res.partner + + + + + + + + +