[REF] account: remove chart template

Rewrite the whole chart template mechanism, removing the templates
stored in the database. The new format will mainly use CSV.

Speed up install time
---------------------

* About half of the time of installing a localization for the first time is
  taken by creating the template records. This new in code format gets
  completely rid of this.
* Creating the template records could often not be done in batch because
  of parent/children relations.
* The instanciation of the accounts on the company has been entirely
  reworked too, by
  - optimizing the order of creation of records to avoid UPDATE queries
  - using precomputed fields to avoid UPDATE queries
  - updating the translation in batch
  - deactivating logging in the chatter
  - avoiding access rights checks by checking the rights at the start

Overall, when installing a chart template for the first time, it is 4
times faster because half of the time spent on saving the template in
the database is not done at all anymore, and the instanciation on the
company is more than twice as fast.

Reduce technical debt
---------------------

There is no need to synchronize the templates with the real records
anymore. No need to use hooks to copy the data from one to the other.

It is easier to change a template in a stable version, which can often
be necessary due to legal reasons (i.e. a change of tax rates, reporting
tags,...)

Two modules have been removed:
* `l10n_generic_coa`: since there is nothing left datawise in this
  module, it can be integrated in `account` for free. It is just code
  and CSV.
* `l10n_multilang`: the fields that this module modified to be
  translatable are now always translatable:
  - there was an issue when updating modules that deleted all the
    translations because the fields were not translatable at some point
    during the loading of the registry, then they because translatable
    again but lost all translations because of the column type change.
  - most devs are not able to understand all the languages needed for
    all the localization available. Therefore, english has been added in
    the sources in most localization to understand better issues while
    debugging.
  - no need to call post init hooks anymore, doing the sync with the
    templates.
  - more: see "Translations" section

Because most of the data is now in CSV, it is also easier for product
owners to edit, audit, modify files themselves, removing one layer
during trivial development processes when only data should be changed.

More flexibility for declaration
--------------------------------

The data declaration can now be done easily in python or CSV.
A nice feature is that you can declare everything at once, even for some
more complex chart of accounts:
* if you have to set default taxes on accounts, would need to
  - declare the accounts because accounts are required on the taxes
  - declare the taxes
  - declare the taxes to put on the accounts
  This would lead to scatter information in multiple files. Now,
  everything can be declared in the same place and the loading of the
  chart of accounts will do the 3 steps automatically.
* if you have a relation of child/parent, you would first need to
  declare the parents then the children, and the loading would not be
  efficient because done one by one. Now, everything is done in batch
  automatically without having to think about it.

It is also easier to update fields on records where there was no field
for that on the templates, like
* setting a restriction for journals on accounts
* setting specific values on the company
* modifying journals and linking them easily by using the xml_id instead
  of having to compute it manually

Translations
------------

Some countries have multiple languages (i.e. Belgium uses officially
French, Dutch and German, and the CoA also has an official English
version) and we must support the languages in all these countries.
All these translations are known, and hard coded without using out
translation platform (Transifex). We also like to have the English
version (even if an official one doesn't exist) so that support can be
done more easily in databases using chart templates in other languages
(especially using a non roman alphabet).

Because the translations were not on Transifex for these records, it was
really hard to maintain: the translation templates (`.pot` files) were
not easy to extract as the automatic export would give values mixing
both the CoA and the menuitmes, the fields' strings,... But we don't
want to translate the CoA as we already know the value.
Managing the translations in the `.po` files was also annoying:
- it is easy to forget that the translations need an update too
- it requires a special editor, special terminal commands that everyone
  is not familiar with
- it is easy to make mistakes in the source string

The new format is the following: `field@en_US` where `field` is the
translatable field (usually `name`) and `en_US` is the locale code.
This allows to have the whole declaration on one line, everything in one
file. It also makes the process easier when debugging: instead of
searching for the translation in the `.po` files, it directly appears
next to the configuration of the account/tax/... .

Update of the code
------------------

The code can be updated using this script
https://github.com/william-andre/transform_coa
Forward ports can be managed too by stashing/resetting/checkout the new
modules or the changes in the modules updated in the same PR.

task-2687567

Part-of: odoo/odoo#110016
This commit is contained in:
wan
2023-02-17 19:30:40 +01:00
committed by william-andre
parent aa041fc75f
commit 5125748616
310 changed files with 2874 additions and 13940 deletions
+23 -38
View File
@@ -1,16 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import demo
from . import wizard
from . import report
from . import populate
from . import tools
SYSCOHADA_LIST = ['BJ', 'BF', 'CM', 'CF', 'KM', 'CG', 'CI', 'GA', 'GN', 'GW', 'GQ', 'ML', 'NE', 'CD', 'SN', 'TD', 'TG']
SYSCOHADA_LIST = ['BJ', 'BF', 'CM', 'CF', 'KM', 'CG', 'CI', 'GA', 'GN', 'GW', 'GQ', 'ML', 'NE',
'CD', 'SN', 'TD', 'TG']
VAT_LIST = ['AT', 'BE', 'CA', 'CO', 'DE', 'EC', 'ES', 'ET', 'FR', 'GR', 'IT', 'LU', 'MX', 'NL',
'NO', 'PL', 'PT', 'RO', 'SI', 'TR', 'GB', 'VE', 'VN']
def _set_fiscal_country(env):
""" Sets the fiscal country on existing companies when installing the module.
@@ -22,36 +16,17 @@ def _set_fiscal_country(env):
def _auto_install_l10n(env):
#check the country of the main company (only) and eventually load some module needed in that country
country_code = env.company.country_id.code
# Check the country of the main company (only) and eventually load some module needed in that country
country = env.company.country_id
country_code = country.code
if country_code:
#auto install localization module(s) if available
to_install_l10n = env['ir.module.module'].search_count([
('category_id', '=', env.ref('base.module_category_accounting_localizations_account_charts').id),
('state', '=', 'to install'),
])
module_list = []
if to_install_l10n:
# We don't install a CoA if one was passed in the command line
# or has been selected to install
pass
elif country_code in SYSCOHADA_LIST:
#countries using OHADA Chart of Accounts
module_list.append('l10n_syscohada')
elif country_code == 'GB':
module_list.append('l10n_uk')
elif country_code == 'DE':
module_list.append('l10n_de_skr03')
module_list.append('l10n_de_skr04')
else:
if env['ir.module.module'].search([('name', '=', 'l10n_' + country_code.lower())]):
module_list.append('l10n_' + country_code.lower())
else:
module_list.append('l10n_generic_coa')
if country_code in SYSCOHADA_LIST + [
'AT', 'BE', 'CA', 'CO', 'DE', 'EC', 'ES', 'ET', 'FR', 'GR', 'IT', 'LU', 'MX', 'NL', 'NO',
'PL', 'PT', 'RO', 'SI', 'TR', 'GB', 'VE', 'VN'
]:
if not env.company.chart_template:
template_code = env['account.chart.template']._guess_chart_template(country)
module_list.extend(env['account.chart.template']._get_chart_template_mapping()[template_code]['modules'])
if country_code in ['US', 'CA']:
module_list.append('account_check_printing')
if country_code in SYSCOHADA_LIST + VAT_LIST:
module_list.append('base_vat')
module_ids = env['ir.module.module'].search([('name', 'in', module_list), ('state', '=', 'uninstalled')])
@@ -61,3 +36,13 @@ def _auto_install_l10n(env):
def _account_post_init(env):
_auto_install_l10n(env)
_set_fiscal_country(env)
# imported here to avoid dependency cycle issues
# pylint: disable=wrong-import-position
from . import controllers
from . import models
from . import demo
from . import wizard
from . import report
from . import populate
from . import tools
-1
View File
@@ -39,7 +39,6 @@ You could use this simplified accounting in case you work with an (external) acc
'views/account_tax_views.xml',
'views/account_full_reconcile_views.xml',
'views/account_payment_term_views.xml',
'views/account_chart_template_views.xml',
'views/res_partner_bank_views.xml',
'views/report_statement.xml',
'views/terms_template.xml',
-6
View File
@@ -145,12 +145,6 @@
<field name="payment_type">outbound</field>
</record>
<!-- Account Tax Group -->
<record id="tax_group_taxes" model="account.tax.group">
<field name="name">Taxes</field>
<field name="sequence">0</field>
</record>
<!-- Partner Trust Property -->
<record forcecreate="True" id="default_followup_trust" model="ir.property">
<field name="name">Followup Trust Property</field>
@@ -0,0 +1,37 @@
"id","name","code","account_type","tag_ids","reconcile"
"current_assets","Current Assets","1010","asset_current","","False"
"stock_valuation","Stock Valuation","1101","asset_current","","False"
"stock_in","Stock Interim (Received)","1102","asset_current","","True"
"stock_out","Stock Interim (Delivered)","1103","asset_current","","True"
"receivable","Account Receivable","1210","asset_receivable","","True"
"to_receive_rec","Products to receive","1211","asset_current","","True"
"tax_paid","Tax Paid","1310","asset_current","","False"
"tax_receivable","Tax Receivable","1320","asset_current","","False"
"prepayments","Prepayments","1410","asset_prepayments","","False"
"fixed_assets","Fixed Asset","1510","asset_fixed","","False"
"non_current_assets","Non-current assets","1910","asset_non_current","","False"
"current_liabilities","Current Liabilities","2010","liability_current","","False"
"payable","Account Payable","2110","liability_payable","","True"
"to_receive_pay","Bills to receive","2111","liability_current","","True"
"tax_received","Tax Received","2510","liability_current","","False"
"tax_payable","Tax Payable","2520","liability_current","","False"
"non_current_liabilities","Non-current Liabilities","2910","liability_non_current","","False"
"capital","Capital","3010","equity","","False"
"dividends","Dividends","3020","equity","","False"
"income","Product Sales","4000","income","account.account_tag_operating","False"
"income_currency_exchange","Foreign Exchange Gain","4410","income","account.account_tag_financing","False"
"cash_diff_income","Cash Difference Gain","4420","income","account.account_tag_investing","False"
"cash_discount_loss","Cash Discount Loss","4430","expense","","False"
"other_income","Other Income","4500","income_other","","False"
"cost_of_goods_sold","Cost of Goods Sold","5000","expense_direct_cost","account.account_tag_operating","False"
"expense","Expenses","6000","expense","account.account_tag_operating","False"
"expense_invest","Purchase of Equipments","6110","expense","account.account_tag_investing","False"
"expense_rent","Rent","6120","expense","account.account_tag_investing","False"
"expense_finance","Bank Fees","6200","expense","account.account_tag_financing","False"
"expense_salary","Salary Expenses","6300","expense","account.account_tag_operating","False"
"expense_currency_exchange","Foreign Exchange Loss","6410","expense","account.account_tag_financing","False"
"cash_diff_expense","Cash Difference Loss","6420","expense","account.account_tag_investing","False"
"cash_discount_gain","Cash Discount Gain","6430","income","","False"
"expense_rd","RD Expenses","9610","expense","account.account_tag_investing","False"
"expense_sales","Sales Expenses","9620","expense","account.account_tag_investing","False"
"pos_receivable","Account Receivable (PoS)","1013","asset_receivable","","True"
1 id name code account_type tag_ids reconcile
2 current_assets Current Assets 1010 asset_current False
3 stock_valuation Stock Valuation 1101 asset_current False
4 stock_in Stock Interim (Received) 1102 asset_current True
5 stock_out Stock Interim (Delivered) 1103 asset_current True
6 receivable Account Receivable 1210 asset_receivable True
7 to_receive_rec Products to receive 1211 asset_current True
8 tax_paid Tax Paid 1310 asset_current False
9 tax_receivable Tax Receivable 1320 asset_current False
10 prepayments Prepayments 1410 asset_prepayments False
11 fixed_assets Fixed Asset 1510 asset_fixed False
12 non_current_assets Non-current assets 1910 asset_non_current False
13 current_liabilities Current Liabilities 2010 liability_current False
14 payable Account Payable 2110 liability_payable True
15 to_receive_pay Bills to receive 2111 liability_current True
16 tax_received Tax Received 2510 liability_current False
17 tax_payable Tax Payable 2520 liability_current False
18 non_current_liabilities Non-current Liabilities 2910 liability_non_current False
19 capital Capital 3010 equity False
20 dividends Dividends 3020 equity False
21 income Product Sales 4000 income account.account_tag_operating False
22 income_currency_exchange Foreign Exchange Gain 4410 income account.account_tag_financing False
23 cash_diff_income Cash Difference Gain 4420 income account.account_tag_investing False
24 cash_discount_loss Cash Discount Loss 4430 expense False
25 other_income Other Income 4500 income_other False
26 cost_of_goods_sold Cost of Goods Sold 5000 expense_direct_cost account.account_tag_operating False
27 expense Expenses 6000 expense account.account_tag_operating False
28 expense_invest Purchase of Equipments 6110 expense account.account_tag_investing False
29 expense_rent Rent 6120 expense account.account_tag_investing False
30 expense_finance Bank Fees 6200 expense account.account_tag_financing False
31 expense_salary Salary Expenses 6300 expense account.account_tag_operating False
32 expense_currency_exchange Foreign Exchange Loss 6410 expense account.account_tag_financing False
33 cash_diff_expense Cash Difference Loss 6420 expense account.account_tag_investing False
34 cash_discount_gain Cash Discount Gain 6430 income False
35 expense_rd RD Expenses 9610 expense account.account_tag_investing False
36 expense_sales Sales Expenses 9620 expense account.account_tag_investing False
37 pos_receivable Account Receivable (PoS) 1013 asset_receivable True
@@ -0,0 +1,9 @@
"id","name","amount","type_tax_use","tax_group_id","repartition_line_ids/document_type","repartition_line_ids/factor_percent","repartition_line_ids/repartition_type","repartition_line_ids/account_id"
"sale_tax_template","Tax 15%","15","sale","tax_group_15","invoice","100","base",""
"","","","","","invoice","100","tax","tax_received"
"","","","","","refund","100","base",""
"","","","","","refund","100","tax","tax_received"
"purchase_tax_template","Purchase Tax 15%","15","purchase","tax_group_15","invoice","100","base",""
"","","","","","invoice","100","tax","tax_received"
"","","","","","refund","100","base",""
"","","","","","refund","100","tax","tax_received"
1 id name amount type_tax_use tax_group_id repartition_line_ids/document_type repartition_line_ids/factor_percent repartition_line_ids/repartition_type repartition_line_ids/account_id
2 sale_tax_template Tax 15% 15 sale tax_group_15 invoice 100 base
3 invoice 100 tax tax_received
4 refund 100 base
5 refund 100 tax tax_received
6 purchase_tax_template Purchase Tax 15% 15 purchase tax_group_15 invoice 100 base
7 invoice 100 tax tax_received
8 refund 100 base
9 refund 100 tax tax_received
@@ -0,0 +1,2 @@
id,name,country_id,tax_payable_account_id,tax_receivable_account_id
tax_group_15,Tax 15%,base.us,tax_payable,tax_receivable
1 id name country_id tax_payable_account_id tax_receivable_account_id
2 tax_group_15 Tax 15% base.us tax_payable tax_receivable
+139 -136
View File
@@ -6,187 +6,208 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, Command
from odoo.tools.misc import file_open, formatLang
from odoo.exceptions import UserError, ValidationError
_logger = logging.getLogger(__name__)
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = "account.chart.template"
@api.model
def _get_demo_data(self):
def _get_demo_data(self, company=False):
"""Generate the demo data related to accounting."""
# This is a generator because data created here might be referenced by xml_id to data
# created later but defined in this same function.
yield self._get_demo_data_move()
yield self._get_demo_data_statement()
yield self._get_demo_data_transactions()
yield self._get_demo_data_reconcile_model()
yield self._get_demo_data_attachment()
yield self._get_demo_data_mail_message()
yield self._get_demo_data_mail_activity()
return {
'account.move': self._get_demo_data_move(company),
'account.bank.statement': self._get_demo_data_statement(company),
'account.bank.statement.line': self._get_demo_data_transactions(company),
'account.reconcile.model': self._get_demo_data_reconcile_model(company),
'ir.attachment': self._get_demo_data_attachment(company),
'mail.message': self._get_demo_data_mail_message(company),
'mail.activity': self._get_demo_data_mail_activity(company),
}
def _post_load_demo_data(self, company=False):
invoices = (
self.ref('demo_invoice_1')
+ self.ref('demo_invoice_2')
+ self.ref('demo_invoice_3')
+ self.ref('demo_invoice_followup')
+ self.ref('demo_invoice_5')
+ self.ref('demo_invoice_equipment_purchase')
).with_context(check_move_validity=False)
# the invoice_extract acts like a placeholder for the OCR to be ran and doesn't contain
# any lines yet
for move in invoices:
try:
move.action_post()
except (UserError, ValidationError):
_logger.exception('Error while posting demo data')
@api.model
def _get_demo_data_move(self):
cid = self.env.company.id
ref = self.env.ref
def _get_demo_data_move(self, company=False):
fifteen_months_ago = fields.Date.today() + relativedelta(months=-15)
return ('account.move', {
f'{cid}_demo_invoice_1': {
return {
'demo_invoice_1': {
'move_type': 'out_invoice',
'partner_id': ref('base.res_partner_12').id,
'invoice_user_id': ref('base.user_demo').id,
'invoice_payment_term_id': ref('account.account_payment_term_end_following_month').id,
'partner_id': 'base.res_partner_12',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': time.strftime('%Y-%m-01'),
'invoice_line_ids': [
Command.create({'product_id': ref('product.consu_delivery_02').id, 'quantity': 5}),
Command.create({'product_id': ref('product.consu_delivery_03').id, 'quantity': 5}),
Command.create({'product_id': 'product.consu_delivery_02', 'quantity': 5}),
Command.create({'product_id': 'product.consu_delivery_03', 'quantity': 5}),
],
},
f'{cid}_demo_invoice_2': {
'demo_invoice_2': {
'move_type': 'out_invoice',
'partner_id': ref('base.res_partner_2').id,
'partner_id': 'base.res_partner_2',
'invoice_user_id': False,
'invoice_date': time.strftime('%Y-%m-08'),
'invoice_line_ids': [
Command.create({'product_id': ref('product.consu_delivery_03').id, 'quantity': 5}),
Command.create({'product_id': ref('product.consu_delivery_01').id, 'quantity': 20}),
Command.create({'product_id': 'product.consu_delivery_03', 'quantity': 5}),
Command.create({'product_id': 'product.consu_delivery_01', 'quantity': 20}),
],
},
f'{cid}_demo_invoice_3': {
'demo_invoice_3': {
'move_type': 'out_invoice',
'partner_id': ref('base.res_partner_2').id,
'partner_id': 'base.res_partner_2',
'invoice_user_id': False,
'invoice_date': time.strftime('%Y-%m-08'),
'invoice_line_ids': [
Command.create({'product_id': ref('product.consu_delivery_01').id, 'quantity': 5}),
Command.create({'product_id': ref('product.consu_delivery_03').id, 'quantity': 5}),
Command.create({'product_id': 'product.consu_delivery_01', 'quantity': 5}),
Command.create({'product_id': 'product.consu_delivery_03', 'quantity': 5}),
],
},
f'{cid}_demo_invoice_followup': {
'demo_invoice_followup': {
'move_type': 'out_invoice',
'partner_id': ref('base.res_partner_2').id,
'invoice_user_id': ref('base.user_demo').id,
'invoice_payment_term_id': ref('account.account_payment_term_immediate').id,
'partner_id': 'base.res_partner_2',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_immediate',
'invoice_date': (fields.Date.today() + timedelta(days=-15)).strftime('%Y-%m-%d'),
'invoice_line_ids': [
Command.create({'product_id': ref('product.consu_delivery_02').id, 'quantity': 5}),
Command.create({'product_id': ref('product.consu_delivery_03').id, 'quantity': 5}),
Command.create({'product_id': 'product.consu_delivery_02', 'quantity': 5}),
Command.create({'product_id': 'product.consu_delivery_03', 'quantity': 5}),
],
},
f'{cid}_demo_invoice_5': {
'demo_invoice_5': {
'move_type': 'in_invoice',
'partner_id': ref('base.res_partner_12').id,
'invoice_user_id': ref('base.user_demo').id,
'invoice_payment_term_id': ref('account.account_payment_term_end_following_month').id,
'partner_id': 'base.res_partner_12',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': time.strftime('%Y-%m-01'),
'invoice_line_ids': [
Command.create({'product_id': ref('product.product_delivery_01').id, 'price_unit': 10.0, 'quantity': 1}),
Command.create({'product_id': ref('product.product_order_01').id, 'price_unit': 4.0, 'quantity': 5}),
Command.create({'product_id': 'product.product_delivery_01', 'price_unit': 10.0, 'quantity': 1}),
Command.create({'product_id': 'product.product_order_01', 'price_unit': 4.0, 'quantity': 5}),
],
},
f'{cid}_demo_invoice_extract': {
'demo_invoice_extract': {
'move_type': 'in_invoice',
'invoice_user_id': ref('base.user_demo').id,
'invoice_user_id': 'base.user_demo',
},
f'{cid}_demo_invoice_equipment_purchase': {
'demo_invoice_equipment_purchase': {
'move_type': 'in_invoice',
'ref': f'INV/{fifteen_months_ago.year}/0057',
'partner_id': ref('base.res_partner_12').id,
'partner_id': 'base.res_partner_12',
'invoice_user_id': False,
'invoice_date': fifteen_months_ago.strftime("%Y-%m-17"),
'invoice_line_ids': [
Command.create({'name': 'Redeem Reference Number: PO02529', 'quantity': 1, 'price_unit': 541.10}),
],
},
})
}
@api.model
def _get_demo_data_statement(self):
cid = self.env.company.id
ref = self.env.ref
def _get_demo_data_statement(self, company=False):
cid = company.id or self.env.company.id
bnk_journal = self.env['account.journal'].search(
domain=[('type', '=', 'bank'), ('company_id', '=', cid)],
limit=1,
)
return ('account.bank.statement', {
f'{cid}_demo_bank_statement_0': {
'name': f'{bnk_journal.name} - {time.strftime("%Y")}-01-01/1',
'balance_end_real': 6378.0,
'balance_start': 0.0,
return {
'demo_bank_statement_1': {
'journal_id': self.env['account.journal'].search([
('type', '=', 'bank'),
('company_id', '=', cid),
], limit=1).id,
'date': time.strftime('%Y')+'-01-01',
'balance_end_real': 9944.87,
'balance_start': 5103.0,
'line_ids': [
Command.create({
'journal_id': bnk_journal.id,
'payment_ref': 'Initial balance',
'amount': 5103.0,
'date': time.strftime('%Y-01-01'),
'partner_id': 'base.res_partner_12',
}),
Command.create({
'journal_id': bnk_journal.id,
'payment_ref': time.strftime('INV/%Y/00002 and INV/%Y/00003'),
'amount': 1275.0,
'date': time.strftime('%Y-01-01'),
'partner_id': ref('base.res_partner_12').id
'partner_id': 'base.res_partner_12',
}),
]
},
})
}
@api.model
def _get_demo_data_transactions(self):
cid = self.env.company.id
ref = self.env.ref
def _get_demo_data_transactions(self, company=False):
cid = company.id or self.env.company.id
bnk_journal = self.env['account.journal'].search(
domain=[('type', '=', 'bank'), ('company_id', '=', cid)],
limit=1,
)
return ('account.bank.statement.line', {
f'{cid}_demo_bank_statement_line_0': {
return {
'demo_bank_statement_line_0': {
'journal_id': bnk_journal.id,
'payment_ref': 'Bank Fees',
'amount': -32.58,
'date': time.strftime('%Y-01-01'),
},
f'{cid}_demo_bank_statement_line_1': {
'demo_bank_statement_line_1': {
'journal_id': bnk_journal.id,
'payment_ref': 'Prepayment',
'amount': 650,
'date': time.strftime('%Y-01-01'),
'partner_id': ref('base.res_partner_12').id
'partner_id': 'base.res_partner_12',
},
f'{cid}_demo_bank_statement_line_2': {
'demo_bank_statement_line_2': {
'journal_id': bnk_journal.id,
'payment_ref': time.strftime(f'First {formatLang(self.env, 2000, currency_obj=self.env.company.currency_id)} of invoice %Y/00001'),
'amount': 2000,
'date': time.strftime('%Y-01-01'),
'partner_id': ref('base.res_partner_12').id
'partner_id': 'base.res_partner_12',
},
f'{cid}_demo_bank_statement_line_3': {
'demo_bank_statement_line_3': {
'journal_id': bnk_journal.id,
'payment_ref': 'Last Year Interests',
'amount': 102.78,
'date': time.strftime('%Y-01-01'),
},
f'{cid}_demo_bank_statement_line_4': {
'demo_bank_statement_line_4': {
'journal_id': bnk_journal.id,
'payment_ref': time.strftime('INV/%Y/00002'),
'amount': 750,
'date': time.strftime('%Y-01-01'),
'partner_id': ref('base.res_partner_2').id
'partner_id': 'base.res_partner_2',
},
f'{cid}_demo_bank_statement_line_5': {
'demo_bank_statement_line_5': {
'journal_id': bnk_journal.id,
'payment_ref': f'R:9772938 10/07 AX 9415116318 T:5 BRT: {formatLang(self.env, 100.0, digits=2)} C/ croip',
'amount': 96.67,
'date': time.strftime('%Y-01-01'),
},
})
}
@api.model
def _get_demo_data_reconcile_model(self):
cid = self.env.company.id
return ('account.reconcile.model', {
f'{cid}_reconcile_from_label': {
def _get_demo_data_reconcile_model(self, company=False):
return {
'reconcile_from_label': {
'name': 'Line with Bank Fees',
'rule_type': 'writeoff_suggestion',
'match_label': 'contains',
@@ -214,113 +235,95 @@ class AccountChartTemplate(models.Model):
}),
]
},
})
}
@api.model
def _get_demo_data_attachment(self):
cid = self.env.company.id
ref = self.env.ref
return ('ir.attachment', {
f'{cid}_ir_attachment_in_invoice_1': {
def _get_demo_data_attachment(self, company=False):
return {
'ir_attachment_in_invoice_1': {
'type': 'binary',
'name': 'in_invoice_yourcompany_demo.pdf',
'res_model': 'account.move',
'res_id': ref(f'account.{cid}_demo_invoice_extract').id,
'res_id': 'demo_invoice_extract',
'raw': file_open(
'account/static/demo/in_invoice_yourcompany_demo_1.pdf', 'rb'
).read()
},
f'{cid}_ir_attachment_in_invoice_2': {
'ir_attachment_in_invoice_2': {
'type': 'binary',
'name': 'in_invoice_yourcompany_demo.pdf',
'res_model': 'account.move',
'res_id': ref(f'account.{cid}_demo_invoice_equipment_purchase').id,
'res_id': 'demo_invoice_equipment_purchase',
'raw': file_open(
'account/static/demo/in_invoice_yourcompany_demo_2.pdf', 'rb'
).read()
},
})
}
@api.model
def _get_demo_data_mail_message(self):
cid = self.env.company.id
ref = self.env.ref
return ('mail.message', {
f'{cid}_mail_message_in_invoice_1': {
def _get_demo_data_mail_message(self, company=False):
return {
'mail_message_in_invoice_1': {
'model': 'account.move',
'res_id': ref(f'account.{cid}_demo_invoice_extract').id,
'res_id': 'demo_invoice_extract',
'body': 'Vendor Bill attachment',
'message_type': 'comment',
'author_id': ref('base.partner_demo').id,
'author_id': 'base.partner_demo',
'attachment_ids': [Command.set([
ref(f'account.{cid}_ir_attachment_in_invoice_1').id
'ir_attachment_in_invoice_1',
])]
},
f'{cid}_mail_message_in_invoice_2': {
'mail_message_in_invoice_2': {
'model': 'account.move',
'res_id': ref(f'account.{cid}_demo_invoice_equipment_purchase').id,
'res_id': 'demo_invoice_equipment_purchase',
'body': 'Vendor Bill attachment',
'message_type': 'comment',
'author_id': ref('base.partner_demo').id,
'author_id': 'base.partner_demo',
'attachment_ids': [Command.set([
ref(f'account.{cid}_ir_attachment_in_invoice_2').id
'ir_attachment_in_invoice_2',
])]
},
})
}
@api.model
def _get_demo_data_mail_activity(self):
cid = self.env.company.id
ref = self.env.ref
return ('mail.activity', {
f'{cid}_invoice_activity_1': {
'res_id': ref(f'account.{cid}_demo_invoice_3').id,
'res_model_id': ref('account.model_account_move').id,
'activity_type_id': ref('mail.mail_activity_data_todo').id,
def _get_demo_data_mail_activity(self, company=False):
return {
'invoice_activity_1': {
'res_id': 'demo_invoice_3',
'res_model_id': 'account.model_account_move',
'activity_type_id': 'mail.mail_activity_data_todo',
'date_deadline': (fields.Datetime.today() + relativedelta(days=5)).strftime('%Y-%m-%d %H:%M'),
'summary': 'Follow-up on payment',
'create_uid': ref('base.user_admin').id,
'user_id': ref('base.user_admin').id,
'create_uid': 'base.user_admin',
'user_id': 'base.user_admin',
},
f'{cid}_invoice_activity_2': {
'res_id': ref(f'account.{cid}_demo_invoice_2').id,
'res_model_id': ref('account.model_account_move').id,
'activity_type_id': ref('mail.mail_activity_data_call').id,
'invoice_activity_2': {
'res_id': 'demo_invoice_2',
'res_model_id': 'account.model_account_move',
'activity_type_id': 'mail.mail_activity_data_call',
'date_deadline': fields.Datetime.today().strftime('%Y-%m-%d %H:%M'),
'create_uid': ref('base.user_admin').id,
'user_id': ref('base.user_admin').id,
'create_uid': 'base.user_admin',
'user_id': 'base.user_admin',
},
f'{cid}_invoice_activity_3': {
'res_id': ref(f'account.{cid}_demo_invoice_1').id,
'res_model_id': ref('account.model_account_move').id,
'activity_type_id': ref('mail.mail_activity_data_todo').id,
'invoice_activity_3': {
'res_id': 'demo_invoice_1',
'res_model_id': 'account.model_account_move',
'activity_type_id': 'mail.mail_activity_data_todo',
'date_deadline': (fields.Datetime.today() + relativedelta(days=5)).strftime('%Y-%m-%d %H:%M'),
'summary': 'Include upsell',
'create_uid': ref('base.user_admin').id,
'user_id': ref('base.user_admin').id,
'create_uid': 'base.user_admin',
'user_id': 'base.user_admin',
},
f'{cid}_invoice_activity_4': {
'res_id': ref(f'account.{cid}_demo_invoice_extract').id,
'res_model_id': ref('account.model_account_move').id,
'activity_type_id': ref('mail.mail_activity_data_todo').id,
'invoice_activity_4': {
'res_id': 'demo_invoice_extract',
'res_model_id': 'account.model_account_move',
'activity_type_id': 'mail.mail_activity_data_todo',
'date_deadline': (fields.Datetime.today() + relativedelta(days=5)).strftime('%Y-%m-%d %H:%M'),
'summary': 'Update address',
'create_uid': ref('base.user_admin').id,
'user_id': ref('base.user_admin').id,
'create_uid': 'base.user_admin',
'user_id': 'base.user_admin',
},
})
@api.model
def _post_create_demo_data(self, created):
cid = self.env.company.id
if created._name == 'account.move':
# the invoice_extract acts like a placeholder for the OCR to be ran and doesn't contain
# any lines yet
for move in created - self.env.ref(f'account.{cid}_demo_invoice_extract'):
try:
move.action_post()
except Exception:
_logger.exception('Error while posting demo data')
}
@api.model
def _get_demo_account(self, xml_id, account_type, company):
+6 -1
View File
@@ -2,8 +2,13 @@
<odoo>
<data>
<!-- TAGS FOR RETRIEVING THE DEMO ACCOUNTS -->
<function model="account.chart.template" name="try_loading">
<value eval="[]"/>
<value>generic_coa</value>
<value model="res.company" search="[('partner_id.country_id.code', 'in', ['US', False])]"/>
</function>
<!-- TAGS FOR RETRIEVING THE DEMO ACCOUNTS -->
<record id="demo_capital_account" model="account.account.tag">
<field name="name">Demo Capital Account</field>
</record>
+2
View File
@@ -32,5 +32,7 @@ from . import account_incoterms
from . import digest
from . import res_users
from . import ir_actions_report
from . import ir_module
from . import res_currency
from . import account_report
from . import template_generic_coa
+77 -43
View File
@@ -34,7 +34,7 @@ class AccountAccount(models.Model):
account_unaffected_earnings = self.browse(res['ids'])
raise ValidationError(_('You cannot have more than one account with "Current Year Earnings" as type. (accounts: %s)', [a.code for a in account_unaffected_earnings]))
name = fields.Char(string="Account Name", required=True, index='trigram', tracking=True)
name = fields.Char(string="Account Name", required=True, index='trigram', tracking=True, translate=True)
currency_id = fields.Many2one('res.currency', string='Account Currency', tracking=True,
help="Forces all journal items in this account to have a specific currency (i.e. bank journals). If no currency is set, entries can use any currency.")
code = fields.Char(size=64, required=True, tracking=True)
@@ -68,8 +68,8 @@ class AccountAccount(models.Model):
)
include_initial_balance = fields.Boolean(string="Bring Accounts Balance Forward",
help="Used in reports to know if we should consider journal items from the beginning of time instead of from the fiscal year only. Account types that should be reset to zero at each new fiscal year (like expenses, revenue..) should not have this option set.",
compute="_compute_include_initial_balance",
store=True)
compute="_compute_include_initial_balance", store=True, precompute=True,
)
internal_group = fields.Selection(
selection=[
('equity', 'Equity'),
@@ -79,12 +79,13 @@ class AccountAccount(models.Model):
('expense', 'Expense'),
('off_balance', 'Off Balance'),
],
string="Internal Group", readonly=True, compute="_compute_internal_group", store=True
string="Internal Group",
compute="_compute_internal_group", store=True, precompute=True,
)
#has_unreconciled_entries = fields.Boolean(compute='_compute_has_unreconciled_entries',
# help="The account has at least one unreconciled debit and credit since last time the invoices & payments matching was performed.")
reconcile = fields.Boolean(string='Allow Reconciliation', tracking=True,
compute='_compute_reconcile', store=True, readonly=False,
compute='_compute_reconcile', store=True, readonly=False, precompute=True,
help="Check this box if this account allows invoices & payments matching of journal items.")
tax_ids = fields.Many2many('account.tax', 'account_account_tax_default_rel',
'account_id', 'tax_id', string='Default Taxes',
@@ -96,7 +97,7 @@ class AccountAccount(models.Model):
tag_ids = fields.Many2many('account.account.tag', 'account_account_account_tag', string='Tags', help="Optional tags you may want to assign for custom reporting")
group_id = fields.Many2one('account.group', compute='_compute_account_group', store=True, readonly=True,
help="Account prefixes can determine account groups.")
root_id = fields.Many2one('account.root', compute='_compute_account_root', store=True)
root_id = fields.Many2one('account.root', compute='_compute_account_root', store=True, precompute=True)
allowed_journal_ids = fields.Many2many('account.journal', string="Allowed Journals", help="Define in which journals this account can be used. If empty, can be used in all journals.")
opening_debit = fields.Monetary(string="Opening Debit", compute='_compute_opening_debit_credit', inverse='_set_opening_debit')
opening_credit = fields.Monetary(string="Opening Credit", compute='_compute_opening_debit_credit', inverse='_set_opening_credit')
@@ -253,7 +254,6 @@ class AccountAccount(models.Model):
if not accounts:
return
self.flush_recordset(['reconcile'])
self.env['account.journal'].flush_model(['company_id', 'default_account_id'])
self.env['res.company'].flush_model(['account_journal_payment_credit_account_id', 'account_journal_payment_debit_account_id'])
self.env['account.payment.method.line'].flush_model(['journal_id', 'payment_account_id'])
@@ -342,9 +342,11 @@ class AccountAccount(models.Model):
record.used = record.id in ids
@api.model
def _search_new_account_code(self, company, digits, prefix):
def _search_new_account_code(self, company, digits, prefix, cache=None):
for num in range(1, 10000):
new_code = str(prefix.ljust(digits - 1, '0')) + str(num)
if new_code in (cache or []):
continue
rec = self.search([('code', '=', new_code), ('company_id', '=', company.id)], limit=1)
if not rec:
return new_code
@@ -615,6 +617,15 @@ class AccountAccount(models.Model):
default['name'] = self.name
return super(AccountAccount, self).copy(default)
def copy_translations(self, new, excluded=()):
super().copy_translations(new, excluded=tuple(excluded)+('name',))
if new.name == _('%s (copy)', self.name):
name_field = self._fields['name']
self.env.cache.update_raw(new, name_field, [{
lang: _('%s (copy)', tr)
for lang, tr in name_field._get_stored_translations(self).items()
}], dirty=True)
@api.model
def load(self, fields, data):
""" Overridden for better performances when importing a list of account
@@ -689,6 +700,17 @@ class AccountAccount(models.Model):
return self.create({'code': code, 'name': name}).name_get()[0]
raise UserError(_("Please create new accounts from the Chart of Accounts menu."))
@api.model_create_multi
def create(self, vals_list):
cache_map = defaultdict(list)
for vals in vals_list:
if 'prefix' in vals:
company = self.env['res.company'].browse(vals.get('company_id')) or self.env.company
cache = cache_map[company.id]
vals['code'] = self._search_new_account_code(company, vals.pop('code_digits'), vals.pop('prefix'), cache)
cache.append(vals['code'])
return super().create(vals_list)
def write(self, vals):
# Do not allow changing the company_id when account_move_line already exist
if vals.get('company_id', False):
@@ -777,9 +799,9 @@ class AccountGroup(models.Model):
parent_id = fields.Many2one('account.group', index=True, ondelete='cascade', readonly=True)
parent_path = fields.Char(index=True, unaccent=False)
name = fields.Char(required=True)
code_prefix_start = fields.Char()
code_prefix_end = fields.Char()
name = fields.Char(required=True, translate=True)
code_prefix_start = fields.Char(compute='_compute_code_prefix_start', readonly=False, store=True, precompute=True)
code_prefix_end = fields.Char(compute='_compute_code_prefix_end', readonly=False, store=True, precompute=True)
company_id = fields.Many2one('res.company', required=True, readonly=True, default=lambda self: self.env.company)
_sql_constraints = [
@@ -790,15 +812,17 @@ class AccountGroup(models.Model):
),
]
@api.onchange('code_prefix_start')
def _onchange_code_prefix_start(self):
if not self.code_prefix_end or self.code_prefix_end < self.code_prefix_start:
self.code_prefix_end = self.code_prefix_start
@api.depends('code_prefix_start')
def _compute_code_prefix_end(self):
for group in self:
if not group.code_prefix_end or group.code_prefix_end < group.code_prefix_start:
group.code_prefix_end = group.code_prefix_start
@api.onchange('code_prefix_end')
def _onchange_code_prefix_end(self):
if not self.code_prefix_start or self.code_prefix_start > self.code_prefix_end:
self.code_prefix_start = self.code_prefix_end
@api.depends('code_prefix_end')
def _compute_code_prefix_start(self):
for group in self:
if not group.code_prefix_start or group.code_prefix_start > group.code_prefix_end:
group.code_prefix_start = group.code_prefix_end
def name_get(self):
result = []
@@ -841,18 +865,22 @@ class AccountGroup(models.Model):
if res:
raise ValidationError(_('Account Groups with the same granularity can\'t overlap'))
def _sanitize_vals(self, vals):
if vals.get('code_prefix_start') and 'code_prefix_end' in vals and not vals['code_prefix_end']:
del vals['code_prefix_end']
if vals.get('code_prefix_end') and 'code_prefix_start' in vals and not vals['code_prefix_start']:
del vals['code_prefix_start']
return vals
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if 'code_prefix_start' in vals and not vals.get('code_prefix_end'):
vals['code_prefix_end'] = vals['code_prefix_start']
res_ids = super(AccountGroup, self).create(vals_list)
res_ids = super(AccountGroup, self).create([self._sanitize_vals(vals) for vals in vals_list])
res_ids._adapt_accounts_for_account_groups()
res_ids._adapt_parent_account_group()
return res_ids
def write(self, vals):
res = super(AccountGroup, self).write(vals)
res = super(AccountGroup, self).write(self._sanitize_vals(vals))
if 'code_prefix_start' in vals or 'code_prefix_end' in vals:
self._adapt_accounts_for_account_groups()
self._adapt_parent_account_group()
@@ -874,12 +902,14 @@ class AccountGroup(models.Model):
The most specific is the one with the longest prefixes and with the starting
prefix being smaller than the account code and the ending prefix being greater.
"""
if self.env.context.get('delay_account_group_sync'):
return
company_ids = account_ids.company_id.ids if account_ids else self.company_id.ids
account_ids = account_ids.ids if account_ids else []
if not company_ids and not account_ids:
if not company_ids and account_ids is None:
return
self.flush_model()
self.env['account.account'].flush_model()
self.env['account.account'].flush_model(['code'])
account_where_clause = ''
where_params = [tuple(company_ids)]
@@ -887,25 +917,25 @@ class AccountGroup(models.Model):
account_where_clause = 'AND account.id IN %s'
where_params.append(tuple(account_ids))
self._cr.execute(f'''
WITH candidates_account_groups AS (
SELECT
account.id AS account_id,
ARRAY_AGG(agroup.id ORDER BY char_length(agroup.code_prefix_start) DESC, agroup.id) AS group_ids
FROM account_account account
LEFT JOIN account_group agroup
ON agroup.code_prefix_start <= LEFT(account.code, char_length(agroup.code_prefix_start))
self._cr.execute(f"""
WITH relation AS (
SELECT DISTINCT ON (account.id)
account.id AS account_id,
agroup.id AS group_id
FROM account_account account
LEFT JOIN account_group agroup
ON agroup.code_prefix_start <= LEFT(account.code, char_length(agroup.code_prefix_start))
AND agroup.code_prefix_end >= LEFT(account.code, char_length(agroup.code_prefix_end))
AND agroup.company_id = account.company_id
WHERE account.company_id IN %s {account_where_clause}
GROUP BY account.id
WHERE account.company_id IN %s {account_where_clause}
ORDER BY account.id, char_length(agroup.code_prefix_start) DESC, agroup.id
)
UPDATE account_account
SET group_id = rel.group_ids[1]
FROM candidates_account_groups rel
WHERE account_account.id = rel.account_id
''', where_params)
self.env['account.account'].invalidate_model(['group_id'])
SET group_id = rel.group_id
FROM relation rel
WHERE account_account.id = rel.account_id
""", where_params)
self.env['account.account'].invalidate_model(['group_id'], flush=False)
def _adapt_parent_account_group(self):
"""Ensure consistency of the hierarchy of account groups.
@@ -914,13 +944,16 @@ class AccountGroup(models.Model):
The most specific is the one with the longest prefixes and with the starting
prefix being smaller than the child prefixes and the ending prefix being greater.
"""
if self.env.context.get('delay_account_group_sync'):
return
if not self:
return
self.flush_model()
query = """
WITH relation AS (
SELECT DISTINCT FIRST_VALUE(parent.id) OVER (PARTITION BY child.id ORDER BY child.id, char_length(parent.code_prefix_start) DESC) AS parent_id,
child.id AS child_id
SELECT DISTINCT ON (child.id)
child.id AS child_id,
parent.id AS parent_id
FROM account_group parent
JOIN account_group child
ON char_length(parent.code_prefix_start) < char_length(child.code_prefix_start)
@@ -929,6 +962,7 @@ class AccountGroup(models.Model):
AND parent.id != child.id
AND parent.company_id = child.company_id
WHERE child.company_id IN %(company_ids)s
ORDER BY child.id, char_length(parent.code_prefix_start) DESC
)
UPDATE account_group child
SET parent_id = relation.parent_id
+1 -1
View File
@@ -8,7 +8,7 @@ class AccountAccountTag(models.Model):
_name = 'account.account.tag'
_description = 'Account Tag'
name = fields.Char('Tag Name', required=True)
name = fields.Char('Tag Name', required=True, translate=True)
applicability = fields.Selection([('accounts', 'Accounts'), ('taxes', 'Taxes'), ('products', 'Products')], required=True, default='accounts')
color = fields.Integer('Color Index')
active = fields.Boolean(default=True, help="Set active to false to hide the Account Tag without removing it.")
+29 -12
View File
@@ -4,6 +4,7 @@ from odoo.osv import expression
from odoo.exceptions import UserError, ValidationError
from odoo.addons.base.models.res_bank import sanitize_account_number
from odoo.tools import remove_accents
from collections import defaultdict
import logging
import re
@@ -62,8 +63,15 @@ class AccountJournal(models.Model):
return model
return 'odoo'
name = fields.Char(string='Journal Name', required=True)
code = fields.Char(string='Short Code', size=5, required=True, help="Shorter name used for display. The journal entries of this journal will also be named using this prefix by default.")
name = fields.Char(string='Journal Name', required=True, translate=True)
code = fields.Char(
string='Short Code',
size=5,
compute='_compute_code', readonly=False, store=True,
required=True, precompute=True,
help="Shorter name used for display. "
"The journal entries of this journal will also be named using this prefix by default."
)
active = fields.Boolean(default=True, help="Set active to false to hide the Journal without removing it.")
type = fields.Selection([
('sale', 'Sales'),
@@ -203,6 +211,18 @@ class AccountJournal(models.Model):
('code_company_uniq', 'unique (company_id, code)', 'Journal codes must be unique per company.'),
]
@api.depends('type', 'company_id')
def _compute_code(self):
cache = defaultdict(list)
for record in self:
if not record.code and record.type in ('bank', 'cash'):
record.code = self.get_next_bank_cash_default_code(
record.type,
record.company_id,
cache.get(record.company_id)
)
cache[record.company_id].append(record.code)
@api.depends('outbound_payment_method_line_ids', 'inbound_payment_method_line_ids')
def _compute_available_payment_method_ids(self):
"""
@@ -572,13 +592,16 @@ class AccountJournal(models.Model):
return result
@api.model
def get_next_bank_cash_default_code(self, journal_type, company):
def get_next_bank_cash_default_code(self, journal_type, company, cache=None):
journal_code_base = (journal_type == 'cash' and 'CSH' or 'BNK')
journals = self.env['account.journal'].search([('code', 'like', journal_code_base + '%'), ('company_id', '=', company.id)])
existing_codes = set(self.env['account.journal'].search([
('code', 'like', journal_code_base + '%'),
('company_id', '=', company.id),
]).mapped('code') + (cache or []))
for num in range(1, 100):
# journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
journal_code = journal_code_base + str(num)
if journal_code not in journals.mapped('code'):
if journal_code not in existing_codes:
return journal_code
@api.model
@@ -607,7 +630,7 @@ class AccountJournal(models.Model):
company = self.env['res.company'].browse(vals['company_id']) if vals.get('company_id') else self.env.company
vals['company_id'] = company.id
# Don't get the digits on 'chart_template_id' since the chart template could be a custom one.
# Don't get the digits on 'chart_template' since the chart template could be a custom one.
random_account = self.env['account.account'].search([('company_id', '=', company.id)], limit=1)
digits = len(random_account.code) if random_account else 6
@@ -624,12 +647,6 @@ class AccountJournal(models.Model):
# === Fill missing name ===
vals['name'] = vals.get('name') or vals.get('bank_acc_number')
# === Fill missing code ===
if 'code' not in vals:
vals['code'] = self.get_next_bank_cash_default_code(journal_type, company)
if not vals['code']:
raise UserError(_("Cannot generate an unused journal code. Please fill the 'Shortcode' field."))
# === Fill missing accounts ===
if not has_liquidity_accounts:
default_account_code = self.env['account.account']._search_new_account_code(company, digits, liquidity_account_prefix)
+1 -1
View File
@@ -31,7 +31,7 @@ class AccountReport(models.Model):
column_ids = fields.One2many(string="Columns", comodel_name='account.report.column', inverse_name='report_id')
root_report_id = fields.Many2one(string="Root Report", comodel_name='account.report', help="The report this report is a variant of.")
variant_report_ids = fields.One2many(string="Variants", comodel_name='account.report', inverse_name='root_report_id')
chart_template_id = fields.Many2one(string="Chart of Accounts", comodel_name='account.chart.template')
chart_template = fields.Selection(string="Chart of Accounts", selection=lambda self: self.env['account.chart.template']._select_chart_template())
country_id = fields.Many2one(string="Country", comodel_name='res.country')
only_tax_exigible = fields.Boolean(
string="Only Tax Exigible Lines",
+80 -43
View File
@@ -25,22 +25,28 @@ class AccountTaxGroup(models.Model):
name = fields.Char(required=True, translate=True)
sequence = fields.Integer(default=10)
property_tax_payable_account_id = fields.Many2one(
company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
tax_payable_account_id = fields.Many2one(
comodel_name='account.account',
company_dependent=True,
check_company=True,
string='Tax Payable Account',
help="Tax current account used as a counterpart to the Tax Closing Entry when in favor of the authorities.")
property_tax_receivable_account_id = fields.Many2one(
tax_receivable_account_id = fields.Many2one(
comodel_name='account.account',
company_dependent=True,
check_company=True,
string='Tax Receivable Account',
help="Tax current account used as a counterpart to the Tax Closing Entry when in favor of the company.")
property_advance_tax_payment_account_id = fields.Many2one(
advance_tax_payment_account_id = fields.Many2one(
comodel_name='account.account',
company_dependent=True,
check_company=True,
string='Tax Advance Account',
help="Downpayments posted on this account will be considered by the Tax Closing Entry.")
country_id = fields.Many2one(string="Country", comodel_name='res.country', help="The country for which this tax group is applicable.")
country_id = fields.Many2one(
string="Country",
comodel_name='res.country',
compute='_compute_country_id', store=True, readonly=False, precompute=True,
help="The country for which this tax group is applicable.",
)
country_code = fields.Char(related="country_id.code")
preceding_subtotal = fields.Char(
string="Preceding Subtotal",
@@ -48,6 +54,11 @@ class AccountTaxGroup(models.Model):
"If not set, the tax group will be displayed after the 'Untaxed amount' subtotal.",
)
@api.depends('company_id.account_fiscal_country_id')
def _compute_country_id(self):
for group in self:
group.country_id = group.company_id.account_fiscal_country_id or group.company_id.country_id
@api.model
def _check_misconfigured_tax_groups(self, company, countries):
""" Searches the tax groups used on the taxes from company in countries that don't have
@@ -56,21 +67,12 @@ class AccountTaxGroup(models.Model):
:return: A boolean telling whether or not there are misconfigured groups for any
of these countries, in this company
"""
# This cannot be refactored to check for misconfigured groups instead
# because of an ORM limitation with search on property fields:
# searching on property = False also returns the properties using the default value,
# even if it's non-empty.
# (introduced here https://github.com/odoo/odoo/pull/6044)
all_configured_groups_ids = self.with_company(company)._search([
('property_tax_payable_account_id', '!=', False),
('property_tax_receivable_account_id', '!=', False),
])
return bool(self.env['account.tax'].search([
('company_id', '=', company.id),
('tax_group_id', 'not in', all_configured_groups_ids),
('country_id', 'in', countries.ids),
'|',
('tax_group_id.tax_payable_account_id', '=', False),
('tax_group_id.tax_receivable_account_id', '=', False),
], limit=1))
@@ -81,11 +83,7 @@ class AccountTax(models.Model):
_check_company_auto = True
_rec_names_search = ['name', 'description']
@api.model
def _default_tax_group(self):
return self.env.ref('account.tax_group_taxes')
name = fields.Char(string='Tax Name', required=True)
name = fields.Char(string='Tax Name', required=True, translate=True)
name_searchable = fields.Char(store=False, search='_search_name',
help="This dummy field lets us use another search method on the field 'name'."
"This allows more freedom on how to search the 'name' compared to 'filter_domain'."
@@ -124,8 +122,12 @@ class AccountTax(models.Model):
default=True,
help="If set, taxes with a lower sequence might affect this one, provided they try to do it.")
analytic = fields.Boolean(string="Include in Analytic Cost", help="If set, the amount computed by this tax will be assigned to the same analytic account as the invoice line (if any)")
tax_group_id = fields.Many2one('account.tax.group', string="Tax Group", default=_default_tax_group, required=True,
domain="[('country_id', 'in', (country_id, False))]")
tax_group_id = fields.Many2one(
comodel_name='account.tax.group',
string="Tax Group",
compute='_compute_tax_group_id', readonly=False, store=True,
required=True, precompute=True,
domain="[('country_id', 'in', (country_id, False))]")
# Technical field to make the 'tax_exigibility' field invisible if the same named field is set to false in 'res.company' model
hide_tax_exigibility = fields.Boolean(string='Hide Use Cash Basis Option', related='company_id.tax_exigibility', readonly=True)
tax_exigibility = fields.Selection(
@@ -160,7 +162,13 @@ class AccountTax(models.Model):
copy=True,
help="Distribution when the tax is used on a refund",
)
country_id = fields.Many2one(string="Country", comodel_name='res.country', required=True, help="The country for which this tax is applicable.")
country_id = fields.Many2one(
string="Country",
comodel_name='res.country',
compute='_compute_country_id', readonly=False, store=True,
required=True, precompute=True,
help="The country for which this tax is applicable.",
)
country_code = fields.Char(related='country_id.code', readonly=True)
_sql_constraints = [
@@ -173,16 +181,36 @@ class AccountTax(models.Model):
if record.tax_group_id.country_id and record.tax_group_id.country_id != record.country_id:
raise ValidationError(_("The tax group must have the same country_id as the tax using it."))
@api.depends('company_id.account_fiscal_country_id')
def _compute_country_id(self):
for tax in self:
tax.country_id = tax.company_id.account_fiscal_country_id or tax.company_id.country_id or tax.country_id
@api.depends('company_id', 'country_id')
def _compute_tax_group_id(self):
by_country_company = defaultdict(self.browse)
for tax in self:
if (
not tax.tax_group_id
or tax.tax_group_id.country_id != tax.country_id
or tax.tax_group_id.company_id != tax.company_id
):
by_country_company[(tax.country_id, tax.company_id)] += tax
for (country, company), taxes in by_country_company.items():
taxes.tax_group_id = self.env['account.tax.group'].search([
('company_id', '=', company.id),
('country_id', '=', country.id),
], limit=1) or self.env['account.tax.group'].search([
('company_id', '=', company.id),
('country_id', '=', False),
], limit=1)
@api.model
def default_get(self, fields_list):
# company_id is added so that we are sure to fetch a default value from it to use in repartition lines, below
rslt = super(AccountTax, self).default_get(fields_list + ['company_id'])
company_id = rslt.get('company_id')
company = self.env['res.company'].browse(company_id)
if 'country_id' in fields_list:
rslt['country_id'] = company.account_fiscal_country_id.id
repartition = rslt.setdefault('repartition_line_ids', [])
if 'repartition_line_ids' in fields_list and not repartition:
@@ -228,8 +256,8 @@ class AccountTax(models.Model):
@api.constrains('invoice_repartition_line_ids', 'refund_repartition_line_ids')
def _validate_repartition_lines(self):
for record in self:
invoice_repartition_line_ids = record.invoice_repartition_line_ids.sorted()
refund_repartition_line_ids = record.refund_repartition_line_ids.sorted()
invoice_repartition_line_ids = record.invoice_repartition_line_ids.sorted(lambda l: (l.sequence, l.id))
refund_repartition_line_ids = record.refund_repartition_line_ids.sorted(lambda l: (l.sequence, l.id))
record._check_repartition_lines(invoice_repartition_line_ids)
record._check_repartition_lines(refund_repartition_line_ids)
@@ -282,19 +310,28 @@ class AccountTax(models.Model):
if self._cr.fetchone():
raise UserError(_("You can't change the company of your tax since there are some journal items linked to it."))
def _sanitize_vals(self, vals):
"""Normalize the create/write values."""
sanitized = vals.copy()
# Allow to provide invoice_repartition_line_ids and refund_repartition_line_ids by dispatching them
# correctly in the repartition_line_ids
for doc_type in ('invoice', 'refund'):
fname = f"{doc_type}_repartition_line_ids"
if fname in sanitized:
repartition = sanitized.setdefault('repartition_line_ids', [])
repartition.extend([
(command, id, {'document_type': doc_type, **v}) if command in (Command.CREATE, Command.UPDATE)
else (command, id, v)
for command, id, v in sanitized.pop(fname)
])
return sanitized
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
for doc_type in ('invoice', 'refund'):
fname = f"{doc_type}_repartition_line_ids"
if fname in vals:
repartition = vals.setdefault('repartition_line_ids', [])
repartition.extend([
(command, id, {'document_type': doc_type, **v})
for command, id, v in vals.pop(fname)
])
return super().create(vals_list)
return super().create([self._sanitize_vals(vals) for vals in vals_list])
def write(self, vals):
return super().write(self._sanitize_vals(vals))
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
File diff suppressed because it is too large Load Diff
+17 -8
View File
@@ -57,7 +57,7 @@ class ResCompany(models.Model):
transfer_account_id = fields.Many2one('account.account',
domain="[('reconcile', '=', True), ('account_type', '=', 'asset_current'), ('deprecated', '=', False)]", string="Inter-Banks Transfer Account", help="Intermediary account used when moving money from a liqity account to another")
expects_chart_of_accounts = fields.Boolean(string='Expects a Chart of Accounts', default=True)
chart_template_id = fields.Many2one('account.chart.template', help='The chart template for the company (if any)')
chart_template = fields.Selection(selection='_chart_template_selection')
bank_account_code_prefix = fields.Char(string='Prefix of the bank accounts')
cash_account_code_prefix = fields.Char(string='Prefix of the cash accounts')
default_cash_difference_income_account_id = fields.Many2one('account.account', string="Cash Difference Income Account")
@@ -91,9 +91,6 @@ class ResCompany(models.Model):
domain="[('deprecated', '=', False), ('company_id', '=', id), \
('account_type', '=', 'expense')]")
anglo_saxon_accounting = fields.Boolean(string="Use anglo-saxon accounting")
property_stock_account_input_categ_id = fields.Many2one('account.account', string="Input Account for Stock Valuation")
property_stock_account_output_categ_id = fields.Many2one('account.account', string="Output Account for Stock Valuation")
property_stock_valuation_account_id = fields.Many2one('account.account', string="Account Template for Stock Valuation")
bank_journal_ids = fields.One2many('account.journal', 'company_id', domain=[('type', '=', 'bank')], string='Bank Journals')
incoterm_id = fields.Many2one('account.incoterms', string='Default incoterm',
help='International Commercial Terms are a series of predefined commercial terms used in international transactions.')
@@ -274,11 +271,15 @@ class ResCompany(models.Model):
return new_prefix + current_code.replace(old_prefix, '', 1).lstrip('0').rjust(digits-len(new_prefix), '0')
def reflect_code_prefix_change(self, old_code, new_code):
accounts = self.env['account.account'].search([('code', 'like', old_code), ('account_type', 'in', ('asset_cash', 'liability_credit_card')),
('company_id', '=', self.id)], order='code asc')
if not old_code:
return
accounts = self.env['account.account'].search([
('code', '=like', old_code + '%'),
('account_type', 'in', ('asset_cash', 'liability_credit_card')),
('company_id', '=', self.id)
], order='code asc')
for account in accounts:
if account.code.startswith(old_code):
account.write({'code': self.get_new_account_code(account.code, old_code, new_code)})
account.write({'code': self.get_new_account_code(account.code, old_code, new_code)})
def _get_fiscalyear_lock_statement_lines_redirect_action(self, unreconciled_statement_lines):
""" Get the action redirecting to the statement lines that are not already reconciled when setting a fiscal
@@ -581,6 +582,14 @@ class ResCompany(models.Model):
raise RedirectWarning(msg, action.id, _("Go to the configuration panel"))
return account
def _existing_accounting(self) -> bool:
"""Return True iff some accounting entries have already been made for the current company."""
self.ensure_one()
return bool(self.env['account.move.line'].search([('company_id', '=', self.id)], order="id", limit=1))
def _chart_template_selection(self):
return self.env['account.chart.template']._select_chart_template(self.country_id)
@api.model
def _action_check_hash_integrity(self):
return self.env.ref('account.action_report_account_hash_integrity').report_action(self.id)
+66
View File
@@ -0,0 +1,66 @@
from importlib import import_module
from inspect import getmembers, ismodule, isclass, isfunction
from odoo import models, fields
def get_flag(country_code):
# get the sequence of 2 regional indicator emoji forming the flag linked to the country code
return "".join(chr(int(f"1f1{ord(c)+165:02x}", base=16)) for c in country_code)
def templ(env, code, name=None, country='', **kwargs):
country_code = country or code.split('_')[0] if country is not None else None
country = country_code and env.ref(f"base.{country_code}", raise_if_not_found=False)
country_name = f"{get_flag(country.code)} {country.name}" if country else ''
return {
'name': country_name and (f"{country_name} - {name}" if name else country_name) or name,
'country_id': country and country.id,
'country_code': country and country.code,
**kwargs,
}
template_module = lambda m: ismodule(m) and m.__name__.split('.')[-1].startswith('template_')
template_class = isclass
template_function = lambda f: isfunction(f) and hasattr(f, '_l10n_template') and f._l10n_template[1] == 'template_data'
class IrModule(models.Model):
_inherit = "ir.module.module"
account_templates = fields.Binary(compute='_compute_account_templates', exportable=False)
def _compute_account_templates(self):
chart_category = self.env.ref('base.module_category_accounting_localizations_account_charts')
ChartTemplate = self.env['account.chart.template']
for module in self:
templates = {}
if module.category_id == chart_category or module.name == 'account':
python_module = import_module(f"odoo.addons.{module.name}.models")
templates = {
fct._l10n_template[0]: {
'name': fct(ChartTemplate).get('name'),
'parent': fct(ChartTemplate).get('parent'),
'country': fct(ChartTemplate).get('country', ''),
'visible': fct(ChartTemplate).get('visible', True),
'module': module.name,
}
for _name, mdl in getmembers(python_module, template_module)
for _name, cls in getmembers(mdl, template_class)
for _name, fct in getmembers(cls, template_function)
}
module.account_templates = {
code: templ(self.env, code, **vals)
for code, vals in templates.items()
}
def write(self, vals):
# Instanciate the first template of the module on the current company upon installing the module
was_installed = len(self) == 1 and self.state == 'installed'
super().write(vals)
is_installed = len(self) == 1 and self.state == 'installed'
if not was_installed and is_installed and not self.env.company.chart_template and self.account_templates:
self.env['account.chart.template'].try_loading(
next(iter(self.account_templates)),
self.env.company,
)
+2 -2
View File
@@ -20,7 +20,7 @@ class AccountFiscalPosition(models.Model):
_order = 'sequence'
sequence = fields.Integer()
name = fields.Char(string='Fiscal Position', required=True)
name = fields.Char(string='Fiscal Position', required=True, translate=True)
active = fields.Boolean(default=True,
help="By unchecking the active field, you may hide a fiscal position without deleting it.")
company_id = fields.Many2one(
@@ -64,7 +64,7 @@ class AccountFiscalPosition(models.Model):
if self.env['account.tax'].search([('country_id', '=', record.country_id.id)], limit=1):
record.foreign_vat_header_mode = None
elif self.env['account.tax.template'].search([('chart_template_id.country_id', '=', record.country_id.id)], limit=1):
elif self.env['account.chart.template']._guess_chart_template(record.country_id) != 'generic_coa':
record.foreign_vat_header_mode = 'templates_found'
else:
record.foreign_vat_header_mode = 'no_template'
+9 -8
View File
@@ -31,8 +31,7 @@ class ResConfigSettings(models.TransientModel):
domain="[('deprecated', '=', False), ('company_id', '=', company_id),\
('account_type', '=', 'expense')]")
has_chart_of_accounts = fields.Boolean(compute='_compute_has_chart_of_accounts', string='Company has a chart of accounts')
chart_template_id = fields.Many2one('account.chart.template', string='Template', default=lambda self: self.env.company.chart_template_id,
domain="[('visible','=', True)]")
chart_template = fields.Selection(selection=lambda self: self.env.company._chart_template_selection(), default=lambda self: self.env.company.chart_template)
sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax", related='company_id.account_sale_tax_id', readonly=False)
purchase_tax_id = fields.Many2one('account.tax', string="Default Purchase Tax", related='company_id.account_purchase_tax_id', readonly=False)
tax_calculation_rounding_method = fields.Selection(
@@ -184,10 +183,12 @@ class ResConfigSettings(models.TransientModel):
def set_values(self):
super().set_values()
# install a chart of accounts for the given company (if required)
if self.env.company == self.company_id \
and self.chart_template_id \
and self.chart_template_id != self.company_id.chart_template_id:
self.chart_template_id._load(self.env.company)
if self.env.company == self.company_id and self.chart_template \
and self.chart_template != self.company_id.chart_template:
self.env['account.chart.template'].try_loading(self.chart_template, company=self.company_id)
def reload_template(self):
self.env['account.chart.template'].try_loading(self.company_id.chart_template, company=self.company_id)
@api.depends('company_id')
def _compute_account_default_credit_limit(self):
@@ -205,8 +206,8 @@ class ResConfigSettings(models.TransientModel):
@api.depends('company_id')
def _compute_has_chart_of_accounts(self):
self.has_chart_of_accounts = bool(self.company_id.chart_template_id)
self.has_accounting_entries = self.env['account.chart.template'].existing_accounting(self.company_id)
self.has_chart_of_accounts = bool(self.company_id.chart_template)
self.has_accounting_entries = self.company_id._existing_accounting()
@api.depends('show_line_subtotals_tax_selection')
def _compute_group_show_line_subtotals(self):
@@ -0,0 +1,59 @@
from odoo import models
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.AbstractModel):
_inherit = "account.chart.template"
@template('generic_coa')
def _get_generic_coa_template_data(self):
"""Return the data necessary for the chart template.
:return: all the values that are not stored but are used to instancieate
the chart of accounts. Common keys are:
* anglo_saxon_accounting
* transfer_account_code_prefix
* property_*
* code_digits
:rtype: dict
"""
return {
'name': "Generic Chart Template",
'country': None,
'anglo_saxon_accounting': True,
'transfer_account_code_prefix': '1017',
'default_pos_receivable_account_id': 'pos_receivable',
'property_account_receivable_id': 'receivable',
'property_account_payable_id': 'payable',
'property_account_expense_id': 'expense',
'property_account_income_id': 'income',
'property_account_expense_categ_id': 'expense',
'property_account_income_categ_id': 'income',
'property_stock_account_input_categ_id': 'stock_in',
'property_stock_account_output_categ_id': 'stock_out',
'property_stock_valuation_account_id': 'stock_valuation',
}
@template('generic_coa', 'res.company')
def _get_generic_coa_res_company(self):
"""Return the data to be written on the company.
The data is a mapping the XMLID to the create/write values of a record.
:rtype: dict[(str, int), dict]
"""
return {
self.env.company.id: {
'account_fiscal_country_id': 'base.us',
'bank_account_code_prefix': '1014',
'cash_account_code_prefix': '1015',
'account_default_pos_receivable_account_id': 'pos_receivable',
'income_currency_exchange_account_id': 'income_currency_exchange',
'expense_currency_exchange_account_id': 'expense_currency_exchange',
'default_cash_difference_income_account_id': 'cash_diff_income',
'default_cash_difference_expense_account_id': 'cash_diff_expense',
'account_journal_early_pay_discount_loss_account_id': 'cash_discount_loss',
'account_journal_early_pay_discount_gain_account_id': 'cash_discount_gain',
}
}
@@ -116,7 +116,7 @@ class AccountBankStatementLine(models.Model):
return currency if currency != (journal.currency_id or journal.company_id.currency_id).id else False
company_ids = self.env['res.company'].search([
('chart_template_id', '!=', False),
('chart_template', '!=', False),
('id', 'in', self.env.registry.populated_models['res.company']),
])
+1 -1
View File
@@ -21,7 +21,7 @@ class AccountJournal(models.Model):
def _populate_factories(self):
company_ids = self.env['res.company'].search([
('chart_template_id', '!=', False),
('chart_template', '!=', False),
('id', 'in', self.env.registry.populated_models['res.company']),
])
return [
+1 -1
View File
@@ -202,7 +202,7 @@ class AccountMove(models.Model):
return False
company_ids = self.env['res.company'].search([
('chart_template_id', '!=', False),
('chart_template', '!=', False),
('id', 'in', self.env.registry.populated_models['res.company']),
])
currencies = self.env['res.currency'].search([
+1 -1
View File
@@ -105,7 +105,7 @@ class AccountPayment(models.Model):
return random.choice(search_payment_method_line_ids(values['payment_type'], values['journal_id']))
company_ids = self.env['res.company'].search([
('chart_template_id', '!=', False),
('chart_template', '!=', False),
('id', 'in', self.env.registry.populated_models['res.company']),
])
return [
@@ -26,7 +26,7 @@ class AccountReconcileModel(models.Model):
return 'model_%s' % counter
company_ids = self.env['res.company'].search([
('chart_template_id', '!=', False),
('chart_template', '!=', False),
('id', 'in', self.env.registry.populated_models['res.company']),
])
return [
+4 -20
View File
@@ -3,12 +3,9 @@
Only adding specificities of basic accounting applications.
"""
from odoo import models, _
from odoo.tools import populate
from odoo.exceptions import UserError
from odoo import models
import logging
from functools import lru_cache
_logger = logging.getLogger(__name__)
@@ -19,22 +16,10 @@ class ResCompany(models.Model):
_inherit = "res.company"
def _populate(self, size):
@lru_cache()
def search_coa_ids(currency_id):
return self.env['account.chart.template'].search([('currency_id', '=', currency_id)])
records = super()._populate(size)
_logger.info('Loading Chart Template')
default_chart_templates = self.env['account.chart.template'].search([], limit=1)
if not default_chart_templates:
# TODO install l10n_generic_coa ?
raise UserError(_(
"At least one localization is needed to be installed in order to populate the "
"database with accounting"
))
random = populate.Random('res.company+chart_template_selector')
records = super()._populate(size)
# Load the a chart of accounts matching the currency of the company for the 3 first created companies
# Load the a chart of accounts matching the country_id of the company for the 3 first created companies
# We are loading an existing CoA and not populating it because:
# * it reflects best real use cases.
# * it allows checking reports by localization
@@ -50,6 +35,5 @@ class ResCompany(models.Model):
# Note that we can still populate some new records on top of the CoA if it makes sense,
# like account.journal for instance.
for company in records[:3]:
chart_templates_cur = search_coa_ids(company.currency_id.id) or default_chart_templates
random.choice(chart_templates_cur).with_company(company.id).try_loading()
self.env['account.chart.template'].try_loading(company=company, template_code=None)
return records
@@ -3,18 +3,6 @@ access_account_cash_rounding_readonly,account.cash.rounding,model_account_cash_r
access_account_cash_rounding_uinvoice,account.cash.rounding,model_account_cash_rounding,account.group_account_invoice,1,1,1,1
access_res_partner_group_account_manager,res_partner group_account_manager,model_res_partner,account.group_account_manager,1,0,0,0
access_account_chart_template,account.chart.template,model_account_chart_template,account.group_account_manager,1,1,1,1
access_account_fiscal_position_template,account.fiscal.position.template,model_account_fiscal_position_template,account.group_account_manager,1,1,1,1
access_account_fiscal_position_tax_template,account.fiscal.position.tax.template,model_account_fiscal_position_tax_template,account.group_account_manager,1,1,1,1
access_account_fiscal_position_account_template,account.fiscal.position.account.template,model_account_fiscal_position_account_template,account.group_account_manager,1,1,1,1
access_product_template_account_manager,product.template.account.manager,product.model_product_template,account.group_account_manager,1,1,1,1
access_account_account_template,account.account.template,model_account_account_template,account.group_account_manager,1,1,1,1
access_account_tax_template,account.tax.template,model_account_tax_template,account.group_account_manager,1,1,1,1
access_account_tax_repartition_line_template_manager,account.tax repartition.line.template.manager,model_account_tax_repartition_line_template,account.group_account_manager,1,1,1,1
access_account_reconcile_model_template,account.reconcile.model.template,model_account_reconcile_model_template,account.group_account_manager,1,1,1,1
access_account_reconcile_model_line_template,account.reconcile.model.line.template,model_account_reconcile_model_line_template,account.group_account_manager,1,1,1,1
access_account_group_template,account.group.template,model_account_group_template,account.group_account_manager,1,1,1,1
access_res_currency_account_manager,res.currency account manager,base.model_res_currency,group_account_manager,1,1,1,1
access_res_currency_rate_account_manager,res.currency.rate account manager,base.model_res_currency_rate,group_account_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_account_cash_rounding_uinvoice account.cash.rounding model_account_cash_rounding account.group_account_invoice 1 1 1 1
4 access_res_partner_group_account_manager res_partner group_account_manager model_res_partner account.group_account_manager 1 0 0 0
5 access_account_chart_template access_res_currency_account_manager account.chart.template res.currency account manager model_account_chart_template base.model_res_currency account.group_account_manager group_account_manager 1 1 1 1
access_account_fiscal_position_template account.fiscal.position.template model_account_fiscal_position_template account.group_account_manager 1 1 1 1
access_account_fiscal_position_tax_template account.fiscal.position.tax.template model_account_fiscal_position_tax_template account.group_account_manager 1 1 1 1
access_account_fiscal_position_account_template account.fiscal.position.account.template model_account_fiscal_position_account_template account.group_account_manager 1 1 1 1
access_product_template_account_manager product.template.account.manager product.model_product_template account.group_account_manager 1 1 1 1
access_account_account_template account.account.template model_account_account_template account.group_account_manager 1 1 1 1
access_account_tax_template account.tax.template model_account_tax_template account.group_account_manager 1 1 1 1
access_account_tax_repartition_line_template_manager account.tax repartition.line.template.manager model_account_tax_repartition_line_template account.group_account_manager 1 1 1 1
access_account_reconcile_model_template account.reconcile.model.template model_account_reconcile_model_template account.group_account_manager 1 1 1 1
access_account_reconcile_model_line_template account.reconcile.model.line.template model_account_reconcile_model_line_template account.group_account_manager 1 1 1 1
access_account_group_template account.group.template model_account_group_template account.group_account_manager 1 1 1 1
access_res_currency_account_manager res.currency account manager base.model_res_currency group_account_manager 1 1 1 1
access_res_currency_rate_account_manager res.currency.rate account manager base.model_res_currency_rate group_account_manager 1 1 1 1
6 access_account_invoice_report_readonly access_res_currency_rate_account_manager account.invoice.report_user res.currency.rate account manager model_account_invoice_report base.model_res_currency_rate account.group_account_readonly group_account_manager 1 0 1 0 1 0 1
7 access_account_invoice_report_billing access_account_invoice_report_readonly account.invoice.report_billing account.invoice.report_user model_account_invoice_report account.group_account_invoice account.group_account_readonly 1 0 0 0
8 access_account_invoice_report access_account_invoice_report_billing account.invoice.report account.invoice.report_billing model_account_invoice_report account.group_account_manager account.group_account_invoice 1 1 0 1 0 1 0
+1 -1
View File
@@ -26,7 +26,7 @@ from . import test_sequence_mixin
from . import test_settings
from . import test_tax
from . import test_invoice_taxes
from . import test_templates_consistency
from . import test_mail_performance
from . import test_account_all_l10n
from . import test_account_onboarding
from . import test_portal_attachment
+15 -26
View File
@@ -7,6 +7,7 @@ import json
import time
import base64
from lxml import etree
from unittest import SkipTest
class AccountTestInvoicingCommon(TransactionCase):
@@ -32,13 +33,10 @@ class AccountTestInvoicingCommon(TransactionCase):
assert 'post_install' in cls.test_tags, 'This test requires a CoA to be installed, it should be tagged "post_install"'
if chart_template_ref:
chart_template = cls.env.ref(chart_template_ref)
else:
chart_template = cls.env.ref('l10n_generic_coa.configurable_chart_template', raise_if_not_found=False)
if not chart_template:
cls.tearDownClass()
# skipTest raises exception
cls.skipTest(cls, "Accounting Tests skipped because the user's company has no chart of accounts.")
template_vals = cls.env['account.chart.template']._get_chart_template_mapping()[chart_template_ref]
template_module = cls.env.ref(f"base.module_{template_vals['module']}")
if template_module.state != 'installed':
raise SkipTest(f"Module required for the test is not installed ({template_module.name})")
# Create user.
user = cls.env['res.users'].create({
@@ -58,8 +56,8 @@ class AccountTestInvoicingCommon(TransactionCase):
cls.env = cls.env(user=user)
cls.cr = cls.env.cr
cls.company_data_2 = cls.setup_company_data('company_2_data', chart_template=chart_template)
cls.company_data = cls.setup_company_data('company_1_data', chart_template=chart_template)
cls.company_data_2 = cls.setup_company_data('company_2_data', chart_template=chart_template_ref)
cls.company_data = cls.setup_company_data('company_1_data', chart_template=chart_template_ref)
user.write({
'company_ids': [(6, 0, (cls.company_data['company'] + cls.company_data_2['company']).ids)],
@@ -176,7 +174,6 @@ class AccountTestInvoicingCommon(TransactionCase):
# ==== Payment methods ====
bank_journal = cls.company_data['default_journal_bank']
cls.inbound_payment_method_line = bank_journal.inbound_payment_method_line_ids[0]
cls.outbound_payment_method_line = bank_journal.outbound_payment_method_line_ids[0]
@@ -190,26 +187,18 @@ class AccountTestInvoicingCommon(TransactionCase):
:param company_name: The name of the company.
:return: A dictionary will be returned containing all relevant accounting data for testing.
'''
def search_account(company, chart_template, field_name, domain):
template_code = chart_template[field_name].code
domain = [('company_id', '=', company.id)] + domain
account = None
if template_code:
account = cls.env['account.account'].search(domain + [('code', '=like', template_code + '%')], limit=1)
if not account:
account = cls.env['account.account'].search(domain, limit=1)
return account
chart_template = chart_template or cls.env.company.chart_template_id
company = cls.env['res.company'].create({
'name': company_name,
**kwargs,
})
cls.env.user.company_ids |= company
chart_template.try_loading(company=company, install_demo=False)
# Install the chart template
chart_template = chart_template or cls.env['account.chart.template']._guess_chart_template(company.country_id)
cls.env['account.chart.template'].try_loading(chart_template, company=company, install_demo=False)
if not company.account_fiscal_country_id:
company.account_fiscal_country_id = cls.env.ref('base.us')
# The currency could be different after the installation of the chart template.
if kwargs.get('currency_id'):
@@ -228,9 +217,9 @@ class AccountTestInvoicingCommon(TransactionCase):
('account_type', '=', 'expense'),
('id', '!=', company.account_journal_early_pay_discount_loss_account_id.id)
], limit=1),
'default_account_receivable': search_account(company, chart_template, 'property_account_receivable_id', [
('account_type', '=', 'asset_receivable')
]),
'default_account_receivable': cls.env['ir.property'].with_company(company)._get(
'property_account_receivable_id', 'res.partner'
),
'default_account_payable': cls.env['account.account'].search([
('company_id', '=', company.id),
('account_type', '=', 'liability_payable')
+22 -11
View File
@@ -13,7 +13,10 @@ def test_all_l10n(env):
As the module install is not yet fully transactional, the modules will
remain installed after the test.
"""
# Ensure the presence of demo data, to see if they can be correctly installed
assert env.ref('base.module_account').demo, "Need the demo to test with data"
# Install the requiriments
l10n_mods = env['ir.module.module'].search([
('name', 'like', 'l10n%'),
('state', '=', 'uninstalled'),
@@ -22,20 +25,28 @@ def test_all_l10n(env):
env.reset() # clear the set of environments
env = env() # get an environment that refers to the new registry
coas = env['account.chart.template'].search([
('id', 'not in', env['res.company'].search([]).chart_template_id.ids)
# Install Charts of Accounts
already_loaded_codes = set(env['res.company'].search([]).mapped('chart_template'))
not_loaded_codes = [
(template_code, template)
for template_code, template in env['account.chart.template']._get_chart_template_mapping().items()
if template_code not in already_loaded_codes
]
companies = env['res.company'].create([
{
'name': f'company_coa_{template_code}',
'country_id': template['country_id'],
}
for template_code, template in not_loaded_codes
])
for coa in coas:
cname = 'company_%s' % str(coa.id)
company = env['res.company'].create({
'name': cname,
'country_id': coa.country_id.id,
})
# Install the CoAs
for (template_code, _template), company in zip(not_loaded_codes, companies):
env.user.company_ids += company
env.user.company_id = company
_logger.info('Testing COA: %s (company: %s)' % (coa.name, cname))
_logger.info('Testing COA: %s (company: %s)', template_code, company.name)
try:
with env.cr.savepoint():
coa.try_loading()
env['account.chart.template'].try_loading(template_code, company, install_demo=True)
except Exception:
_logger.error("Error when creating COA %s", coa.name, exc_info=True)
_logger.error("Error when creating COA %s", template_code, exc_info=True)
@@ -3915,6 +3915,11 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon):
'foreign_vat': 'WH1234',
})
self.env['account.tax.group'].create({
'name': 'tax_group',
'country_id': test_country.id,
})
foreign_caba_tax = self.env['account.tax'].create({
'name': 'tax_1',
'amount': 33.3333,
+124 -134
View File
@@ -1,45 +1,97 @@
from unittest.mock import patch
from odoo import Command
from odoo.addons.account.models.chart_template import update_taxes_from_templates
from odoo.addons.account.models.chart_template import AccountChartTemplate
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
def _get_chart_template_mapping(self):
return {'test': {
'name': 'test',
'country_id': None,
'country_code': None,
'modules': ['account'],
'parent': None,
}}
def test_get_data(self, template_code):
return {
'template_data': {
'code_digits': 6,
'currency_id': 'base.EUR',
'property_account_income_categ_id': 'test_account_income_template',
'property_account_expense_categ_id': 'test_account_expense_template',
},
'account.tax.group': {
'tax_group_taxes': {
'name': "Taxes",
'sequence': 0,
},
},
'account.journal': self._get_account_journal(template_code),
'res.company': {
self.env.company.id: {
'bank_account_code_prefix': '1000',
'cash_account_code_prefix': '2000',
'transfer_account_code_prefix': '3000',
},
},
'account.tax': {
xmlid: _tax_vals(name, amount)
for name, xmlid, amount in [
('Tax 1', 'test_tax_1_template', 15),
('Tax 2', 'test_tax_2_template', 0),
]
},
'account.account': {
'test_account_income_template': {
'name': 'property_income_account',
'code': '222221',
'account_type': 'income',
},
'test_account_expense_template': {
'name': 'property_expense_account',
'code': '222222',
'account_type': 'expense',
},
},
'account.fiscal.position': {
'test_fiscal_position_template': {
'name': 'Fiscal Position',
'country_id': 'base.be',
'auto_apply': True,
'tax_ids': [
Command.create({
'tax_src_id': 'test_tax_1_template',
'tax_dest_id': 'test_tax_2_template',
})
]
}
},
}
def _tax_vals(name, amount):
return {
'name': name,
'amount': amount,
'tax_group_id': 'tax_group_taxes',
'invoice_repartition_line_ids': [
Command.create({'factor_percent': 100, 'repartition_type': 'base'}),
Command.create({'factor_percent': 100, 'repartition_type': 'tax'}),
],
'refund_repartition_line_ids': [
Command.create({'factor_percent': 100, 'repartition_type': 'base'}),
Command.create({'factor_percent': 100, 'repartition_type': 'tax'}),
],
}
@tagged('post_install', '-at_install')
@patch.object(AccountChartTemplate, '_get_chart_template_mapping', _get_chart_template_mapping)
class TestChartTemplate(TransactionCase):
@classmethod
def create_tax_template(cls, name, template_name, amount):
return cls.env['account.tax.template']._load_records([{
'xml_id': template_name,
'values': {
'name': name,
'amount': amount,
'chart_template_id': cls.chart_template.id,
'invoice_repartition_line_ids': [
Command.create({
'factor_percent': 100,
'repartition_type': 'base',
}),
Command.create({
'factor_percent': 100,
'repartition_type': 'tax',
}),
],
'refund_repartition_line_ids': [
Command.create({
'factor_percent': 100,
'repartition_type': 'base',
}),
Command.create({
'factor_percent': 100,
'repartition_type': 'tax',
}),
],
},
}])
@classmethod
@patch.object(AccountChartTemplate, '_get_chart_template_mapping', _get_chart_template_mapping)
def setUpClass(cls):
"""
Setups a company with a custom chart template, containing a tax and a fiscal position.
@@ -69,104 +121,37 @@ class TestChartTemplate(TransactionCase):
'company_id': cls.company_1.id,
})
cls.chart_template = cls.env['account.chart.template']._load_records([{
'xml_id': 'account.test_chart_template',
'values': {
'name': 'Test Chart Template',
'code_digits': 6,
'currency_id': cls.env.ref('base.EUR').id,
'bank_account_code_prefix': 1000,
'cash_account_code_prefix': 2000,
'transfer_account_code_prefix': 3000,
},
}])
account_templates = cls.env['account.account.template']._load_records([
{
'xml_id': 'account.test_account_income_template',
'values':
{
'name': 'property_income_account',
'code': '222221',
'account_type': 'income',
'chart_template_id': cls.chart_template.id,
}
},
{
'xml_id': 'account.test_account_expense_template',
'values':
{
'name': 'property_expense_account',
'code': '222222',
'account_type': 'expense',
'chart_template_id': cls.chart_template.id,
}
},
])
cls.chart_template.property_account_income_categ_id = account_templates[0].id
cls.chart_template.property_account_expense_categ_id = account_templates[1].id
cls.tax_1_template = cls.create_tax_template('Tax 1', 'account.test_tax_1_template', 15)
cls.tax_2_template = cls.create_tax_template('Tax 2', 'account.test_tax_2_template', 0)
cls.fiscal_position_template = cls.env['account.fiscal.position.template']._load_records([{
'xml_id': 'account.test_fiscal_position_template',
'values': {
'name': 'Fiscal Position',
'chart_template_id': cls.chart_template.id,
'country_id': cls.env.ref('base.be').id,
'auto_apply': True,
},
}])
cls.env['account.fiscal.position.tax.template']._load_records([{
'xml_id': 'account.test_fiscal_position_tax_template_1',
'values': {
'tax_src_id': cls.tax_1_template.id,
'tax_dest_id': cls.tax_2_template.id,
'position_id': cls.fiscal_position_template.id,
},
}])
cls.chart_template.try_loading(company=cls.company_1, install_demo=False)
with patch.object(AccountChartTemplate, '_get_chart_template_data', side_effect=test_get_data, autospec=True):
cls.env['account.chart.template'].try_loading('test', company=cls.company_1, install_demo=False)
def test_update_taxes_from_templates(self):
"""
Tests that adding a new tax template and a fiscal position tax template with this new tax template
creates this new tax and fiscal position line when updating
"""
fiscal_position = self.env['account.fiscal.position'].search([])
tax_3_template = self.create_tax_template('Tax 3', 'account.test_tax_3_template', 16)
tax_4_template = self.create_tax_template('Tax 4', 'account.test_tax_4_template', 17)
def local_get_data(self, template_code):
data = test_get_data(self, template_code)
data['account.tax'].update({
xmlid: _tax_vals(name, amount)
for name, xmlid, amount in [
('Tax 3', 'test_tax_3_template', 16),
('Tax 4', 'test_tax_4_template', 17),
]
})
data['account.fiscal.position']['test_fiscal_position_template']['tax_ids'].extend([
Command.create({
'tax_src_id': 'test_tax_3_template',
'tax_dest_id': 'test_tax_1_template',
}),
Command.create({
'tax_src_id': 'test_tax_2_template',
'tax_dest_id': 'test_tax_4_template',
}),
])
return data
self.env['account.fiscal.position.tax.template']._load_records([
{
'xml_id': 'account.test_fiscal_position_new_tax_src_template',
'values': {
'tax_src_id': tax_3_template.id,
'tax_dest_id': self.tax_1_template.id,
'position_id': self.fiscal_position_template.id,
},
},
{
'xml_id': 'account.test_fiscal_position_new_tax_dest_template',
'values': {
'tax_src_id': self.tax_2_template.id,
'tax_dest_id': tax_4_template.id,
'position_id': self.fiscal_position_template.id,
},
},
])
taxes = self.env['account.tax'].search([('company_id', '=', self.company_1.id)])
# Only the template have been created, so it should not be reflected yet on the company's chart template
self.assertEqual(len(fiscal_position.tax_ids), 1)
self.assertEqual(len(taxes), 2)
chart_template_xml_id = self.chart_template.get_external_id()[self.chart_template.id]
update_taxes_from_templates(self.env.cr, chart_template_xml_id)
with patch.object(AccountChartTemplate, '_get_chart_template_data', side_effect=local_get_data, autospec=True):
self.env['account.chart.template'].try_loading('test', company=self.company_1, install_demo=False)
taxes = self.env['account.tax'].search([('company_id', '=', self.company_1.id)])
self.assertRecordValues(taxes, [
@@ -176,6 +161,7 @@ class TestChartTemplate(TransactionCase):
{'name': 'Tax 4'},
])
fiscal_position = self.env['account.fiscal.position'].search([])
self.assertRecordValues(fiscal_position.tax_ids.tax_src_id, [
{'name': 'Tax 1'},
{'name': 'Tax 3'},
@@ -196,26 +182,30 @@ class TestChartTemplate(TransactionCase):
fiscal_position.tax_ids.unlink()
self.env['account.tax'].search([('company_id', '=', self.company_1.id)]).unlink()
chart_template_xml_id = self.chart_template.get_external_id()[self.chart_template.id]
update_taxes_from_templates(self.env.cr, chart_template_xml_id)
with patch.object(AccountChartTemplate, '_get_chart_template_data', side_effect=test_get_data, autospec=True):
self.env['account.chart.template'].try_loading('test', company=self.company_1, install_demo=False)
# if taxes have been deleted, they will be recreated, and the fiscal position mapping for it too
self.assertEqual(len(self.env['account.tax'].search([('company_id', '=', self.company_1.id)])), 2)
self.assertEqual(len(fiscal_position.tax_ids), 1)
fiscal_position.tax_ids.unlink()
update_taxes_from_templates(self.env.cr, chart_template_xml_id)
with patch.object(AccountChartTemplate, '_get_chart_template_data', side_effect=test_get_data, autospec=True):
self.env['account.chart.template'].try_loading('test', company=self.company_1, install_demo=False)
# if only the fiscal position mapping has been removed, it won't be recreated
self.assertEqual(len(fiscal_position.tax_ids), 0)
def test_update_taxes_conflict_name(self):
chart_template_xml_id = self.chart_template.get_external_id()[self.chart_template.id]
template_vals = self.tax_1_template._get_tax_vals_complete(self.company_1)
template_vals['amount'] = 20
self.chart_template.create_record_with_xmlid(self.company_1, self.tax_1_template, "account.tax", template_vals)
update_taxes_from_templates(self.env.cr, chart_template_xml_id)
tax_1_old = self.env['account.tax'].search([('company_id', '=', self.company_1.id), ('name', '=', "[old] " + self.tax_1_template.name)])
tax_1_new = self.env['account.tax'].search([('company_id', '=', self.company_1.id), ('name', '=', self.tax_1_template.name)])
self.assertEqual(len(tax_1_old), 1, "Old tax still exists but with a different name.")
def local_get_data(self, template_code):
data = test_get_data(self, template_code)
data['account.tax']['test_tax_1_template']['amount'] = 40
return data
tax_1_existing = self.env['account.tax'].search([('company_id', '=', self.company_1.id), ('name', '=', "Tax 1")])
with patch.object(AccountChartTemplate, '_get_chart_template_data', side_effect=local_get_data, autospec=True):
self.env['account.chart.template'].try_loading('test', company=self.company_1, install_demo=False)
tax_1_old = self.env['account.tax'].search([('company_id', '=', self.company_1.id), ('name', '=', "[old] Tax 1")])
tax_1_new = self.env['account.tax'].search([('company_id', '=', self.company_1.id), ('name', '=', "Tax 1")])
self.assertEqual(tax_1_old, tax_1_existing, "Old tax still exists but with a different name.")
self.assertEqual(len(tax_1_new), 1, "New tax have been created with the original name.")
+4
View File
@@ -186,6 +186,10 @@ class TaxReportTest(AccountTestInvoicingCommon):
tag_name = "55b"
tax_report_line = self._create_basic_tax_report_line(self.tax_report_1, "Line 55 bis", tag_name)
test_tag = tax_report_line.expression_ids._get_matching_tags().filtered(lambda tag: not tag.tax_negate)
self.env['account.tax.group'].create({
'name': 'Tax group',
'country_id': self.tax_report_1.country_id.id,
})
test_tax = self.env['account.tax'].create({
'name': "Test tax",
'amount_type': 'percent',
@@ -1,87 +0,0 @@
# -*- coding: utf-8 -*-
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@tagged('post_install', '-at_install')
class AccountingTestTemplConsistency(TransactionCase):
'''Test the templates consistency between some objects like account.account when account.account.template.
'''
def get_model_fields(self, model, extra_domain=None):
# Retrieve fields to compare
domain = [
('model', '=', model),
('state', '=', 'base'),
('related', '=', False),
('compute', '=', False),
('store', '=', True),
]
if extra_domain:
domain += extra_domain
return self.env['ir.model.fields'].search(domain)
def check_fields_consistency(self, model_from, model_to, exceptions=None):
'''Check the consistency of fields from one model to another by comparing if all fields
in the model_from are present in the model_to.
:param model_from: The model to compare.
:param model_to: The compared model.
:param exceptions: Not copied model's fields.
'''
extra_domain = [('name', 'not in', exceptions)] if exceptions else []
from_fields = self.get_model_fields(model_from, extra_domain=extra_domain).filtered_domain([('modules', '=', 'account')])
to_fields_set = set([f.name for f in self.get_model_fields(model_to)])
for field in from_fields:
assert field.name in to_fields_set,\
'Missing field "%s" from "%s" in model "%s".' % (field.name, model_from, model_to)
def test_account_account_fields(self):
'''Test fields consistency for ('account.account', 'account.account.template')
'''
self.check_fields_consistency(
'account.account.template', 'account.account', exceptions=['chart_template_id', 'nocreate'])
self.check_fields_consistency(
'account.account', 'account.account.template', exceptions=['company_id', 'deprecated', 'opening_debit', 'opening_credit', 'allowed_journal_ids', 'group_id', 'root_id', 'non_trade', 'include_initial_balance', 'internal_group'])
def test_account_tax_fields(self):
'''Test fields consistency for ('account.tax', 'account.tax.template')
'''
self.check_fields_consistency('account.tax.template', 'account.tax', exceptions=['chart_template_id'])
self.check_fields_consistency('account.tax', 'account.tax.template', exceptions=['company_id', 'country_id', 'real_amount'])
self.check_fields_consistency('account.tax.repartition.line.template', 'account.tax.repartition.line', exceptions=['plus_report_expression_ids', 'minus_report_expression_ids'])
self.check_fields_consistency('account.tax.repartition.line', 'account.tax.repartition.line.template', exceptions=['tag_ids', 'country_id', 'company_id', 'sequence'])
def test_fiscal_position_fields(self):
'''Test fields consistency for ('account.fiscal.position', 'account.fiscal.position.template')
'''
#main
self.check_fields_consistency('account.fiscal.position.template', 'account.fiscal.position', exceptions=['chart_template_id'])
self.check_fields_consistency('account.fiscal.position', 'account.fiscal.position.template', exceptions=['active', 'company_id', 'states_count', 'foreign_vat'])
#taxes
self.check_fields_consistency('account.fiscal.position.tax.template', 'account.fiscal.position.tax')
self.check_fields_consistency('account.fiscal.position.tax', 'account.fiscal.position.tax.template')
#accounts
self.check_fields_consistency('account.fiscal.position.account.template', 'account.fiscal.position.account')
self.check_fields_consistency('account.fiscal.position.account', 'account.fiscal.position.account.template')
def test_reconcile_model_fields(self):
'''Test fields consistency for ('account.reconcile.model', 'account.reconcile.model.template')
'''
self.check_fields_consistency('account.reconcile.model.template', 'account.reconcile.model', exceptions=['chart_template_id'])
# exclude fields from inherited 'mail.thread'
mail_thread_fields = [field.name for field in self.get_model_fields('mail.thread')]
self.check_fields_consistency(
'account.reconcile.model',
'account.reconcile.model.template',
exceptions=mail_thread_fields + ['active', 'company_id', 'past_months_limit', 'partner_mapping_line_ids'],
)
# lines
self.check_fields_consistency('account.reconcile.model.line.template', 'account.reconcile.model.line', exceptions=['chart_template_id'])
self.check_fields_consistency('account.reconcile.model.line', 'account.reconcile.model.line.template', exceptions=['company_id', 'journal_id', 'analytic_distribution', 'amount'])
def test_account_group_fields(self):
'''Test fields consistency for ('account.group', 'account.group.template')
'''
self.check_fields_consistency('account.group', 'account.group.template', exceptions=['company_id', 'parent_path'])
self.check_fields_consistency('account.group.template', 'account.group', exceptions=['chart_template_id'])
@@ -1,158 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Account Tax Templates -->
<record id="view_account_tax_template_form" model="ir.ui.view">
<field name="name">account.tax.template.form</field>
<field name="model">account.tax.template</field>
<field name="arch" type="xml">
<form string="Account Tax Template">
<sheet>
<group name="main_group">
<group>
<field name="name"/>
</group>
<group>
<field name="type_tax_use"/>
</group>
</group>
<notebook>
<page name="definition" string="Definition">
<group name="tax_definitions">
<group>
<field name="amount_type" />
<label for="amount" attrs="{'invisible':[('amount_type','=', 'group')]}"/>
<div attrs="{'invisible':[('amount_type','=', 'group')]}">
<field name="amount" class="oe_inline" />
<span class="o_form_label oe_inline" attrs="{'invisible':[('amount_type','=','fixed')]}">%</span>
</div>
</group>
</group>
<field name="children_tax_ids"
attrs="{'invisible':['|', ('amount_type','!=','group'), ('type_tax_use','=','none')]}"
domain="[('type_tax_use','in',('none',type_tax_use)), ('amount_type','!=','group')]">
<tree string="Children Taxes">
<field name="sequence" widget="handle" />
<field name="name"/>
<field name="amount_type" />
<field name="amount" />
</tree>
</field>
</page>
<page name="advanced_options" string="Advanced Options">
<group name="advanced_definitions">
<group>
<field name="description" attrs="{'invisible':[('amount_type','=', 'group')]}"/>
<field name="analytic" attrs="{'invisible':[('amount_type','=', 'group')]}" groups="analytic.group_analytic_accounting" />
</group>
<group name="price_definitions">
<field name="price_include" attrs="{'invisible':[('amount_type','=', 'group')]}" />
<field name="include_base_amount" attrs="{'invisible':[('amount_type','=', 'group')]}" />
<field name="is_base_affected"
attrs="{'invisible': ['|', ('amount_type','=', 'group'), ('price_include', '=', True)]}"/>
</group>
<group name="tax_configuration">
<field name="active" groups="base.group_no_one"/>
<field name="tax_exigibility" widget="radio" attrs="{'invisible':[('amount_type','=', 'group')]}"/>
<field name="cash_basis_transition_account_id" attrs="{'invisible': [('tax_exigibility', '=', 'on_invoice')], 'required': [('tax_exigibility', '=', 'on_payment')]}"/>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_account_tax_template_tree" model="ir.ui.view">
<field name="name">account.tax.template.tree</field>
<field name="model">account.tax.template</field>
<field name="arch" type="xml">
<tree string="Account Tax Template">
<field name="name"/>
<field name="description"/>
</tree>
</field>
</record>
<record id="view_account_tax_template_search" model="ir.ui.view">
<field name="name">account.tax.template.search</field>
<field name="model">account.tax.template</field>
<field name="arch" type="xml">
<search string="Search Tax Templates">
<field name="name" filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]" string="Tax Template"/>
<field name="chart_template_id"/>
<filter string="Sale" name="sale" domain="[('type_tax_use', '=', 'sale')]" help="Taxes used in Sales"/>
<filter string="Purchase" name="purchase" domain="[('type_tax_use', '=', 'purchase')]" help="Taxes used in Purchases"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
</search>
</field>
</record>
<record id="action_account_tax_template_form" model="ir.actions.act_window">
<field name="name">Tax Templates</field>
<field name="res_model">account.tax.template</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_account_tax_template_search"/>
</record>
<!-- Fiscal Position Templates -->
<record id="view_account_position_template_search" model="ir.ui.view">
<field name="name">account.fiscal.position.template.search</field>
<field name="model">account.fiscal.position.template</field>
<field name="arch" type="xml">
<search string="Fiscal Position">
<field name="name" string="Fiscal Position Template"/>
</search>
</field>
</record>
<record id="view_account_position_template_form" model="ir.ui.view">
<field name="name">account.fiscal.position.template.form</field>
<field name="model">account.fiscal.position.template</field>
<field name="arch" type="xml">
<form string="Fiscal Position Template">
<group col="4">
<field name="name"/>
<field name="chart_template_id"/>
</group>
<field name="tax_ids">
<tree string="Taxes Mapping" editable="bottom">
<field name="tax_src_id" domain="[('type_tax_use', '!=', None)]"/>
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None)]"/>
</tree>
<form string="Taxes Mapping">
<field name="tax_src_id" domain="[('type_tax_use', '!=', None)]"/>
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None)]"/>
</form>
</field>
<field name="account_ids">
<tree string="Accounts Mapping" editable="bottom">
<field name="account_src_id"/>
<field name="account_dest_id"/>
</tree>
<form string="Accounts Mapping">
<field name="account_src_id"/>
<field name="account_dest_id"/>
</form>
</field>
</form>
</field>
</record>
<record id="view_account_position_template_tree" model="ir.ui.view">
<field name="name">account.fiscal.position.template.tree</field>
<field name="model">account.fiscal.position.template</field>
<field name="arch" type="xml">
<tree string="Fiscal Position">
<field name="name"/>
</tree>
</field>
</record>
</data>
</odoo>
+10 -18
View File
@@ -228,23 +228,13 @@
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="country_id"/>
<field name="company_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="country_code" invisible="True"/>
<!--
'force_account_company' context key is used so that the RedirectWarning
potentially raised during tax closing doesn't allow the user choosing
accounts from another company in case the multicompany selector
currently grants access to multiple companies.
-->
<field name="property_tax_payable_account_id"
domain="[('company_id', '=', context['force_account_company'])] if context.get('force_account_company') else []"
/>
<field name="property_tax_receivable_account_id"
domain="[('company_id', '=', context['force_account_company'])] if context.get('force_account_company') else []"
/>
<field name="property_advance_tax_payment_account_id"
domain="[('company_id', '=', context['force_account_company'])] if context.get('force_account_company') else []"
/>
<field name="tax_payable_account_id" domain="[('company_id', '=', company_id)]"/>
<field name="tax_receivable_account_id" domain="[('company_id', '=', company_id)]"/>
<field name="advance_tax_payment_account_id" domain="[('company_id', '=', company_id)]"/>
<field name="preceding_subtotal" optional="hide"/>
</tree>
@@ -256,17 +246,19 @@
<field name="model">account.tax.group</field>
<field name="arch" type="xml">
<form string="Account Tax Group">
<field name="company_id" invisible="1"/>
<sheet>
<group>
<group>
<field name="name"/>
<field name="country_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="sequence"/>
</group>
<group>
<field name="property_tax_payable_account_id"/>
<field name="property_tax_receivable_account_id"/>
<field name="property_advance_tax_payment_account_id"/>
<field name="tax_payable_account_id" domain="[('company_id', '=', company_id)]"/>
<field name="tax_receivable_account_id" domain="[('company_id', '=', company_id)]"/>
<field name="advance_tax_payment_account_id" domain="[('company_id', '=', company_id)]"/>
<field name="preceding_subtotal"/>
</group>
</group>
@@ -23,15 +23,13 @@
<app data-string="Invoicing" string="Invoicing" name="account" groups="account.group_account_manager">
<field name="has_chart_of_accounts" invisible="1"/>
<field name="has_accounting_entries" invisible="1"/>
<block title="Fiscal Localization" name="fiscal_localization_setting_container" attrs="{'invisible': [('has_accounting_entries','!=',False)]}">
<block title="Fiscal Localization" name="fiscal_localization_setting_container">
<setting string="Fiscal Localization" company_dependent="1" help="Taxes, fiscal positions, chart of accounts &amp; legal statements for your country">
<div class="content-group">
<div class="row mt16">
<label for="chart_template_id" string="Package" class="col-2 o_light_label"/>
<field name="chart_template_id" options="{'no_open': True, 'no_create': True}"/>
</div>
<div class="mt8">
<button name="%(account.open_account_charts_modules)d" icon="fa-arrow-right" type="action" string="Install More Packages" discard="0" class="btn-link"/>
<label for="chart_template" string="Package" class="col-2 o_light_label"/>
<field name="chart_template" attrs="{'readonly': [('has_accounting_entries','!=', False)], 'required': [('chart_template', '!=', False)]}"/>
<button name="reload_template" type="object" string="Reload" class="btn-secondary" icon="fa-refresh" attrs="{'invisible': [('has_accounting_entries','=', False)]}"/>
</div>
</div>
</setting>
@@ -51,34 +51,3 @@ class AccountTaxPython(models.Model):
if localdict.get('result', False):
taxes += tax
return super(AccountTaxPython, taxes).compute_all(price_unit, currency, quantity, product, partner, is_refund=is_refund, handle_price_include=handle_price_include, include_caba_tags=include_caba_tags, fixed_multiplicator=fixed_multiplicator)
class AccountTaxTemplatePython(models.Model):
_inherit = 'account.tax.template'
amount_type = fields.Selection(selection_add=[
('code', 'Python Code')
], ondelete={'code': 'cascade'})
python_compute = fields.Text(string='Python Code', default="result = price_unit * 0.10",
help="Compute the amount of the tax by setting the variable 'result'.\n\n"
":param base_amount: float, actual amount on which the tax is applied\n"
":param price_unit: float\n"
":param quantity: float\n"
":param product: product.product recordset singleton or None\n"
":param partner: res.partner recordset singleton or None")
python_applicable = fields.Text(string='Applicable Code', default="result = True",
help="Determine if the tax will be applied by setting the variable 'result' to True or False.\n\n"
":param price_unit: float\n"
":param quantity: float\n"
":param product: product.product recordset singleton or None\n"
":param partner: res.partner recordset singleton or None")
def _get_tax_vals(self, company, tax_template_to_tax):
""" This method generates a dictionnary of all the values for the tax that will be created.
"""
self.ensure_one()
res = super(AccountTaxTemplatePython, self)._get_tax_vals(company, tax_template_to_tax)
res['python_compute'] = self.python_compute
res['python_applicable'] = self.python_applicable
return res
@@ -19,6 +19,7 @@ class AccountAnalyticAccount(models.Model):
index='trigram',
required=True,
tracking=True,
translate=True,
)
code = fields.Char(
string='Reference',
+1 -1
View File
@@ -15,7 +15,7 @@ class AccountAnalyticPlan(models.Model):
def _default_color(self):
return randint(1, 11)
name = fields.Char(required=True)
name = fields.Char(required=True, translate=True)
description = fields.Text(string='Description')
parent_id = fields.Many2one(
'account.analytic.plan',
+8 -6
View File
@@ -246,16 +246,18 @@ class TestCRMLead(TestCrmCommon):
@users('user_sales_manager')
def test_crm_lead_currency_sync(self):
self.company_main.currency_id = self.env.ref('base.EUR')
lead = self.env['crm.lead'].create({
lead_company = self.env['res.company'].sudo().create({
'name': 'EUR company',
'currency_id': self.env.ref('base.EUR').id,
})
lead = self.env['crm.lead'].with_company(lead_company).create({
'name': 'Lead 1',
'company_id': self.company_main.id
'company_id': lead_company.id
})
self.assertEqual(lead.company_currency, self.env.ref('base.EUR'))
self.company_main.currency_id = self.env.ref('base.CHF')
lead.with_company(self.company_main).update({'company_id': False})
lead_company.currency_id = self.env.ref('base.CHF')
lead.update({'company_id': False})
self.assertEqual(lead.company_currency, self.env.ref('base.CHF'))
@users('user_sales_manager')
@@ -4,21 +4,20 @@
'version': '1.0',
'category': 'Hidden/Tests',
'description': """
This module tests the module 'account_edi_ubl_cii', it is separated since dependencies to some
This module tests the module 'account_edi_ubl_cii', it is separated since dependencies to some
localizations were required. Its name begins with 'l10n' to not overload runbot.
The test files are separated by sources, they were taken from:
* the factur-x doc (form the FNFE)
* the peppol-bis-invoice-3 doc (the github repository: https://github.com/OpenPEPPOL/peppol-bis-invoice-3/tree/master/rules/examples contains examples)
* odoo, these files pass all validation tests (using ecosio or the FNFE validator)
We test that the external examples are correctly imported (currency, total amount and total tax match).
We also test that generating xml from odoo with given parameters gives exactly the same xml as the expected,
We also test that generating xml from odoo with given parameters gives exactly the same xml as the expected,
valid ones.
""",
'depends': [
'l10n_generic_coa',
'account_edi_ubl_cii',
'l10n_fr',
'l10n_be',
@@ -8,7 +8,7 @@ class TestCIIFR(TestUBLCommon):
@classmethod
def setUpClass(cls,
chart_template_ref="l10n_fr.l10n_fr_pcg_chart_template",
chart_template_ref="fr",
edi_format_ref="account_edi_ubl_cii.edi_facturx_1_0_05",
):
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
@@ -8,7 +8,7 @@ class TestUBLAU(TestUBLCommon):
@classmethod
def setUpClass(cls,
chart_template_ref="l10n_au.l10n_au_chart_template",
chart_template_ref="au",
edi_format_ref="account_edi_ubl_cii.ubl_a_nz",
):
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
@@ -8,7 +8,7 @@ class TestUBLBE(TestUBLCommon):
@classmethod
def setUpClass(cls,
chart_template_ref="l10n_be.l10nbe_chart_template",
chart_template_ref="be",
edi_format_ref="account_edi_ubl_cii.ubl_bis3",
):
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
@@ -9,7 +9,7 @@ class TestUBLDE(TestUBLCommon):
@classmethod
def setUpClass(cls,
chart_template_ref="l10n_de_skr03.l10n_de_chart_template",
chart_template_ref="de_skr03",
edi_format_ref="account_edi_ubl_cii.ubl_de",
):
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
@@ -8,7 +8,7 @@ class TestUBLNL(TestUBLCommon):
@classmethod
def setUpClass(cls,
chart_template_ref="l10n_nl.l10nnl_chart_template",
chart_template_ref="nl",
edi_format_ref="account_edi_ubl_cii.edi_nlcius_1",
):
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
@@ -8,6 +8,8 @@
<field name="expense_currency_exchange_account_id" ref="uae_account_400053"/>
<field name="income_currency_exchange_account_id" ref="uae_account_500011"/>
<field name="default_pos_receivable_account_id" ref="uae_account_102012"/>
<field name="property_tax_payable_account_id" ref="uae_account_202003"/>
<field name="property_tax_receivable_account_id" ref="uae_account_100103"/>
<field name="account_journal_early_pay_discount_loss_account_id" ref="uae_account_400071"/>
<field name="account_journal_early_pay_discount_gain_account_id" ref="uae_account_500014"/>
</record>
+34 -31
View File
@@ -1,39 +1,42 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo import models, Command
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
@template('ae', 'account.journal')
def _get_ae_account_journal(self):
""" If UAE chart, we add 2 new journals TA and IFRS"""
if self == self.env.ref('l10n_ae.uae_chart_template_standard'):
if not journals_dict:
journals_dict = []
journals_dict.extend(
[{"name": "Tax Adjustments", "company_id": company.id, "code": "TA", "type": "general", "sequence": 1,
"favorite": True},
{"name": "IFRS 16", "company_id": company.id, "code": "IFRS", "type": "general", "favorite": True,
"sequence": 10}])
return super()._prepare_all_journals(acc_template_ref, company, journals_dict=journals_dict)
return {
"tax_adjustment":{
"name": "Tax Adjustments",
"code": "TA",
"type": "general",
"show_on_dashboard": True,
"sequence": 1,
},
"ifrs16": {
"name": "IFRS 16",
"code": "IFRS",
"type": "general",
"show_on_dashboard": True,
"sequence": 10,
}
}
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_ae.uae_chart_template_standard'):
ifrs_journal = self.env['account.journal'].search(
[('company_id', '=', company.id), ('code', '=', 'IFRS')]).id
if ifrs_journal:
ifrs_account_ids = [self.env.ref('l10n_ae.uae_account_100101').id,
self.env.ref('l10n_ae.uae_account_100102').id,
self.env.ref('l10n_ae.uae_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, 0)]
self.env.ref('l10n_ae.ae_tax_group_5').write(
{'property_tax_payable_account_id': account_ref.get(self.env.ref('l10n_ae.uae_account_202003').id),
'property_tax_receivable_account_id': account_ref.get(self.env.ref('l10n_ae.uae_account_100103').id)})
return account_ref, taxes_ref
@template('ae', 'account.account')
def _get_ae_account_account(self):
return {
"uae_account_100101": {
'allowed_journal_ids': [Command.link('ifrs16')],
},
"uae_account_100102": {
'allowed_journal_ids': [Command.link('ifrs16')],
},
"uae_account_400070": {
'allowed_journal_ids': [Command.link('ifrs16')],
},
}
+33 -31
View File
@@ -3,41 +3,43 @@ import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = "account.chart.template"
@api.model
def _get_demo_data(self):
ref = self.env.ref
def _get_demo_data(self, company=False):
demo_data = super()._get_demo_data(company)
if company in (
self.env.ref('l10n_ar.company_mono', raise_if_not_found=False),
self.env.ref('l10n_ar.company_exento', raise_if_not_found=False),
self.env.ref('l10n_ar.company_ri', raise_if_not_found=False),
):
# Do not load generic demo data on these companies
return {}
# Do not load generic demo data on these companies
ar_demo_companies = (
ref('l10n_ar.company_mono', raise_if_not_found=False),
ref('l10n_ar.company_exento', raise_if_not_found=False),
ref('l10n_ar.company_ri', raise_if_not_found=False),
)
if self.env.company in ar_demo_companies:
return []
yield ('res.partner', {
'base.res_partner_12': {
'l10n_ar_afip_responsibility_type_id': ref('l10n_ar.res_IVARI').id,
},
'base.res_partner_2': {
'l10n_ar_afip_responsibility_type_id': ref('l10n_ar.res_IVARI').id,
},
})
for model, data in super()._get_demo_data():
yield model, data
if company.account_fiscal_country_id.code == "AR":
demo_data.setdefault('res.partner', {})
demo_data['account.move'] = demo_data.pop('account.move')
demo_data['res.partner'].setdefault('base.res_partner_2', {})
demo_data['res.partner']['base.res_partner_2']['l10n_ar_afip_responsibility_type_id'] = 'l10n_ar.res_IVARI'
demo_data['res.partner'].setdefault('base.res_partner_12', {})
demo_data['res.partner']['base.res_partner_12']['l10n_ar_afip_responsibility_type_id'] = 'l10n_ar.res_IVARI'
return demo_data
@api.model
def _get_demo_data_move(self):
cid = self.env.company.id
model, data = super()._get_demo_data_move()
if self.env.company.account_fiscal_country_id.code == "AR":
data[f'{cid}_demo_invoice_5']['l10n_latam_document_number'] = '1-1'
data[f'{cid}_demo_invoice_equipment_purchase']['l10n_latam_document_number'] = '1-2'
return model, data
def _get_demo_data_move(self, company=False):
data = super()._get_demo_data_move(company)
if company.account_fiscal_country_id.code == "AR":
data['demo_invoice_5']['l10n_latam_document_number'] = '1-1'
data['demo_invoice_equipment_purchase']['l10n_latam_document_number'] = '1-2'
return data
def _post_load_demo_data(self, company=False):
if company not in (
self.env.ref('l10n_ar.company_mono', raise_if_not_found=False),
self.env.ref('l10n_ar.company_exento', raise_if_not_found=False),
self.env.ref('l10n_ar.company_ri', raise_if_not_found=False),
):
# Do not load generic demo data on these companies
return super()._post_load_demo_data(company)
@@ -163,32 +163,32 @@
(0, 0, {
'name': 'FOB Total',
'price_unit': 28936.06,
'tax_ids': [(6, 0, [ref('l10n_ar.{}_ri_tax_vat_21_compras'.format(ref('company_ri')))])],
'tax_ids': [(6, 0, [ref('account.{}_ri_tax_vat_21_compras'.format(ref('company_ri')))])],
}),
(0, 0, {
'name': 'Flete',
'price_unit': 1350.00,
'tax_ids': [(6, 0, [ref('l10n_ar.{}_ri_tax_vat_21_compras'.format(ref('company_ri')))])],
'tax_ids': [(6, 0, [ref('account.{}_ri_tax_vat_21_compras'.format(ref('company_ri')))])],
}),
(0, 0, {
'name': 'Seguro',
'price_unit': 130.21,
'tax_ids': [(6, 0, [ref('l10n_ar.{}_ri_tax_vat_21_compras'.format(ref('company_ri')))])],
'tax_ids': [(6, 0, [ref('account.{}_ri_tax_vat_21_compras'.format(ref('company_ri')))])],
}),
(0, 0, {
'name': '-FOB Total',
'price_unit': -28936.06,
'tax_ids': [(6, 0, [ref('l10n_ar.{}_ri_tax_vat_no_gravado_compras'.format(ref('company_ri')))])],
'tax_ids': [(6, 0, [ref('account.{}_ri_tax_vat_no_gravado_compras'.format(ref('company_ri')))])],
}),
(0, 0, {
'name': '-Flete',
'price_unit': -1350.00,
'tax_ids': [(6, 0, [ref('l10n_ar.{}_ri_tax_vat_no_gravado_compras'.format(ref('company_ri')))])],
'tax_ids': [(6, 0, [ref('account.{}_ri_tax_vat_no_gravado_compras'.format(ref('company_ri')))])],
}),
(0, 0, {
'name': '-Seguro',
'price_unit': -130.21,
'tax_ids': [(6, 0, [ref('l10n_ar.{}_ri_tax_vat_no_gravado_compras'.format(ref('company_ri')))])],
'tax_ids': [(6, 0, [ref('account.{}_ri_tax_vat_no_gravado_compras'.format(ref('company_ri')))])],
}),
]"/>
</record>
+17 -17
View File
@@ -13,8 +13,8 @@
(0,0, {
'repartition_type': 'tax',
'account_id': obj().search([
('company_id', '=', obj().env.ref('l10n_ar.company_ri').id),
('group_id', '=', obj().env.ref('l10n_ar.account_group_otros_impuestos').id)
('company_id', '=', ref('company_ri')),
('group_id', '=', ref('account.{}_account_group_otros_impuestos'.format(ref('company_ri'))))
], limit=1).id,
}),
]"/>
@@ -24,13 +24,13 @@
(0,0, {
'repartition_type': 'tax',
'account_id': obj().search([
('company_id', '=', obj().env.ref('l10n_ar.company_ri').id),
('group_id', '=', obj().env.ref('l10n_ar.account_group_otros_impuestos').id)
('company_id', '=', ref('company_ri')),
('group_id', '=', ref('account.{}_account_group_otros_impuestos'.format(ref('company_ri'))))
], limit=1).id,
}),
]"/>
<field name="type_tax_use">sale</field>
<field name="tax_group_id" ref="l10n_ar.tax_group_otros_impuestos"/>
<field name="tax_group_id" model="account.tax.group" eval="ref('account.{}_tax_group_otros_impuestos'.format(ref('company_ri')))"/>
<field name="company_id" ref="l10n_ar.company_ri"/>
</record>
@@ -46,8 +46,8 @@
(0,0, {
'repartition_type': 'tax',
'account_id': obj().search([
('company_id', '=', obj().env.ref('l10n_ar.company_ri').id),
('group_id', '=', obj().env.ref('l10n_ar.account_group_otros_impuestos').id)
('company_id', '=', ref('company_ri')),
('group_id', '=', ref('account.{}_account_group_otros_impuestos'.format(ref('company_ri'))))
], limit=1).id,
}),
@@ -58,13 +58,13 @@
(0,0, {
'repartition_type': 'tax',
'account_id': obj().search([
('company_id', '=', obj().env.ref('l10n_ar.company_ri').id),
('group_id', '=', obj().env.ref('l10n_ar.account_group_otros_impuestos').id)
('company_id', '=', ref('company_ri')),
('group_id', '=', ref('account.{}_account_group_otros_impuestos'.format(ref('company_ri'))))
], limit=1).id,
}),
]"/>
<field name="type_tax_use">purchase</field>
<field name="tax_group_id" ref="l10n_ar.tax_group_otros_impuestos"/>
<field name="tax_group_id" model="account.tax.group" eval="ref('account.{}_tax_group_otros_impuestos'.format(ref('company_ri')))"/>
<field name="company_id" ref="l10n_ar.company_ri"/>
</record>
@@ -72,13 +72,13 @@
<function model="account.tax" name="write" context="{'active_test': False}">
<value model="account.tax" eval="obj().search([('company_id', '=', ref('company_ri')), ('tax_group_id', 'in',
[ref('tax_group_percepcion_iva'),
ref('tax_group_percepcion_ganancias'),
ref('tax_group_percepcion_iibb_caba'),
ref('tax_group_percepcion_iibb_ba'),
ref('tax_group_percepcion_iibb_co'),
ref('tax_group_percepcion_iibb_sf'),
ref('l10n_ar.tax_group_otros_impuestos')]
[ref('account.{}_tax_group_percepcion_iva'.format(ref('company_ri'))),
ref('account.{}_tax_group_percepcion_ganancias'.format(ref('company_ri'))),
ref('account.{}_tax_group_percepcion_iibb_caba'.format(ref('company_ri'))),
ref('account.{}_tax_group_percepcion_iibb_ba'.format(ref('company_ri'))),
ref('account.{}_tax_group_percepcion_iibb_co'.format(ref('company_ri'))),
ref('account.{}_tax_group_percepcion_iibb_sf'.format(ref('company_ri'))),
ref('account.{}_tax_group_otros_impuestos'.format(ref('company_ri')))]
)]).ids"/>
<value eval="{'amount': 0.1, 'active': True}"/>
</function>
-1
View File
@@ -5,7 +5,6 @@ from . import l10n_ar_afip_responsibility_type
from . import account_journal
from . import account_tax_group
from . import account_fiscal_position
from . import account_fiscal_position_template
from . import l10n_latam_document_type
from . import res_partner
from . import res_country
+24 -38
View File
@@ -1,59 +1,46 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, api, _
from odoo.exceptions import UserError
from odoo.http import request
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
def _get_fp_vals(self, company, position):
res = super()._get_fp_vals(company, position)
if company.country_id.code == "AR":
res['l10n_ar_afip_responsibility_type_ids'] = [
(6, False, position.l10n_ar_afip_responsibility_type_ids.ids)]
return res
def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
@template('ar_base', 'account.journal')
def _get_ar_account_journal(self):
""" In case of an Argentinean CoA, we modify the default values of the sales journal to be a preprinted journal"""
res = super()._prepare_all_journals(acc_template_ref, company, journals_dict=journals_dict)
if company.country_id.code == "AR":
for vals in res:
if vals['type'] == 'sale':
vals.update({
"name": "Ventas Preimpreso",
"code": "0001",
"l10n_ar_afip_pos_number": 1,
"l10n_ar_afip_pos_partner_id": company.partner_id.id,
"l10n_ar_afip_pos_system": 'II_IM',
"refund_sequence": False
})
return res
return {
'sale': {
"name": "Ventas Preimpreso",
"code": "0001",
"l10n_ar_afip_pos_number": 1,
"l10n_ar_afip_pos_partner_id": self.env.company.partner_id.id,
"l10n_ar_afip_pos_system": 'II_IM',
"refund_sequence": False,
},
}
@api.model
def _get_ar_responsibility_match(self, chart_template_id):
""" return responsibility type that match with the given chart_template_id
def _get_ar_responsibility_match(self, chart_template):
""" return responsibility type that match with the given chart_template code
"""
match = {
self.env.ref('l10n_ar.l10nar_base_chart_template').id: self.env.ref('l10n_ar.res_RM'),
self.env.ref('l10n_ar.l10nar_ex_chart_template').id: self.env.ref('l10n_ar.res_IVAE'),
self.env.ref('l10n_ar.l10nar_ri_chart_template').id: self.env.ref('l10n_ar.res_IVARI'),
'ar_base': self.env.ref('l10n_ar.res_RM'),
'ar_ex': self.env.ref('l10n_ar.res_IVAE'),
'ar_ri': self.env.ref('l10n_ar.res_IVARI'),
}
return match.get(chart_template_id)
return match.get(chart_template)
def _load(self, company):
def _load(self, template_code, company, install_demo):
""" Set companies AFIP Responsibility and Country if AR CoA is installed, also set tax calculation rounding
method required in order to properly validate match AFIP invoices.
Also, raise a warning if the user is trying to install a CoA that does not match with the defined AFIP
Responsibility defined in the company
"""
self.ensure_one()
coa_responsibility = self._get_ar_responsibility_match(self.id)
coa_responsibility = self._get_ar_responsibility_match(template_code)
if coa_responsibility:
company_responsibility = company.l10n_ar_afip_responsibility_type_id
company.write({
'l10n_ar_afip_responsibility_type_id': coa_responsibility.id,
'country_id': self.env['res.country'].search([('code', '=', 'AR')]).id,
@@ -63,11 +50,10 @@ class AccountChartTemplate(models.Model):
# the default VAT type.
company.partner_id.l10n_latam_identification_type_id = self.env.ref('l10n_ar.it_cuit')
res = super()._load(company)
res = super()._load(template_code, company, install_demo)
# If Responsable Monotributista remove the default purchase tax
if self == self.env.ref('l10n_ar.l10nar_base_chart_template') or \
self == self.env.ref('l10n_ar.l10nar_ex_chart_template'):
if template_code in ('ar_base', 'ar_ex'):
company.account_purchase_tax_id = self.env['account.tax']
return res
@@ -1,11 +0,0 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class AccountFiscalPositionTemplate(models.Model):
_inherit = 'account.fiscal.position.template'
l10n_ar_afip_responsibility_type_ids = fields.Many2many(
'l10n_ar.afip.responsibility.type', 'l10n_ar_afip_reponsibility_type_fiscal_pos_temp_rel',
string='AFIP Responsibility Types')
+1 -1
View File
@@ -268,7 +268,7 @@ class AccountMove(models.Model):
if any(tax.tax_group_id.l10n_ar_vat_afip_code and tax.tax_group_id.l10n_ar_vat_afip_code not in ['0', '1', '2'] for tax in line.tax_ids):
vat_taxable |= line
profits_tax_group = self.env.ref('l10n_ar.tax_group_percepcion_ganancias')
profits_tax_group = self.env.ref(f'account.{self.company_id.id}_tax_group_percepcion_ganancias')
return {'vat_amount': sign * sum(vat_taxes.mapped(amount_field)),
# For invoices of letter C should not pass VAT
'vat_taxable_amount': sign * sum(vat_taxable.mapped(amount_field)) if self.l10n_latam_document_type_id.l10n_ar_letter != 'C' else self.amount_untaxed,
+1 -1
View File
@@ -39,6 +39,6 @@ class ResCompany(models.Model):
def _check_accounting_info(self):
""" Do not let to change the AFIP Responsibility of the company if there is already installed a chart of
account and if there has accounting entries """
if self.env['account.chart.template'].existing_accounting(self):
if self._existing_accounting():
raise ValidationError(_(
'Could not change the AFIP Responsibility of this company because there are already accounting entries.'))
+3 -3
View File
@@ -12,7 +12,7 @@ _logger = logging.getLogger(__name__)
class TestAr(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref='l10n_ar.l10nar_ri_chart_template'):
def setUpClass(cls, chart_template_ref='ar_ri'):
super(TestAr, cls).setUpClass(chart_template_ref=chart_template_ref)
# ==== Company ====
@@ -43,7 +43,7 @@ class TestAr(AccountTestInvoicingCommon):
cls.partner_ri = cls.company_ri.partner_id
# ==== Company MONO ====
cls.company_mono = cls.setup_company_data('(AR) Monotributista (Unit Tests)', chart_template=cls.env.ref('l10n_ar.l10nar_base_chart_template'))['company']
cls.company_mono = cls.setup_company_data('(AR) Monotributista (Unit Tests)', chart_template=chart_template_ref)['company']
cls.company_mono.write({
'parent_id': cls.env.ref('base.main_company').id,
'currency_id': cls.env.ref('base.ARS').id,
@@ -719,7 +719,7 @@ class TestAr(AccountTestInvoicingCommon):
res = self.env['account.tax'].with_context(active_test=False).search([
('type_tax_use', '=', type_tax_use),
('company_id', '=', self.env.company.id),
('tax_group_id', '=', self.env.ref('l10n_ar.tax_group_' + tax_type).id)], limit=1)
('tax_group_id', '=', self.env.ref(f'account.{self.env.company.id}_tax_group_{tax_type}').id)], limit=1)
self.assertTrue(res, '%s Tax was not found' % (tax_type))
return res
@@ -17,7 +17,7 @@
</function>
<function model="product.product" name="write">
<value model="product.product" eval="obj().search([('taxes_id', '=', False)]).ids"/>
<value model="account.tax" eval="{'taxes_id': [(4, obj().search([('type_tax_use', '=', 'sale'), ('tax_group_id', '=', ref('l10n_ar.tax_group_iva_21')), ('company_id', '=', ref('l10n_ar.company_ri'))]).id)]}"/>
<value model="account.tax" eval="{'taxes_id': [(4, obj().search([('type_tax_use', '=', 'sale'), ('tax_group_id', '=', ref('account.%s_tax_group_iva_21' % ref('l10n_ar.company_ri'))), ('company_id', '=', ref('l10n_ar.company_ri'))]).id)]}"/>
</function>
</odoo>
+10 -10
View File
@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
from odoo import models
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
# Write paperformat and report template used on company
def _load(self, company):
res = super(AccountChartTemplate, self)._load(company)
if self == self.env.ref('l10n_at.l10n_at_chart_template'):
company.write({
'external_report_layout_id': self.env.ref('l10n_din5008.external_layout_din5008').id,
'paperformat_id': self.env.ref('l10n_din5008.paperformat_euro_din').id
})
return res
@template('at', 'res.company')
def _get_at_res_company(self):
company_data = super()._get_at_res_company()
company_data[self.env.company.id].update({
'external_report_layout_id': 'l10n_din5008.external_layout_din5008',
'paperformat_id': 'l10n_din5008.paperformat_euro_din',
})
return company_data
+1 -5
View File
@@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
def load_translations(env):
env.ref('l10n_be.l10nbe_chart_template').process_coa_translations()
from . import demo
-2
View File
@@ -57,9 +57,7 @@ Wizards provided by this module:
'data/menuitem_data.xml',
],
'demo': [
'demo/l10n_be_demo.xml',
'demo/demo_company.xml',
],
'post_init_hook': 'load_translations',
'license': 'LGPL-3',
}
+1
View File
@@ -0,0 +1 @@
from . import account_demo
+20
View File
@@ -0,0 +1,20 @@
from odoo import api, models, Command
class AccountChartTemplate(models.AbstractModel):
_inherit = "account.chart.template"
@api.model
def _get_demo_data(self, company=False):
demo_data = super()._get_demo_data(company)
if company.account_fiscal_country_id.code == 'BE':
cid = company.id
demo_data['account.account'] = {
f"account.{cid}_a100": {'tag_ids': [Command.link(self.env.ref('account.demo_capital_account').id)]},
f"account.{cid}_a300": {'tag_ids': [Command.link(self.env.ref('account.demo_stock_account').id)]},
f"account.{cid}_a7600": {'tag_ids': [Command.link(self.env.ref('account.demo_sale_of_land_account').id)]},
f"account.{cid}_a6201": {'tag_ids': [Command.link(self.env.ref('account.demo_ceo_wages_account').id)]},
f"account.{cid}_a242": {'tag_ids': [Command.link(self.env.ref('account.demo_office_furniture_account').id)]},
}
return demo_data
-38
View File
@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="l10n_be.a100" model="account.account.template">
<field name="tag_ids" eval="[(4, ref('account.demo_capital_account'))]"/>
</record>
<record id="l10n_be.a300" model="account.account.template">
<field name="tag_ids" eval="[(4, ref('account.demo_stock_account'))]"/>
</record>
<record id="l10n_be.a7600" model="account.account.template">
<field name="tag_ids" eval="[(4, ref('account.demo_sale_of_land_account'))]"/>
</record>
<record id="l10n_be.a6201" model="account.account.template">
<field name="tag_ids" eval="[(4, ref('account.demo_ceo_wages_account'))]"/>
</record>
<record id="l10n_be.a242" model="account.account.template">
<field name="tag_ids" eval="[(4, ref('account.demo_office_furniture_account'))]"/>
</record>
<function model="account.account" name="write">
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a100').code)]"/>
<value eval="{'tag_ids': [(4, ref('account.demo_capital_account'))]}"/>
</function>
<function model="account.account" name="write">
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a300').code)]"/>
<value eval="{'tag_ids': [(4, ref('account.demo_stock_account'))]}"/>
</function>
<function model="account.account" name="write">
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a7600').code)]"/>
<value eval="{'tag_ids': [(4, ref('account.demo_sale_of_land_account'))]}"/>
</function>
<function model="account.account" name="write">
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a6201').code)]"/>
<value eval="{'tag_ids': [(4, ref('account.demo_ceo_wages_account'))]}"/>
</function>
<function model="account.account" name="write">
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a242').code)]"/>
<value eval="{'tag_ids': [(4, ref('account.demo_office_furniture_account'))]}"/>
</function>
</odoo>
@@ -2,16 +2,15 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
@api.model
def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
journal_data = super(AccountChartTemplate, self)._prepare_all_journals(
acc_template_ref, company, journals_dict)
for journal in journal_data:
if journal['type'] in ('sale', 'purchase') and company.country_id.code == "BE":
journal.update({'refund_sequence': True})
return journal_data
@template('be', 'account.journal')
def _get_be_account_journal(self):
return {
'sale': {'refund_sequence': True},
'purchase': {'refund_sequence': True},
}
-3
View File
@@ -1,5 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
def load_translations(env):
env.ref('l10n_bg.l10n_bg_chart_template').process_coa_translations()
-1
View File
@@ -24,6 +24,5 @@
'demo': [
'demo/demo_company.xml',
],
'post_init_hook': 'load_translations',
'license': 'LGPL-3',
}
-3
View File
@@ -1,5 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
def load_translations(env):
env.ref('l10n_bo.bo_chart_template').process_coa_translations()
-1
View File
@@ -30,5 +30,4 @@ Plan contable boliviano e impuestos de acuerdo a disposiciones vigentes
'demo/demo_company.xml',
],
'license': 'LGPL-3',
'post_init_hook': 'load_translations',
}
-5
View File
@@ -2,8 +2,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (C) 2009 Renato Lima - Akretion
from . import models
def load_translations(env):
env.ref("l10n_br.l10n_br_account_chart_template").process_coa_translations()
-1
View File
@@ -63,5 +63,4 @@ come with any additional paid permission for online use of 'private modules'.
'demo/demo_company.xml',
],
'license': 'LGPL-3',
'post_init_hook': 'load_translations',
}
+1 -3
View File
@@ -4,8 +4,6 @@
# Copyright (C) 2009 Renato Lima - Akretion
from . import account
from . import account_fiscal_position_template
from . import account_fiscal_position
from . import account_chart_template
from . import res_company
from . import res_partner
from . import res_company
-12
View File
@@ -4,18 +4,6 @@
from odoo import fields, models
class AccountTaxTemplate(models.Model):
""" Add fields used to define some brazilian taxes """
_inherit = 'account.tax.template'
tax_discount = fields.Boolean(string='Discount this Tax in Price',
help="Mark it for (ICMS, PIS e etc.).")
base_reduction = fields.Float(string='Redution', digits=0, required=True,
help="Um percentual decimal em % entre 0-1.", default=0)
amount_mva = fields.Float(string='MVA Percent', digits=0, required=True,
help="Um percentual decimal em % entre 0-1.", default=0)
class AccountTax(models.Model):
""" Add fields used to define some brazilian taxes """
_inherit = 'account.tax'
@@ -1,13 +0,0 @@
# 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 _get_fp_vals(self, company, position):
res = super()._get_fp_vals(company, position)
if company.country_id.code == 'BR':
res['l10n_br_fp_type'] = position['l10n_br_fp_type']
return res
@@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class AccountFiscalPositionTemplate(models.Model):
_inherit = 'account.fiscal.position.template'
l10n_br_fp_type = fields.Selection(
selection=[
('internal', 'Internal'),
('ss_nnm', 'South/Southeast selling to North/Northeast/Midwest'),
('interstate', 'Other interstate'),
],
string='Interstate Fiscal Position Type',
)
-16
View File
@@ -1,21 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Include fields created in account.tax and account.tax.template form views -->
<record model="ir.ui.view" id="view_l10n_br_account_tax_template_form">
<field name="name">l10n_br_account.tax.template.form</field>
<field name="model">account.tax.template</field>
<field name="inherit_id" ref="account.view_account_tax_template_form"/>
<field name="arch" type="xml">
<field position="after" name="price_include">
<field name="tax_discount"/>
</field>
<field position="after" name="tax_discount">
<field name="base_reduction" widget="monetary"/>
<field name="amount_mva" widget="monetary"/>
</field>
</field>
</record>
<record model="ir.ui.view" id="view_l10n_br_account_tax_form">
<field name="name">l10n_br_account.tax.form</field>
<field name="model">account.tax</field>
-4
View File
@@ -4,7 +4,3 @@
# Copyright (C) 2010 Savoir-faire Linux (<https://www.savoirfairelinux.com>).
from . import models
def load_translations(env):
env.ref('l10n_ca.ca_en_chart_template_en').process_coa_translations()
-1
View File
@@ -55,6 +55,5 @@ position.
'demo': [
'demo/demo_company.xml',
],
'post_init_hook': 'load_translations',
'license': 'LGPL-3',
}
-5
View File
@@ -6,10 +6,6 @@ from . import report
from . import wizard
def load_translations(env):
env.ref('l10n_ch.l10nch_chart_template').process_coa_translations()
def init_settings(env):
'''If the company is localized in Switzerland, activate the cash rounding by default.
'''
@@ -24,5 +20,4 @@ def init_settings(env):
def post_init(env):
load_translations(env)
init_settings(env)
+10 -10
View File
@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
from odoo import models
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
# Write paperformat and report template used on company
def _load(self, company):
res = super(AccountChartTemplate, self)._load(company)
if self == self.env.ref('l10n_ch.l10nch_chart_template'):
company.write({
'external_report_layout_id': self.env.ref('l10n_din5008.external_layout_din5008').id,
'paperformat_id': self.env.ref('l10n_din5008.paperformat_euro_din').id
})
return res
@template('ch', 'res.company')
def _get_ch_res_company(self):
company_data = super()._get_ch_res_company()
company_data[self.env.company.id].update({
'external_report_layout_id': 'l10n_din5008.external_layout_din5008',
'paperformat_id': 'l10n_din5008.paperformat_euro_din',
})
return company_data
+2 -1
View File
@@ -388,5 +388,6 @@ class ResPartnerBank(models.Model):
@api.model
def _get_available_qr_methods(self):
rslt = super()._get_available_qr_methods()
rslt.append(('ch_qr', _("Swiss QR bill"), 10))
if self.env.company.country_id.code == 'CH':
rslt.append(('ch_qr', _("Swiss QR bill"), 10))
return rslt
+5 -2
View File
@@ -12,11 +12,11 @@ class TestSwissQRCode(AccountTestInvoicingCommon):
"""
@classmethod
def setUpClass(cls, chart_template_ref='l10n_ch.l10nch_chart_template'):
def setUpClass(cls, chart_template_ref='ch'):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.company_data['company'].qr_code = True
cls.company_data['company'].country_id = None
cls.company_data['company'].country_id = cls.env.ref('base.ch')
cls.swiss_iban = cls.env['res.partner.bank'].create({
'acc_number': 'CH15 3881 5158 3845 3843 7',
@@ -58,6 +58,9 @@ class TestSwissQRCode(AccountTestInvoicingCommon):
"""
self.ch_qr_invoice.qr_code_method = 'ch_qr'
# flush manually to have the right env to get possible values of `qr_code_method`
self.env.flush_all()
# First check with a regular IBAN
with self.assertRaises(UserError, msg="It shouldn't be possible to generate a Swiss QR-code for partners without a complete Swiss address."):
self.ch_qr_invoice._generate_qr_code()
@@ -13,7 +13,7 @@ class TestGenISRReference(AccountTestInvoicingCommon):
"""Check condition of generation of and content of the structured ref"""
@classmethod
def setUpClass(cls, chart_template_ref="l10n_ch.l10nch_chart_template"):
def setUpClass(cls, chart_template_ref="ch"):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.bank = cls.env["res.bank"].create(
@@ -9,7 +9,7 @@ from odoo.exceptions import ValidationError
class ISRTest(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref='l10n_ch.l10nch_chart_template'):
def setUpClass(cls, chart_template_ref='ch'):
super().setUpClass(chart_template_ref=chart_template_ref)
def print_isr(self, invoice):
@@ -12,7 +12,7 @@ _logger = logging.getLogger(__name__)
class QRPrintTest(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref='l10n_ch.l10nch_chart_template'):
def setUpClass(cls, chart_template_ref='ch'):
super().setUpClass(chart_template_ref=chart_template_ref)
# the partner must be located in Switzerland.
cls.partner = cls.env['res.partner'].create({
+1 -1
View File
@@ -14,7 +14,7 @@ QR_IBAN = 'CH21 3080 8001 2345 6782 7'
class TestSwissQR(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref='l10n_ch.l10nch_chart_template'):
def setUpClass(cls, chart_template_ref='ch'):
super().setUpClass(chart_template_ref=chart_template_ref)
def setUp(self):
-5
View File
@@ -2,8 +2,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
from . import demo
from odoo import api, SUPERUSER_ID
def load_translations(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
env.ref('l10n_cl.cl_chart_template').process_coa_translations()
+22 -24
View File
@@ -6,38 +6,36 @@ from odoo import api, models
_logger = logging.getLogger(__name__)
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = "account.chart.template"
@api.model
def _get_demo_data(self):
yield ('res.partner', {
'base.res_partner_12': {
'l10n_cl_sii_taxpayer_type': '4',
},
'base.res_partner_2': {
'l10n_cl_sii_taxpayer_type': '4',
},
})
yield ('l10n_latam.document.type', {
'l10n_cl.dc_fe_dte': {'active': True},
})
for model, data in super()._get_demo_data():
yield model, data
def _get_demo_data(self, company=False):
data = super()._get_demo_data(company)
if company.account_fiscal_country_id.code == "CL":
data.setdefault('res.partner', {})
data['res.partner'].setdefault('base.res_partner_2', {})
data['res.partner']['base.res_partner_2']['l10n_cl_sii_taxpayer_type'] = '4'
data['res.partner'].setdefault('base.res_partner_12', {})
data['res.partner']['base.res_partner_12']['l10n_cl_sii_taxpayer_type'] = '4'
data.setdefault('l10n_latam.document.type', {})
data['l10n_latam.document.type'].setdefault('l10n_cl.dc_fe_dte', {})
data['l10n_latam.document.type']['l10n_cl.dc_fe_dte']['active'] = True
return data
@api.model
def _get_demo_data_move(self):
def _get_demo_data_move(self, company=False):
ref = self.env.ref
cid = self.env.company.id
model, data = super()._get_demo_data_move()
if self.env.company.account_fiscal_country_id.code == "CL":
move_data = super()._get_demo_data_move(company)
if company.account_fiscal_country_id.code == "CL":
foreign = ref('l10n_cl.dc_fe_dte').id
self.env['account.journal'].search([
('type', '=', 'purchase'),
('company_id', '=', self.env.company.id),
]).l10n_latam_use_documents = False
data[f'{cid}_demo_invoice_1']['l10n_latam_document_type_id'] = foreign
data[f'{cid}_demo_invoice_2']['l10n_latam_document_type_id'] = foreign
data[f'{cid}_demo_invoice_3']['l10n_latam_document_type_id'] = foreign
data[f'{cid}_demo_invoice_followup']['l10n_latam_document_type_id'] = foreign
return model, data
move_data['demo_invoice_1']['l10n_latam_document_type_id'] = foreign
move_data['demo_invoice_2']['l10n_latam_document_type_id'] = foreign
move_data['demo_invoice_3']['l10n_latam_document_type_id'] = foreign
move_data['demo_invoice_followup']['l10n_latam_document_type_id'] = foreign
return move_data
+10 -10
View File
@@ -1,15 +1,15 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# -*- coding: utf-8 -*-
from odoo import models
from odoo.http import request
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_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
@template('cl', 'res.company')
def _get_cl_res_company(self):
company_data = super()._get_cl_res_company()
company_data[self.env.company.id].update({
'tax_calculation_rounding_method': 'round_globally',
})
return company_data
-18
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
-22
View File
@@ -22,27 +22,5 @@
</field>
</field>
</record>
<record id="view_account_tax_template_form" model="ir.ui.view">
<field name="name">account.tax.template.form</field>
<field name="model">account.tax.template</field>
<field name="inherit_id" ref="account.view_account_tax_template_form"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="l10n_cl_sii_code" options="{'format': false}" optional="hide"/>
</field>
</field>
</record>
<record id="view_account_tax_template_tree" model="ir.ui.view">
<field name="name">account.tax.template.sii.tree</field>
<field name="model">account.tax.template</field>
<field name="inherit_id" ref="account.view_account_tax_template_tree" />
<field name="arch" type="xml">
<field name="name" position="before">
<field name="l10n_cl_sii_code" options="{'format': false}" optional="hide"/>
</field>
</field>
</record>
</data>
</odoo>
-3
View File
@@ -4,6 +4,3 @@
# Copyright (C) 2007-2014 Jeff Wang(<http://jeff@osbzr.com>).
from . import models
def load_translations(env):
env.ref('l10n_cn.l10n_chart_china_small_business').process_coa_translations()
+1 -2
View File
@@ -32,7 +32,7 @@ State Data/省份数据
修改小企业会计税率
We added the option to print a voucher which will also
We added the option to print a voucher which will also
print the amount in words (special Chinese characters for numbers)
correctly when the cn2an library is installed. (e.g. with pip3 install cn2an)
""",
@@ -51,6 +51,5 @@ correctly when the cn2an library is installed. (e.g. with pip3 install cn2an)
'demo': [
'demo/demo_company.xml',
],
'post_init_hook': 'load_translations',
'license': 'LGPL-3',
}
-3
View File
@@ -9,6 +9,3 @@
# Humberto Ochoa (Vauxoo)
from . import models
def load_translations(env):
env.ref('l10n_co.l10n_co_chart_template_generic').process_coa_translations()
-1
View File
@@ -32,6 +32,5 @@
'demo': [
'demo/demo_company.xml',
],
'post_init_hook': 'load_translations',
'license': 'LGPL-3',
}
+6 -9
View File
@@ -7,17 +7,17 @@
# l10n_cr_account
# First author: Carlos Vásquez <carlos.vasquez@clearcorp.co.cr> (ClearCorp S.A.)
# Copyright (c) 2010-TODAY ClearCorp S.A. (http://clearcorp.co.cr). All rights reserved.
#
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other materials
# provided with the distribution.
#
#
# THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
@@ -27,12 +27,9 @@
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#
# The views and conclusions contained in the software and documentation are those of the
# authors and should not be interpreted as representing official policies, either expressed
# or implied, of ClearCorp S.A..
#
#
##############################################################################
def load_translations(env):
env.ref('l10n_cr.account_chart_template_0').process_coa_translations()
-1
View File
@@ -16,7 +16,6 @@ German accounting chart and localization.
'base_iban',
'base_vat',
'l10n_din5008',
'l10n_multilang',
],
'data': [
'data/account_account_tags_data.xml',
+12 -14
View File
@@ -1,19 +1,17 @@
# -*- coding: utf-8 -*-
from odoo import api, models
from odoo import models
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.Model):
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
# Write paperformat and report template used on company
def _load(self, company):
res = super(AccountChartTemplate, self)._load(company)
if self in [
self.env.ref('l10n_de_skr03.l10n_de_chart_template', raise_if_not_found=False),
self.env.ref('l10n_de_skr04.l10n_chart_de_skr04', raise_if_not_found=False)
]:
company.write({
'external_report_layout_id': self.env.ref('l10n_din5008.external_layout_din5008').id,
'paperformat_id': self.env.ref('l10n_din5008.paperformat_euro_din').id
})
return res
@template('de_skr03', 'res.company')
@template('de_skr04', 'res.company')
def _get_de_res_company(self):
return {
self.env.company.id: {
'external_report_layout_id': 'l10n_din5008.external_layout_din5008',
'paperformat_id': 'l10n_din5008.paperformat_euro_din',
}
}

Some files were not shown because too many files have changed in this diff Show More