diff --git a/addons/l10n_sa_invoice/__init__.py b/addons/l10n_sa_invoice/__init__.py new file mode 100644 index 00000000000..8134f974fbf --- /dev/null +++ b/addons/l10n_sa_invoice/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/l10n_sa_invoice/__manifest__.py b/addons/l10n_sa_invoice/__manifest__.py new file mode 100644 index 00000000000..399af3cb6a6 --- /dev/null +++ b/addons/l10n_sa_invoice/__manifest__.py @@ -0,0 +1,17 @@ +# -*- encoding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': 'K.S.A - Invoice', + 'version': '1.0.0', + 'author': 'Odoo S.A.', + 'category': 'Accounting/Localizations', + 'license': 'LGPL-3', + 'description': """ + Invoices for the Kingdom of Saudi Arabia +""", + 'depends': ['l10n_sa', 'l10n_gcc_invoice'], + 'data': [ + 'views/view_move_form.xml', + 'views/report_invoice.xml', + ], +} diff --git a/addons/l10n_sa_invoice/models/__init__.py b/addons/l10n_sa_invoice/models/__init__.py new file mode 100644 index 00000000000..6825e16989f --- /dev/null +++ b/addons/l10n_sa_invoice/models/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import account_move diff --git a/addons/l10n_sa_invoice/models/account_move.py b/addons/l10n_sa_invoice/models/account_move.py new file mode 100644 index 00000000000..ebf0acab229 --- /dev/null +++ b/addons/l10n_sa_invoice/models/account_move.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import base64 + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = 'account.move' + + l10n_sa_delivery_date = fields.Date(string='Delivery Date', default=fields.Date.context_today, copy=False) + l10n_sa_show_delivery_date = fields.Boolean(compute='_compute_show_delivery_date') + l10n_sa_qr_code_str = fields.Char(string='Zatka QR Code', compute='_compute_qr_code_str') + l10n_sa_confirmation_datetime = fields.Datetime(string='Confirmation Date', readonly=True, copy=False) + + @api.depends('country_code', 'move_type') + def _compute_show_delivery_date(self): + for move in self: + move.l10n_sa_show_delivery_date = move.country_code == 'SA' and move.move_type in ('out_invoice', 'out_refund') + + @api.depends('amount_total', 'amount_untaxed', 'l10n_sa_confirmation_datetime', 'company_id', 'company_id.vat') + def _compute_qr_code_str(self): + """ Generate the qr code for Saudi e-invoicing. Specs are available at the following link at page 23 + https://zatca.gov.sa/ar/E-Invoicing/SystemsDevelopers/Documents/20210528_ZATCA_Electronic_Invoice_Security_Features_Implementation_Standards_vShared.pdf + """ + def get_qr_encoding(tag, field): + company_name_byte_array = field.encode('UTF-8') + company_name_tag_encoding = tag.to_bytes(length=1, byteorder='big') + company_name_length_encoding = len(company_name_byte_array).to_bytes(length=1, byteorder='big') + return company_name_tag_encoding + company_name_length_encoding + company_name_byte_array + + for record in self: + qr_code_str = '' + if record.l10n_sa_confirmation_datetime and record.company_id.vat: + seller_name_enc = get_qr_encoding(1, record.company_id.display_name) + company_vat_enc = get_qr_encoding(2, record.company_id.vat) + time_sa = fields.Datetime.context_timestamp(self.with_context(tz='Asia/Riyadh'), record.l10n_sa_confirmation_datetime) + timestamp_enc = get_qr_encoding(3, time_sa.isoformat()) + invoice_total_enc = get_qr_encoding(4, str(record.amount_total)) + total_vat_enc = get_qr_encoding(5, str(record.currency_id.round(record.amount_total - record.amount_untaxed))) + + str_to_encode = seller_name_enc + company_vat_enc + timestamp_enc + invoice_total_enc + total_vat_enc + qr_code_str = base64.b64encode(str_to_encode).decode('UTF-8') + record.l10n_sa_qr_code_str = qr_code_str + + def _post(self, soft=True): + res = super()._post(soft) + for record in self: + if record.country_code == 'SA' and record.move_type in ('out_invoice', 'out_refund'): + if not record.l10n_sa_show_delivery_date: + raise UserError(_('Delivery Date cannot be empty')) + if record.l10n_sa_delivery_date < record.invoice_date: + raise UserError(_('Delivery Date cannot be before Invoice Date')) + self.write({ + 'l10n_sa_confirmation_datetime': fields.Datetime.now() + }) + return res diff --git a/addons/l10n_sa_invoice/views/report_invoice.xml b/addons/l10n_sa_invoice/views/report_invoice.xml new file mode 100644 index 00000000000..9e87eec6393 --- /dev/null +++ b/addons/l10n_sa_invoice/views/report_invoice.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/addons/l10n_sa_invoice/views/view_move_form.xml b/addons/l10n_sa_invoice/views/view_move_form.xml new file mode 100644 index 00000000000..ced3c984c9b --- /dev/null +++ b/addons/l10n_sa_invoice/views/view_move_form.xml @@ -0,0 +1,15 @@ + + + + account.move.deliver_date + account.move + + + + + + + + + + \ No newline at end of file