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 @@
+
+
+
+
+
+
+
+ Delivery Date:
+
+
+
+
+
+
+ :
+ تاريخ التوصيل
+
+
+
+
+
+
+
+
+
+
+
+
+
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