Initial commit: L10N_Me Africa packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:53 +02:00
commit c265268138
611 changed files with 75334 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_chart_template
from . import account_move

View file

@ -0,0 +1,44 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class AccountChartTemplate(models.Model):
_inherit = 'account.chart.template'
def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
""" If Saudi Arabia chart, we add 3 new journals Tax Adjustments, IFRS 16 and Zakat"""
if self == self.env.ref('l10n_sa.sa_chart_template_standard'):
if not journals_dict:
journals_dict = []
journals_dict.extend(
[{'name': 'Tax Adjustments', 'company_id': company.id, 'code': 'TA', 'type': 'general',
'favorite': True, 'sequence': 1},
{'name': 'IFRS 16 Right of Use Asset', 'company_id': company.id, 'code': 'IFRS', 'type': 'general',
'favorite': True, 'sequence': 10},
{'name': 'Zakat', 'company_id': company.id, 'code': 'ZAKAT', 'type': 'general', 'favorite': True,
'sequence': 10}])
return super()._prepare_all_journals(acc_template_ref, company, journals_dict=journals_dict)
def _load_template(self, company, code_digits=None, account_ref=None, taxes_ref=None):
account_ref, taxes_ref = super(AccountChartTemplate, self)._load_template(company=company,
code_digits=code_digits,
account_ref=account_ref,
taxes_ref=taxes_ref)
if self == self.env.ref('l10n_sa.sa_chart_template_standard'):
ifrs_journal_id = self.env['account.journal'].search([('company_id', '=', company.id), ('code', '=', 'IFRS')], limit=1)
if ifrs_journal_id:
ifrs_account_ids = [self.env.ref('l10n_sa.sa_account_100101').id,
self.env.ref('l10n_sa.sa_account_100102').id,
self.env.ref('l10n_sa.sa_account_400070').id]
ifrs_accounts = self.env['account.account'].browse([account_ref.get(id) for id in ifrs_account_ids])
for account in ifrs_accounts:
account.allowed_journal_ids = [(4, ifrs_journal_id.id, 0)]
zakat_journal_id = self.env['account.journal'].search([('company_id', '=', company.id), ('code', '=', 'ZAKAT')], limit=1)
if zakat_journal_id:
zakat_account_ids = [self.env.ref('l10n_sa.sa_account_201019').id,
self.env.ref('l10n_sa.sa_account_400072').id]
zakat_accounts = self.env['account.account'].browse([account_ref.get(id) for id in zakat_account_ids])
for account in zakat_accounts:
account.allowed_journal_ids = [(4, zakat_journal_id.id, 0)]
return account_ref, taxes_ref

View file

@ -0,0 +1,86 @@
# -*- 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
from odoo.tools import float_repr, format_datetime
class AccountMove(models.Model):
_inherit = 'account.move'
l10n_sa_delivery_date = fields.Date(string='Supply Date',
default=fields.Date.context_today,
copy=False,
readonly=True,
states={'draft': [('readonly', False)]},
help="""Date when the supply of the goods and services is performed.
You can set this date to when you want ZATCA to recognize the VAT Due Liability as ZATCA will consider the earlier of this date and the Issue Date.
In case of multiple deliveries, you should take the date of the latest one.""")
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,
help="""Date when the invoice is confirmed and posted.
In other words, it is the date on which the invoice is generated as final document (after securing all internal approvals).""")
@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_signed', 'amount_tax_signed', '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()
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())
totals = record._get_l10n_sa_totals()
invoice_total_enc = get_qr_encoding(4, float_repr(abs(totals['total_amount']), 2))
total_vat_enc = get_qr_encoding(5, float_repr(abs(totals['total_tax']), 2))
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()
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 not record.l10n_sa_confirmation_datetime:
record.l10n_sa_confirmation_datetime = fields.Datetime.now()
return res
def get_l10n_sa_confirmation_datetime_sa_tz(self):
self.ensure_one()
return format_datetime(self.env, self.l10n_sa_confirmation_datetime, tz='Asia/Riyadh', dt_format='Y-MM-dd\nHH:mm:ss')
def _l10n_sa_reset_confirmation_datetime(self):
self.filtered(lambda m: m.country_code == 'SA').l10n_sa_confirmation_datetime = False
def button_draft(self):
self._l10n_sa_reset_confirmation_datetime()
super().button_draft()
def _get_l10n_sa_totals(self):
self.ensure_one()
return {
'total_amount': self.amount_total_signed,
'total_tax': self.amount_tax_signed,
}