[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:
+23
-38
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
|
+139
-136
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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):
|
||||
|
||||
+677
-1559
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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'
|
||||
|
||||
@@ -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']),
|
||||
])
|
||||
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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>
|
||||
@@ -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 & 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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
@@ -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,
|
||||
|
||||
@@ -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.'))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import account_demo
|
||||
@@ -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
|
||||
@@ -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},
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -24,6 +24,5 @@
|
||||
'demo': [
|
||||
'demo/demo_company.xml',
|
||||
],
|
||||
'post_init_hook': 'load_translations',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
@@ -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,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()
|
||||
|
||||
@@ -55,6 +55,5 @@ position.
|
||||
'demo': [
|
||||
'demo/demo_company.xml',
|
||||
],
|
||||
'post_init_hook': 'load_translations',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -32,6 +32,5 @@
|
||||
'demo': [
|
||||
'demo/demo_company.xml',
|
||||
],
|
||||
'post_init_hook': 'load_translations',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -16,7 +16,6 @@ German accounting chart and localization.
|
||||
'base_iban',
|
||||
'base_vat',
|
||||
'l10n_din5008',
|
||||
'l10n_multilang',
|
||||
],
|
||||
'data': [
|
||||
'data/account_account_tags_data.xml',
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user