19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:16 +01:00
parent 89c6e82fe7
commit 1b82c20a58
572 changed files with 43570 additions and 53303 deletions

View file

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_chart_template
from . import template_cl
from . import account_move
from . import account_move_line
from . import account_tax

View file

@ -1,15 +0,0 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.http import request
class AccountChartTemplate(models.Model):
_inherit = 'account.chart.template'
def _load(self, company):
""" Set tax calculation rounding method required in Chilean localization"""
res = super()._load(company)
if company.account_fiscal_country_id.code == 'CL':
company.write({'tax_calculation_rounding_method': 'round_globally'})
return res

View file

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from odoo.exceptions import ValidationError
from odoo import models, fields, api, _
from odoo.tools.misc import formatLang
@ -15,10 +17,23 @@ class AccountMove(models.Model):
l10n_latam_internal_type = fields.Selection(
related='l10n_latam_document_type_id.internal_type', string='L10n Latam Internal Type')
@api.constrains("l10n_latam_document_number")
def _check_l10n_latam_document_number_is_numeric(self):
for move in self:
if (
move.company_id.country_id.code == "CL"
and move.l10n_latam_use_documents
and move.l10n_latam_document_number
and not re.fullmatch(r"[0-9]+", move.l10n_latam_document_number)
):
raise ValidationError(self.env._(
"The DTE document number (folio) must contain only digits."
))
def _get_l10n_latam_documents_domain(self):
self.ensure_one()
if self.journal_id.company_id.account_fiscal_country_id != self.env.ref('base.cl') or not \
self.journal_id.l10n_latam_use_documents:
self.l10n_latam_use_documents:
return super()._get_l10n_latam_documents_domain()
if self.journal_id.type == 'sale':
domain = [('country_id.code', '=', 'CL')]
@ -67,13 +82,13 @@ class AccountMove(models.Model):
and latam_document_type_code not in ['35', '38', '39', '41']):
raise ValidationError(_('Tax payer type and vat number are mandatory for this type of '
'document. Please set the current tax payer type of this customer'))
if rec.journal_id.type == 'sale' and rec.journal_id.l10n_latam_use_documents:
if rec.journal_id.type == 'sale' and rec.l10n_latam_use_documents:
if country_id.code != "CL":
if not ((tax_payer_type == '4' and latam_document_type_code in ['110', '111', '112']) or (
tax_payer_type == '3' and latam_document_type_code in ['39', '41', '61', '56'])):
raise ValidationError(_(
'Document types for foreign customers must be export type (codes 110, 111 or 112) or you should define the customer as an end consumer and use receipts (codes 39 or 41)'))
if rec.journal_id.type == 'purchase' and rec.journal_id.l10n_latam_use_documents:
if rec.journal_id.type == 'purchase' and rec.l10n_latam_use_documents:
if vat != SII_VAT and latam_document_type_code == '914':
raise ValidationError(_('The DIN document is intended to be used only with RUT 60805000-0'
' (Tesorería General de La República)'))
@ -111,7 +126,7 @@ class AccountMove(models.Model):
def _get_starting_sequence(self):
""" If use documents then will create a new starting sequence using the document type code prefix and the
journal document number with a 6 padding number """
if self.journal_id.l10n_latam_use_documents and self.company_id.account_fiscal_country_id.code == "CL":
if self.l10n_latam_use_documents and self.company_id.account_fiscal_country_id.code == "CL":
if self.l10n_latam_document_type_id:
return self._l10n_cl_get_formatted_sequence()
return super()._get_starting_sequence()
@ -131,7 +146,13 @@ class AccountMove(models.Model):
def _get_name_invoice_report(self):
self.ensure_one()
if self.l10n_latam_use_documents and self.company_id.account_fiscal_country_id.code == 'CL':
if (
self.l10n_latam_use_documents and self.company_id.account_fiscal_country_id.code == "CL"
and (
self.move_type in {"out_invoice", "out_refund"}
or self.l10n_latam_document_type_id.code == "46"
)
):
return 'l10n_cl.report_invoice_document'
return super()._get_name_invoice_report()
@ -141,34 +162,18 @@ class AccountMove(models.Model):
def _l10n_cl_get_invoice_totals_for_report(self):
self.ensure_one()
include_sii = self._l10n_cl_include_sii()
tax_totals = self.tax_totals
if not include_sii:
return tax_totals
base_lines = self.line_ids.filtered(lambda x: x.display_type == 'product')
tax_lines = self.line_ids.filtered(lambda x: x.display_type == 'tax')
base_line_vals_list = [x._convert_to_tax_base_line_dict() for x in base_lines]
if include_sii:
for vals in base_line_vals_list:
vals['taxes'] = vals['taxes'].flatten_taxes_hierarchy().filtered(lambda tax: tax.l10n_cl_sii_code != 14)
tax_line_vals_list = [x._convert_to_tax_line_dict() for x in tax_lines]
if include_sii:
tax_line_vals_list = [x for x in tax_line_vals_list if x['tax_repartition_line'].tax_id.l10n_cl_sii_code != 14]
tax_totals = self.env['account.tax']._prepare_tax_totals(
base_line_vals_list,
self.currency_id,
tax_lines=tax_line_vals_list,
)
if include_sii:
tax_totals['amount_total'] = self.amount_total
tax_totals['amount_untaxed'] = self.currency_id.round(
tax_totals['amount_total'] - sum([x['tax_amount'] for x in tax_line_vals_list if 'tax_amount' in x]))
tax_totals['formatted_amount_total'] = formatLang(self.env, tax_totals['amount_total'], currency_obj=self.currency_id)
tax_totals['formatted_amount_untaxed'] = formatLang(self.env, tax_totals['amount_untaxed'], currency_obj=self.currency_id)
if tax_totals['subtotals']:
tax_totals['subtotals'][0]['formatted_amount'] = tax_totals['formatted_amount_untaxed']
tax_group_ids = {
tax_group['id']
for subtotal in tax_totals['subtotals']
for tax_group in subtotal['tax_groups']
}
tax_group_ids_to_exclude = self.env['account.tax.group'].browse(tax_group_ids).filtered(lambda x: x.l10n_cl_sii_code == 14).ids
if tax_group_ids_to_exclude:
return self.env['account.tax']._exclude_tax_groups_from_tax_totals_summary(tax_totals, tax_group_ids_to_exclude)
return tax_totals
def _l10n_cl_include_sii(self):
@ -212,18 +217,18 @@ class AccountMove(models.Model):
'subtotal_amount_taxable': 0,
'subtotal_amount_exempt': 0,
'vat_amount': 0,
'total_amount': currency_round_other_currency.round(abs(self.amount_total_signed)) \
'total_amount': currency_round_other_currency.round(abs(self.amount_total_signed))
if export else currency_round_other_currency.round(self.amount_total),
'round_currency': currency_round_other_currency.decimal_places,
'name': self._l10n_cl_normalize_currency_name(currency_round_other_currency.name),
'rate': round(abs(self.amount_total_signed) / self.amount_total, 4),
'rate': round(abs(self.amount_total_signed) / self.amount_total, 4) if self.amount_total else 1,
}
for line in self.line_ids:
if line.tax_line_id and line.tax_line_id.l10n_cl_sii_code == 14:
values['vat_amount'] += line[key_main_currency] * sign_main_currency
if other_currency:
values['second_currency']['vat_amount'] += line[key_other_currency] * sign_main_currency # amount_currency behaves as balance
vat_percent = line.tax_line_id.amount if line.tax_line_id.amount > vat_percent else vat_percent
values['second_currency']['vat_amount'] += line[key_other_currency] * sign_main_currency
vat_percent = max(vat_percent, line.tax_line_id.amount)
if line.display_type == 'product':
if line.tax_ids.filtered(lambda x: x.l10n_cl_sii_code == 14):
values['subtotal_amount_taxable'] += line[key_main_currency] * sign_main_currency
@ -263,7 +268,6 @@ class AccountMove(models.Model):
:return:
"""
self.ensure_one()
tax = [{'tax_code': line.tax_line_id.l10n_cl_sii_code,
'tax_name': line.tax_line_id.name,
'tax_base': abs(sum(self.invoice_line_ids.filtered(
@ -272,9 +276,18 @@ class AccountMove(models.Model):
'tax_percent': abs(line.tax_line_id.amount),
'tax_amount_currency': self.currency_id.round(abs(line.amount_currency)),
'tax_amount': self.currency_id.round(abs(line.balance))} for line in self.line_ids.filtered(
lambda x: x.tax_group_id.id in [
self.env.ref('l10n_cl.tax_group_ila').id, self.env.ref('l10n_cl.tax_group_retenciones').id])]
lambda x: x.tax_group_id.id in [self.env['account.chart.template'].with_company(self.company_id).ref('tax_group_ila').id,
self.env['account.chart.template'].with_company(self.company_id).ref('tax_group_retenciones').id])]
return tax
def _float_repr_float_round(self, value, decimal_places):
return float_repr(float_round(value, decimal_places), decimal_places)
def _compute_tax_totals(self):
# OVERRIDE 'account'
super()._compute_tax_totals()
for move in self:
if move.tax_totals and move._get_name_invoice_report() == 'l10n_cl.report_invoice_document':
# Disable the recap of tax totals in company currency at the bottom right of the invoice,
# since this info is already present in our custom tax totals grid.
move.tax_totals['display_in_company_currency'] = False

View file

@ -5,7 +5,6 @@ from odoo.tools.float_utils import float_repr
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
def _l10n_cl_prices_and_taxes(self):
@ -18,8 +17,13 @@ class AccountMoveLine(models.Model):
invoice = self.move_id
included_taxes = self.tax_ids.filtered(lambda x: x.l10n_cl_sii_code == 14) if self.move_id._l10n_cl_include_sii() else self.tax_ids
if not included_taxes:
price_unit = self.tax_ids.with_context(round=False).compute_all(
self.price_unit, invoice.currency_id, 1.0, self.product_id, invoice.partner_id)
price_unit = self.tax_ids.compute_all(
self.price_unit,
currency=invoice.currency_id,
product=self.product_id,
partner=invoice.partner_id,
rounding_method='round_globally',
)
price_unit = price_unit['total_excluded']
price_subtotal = self.price_subtotal
else:
@ -65,7 +69,7 @@ class AccountMoveLine(models.Model):
second_currency_field = 'price_subtotal'
second_currency = self.currency_id
main_currency_rate = 1
second_currency_rate = abs(self.move_id.amount_total_signed) / self.move_id.amount_total if self.move_id.amount_total else 1
second_currency_rate = 1 / self.move_id.invoice_currency_rate if self.move_id.invoice_currency_rate else 1
inverse_rate = second_currency_rate if domestic_invoice_other_currency else main_currency_rate
else:
# This is to manage case 5 (export docs)
@ -73,7 +77,7 @@ class AccountMoveLine(models.Model):
second_currency = self.move_id.company_id.currency_id
main_currency_field = 'price_subtotal'
second_currency_field = 'balance'
inverse_rate = abs(self.move_id.amount_total_signed) / self.move_id.amount_total if self.move_id.amount_total else 1
inverse_rate = 1 / self.move_id.invoice_currency_rate if self.move_id.invoice_currency_rate else 1
price_subtotal = abs(self[main_currency_field]) * line_sign
if self.quantity and self.discount != 100.0:
price_unit = (price_subtotal / abs(self.quantity)) / (1 - self.discount / 100)

View file

@ -4,24 +4,6 @@ from odoo import fields, models
class AccountTax(models.Model):
_name = 'account.tax'
_inherit = 'account.tax'
l10n_cl_sii_code = fields.Integer('SII Code', group_operator=False)
class AccountTaxTemplate(models.Model):
_name = 'account.tax.template'
_inherit = 'account.tax.template'
l10n_cl_sii_code = fields.Integer('SII Code')
def _get_tax_vals(self, company, tax_template_to_tax):
self.ensure_one()
vals = super(AccountTaxTemplate, self)._get_tax_vals(company, tax_template_to_tax)
vals.update({
'l10n_cl_sii_code': self.l10n_cl_sii_code,
})
if self.tax_group_id:
vals['tax_group_id'] = self.tax_group_id.id
return vals
l10n_cl_sii_code = fields.Integer('SII Code', aggregator=False)

View file

@ -3,8 +3,7 @@
from odoo import models, fields
class L10nLatamDocumentType(models.Model):
class L10n_LatamDocumentType(models.Model):
_inherit = 'l10n_latam.document.type'
internal_type = fields.Selection(

View file

@ -12,4 +12,4 @@ class ResCompany(models.Model):
def _localization_use_documents(self):
""" Chilean localization use documents """
self.ensure_one()
return self.account_fiscal_country_id.code == "CL" or super()._localization_use_documents()
return self.chart_template == 'cl' or self.account_fiscal_country_id.code == "CL" or super()._localization_use_documents()

View file

@ -3,8 +3,7 @@
from odoo import fields, models
class ResPartner(models.Model):
_name = 'res.country'
class ResCountry(models.Model):
_inherit = 'res.country'
l10n_cl_customs_code = fields.Char('Customs Code')

View file

@ -3,8 +3,7 @@ from odoo import _, api, fields, models
class ResCurrency(models.Model):
_name = "res.currency"
_inherit = "res.currency"
l10n_cl_currency_code = fields.Char('Currency Code')
l10n_cl_short_name = fields.Char('Short Name')
l10n_cl_currency_code = fields.Char('Currency Code', translate=True)
l10n_cl_short_name = fields.Char('Short Name', translate=True)

View file

@ -1,69 +1,46 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import stdnum
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.exceptions import ValidationError
from odoo import api, fields, models, _
class ResPartner(models.Model):
_name = 'res.partner'
_inherit = 'res.partner'
_sii_taxpayer_types = [
('1', _('VAT Affected (1st Category)')),
('2', _('Fees Receipt Issuer (2nd category)')),
('3', _('End Consumer')),
('4', _('Foreigner')),
]
l10n_cl_sii_taxpayer_type = fields.Selection(
_sii_taxpayer_types, 'Taxpayer Type', index='btree_not_null',
[
('1', 'VAT Affected (1st Category)'),
('2', 'Fees Receipt Issuer (2nd category)'),
('3', 'End Consumer'),
('4', 'Foreigner'),
],
string='Taxpayer Type',
index='btree_not_null',
help='1 - VAT Affected (1st Category) (Most of the cases)\n'
'2 - Fees Receipt Issuer (Applies to suppliers who issue fees receipt)\n'
'3 - End consumer (only receipts)\n'
'4 - Foreigner')
l10n_cl_activity_description = fields.Char(string='Activity Description')
l10n_cl_activity_description = fields.Char(string='Activity Description', help="Chile: Economic activity.")
@api.model
def _commercial_fields(self):
return super()._commercial_fields() + ['l10n_cl_sii_taxpayer_type']
def _format_vat_cl(self, values):
identification_types = [self.env.ref('l10n_latam_base.it_vat').id, self.env.ref('l10n_cl.it_RUT').id,
self.env.ref('l10n_cl.it_RUN').id]
country = self.env["res.country"].browse(values.get('country_id'))
identification_type = self.env['l10n_latam.identification.type'].browse(
values.get('l10n_latam_identification_type_id')
)
partner_country_is_chile = country.code == "CL" or identification_type.country_id.code == "CL"
if partner_country_is_chile and \
values.get('l10n_latam_identification_type_id') in identification_types and values.get('vat') and\
stdnum.util.get_cc_module('cl', 'vat').is_valid(values['vat']):
return stdnum.util.get_cc_module('cl', 'vat').format(values['vat']).replace('.', '').replace(
'CL', '').upper()
else:
return values['vat']
def _run_check_identification(self, validation='error'):
""" We format the RUN thing (actually, it could just use the parent method)"""
l10n_cl_partners = self.filtered(lambda p: p.vat and len(p.vat) > 2 and p.country_code == 'CL')
if l10n_cl_partners:
identification_types = [self.env.ref('l10n_cl.it_RUN').id]
for partner in l10n_cl_partners.filtered(lambda p: p.l10n_latam_identification_type_id.id in identification_types):
partner.vat = partner.format_vat_cl(partner.vat)
if validation == 'error':
if not partner._check_vat_number('CL', partner.vat):
raise ValidationError(_('The format of your RUN is not valid. It should be like 76086428-5.'))
return super(ResPartner, self - l10n_cl_partners)._run_check_identification(validation=validation)
def _format_dotted_vat_cl(self, vat):
vat_l = vat.split('-')
n_vat, n_dv = vat_l[0], vat_l[1]
return '%s-%s' % (format(int(n_vat), ',d').replace(',', '.'), n_dv)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('vat'):
vals['vat'] = self._format_vat_cl(vals)
return super().create(vals_list)
def write(self, values):
if any(field in values for field in ['vat', 'l10n_latam_identification_type_id', 'country_id']):
for record in self:
vat_values = {
'vat': values.get('vat', record.vat),
'l10n_latam_identification_type_id': values.get(
'l10n_latam_identification_type_id', record.l10n_latam_identification_type_id.id),
'country_id': values.get('country_id', record.country_id.id)
}
values['vat'] = self._format_vat_cl(vat_values)
return super().write(values)

View file

@ -3,7 +3,10 @@ from odoo import fields, models
class ResBank(models.Model):
_name = 'res.bank'
_inherit = 'res.bank'
def _get_fiscal_country_codes(self):
return ','.join(self.env.companies.mapped('account_fiscal_country_id.code'))
l10n_cl_sbif_code = fields.Char('Cod. SBIF', size=10)
fiscal_country_codes = fields.Char(store=False, default=_get_fiscal_country_codes)

View file

@ -0,0 +1,47 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
@template('cl')
def _get_cl_template_data(self):
return {
'code_digits': '6',
'property_account_receivable_id': 'account_110310',
'property_account_payable_id': 'account_210210',
'property_stock_valuation_account_id': 'account_110610',
}
@template('cl', 'res.company')
def _get_cl_res_company(self):
return {
self.env.company.id: {
'anglo_saxon_accounting': True,
'account_fiscal_country_id': 'base.cl',
'bank_account_code_prefix': '1101',
'cash_account_code_prefix': '1101',
'transfer_account_code_prefix': '117',
'account_default_pos_receivable_account_id': 'account_110421',
'income_currency_exchange_account_id': 'account_320265',
'expense_currency_exchange_account_id': 'account_410195',
'tax_calculation_rounding_method': 'round_globally',
'account_sale_tax_id': 'ITAX_19',
'account_purchase_tax_id': 'OTAX_19',
'expense_account_id': 'account_410235',
'income_account_id': 'account_310115',
'account_stock_journal_id': 'inventory_valuation',
'account_stock_valuation_id': 'account_110612',
},
}
@template('cl', 'account.account')
def _get_cl_account_account(self):
return {
'account_110612': {
'account_stock_expense_id': 'account_410230',
'account_stock_variation_id': 'account_603100',
},
}