[IMP] account,*: manage subsidiary companies

Allow sharing records between company
* accounts
* taxes
* fiscal positions
* products
* ...and some related models

These records can be read and used in children companies.

This can be used to
* have different branding for different businesses
* allow more complex security rules
* consolidate branches differently
* manage different tax reports with different tax ids in the same
  country

task-3371677

closes odoo/odoo#125642

Related: odoo/enterprise#43215
Signed-off-by: Quentin De Paoli <qdp@odoo.com>
This commit is contained in:
william-andre
2023-07-20 11:49:06 +02:00
committed by qdp-odoo
parent 0eff29409d
commit 0479b2b594
154 changed files with 1225 additions and 700 deletions
+13 -5
View File
@@ -50,7 +50,7 @@ class AccountChartTemplate(models.AbstractModel):
@api.model
def _get_demo_data_products(self, company=False):
prod_templates = self.env['product.product'].search(['|', ('company_id', '=', self.env.company.id), ('company_id', '=', False)])
prod_templates = self.env['product.product'].search(self.env['product.product']._check_company_domain(company))
if self.env.company.account_sale_tax_id:
prod_templates.write({'taxes_id': [Command.link(self.env.company.account_sale_tax_id.id)]})
if self.env.company.account_purchase_tax_id:
@@ -138,7 +138,10 @@ class AccountChartTemplate(models.AbstractModel):
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)],
domain=[
*self.env['account.journal']._check_company_domain(cid),
('type', '=', 'bank'),
],
limit=1,
)
return {
@@ -168,7 +171,10 @@ class AccountChartTemplate(models.AbstractModel):
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)],
domain=[
*self.env['account.journal']._check_company_domain(cid),
('type', '=', 'bank'),
],
limit=1,
)
return {
@@ -350,8 +356,10 @@ class AccountChartTemplate(models.AbstractModel):
('module', '=like', 'l10n%')
], limit=1).res_id)
or self.env['account.account'].search([
*self.env['account.account']._check_company_domain(company),
('account_type', '=', account_type),
('company_id', '=', company.id)
], limit=1)
or self.env['account.account'].search([('company_id', '=', company.id)], limit=1)
or self.env['account.account'].search([
*self.env['account.account']._check_company_domain(company),
], limit=1)
)
+62 -51
View File
@@ -16,6 +16,7 @@ class AccountAccount(models.Model):
_description = "Account"
_order = "code, company_id"
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
@api.constrains('account_type', 'reconcile')
def _check_reconcile(self):
@@ -92,13 +93,18 @@ class AccountAccount(models.Model):
check_company=True,
context={'append_type_to_tax_name': True})
note = fields.Text('Internal Notes', tracking=True)
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True,
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=False,
default=lambda self: self.env.company)
tag_ids = fields.Many2many('account.account.tag', 'account_account_account_tag', string='Tags', help="Optional tags you may want to assign for custom reporting", ondelete='restrict')
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, 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.")
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.",
check_company=True,
)
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')
opening_balance = fields.Monetary(string="Opening Balance", compute='_compute_opening_debit_credit', inverse='_set_opening_balance')
@@ -106,14 +112,25 @@ class AccountAccount(models.Model):
current_balance = fields.Float(compute='_compute_current_balance')
related_taxes_amount = fields.Integer(compute='_compute_related_taxes_amount')
_sql_constraints = [
('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company!')
]
non_trade = fields.Boolean(default=False,
help="If set, this account will belong to Non Trade Receivable/Payable in reports and filters.\n"
"If not, this account will belong to Trade Receivable/Payable in reports and filters.")
@api.constrains('company_id', 'code')
def _constrains_code(self):
domains = []
for record in self:
domains.append([
('company_id', 'child_of', record.company_id.root_id.id),
('code', '=', record.code),
('id', '!=', record.id),
])
if duplicates := self.search(expression.OR(domains)):
raise ValidationError(
_("The code of the account must be unique per company!")
+ "\n" + "\n".join(f"- {duplicate.code} in {duplicate.company_id.name}" for duplicate in duplicates)
)
@api.constrains('reconcile', 'internal_group', 'tax_ids')
def _constrains_reconcile(self):
for record in self:
@@ -213,20 +230,12 @@ class AccountAccount(models.Model):
@api.constrains('company_id')
def _check_company_consistency(self):
if not self:
return
self.env['account.move.line'].flush_model(['account_id', 'company_id'])
self.flush_recordset(['company_id'])
self._cr.execute('''
SELECT line.id
FROM account_move_line line
JOIN account_account account ON account.id = line.account_id
WHERE line.account_id IN %s
AND line.company_id != account.company_id
''', [tuple(self.ids)])
if self._cr.fetchone():
raise UserError(_("You can't change the company of your account since there are some journal items linked to it."))
for company, accounts in tools.groupby(self, lambda account: account.company_id):
if self.env['account.move.line'].search([
('account_id', 'in', [account.id for account in accounts]),
'!', ('company_id', 'child_of', company.id)
], limit=1):
raise UserError(_("You can't change the company of your account since there are some journal items linked to it."))
@api.constrains('account_type')
def _check_account_type_sales_purchase_journal(self):
@@ -347,7 +356,7 @@ class AccountAccount(models.Model):
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)
rec = self.search([('code', '=', new_code), ('company_id', 'child_of', company.root_id.id)], limit=1)
if not rec:
return new_code
raise UserError(_('Cannot generate an unused account code.'))
@@ -531,28 +540,31 @@ class AccountAccount(models.Model):
:param limit: the maximum number of accounts to retrieve
:returns: List of account ids, ordered by frequency (from most to least frequent)
"""
join = "INNER JOIN" if filter_never_user_accounts else "LEFT JOIN"
limit = f"LIMIT {limit:d}" if limit else ""
where_internal_group = ""
domain = [
*self.env['account.move.line']._check_company_domain(company_id),
('partner_id', '=', partner_id),
('account_id.deprecated', '=', False),
('date', '>=', fields.Date.add(fields.Date.today(), days=-365 * 2)),
]
if move_type in self.env['account.move'].get_inbound_types(include_receipts=True):
where_internal_group = "AND account.internal_group = 'income'"
domain.append(('account_id.internal_group', '=', 'income'))
elif move_type in self.env['account.move'].get_outbound_types(include_receipts=True):
where_internal_group = "AND account.internal_group = 'expense'"
domain.append(('account_id.internal_group', '=', 'expense'))
query = self.env['account.move.line']._where_calc(domain)
if not filter_never_user_accounts:
_kind, rhs_table, condition, condition_params = query._joins['account_move_line__account_id']
query._joins['account_move_line__account_id'] = ("RIGHT JOIN", rhs_table, condition, condition_params)
from_clause, where_clause, params = query.get_sql()
self._cr.execute(f"""
SELECT account.id
FROM account_account account
{join} account_move_line aml
ON aml.account_id = account.id
AND aml.partner_id = %s
AND account.deprecated = FALSE
AND account.company_id = aml.company_id
AND aml.date >= now() - interval '2 years'
WHERE account.company_id = %s
{where_internal_group}
GROUP BY account.id
ORDER BY COUNT(aml.id) DESC, account.code
{limit}
""", [partner_id, company_id])
SELECT account_move_line__account_id.id
FROM {from_clause}
WHERE {where_clause}
GROUP BY account_move_line__account_id.id
ORDER BY COUNT(account_move_line.id) DESC, account_move_line__account_id.code
{f"LIMIT {limit:d}" if limit else ""}
""", params)
return [r[0] for r in self._cr.fetchall()]
@api.model
@@ -610,8 +622,10 @@ class AccountAccount(models.Model):
try:
default['code'] = (str(int(self.code) + 10) or '').zfill(len(self.code))
default.setdefault('name', _("%s (copy)") % (self.name or ''))
while self.env['account.account'].search([('code', '=', default['code']),
('company_id', '=', default.get('company_id', False) or self.company_id.id)], limit=1):
while self.env['account.account'].search([
*self.env['account.account']._check_company_domain(default.get('company_id', False) or self.company_id),
('code', '=', default['code']),
], limit=1):
default['code'] = (str(int(default['code']) + 10) or '')
default['name'] = _("%s (copy)") % (self.name or '')
except ValueError:
@@ -715,12 +729,6 @@ class AccountAccount(models.Model):
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):
move_lines = self.env['account.move.line'].search([('account_id', 'in', self.ids)], limit=1)
for account in self:
if (account.company_id.id != vals['company_id']) and move_lines:
raise UserError(_('You cannot change the owner company of an account that already contains journal items.'))
if 'reconcile' in vals:
if vals['reconcile']:
self.filtered(lambda r: not r.reconcile)._toggle_reconcile_to_true()
@@ -799,8 +807,10 @@ class AccountGroup(models.Model):
_description = 'Account Group'
_parent_store = True
_order = 'code_prefix_start'
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
parent_id = fields.Many2one('account.group', index=True, ondelete='cascade', readonly=True)
parent_id = fields.Many2one('account.group', index=True, ondelete='cascade', readonly=True, check_company=True)
parent_path = fields.Char(index=True, unaccent=False)
name = fields.Char(required=True, translate=True)
code_prefix_start = fields.Char(compute='_compute_code_prefix_start', readonly=False, store=True, precompute=True)
@@ -906,7 +916,7 @@ class AccountGroup(models.Model):
"""
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
company_ids = account_ids.company_id.root_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:
return
@@ -925,10 +935,11 @@ class AccountGroup(models.Model):
account.id AS account_id,
agroup.id AS group_id
FROM account_account account
JOIN res_company account_company ON account_company.id = account.company_id
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
AND agroup.company_id = split_part(account_company.parent_path, '/', 1)::int
WHERE account.company_id IN %s {account_where_clause}
ORDER BY account.id, char_length(agroup.code_prefix_start) DESC, agroup.id
)
@@ -14,6 +14,7 @@ class AccountAnalyticDistributionModel(models.Model):
'product.product',
string='Product',
ondelete='cascade',
check_company=True,
help="Select a product for which the analytic distribution will be used (e.g. create new customer invoice or Sales order if we select this product, it will automatically take this as an analytic account)",
)
product_categ_id = fields.Many2one(
@@ -16,7 +16,8 @@ class AccountAnalyticLine(models.Model):
'account.account',
string='Financial Account',
ondelete='restrict',
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
domain="[('deprecated', '=', False)]",
check_company=True,
compute='_compute_general_account_id', store=True, readonly=False
)
journal_id = fields.Many2one(
@@ -453,7 +453,7 @@ class AccountBankStatementLine(models.Model):
# Base domain.
('display_type', 'not in', ('line_section', 'line_note')),
('parent_state', '=', 'posted'),
('company_id', '=', self.company_id.id),
('company_id', 'child_of', self.company_id.root_id.id),
# Reconciliation domain.
('reconciled', '=', False),
('account_id.reconcile', '=', True),
@@ -469,8 +469,8 @@ class AccountBankStatementLine(models.Model):
def _get_default_journal(self):
journal_type = self.env.context.get('journal_type', 'bank')
return self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(self.env.company),
('type', '=', journal_type),
('company_id', '=', self.env.company.id)
], limit=1)
@api.model
+13 -2
View File
@@ -14,6 +14,7 @@ class AccountCashRounding(models.Model):
"""
_name = 'account.cash.rounding'
_description = 'Account Cash Rounding'
_check_company_auto = True
name = fields.Char(string='Name', translate=True, required=True)
rounding = fields.Float(string='Rounding Precision', required=True, default=0.01,
@@ -21,8 +22,18 @@ class AccountCashRounding(models.Model):
strategy = fields.Selection([('biggest_tax', 'Modify tax amount'), ('add_invoice_line', 'Add a rounding line')],
string='Rounding Strategy', default='add_invoice_line', required=True,
help='Specify which way will be used to round the invoice amount to the rounding precision')
profit_account_id = fields.Many2one('account.account', string='Profit Account', company_dependent=True, domain="[('deprecated', '=', False), ('company_id', '=', current_company_id)]")
loss_account_id = fields.Many2one('account.account', string='Loss Account', company_dependent=True, domain="[('deprecated', '=', False), ('company_id', '=', current_company_id)]")
profit_account_id = fields.Many2one(
'account.account',
string='Profit Account',
company_dependent=True,
domain="[('deprecated', '=', False)]",
)
loss_account_id = fields.Many2one(
'account.account',
string='Loss Account',
company_dependent=True,
domain="[('deprecated', '=', False)]",
)
rounding_method = fields.Selection(string='Rounding Method', required=True,
selection=[('UP', 'UP'), ('DOWN', 'DOWN'), ('HALF-UP', 'HALF-UP')],
default='HALF-UP', help='The tie-breaking rule used for float rounding operations')
+28 -30
View File
@@ -2,7 +2,7 @@
from odoo import api, Command, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.addons.base.models.res_bank import sanitize_account_number
from odoo.tools import remove_accents
from odoo.tools import remove_accents, groupby
from collections import defaultdict
import logging
import re
@@ -21,10 +21,11 @@ class AccountJournalGroup(models.Model):
_name = 'account.journal.group'
_description = "Account Journal Group"
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
name = fields.Char("Journal Group", required=True, translate=True)
company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
excluded_journal_ids = fields.Many2many('account.journal', string="Excluded Journals", domain="[('company_id', '=', company_id)]",
excluded_journal_ids = fields.Many2many('account.journal', string="Excluded Journals",
check_company=True)
sequence = fields.Integer(default=10)
@@ -38,6 +39,7 @@ class AccountJournal(models.Model):
_order = 'sequence, type, code'
_inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
_rec_names_search = ['name', 'code']
def _default_inbound_payment_methods(self):
@@ -86,20 +88,20 @@ class AccountJournal(models.Model):
"Select 'General' for miscellaneous operations journals.")
account_control_ids = fields.Many2many('account.account', 'journal_account_control_rel', 'journal_id', 'account_id', string='Allowed accounts',
check_company=True,
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', '!=', 'off_balance')]")
domain="[('deprecated', '=', False), ('account_type', '!=', 'off_balance')]")
default_account_type = fields.Char(string='Default Account Type', compute="_compute_default_account_type")
default_account_id = fields.Many2one(
comodel_name='account.account', check_company=True, copy=False, ondelete='restrict',
string='Default Account',
domain="[('deprecated', '=', False), ('company_id', '=', company_id),"
domain="[('deprecated', '=', False),"
"('account_type', '=', default_account_type), ('account_type', 'not in', ('asset_receivable', 'liability_payable'))]")
suspense_account_id = fields.Many2one(
comodel_name='account.account', check_company=True, ondelete='restrict', readonly=False, store=True,
compute='_compute_suspense_account_id',
help="Bank statements transactions will be posted on the suspense account until the final reconciliation "
"allowing finding the right account.", string='Suspense Account',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), \
('account_type', '=', 'asset_current')]")
domain="[('deprecated', '=', False), ('account_type', '=', 'asset_current')]",
)
restrict_mode_hash_table = fields.Boolean(string="Lock Posted Entries with Hash",
help="If ticked, the accounting entry or invoice receives a hash as soon as it is posted and cannot be modified anymore.")
sequence = fields.Integer(help='Used to order Journals in the dashboard view', default=10)
@@ -158,13 +160,13 @@ class AccountJournal(models.Model):
comodel_name='account.account', check_company=True,
help="Used to register a profit when the ending balance of a cash register differs from what the system computes",
string='Profit Account',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), \
domain="[('deprecated', '=', False), \
('account_type', 'in', ('income', 'income_other'))]")
loss_account_id = fields.Many2one(
comodel_name='account.account', check_company=True,
help="Used to register a loss when the ending balance of a cash register differs from what the system computes",
string='Loss Account',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), \
domain="[('deprecated', '=', False), \
('account_type', '=', 'expense')]")
# Bank journals fields
@@ -173,7 +175,7 @@ class AccountJournal(models.Model):
string="Bank Account",
ondelete='restrict', copy=False,
check_company=True,
domain="[('partner_id','=', company_partner_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]")
domain="[('partner_id','=', company_partner_id)]")
bank_statements_source = fields.Selection(selection=_get_bank_statements_available_sources, string='Bank Feeds', default='undefined', help="Defines how the bank statements will be registered")
bank_acc_number = fields.Char(related='bank_account_id.acc_number', readonly=False)
bank_id = fields.Many2one('res.bank', related='bank_account_id.bank_id', readonly=False)
@@ -191,7 +193,6 @@ class AccountJournal(models.Model):
alias_name = fields.Char('Alias Name', copy=False, compute='_compute_alias_name', inverse='_inverse_type', help="It creates draft invoices and bills by sending an email.")
journal_group_ids = fields.Many2many('account.journal.group',
domain="[('company_id', '=', company_id)]",
check_company=True,
string="Journal Groups")
@@ -432,20 +433,12 @@ class AccountJournal(models.Model):
@api.constrains('company_id')
def _check_company_consistency(self):
if not self:
return
self.env['account.move'].flush_model(['company_id', 'journal_id'])
self.flush_recordset(['company_id'])
self._cr.execute('''
SELECT move.id
FROM account_move move
JOIN account_journal journal ON journal.id = move.journal_id
WHERE move.journal_id IN %s
AND move.company_id != journal.company_id
''', [tuple(self.ids)])
if self._cr.fetchone():
raise UserError(_("You can't change the company of your journal since there are some journal entries linked to it."))
for company, journals in groupby(self, lambda journal: journal.company_id):
if self.env['account.move'].search([
('journal_id', 'in', [journal.id for journal in journals]),
'!', ('company_id', 'child_of', company.id)
], limit=1):
raise UserError(_("You can't change the company of your journal since there are some journal entries linked to it."))
@api.constrains('type', 'default_account_id')
def _check_type_default_account_id_type(self):
@@ -529,7 +522,10 @@ class AccountJournal(models.Model):
default = dict(default or {})
# Find a unique code for the copied journal
read_codes = self.env['account.journal'].with_context(active_test=False).search_read([('company_id', '=', self.company_id.id)], ['code'])
read_codes = self.env['account.journal'].with_context(active_test=False).search_read(
self.env['account.journal']._check_company_domain(self.company_id),
['code'],
)
all_journal_codes = {code_data['code'] for code_data in read_codes}
copy_code = self.code
@@ -556,8 +552,6 @@ class AccountJournal(models.Model):
for journal in self:
company = journal.company_id
if ('company_id' in vals and journal.company_id.id != vals['company_id']):
if self.env['account.move'].search([('journal_id', '=', journal.id)], limit=1):
raise UserError(_('This journal already contains items, therefore you cannot modify its company.'))
company = self.env['res.company'].browse(vals['company_id'])
if journal.bank_account_id.company_id and journal.bank_account_id.company_id != company:
journal.bank_account_id.write({
@@ -598,8 +592,8 @@ class AccountJournal(models.Model):
def get_next_bank_cash_default_code(self, journal_type, company, cache=None):
journal_code_base = (journal_type == 'cash' and 'CSH' or 'BNK')
existing_codes = set(self.env['account.journal'].with_context(active_test=False).search([
*self.env['account.journal']._check_company_domain(company),
('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
@@ -634,7 +628,10 @@ class AccountJournal(models.Model):
vals['company_id'] = company.id
# 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)
random_account = self.env['account.account'].search(
self.env['account.account']._check_company_domain(company),
limit=1,
)
digits = len(random_account.code) if random_account else 6
if journal_type in ('bank', 'cash'):
@@ -729,7 +726,8 @@ class AccountJournal(models.Model):
else:
raise UserError(_("The journal in which to upload the invoice is not specified. "))
self = self.env['account.journal'].search([
('company_id', '=', self.env.company.id), ('type', '=', journal_type)
*self.env['account.journal']._check_company_domain(self.env.company),
('type', '=', journal_type),
], limit=1)
attachments = self.env['ir.attachment'].browse(attachment_ids)
@@ -78,8 +78,14 @@ class account_journal(models.Model):
JOIN mail_activity activity ON activity.res_id = move.id AND activity.res_model = 'account.move'
LEFT JOIN mail_activity_type act_type ON activity.activity_type_id = act_type.id
WHERE move.journal_id = ANY(%(ids)s)
AND move.company_id = ANY(%(company_ids)s)
"""
self.env.cr.execute(sql_query, {'ids': self.ids, 'today': today, 'lang': lang})
self.env.cr.execute(sql_query, {
'ids': self.ids,
'company_ids': self.env.companies.ids,
'today': today,
'lang': lang,
})
for activity in self.env.cr.dictfetchall():
act = {
'id': activity['id'],
@@ -335,14 +341,15 @@ class account_journal(models.Model):
SELECT journal.id, statement.id
FROM account_journal journal
LEFT JOIN LATERAL (
SELECT id
SELECT id, company_id
FROM account_bank_statement
WHERE journal_id = journal.id
ORDER BY first_line_index DESC
LIMIT 1
) statement ON TRUE
WHERE journal.id = ANY(%s)
""", [self.ids])
AND statement.company_id = ANY(%s)
""", [self.ids, self.env.companies.ids])
last_statements = {journal_id: statement_id for journal_id, statement_id in self.env.cr.fetchall()}
self.env['account.bank.statement'].browse(i for i in last_statements.values() if i).mapped('balance_end_real') # prefetch
@@ -463,6 +470,7 @@ class account_journal(models.Model):
def _get_open_bills_to_pay_query(self):
return self.env['account.move']._where_calc([
*self.env['account.move']._check_company_domain(self.env.companies),
('journal_id', 'in', self.ids),
('state', '=', 'posted'),
('payment_state', 'in', ('not_paid', 'partial')),
@@ -471,6 +479,7 @@ class account_journal(models.Model):
def _get_draft_bills_query(self):
return self.env['account.move']._where_calc([
*self.env['account.move']._check_company_domain(self.env.companies),
('journal_id', 'in', self.ids),
('state', '=', 'draft'),
('payment_state', 'in', ('not_paid', 'partial')),
@@ -479,6 +488,7 @@ class account_journal(models.Model):
def _get_late_bills_query(self):
return self.env['account.move']._where_calc([
*self.env['account.move']._check_company_domain(self.env.companies),
('journal_id', 'in', self.ids),
('invoice_date_due', '<', fields.Date.context_today(self)),
('state', '=', 'posted'),
+46 -46
View File
@@ -329,7 +329,7 @@ class AccountMove(models.Model):
comodel_name='res.partner',
string='Delivery Address',
compute='_compute_partner_shipping_id', store=True, readonly=False, precompute=True,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
check_company=True,
help="The delivery address will be used in the computation of the fiscal position.",
)
partner_bank_id = fields.Many2one(
@@ -349,7 +349,6 @@ class AccountMove(models.Model):
check_company=True,
compute='_compute_fiscal_position_id', store=True, readonly=False, precompute=True,
states={'posted': [('readonly', True)], 'cancel': [('readonly', True)]},
domain="[('company_id', '=', company_id)]",
ondelete="restrict",
help="Fiscal positions are used to adapt taxes and accounts for particular "
"customers or sales orders/invoices. The default value comes from the customer.",
@@ -678,9 +677,8 @@ class AccountMove(models.Model):
@api.depends('journal_id')
def _compute_company_id(self):
for move in self:
company_id = move.journal_id.company_id or self.env.company
if company_id != move.company_id:
move.company_id = company_id
if move.journal_id.company_id not in move.company_id.parent_ids:
move.company_id = (move.journal_id.company_id or self.env.company)._accessible_branches()[:1]
@api.depends('move_type')
def _compute_journal_id(self):
@@ -705,15 +703,18 @@ class AccountMove(models.Model):
return self.statement_line_ids.statement_id.journal_id[:1]
journal_types = self._get_valid_journal_types()
company_id = (self.company_id or self.env.company).id
domain = [('company_id', '=', company_id), ('type', 'in', journal_types)]
company = self.company_id or self.env.company
domain = [
*self.env['account.journal']._check_company_domain(company),
('type', 'in', journal_types),
]
journal = None
# the currency is not a hard dependence, it triggers via manual add_to_compute
# avoid computing the currency before all it's dependences are set (like the journal...)
if self.env.cache.contains(self, self._fields['currency_id']):
currency_id = self.currency_id.id or self._context.get('default_currency_id')
if currency_id and currency_id != self.company_id.currency_id.id:
if currency_id and currency_id != company.currency_id.id:
currency_domain = domain + [('currency_id', '=', currency_id)]
journal = self.env['account.journal'].search(currency_domain, limit=1)
@@ -721,8 +722,6 @@ class AccountMove(models.Model):
journal = self.env['account.journal'].search(domain, limit=1)
if not journal:
company = self.env['res.company'].browse(company_id)
error_msg = _(
"No journal could be found in company %(company_name)s for any of those types: %(journal_types)s",
company_name=company.display_name,
@@ -741,9 +740,11 @@ class AccountMove(models.Model):
def _compute_suitable_journal_ids(self):
for m in self:
journal_type = m.invoice_filter_type_domain or 'general'
company_id = m.company_id.id or self.env.company.id
domain = [('company_id', '=', company_id), ('type', '=', journal_type)]
m.suitable_journal_ids = self.env['account.journal'].search(domain)
company = m.company_id or self.env.company
m.suitable_journal_ids = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(company),
('type', '=', journal_type),
])
@api.depends('posted_before', 'state', 'journal_id', 'date', 'move_type', 'payment_id')
def _compute_name(self):
@@ -1176,7 +1177,7 @@ class AccountMove(models.Model):
if move.state == 'posted' and move.is_invoice(include_receipts=True):
reconciled_vals = []
reconciled_partials = move._get_all_reconciled_invoice_partials()
reconciled_partials = move.sudo()._get_all_reconciled_invoice_partials()
for reconciled_partial in reconciled_partials:
counterpart_line = reconciled_partial['aml']
if counterpart_line.move_id.ref:
@@ -1654,7 +1655,7 @@ class AccountMove(models.Model):
@api.onchange('company_id')
def _inverse_company_id(self):
self._conditional_add_to_compute('journal_id', lambda m: (
m.journal_id.company_id != m.company_id
not m.journal_id.filtered_domain(self.env['account.journal']._check_company_domain(m.company_id))
))
@api.onchange('currency_id')
@@ -2621,9 +2622,9 @@ class AccountMove(models.Model):
domain += [('move_type', 'in' if self.move_type in refund_types else 'not in', refund_types)]
if self.journal_id.payment_sequence:
domain += [('payment_id', '!=' if self.payment_id else '=', False)]
reference_move_name = self.search(domain + [('date', '<=', self.date)], order='date desc', limit=1).name
reference_move_name = self.sudo().search(domain + [('date', '<=', self.date)], order='date desc', limit=1).name
if not reference_move_name:
reference_move_name = self.search(domain, order='date asc', limit=1).name
reference_move_name = self.sudo().search(domain, order='date asc', limit=1).name
sequence_number_reset = self._deduce_sequence_number_reset(reference_move_name)
if sequence_number_reset == 'year':
where_string += " AND date_trunc('year', date::timestamp without time zone) = date_trunc('year', %(date)s) "
@@ -2732,38 +2733,37 @@ class AccountMove(models.Model):
"""
if not partner_id:
return 0, False, False
where_internal_group = ""
domain = [
*self.env['account.move.line']._check_company_domain(company_id),
('partner_id', '=', partner_id),
('account_id.deprecated', '=', False),
('date', '>=', date.today() - timedelta(days=365 * 2)),
]
if move_type in self.env['account.move'].get_inbound_types(include_receipts=True):
where_internal_group = "AND account.internal_group = 'income'"
domain.append(('account_id.internal_group', '=', 'income'))
elif move_type in self.env['account.move'].get_outbound_types(include_receipts=True):
where_internal_group = "AND account.internal_group = 'expense'"
domain.append(('account_id.internal_group', '=', 'expense'))
query = self.env['account.move.line']._where_calc(domain)
from_clause, where_clause, params = query.get_sql()
self._cr.execute(f"""
SELECT
COUNT(foo.id), foo.account_id, foo.taxes
FROM
(
SELECT
account.id AS account_id,
account.code,
aml.id,
ARRAY_AGG(tax_rel.account_tax_id) AS taxes
FROM account_account account
LEFT JOIN account_move_line aml
ON (account.id = aml.account_id
AND aml.partner_id = %s
AND aml.date >= now() - interval '2 years')
LEFT JOIN account_move_line_account_tax_rel tax_rel ON (aml.id = tax_rel.account_move_line_id)
WHERE
account.company_id = %s
AND account.deprecated = FALSE
{where_internal_group}
GROUP BY account.id, account.code, aml.id
) AS foo
GROUP BY foo.account_id, foo.code, foo.taxes
ORDER BY COUNT(foo.id) DESC, foo.code
LIMIT 1
""", [partner_id, company_id])
return self._cr.fetchone()
SELECT COUNT(foo.id), foo.account_id, foo.taxes
FROM (
SELECT account_move_line__account_id.id AS account_id,
account_move_line__account_id.code,
account_move_line.id,
ARRAY_AGG(tax_rel.account_tax_id) AS taxes
FROM {from_clause}
LEFT JOIN account_move_line_account_tax_rel tax_rel ON account_move_line.id = tax_rel.account_move_line_id
WHERE {where_clause}
GROUP BY account_move_line__account_id.id,
account_move_line.id
) AS foo
GROUP BY foo.account_id, foo.code, foo.taxes
ORDER BY COUNT(foo.id) DESC, foo.code
LIMIT 1
""", params)
return self._cr.fetchone() or (0, False, False)
def _get_quick_edit_suggestions(self):
"""
+14 -8
View File
@@ -84,7 +84,7 @@ class AccountMoveLine(models.Model):
index=True,
auto_join=True,
ondelete="cascade",
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', '!=', 'off_balance')]",
domain="[('deprecated', '=', False), ('account_type', '!=', 'off_balance')]",
check_company=True,
tracking=True,
)
@@ -178,6 +178,7 @@ class AccountMoveLine(models.Model):
comodel_name='account.tax',
string="Originator Group of Taxes",
index='btree_not_null',
check_company=True,
)
tax_line_id = fields.Many2one(
comodel_name='account.tax',
@@ -295,6 +296,7 @@ class AccountMoveLine(models.Model):
string='Product',
inverse='_inverse_product_id',
ondelete='restrict',
check_company=True,
)
product_uom_id = fields.Many2one(
comodel_name='uom.uom',
@@ -850,17 +852,18 @@ class AccountMoveLine(models.Model):
def _get_computed_taxes(self):
self.ensure_one()
company_domain = self.env['account.tax']._check_company_domain(self.move_id.company_id)
if self.move_id.is_sale_document(include_receipts=True):
# Out invoice.
if self.product_id.taxes_id:
tax_ids = self.product_id.taxes_id.filtered(lambda tax: tax.company_id == self.move_id.company_id)
tax_ids = self.product_id.taxes_id.filtered_domain(company_domain)
else:
tax_ids = self.account_id.tax_ids.filtered(lambda tax: tax.type_tax_use == 'sale')
elif self.move_id.is_purchase_document(include_receipts=True):
# In invoice.
if self.product_id.supplier_taxes_id:
tax_ids = self.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == self.move_id.company_id)
tax_ids = self.product_id.supplier_taxes_id.filtered_domain(company_domain)
else:
tax_ids = self.account_id.tax_ids.filtered(lambda tax: tax.type_tax_use == 'purchase')
@@ -868,7 +871,7 @@ class AccountMoveLine(models.Model):
tax_ids = self.account_id.tax_ids
if self.company_id and tax_ids:
tax_ids = tax_ids.filtered(lambda tax: tax.company_id == self.company_id)
tax_ids = tax_ids.filtered_domain(company_domain)
if tax_ids and self.move_id.fiscal_position_id:
tax_ids = self.move_id.fiscal_position_id.map_tax(tax_ids)
@@ -1192,10 +1195,10 @@ class AccountMoveLine(models.Model):
def _check_tax_lock_date(self):
for line in self.filtered(lambda l: l.move_id.state == 'posted'):
move = line.move_id
if move.company_id.tax_lock_date and move.date <= move.company_id.tax_lock_date and line._affect_tax_report():
if move.company_id.max_tax_lock_date and move.date <= move.company_id.max_tax_lock_date and line._affect_tax_report():
raise UserError(_("The operation is refused as it would impact an already issued tax statement. "
"Please change the journal entry date or the tax lock date set in the settings (%s) to proceed.")
% format_date(self.env, move.company_id.tax_lock_date))
% format_date(self.env, move.company_id.max_tax_lock_date))
def _check_reconciliation(self):
for line in self:
@@ -2026,7 +2029,7 @@ class AccountMoveLine(models.Model):
"Entries are not from the same account: %s",
", ".join(accounts.mapped('display_name')),
))
if len(self.company_id) > 1:
if len(self.company_id.root_id) > 1:
raise UserError(_(
"Entries don't belong to the same company: %s",
", ".join(self.company_id.mapped('display_name')),
@@ -2417,7 +2420,10 @@ class AccountMoveLine(models.Model):
* to_reconcile: A list of tuple <move_line, sequence> in order to perform the reconciliation after the move
creation.
"""
company = self.company_id or company
company = (
(self.move_id.filtered(lambda m: m.is_invoice(True)) or self.move_id).company_id
or company
)[:1]
if not company:
return
@@ -54,7 +54,7 @@ class AccountPartialReconcile(models.Model):
company_id = fields.Many2one(
comodel_name='res.company',
string="Company", store=True, readonly=False,
related='debit_move_id.company_id')
compute='_compute_company_id')
max_date = fields.Date(
string="Max Date of Matched Lines", store=True,
compute='_compute_max_date')
@@ -82,6 +82,15 @@ class AccountPartialReconcile(models.Model):
partial.credit_move_id.date
)
@api.depends('debit_move_id', 'credit_move_id')
def _compute_company_id(self):
for partial in self:
# Potential exchange diff and caba entries should be created on the invoice side if any
if partial.debit_move_id.move_id.is_invoice(True):
partial.company_id = partial.debit_move_id.company_id
else:
partial.company_id = partial.credit_move_id.company_id
# -------------------------------------------------------------------------
# LOW-LEVEL METHODS
# -------------------------------------------------------------------------
+6 -5
View File
@@ -105,12 +105,12 @@ class AccountPayment(models.Model):
string='Destination Account',
store=True, readonly=False,
compute='_compute_destination_account_id',
domain="[('account_type', 'in', ('asset_receivable', 'liability_payable')), ('company_id', '=', company_id)]",
domain="[('account_type', 'in', ('asset_receivable', 'liability_payable'))]",
check_company=True)
destination_journal_id = fields.Many2one(
comodel_name='account.journal',
string='Destination Journal',
domain="[('type', 'in', ('bank','cash')), ('company_id', '=', company_id), ('id', '!=', journal_id)]",
domain="[('type', 'in', ('bank','cash')), ('id', '!=', journal_id)]",
check_company=True,
)
@@ -458,7 +458,8 @@ class AccountPayment(models.Model):
Get all journals having at least one payment method for inbound/outbound depending on the payment_type.
"""
journals = self.env['account.journal'].search([
('company_id', 'in', self.company_id.ids), ('type', 'in', ('bank', 'cash'))
*self.env['account.journal']._check_company_domain(self.company_id),
('type', 'in', ('bank', 'cash')),
])
for pay in self:
if pay.payment_type == 'inbound':
@@ -514,7 +515,7 @@ class AccountPayment(models.Model):
pay.destination_account_id = pay.partner_id.with_company(pay.company_id).property_account_receivable_id
else:
pay.destination_account_id = self.env['account.account'].search([
('company_id', '=', pay.company_id.id),
*self.env['account.account']._check_company_domain(pay.company_id),
('account_type', '=', 'asset_receivable'),
('deprecated', '=', False),
], limit=1)
@@ -524,7 +525,7 @@ class AccountPayment(models.Model):
pay.destination_account_id = pay.partner_id.with_company(pay.company_id).property_account_payable_id
else:
pay.destination_account_id = self.env['account.account'].search([
('company_id', '=', pay.company_id.id),
*self.env['account.account']._check_company_domain(pay.company_id),
('account_type', '=', 'liability_payable'),
('deprecated', '=', False),
], limit=1)
@@ -108,10 +108,13 @@ class AccountPaymentMethodLine(models.Model):
copy=False,
ondelete='restrict',
domain="[('deprecated', '=', False), "
"('company_id', '=', company_id), "
"'|', ('account_type', 'in', ('asset_current', 'liability_current')), ('id', '=', parent.default_account_id)]"
)
journal_id = fields.Many2one(comodel_name='account.journal', ondelete="cascade")
journal_id = fields.Many2one(
comodel_name='account.journal',
ondelete="cascade",
check_company=True,
)
# == Display purpose fields ==
code = fields.Char(related='payment_method_id.code')
@@ -11,9 +11,11 @@ from dateutil.relativedelta import relativedelta
class AccountReconcileModelPartnerMapping(models.Model):
_name = 'account.reconcile.model.partner.mapping'
_description = 'Partner mapping for reconciliation models'
_check_company_auto = True
model_id = fields.Many2one(comodel_name='account.reconcile.model', readonly=True, required=True, ondelete='cascade')
partner_id = fields.Many2one(comodel_name='res.partner', string="Partner", required=True, ondelete='cascade')
company_id = fields.Many2one(related='model_id.company_id')
partner_id = fields.Many2one(comodel_name='res.partner', string="Partner", required=True, ondelete='cascade', check_company=True)
payment_ref_regex = fields.Char(string="Find Text in Label")
narration_regex = fields.Char(string="Find Text in Notes")
@@ -48,12 +50,12 @@ class AccountReconcileModelLine(models.Model):
company_id = fields.Many2one(related='model_id.company_id', store=True)
sequence = fields.Integer(required=True, default=10)
account_id = fields.Many2one('account.account', string='Account', ondelete='cascade',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', '!=', 'off_balance')]",
domain="[('deprecated', '=', False), ('account_type', '!=', 'off_balance')]",
required=True, check_company=True)
# This field is ignored in a bank statement reconciliation.
journal_id = fields.Many2one('account.journal', string='Journal', ondelete='cascade',
domain="[('type', '=', 'general'), ('company_id', '=', company_id)]", check_company=True)
domain="[('type', '=', 'general')]", check_company=True)
label = fields.Char(string='Journal Item Label')
amount_type = fields.Selection([
('fixed', 'Fixed'),
@@ -169,7 +171,7 @@ class AccountReconcileModel(models.Model):
tracking=True,
)
match_journal_ids = fields.Many2many('account.journal', string='Journals Availability',
domain="[('type', 'in', ('bank', 'cash')), ('company_id', '=', company_id)]",
domain="[('type', 'in', ('bank', 'cash'))]",
check_company=True,
help='The reconciliation model will only be available from the selected journals.')
match_nature = fields.Selection(selection=[
+1 -1
View File
@@ -773,7 +773,7 @@ class AccountReportExternalValue(models.Model):
foreign_vat_fiscal_position_id = fields.Many2one(
string="Fiscal position",
comodel_name='account.fiscal.position',
domain="[('company_id', '=', company_id), ('country_id', '=', report_country_id), ('foreign_vat', '!=', False)]",
domain="[('country_id', '=', report_country_id), ('foreign_vat', '!=', False)]",
check_company=True,
help="The foreign fiscal position for which this external value is made.",
)
+36 -33
View File
@@ -4,7 +4,7 @@ from odoo.osv import expression
from odoo.tools.float_utils import float_round
from odoo.exceptions import UserError, ValidationError
from odoo.tools.misc import formatLang
from odoo.tools import frozendict
from odoo.tools import frozendict, groupby
from collections import defaultdict
import math
@@ -22,6 +22,8 @@ class AccountTaxGroup(models.Model):
_name = 'account.tax.group'
_description = 'Tax Group'
_order = 'sequence asc'
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
name = fields.Char(required=True, translate=True)
sequence = fields.Integer(default=10)
@@ -68,7 +70,7 @@ class AccountTaxGroup(models.Model):
of these countries, in this company
"""
return bool(self.env['account.tax'].search([
('company_id', '=', company.id),
*self.env['account.tax']._check_company_domain(company),
('country_id', 'in', countries.ids),
'|',
('tax_group_id.tax_payable_account_id', '=', False),
@@ -82,6 +84,7 @@ class AccountTax(models.Model):
_order = 'sequence,id'
_check_company_auto = True
_rec_names_search = ['name', 'description', 'invoice_label']
_check_company_domain = models.check_company_domain_parent_of
name = fields.Char(string='Tax Name', required=True, translate=True)
name_searchable = fields.Char(store=False, search='_search_name',
@@ -139,7 +142,7 @@ class AccountTax(models.Model):
"Based on Payment: the tax is due as soon as the payment of the invoice is received.")
cash_basis_transition_account_id = fields.Many2one(string="Cash Basis Transition Account",
check_company=True,
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
domain="[('deprecated', '=', False)]",
comodel_name='account.account',
help="Account used to transition the tax amount for cash basis taxes. It will contain the tax amount as long as the original invoice has not been reconciled ; at reconciliation, this amount cancelled on this account and put on the regular tax account.")
invoice_repartition_line_ids = fields.One2many(
@@ -172,9 +175,23 @@ class AccountTax(models.Model):
)
country_code = fields.Char(related='country_id.code', readonly=True)
_sql_constraints = [
('name_company_uniq', 'unique(name, company_id, type_tax_use, tax_scope)', 'Tax names must be unique!'),
]
@api.constrains('company_id', 'name', 'type_tax_use', 'tax_scope')
def _constrains_name(self):
domains = []
for record in self:
if record.type_tax_use != 'none':
domains.append([
('company_id', 'child_of', record.company_id.root_id.id),
('name', '=', record.name),
('type_tax_use', '=', record.type_tax_use),
('tax_scope', '=', record.tax_scope),
('id', '!=', record.id),
])
if duplicates := self.search(expression.OR(domains)):
raise ValidationError(
_("Tax names must be unique!")
+ "\n" + "\n".join(f"- {duplicate.name} in {duplicate.company_id.name}" for duplicate in duplicates)
)
@api.constrains('tax_group_id')
def validate_tax_group_id(self):
@@ -199,10 +216,10 @@ class AccountTax(models.Model):
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),
*self.env['account.tax.group']._check_company_domain(company),
('country_id', '=', country.id),
], limit=1) or self.env['account.tax.group'].search([
('company_id', '=', company.id),
*self.env['account.tax.group']._check_company_domain(company),
('country_id', '=', False),
], limit=1)
@@ -308,29 +325,14 @@ class AccountTax(models.Model):
@api.constrains('company_id')
def _check_company_consistency(self):
if not self:
return
self.env['account.move.line'].flush_model(['company_id', 'tax_line_id'])
self.flush_recordset(['company_id'])
self._cr.execute('''
SELECT line.id
FROM account_move_line line
JOIN account_tax tax ON tax.id = line.tax_line_id
WHERE line.tax_line_id IN %s
AND line.company_id != tax.company_id
UNION ALL
SELECT line.id
FROM account_move_line_account_tax_rel tax_rel
JOIN account_tax tax ON tax.id = tax_rel.account_tax_id
JOIN account_move_line line ON line.id = tax_rel.account_move_line_id
WHERE tax_rel.account_tax_id IN %s
AND line.company_id != tax.company_id
''', [tuple(self.ids)] * 2)
if self._cr.fetchone():
raise UserError(_("You can't change the company of your tax since there are some journal items linked to it."))
for company, taxes in groupby(self, lambda tax: tax.company_id):
if self.env['account.move.line'].search([
'|',
('tax_line_id', 'in', [tax.id for tax in taxes]),
('tax_ids', 'in', [tax.id for tax in taxes]),
'!', ('company_id', 'child_of', company.id)
], limit=1):
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."""
@@ -522,7 +524,7 @@ class AccountTax(models.Model):
if not self:
company = self.env.company
else:
company = self[0].company_id
company = self[0].company_id._accessible_branches()[:1]
# 1) Flatten the taxes.
taxes, groups_map = self.flatten_taxes_hierarchy(create_map=True)
@@ -1341,6 +1343,7 @@ class AccountTaxRepartitionLine(models.Model):
_description = "Tax Repartition Line"
_order = 'sequence, repartition_type, id'
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
factor_percent = fields.Float(
string="%",
@@ -1353,7 +1356,7 @@ class AccountTaxRepartitionLine(models.Model):
document_type = fields.Selection(string="Related to", selection=[('invoice', 'Invoice'), ('refund', 'Refund')], required=True)
account_id = fields.Many2one(string="Account",
comodel_name='account.account',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', 'not in', ('asset_receivable', 'liability_payable'))]",
domain="[('deprecated', '=', False), ('account_type', 'not in', ('asset_receivable', 'liability_payable'))]",
check_company=True,
help="Account on which to post the tax amount")
tag_ids = fields.Many2many(string="Tax Grids", comodel_name='account.account.tag', domain=[('applicability', '=', 'taxes')], copy=True, ondelete='restrict')
+37 -16
View File
@@ -174,10 +174,15 @@ class AccountChartTemplate(models.AbstractModel):
if not reload_template:
for model in ('account.move',) + TEMPLATE_MODELS[::-1]:
self.env[model].sudo().search([('company_id', '=', company.id)]).with_context({MODULE_UNINSTALL_FLAG: True}).unlink()
if not company.parent_id:
self.env[model].sudo().search([('company_id', 'child_of', company.id)]).with_context({MODULE_UNINSTALL_FLAG: True}).unlink()
data = self._get_chart_template_data(template_code)
template_data = data.pop('template_data')
if company.parent_id:
data = {
'res.company': data['res.company'],
}
if reload_template:
self._pre_reload_data(company, template_data, data)
@@ -201,6 +206,8 @@ class AccountChartTemplate(models.AbstractModel):
except Exception:
# Do not rollback installation of CoA if demo data failed
_logger.exception('Error while loading accounting demo data')
for subsidiary in company.child_ids:
self._load(template_code, subsidiary, install_demo)
def _pre_reload_data(self, company, template_data, data):
"""Pre-process the data in case of reloading the chart of accounts.
@@ -221,15 +228,15 @@ class AccountChartTemplate(models.AbstractModel):
else:
if 'code' in journal_data:
journal = self.env['account.journal'].with_context(active_test=False).search([
*self.env['account.journal']._check_company_domain(company),
('code', '=', journal_data['code']),
('company_id', '=', company.id),
])
# Try to match by journal name to avoid conflict in the unique constraint on the mail alias
if not journal and 'name' in journal_data and 'type' in journal_data:
journal = self.env['account.journal'].with_context(active_test=False).search([
*self.env['account.journal']._check_company_domain(company),
('type', '=', journal_data['type']),
('name', '=', journal_data['name']),
('company_id', '=', company.id),
], limit=1)
if journal:
del data['account.journal'][xmlid]
@@ -239,11 +246,13 @@ class AccountChartTemplate(models.AbstractModel):
'noupdate': True,
}])
account_group_count = self.env['account.group'].search_count([('company_id', '=', company.id)])
account_group_count = self.env['account.group'].search_count([])
if account_group_count:
data.pop('account.group', None)
current_taxes = self.env['account.tax'].search([('company_id', '=', company.id)])
current_taxes = self.env['account.tax'].search([
*self.env['account.tax']._check_company_domain(company),
])
unique_tax_name_key = lambda t: (t.name, t.type_tax_use, t.tax_scope, t.company_id)
unique_tax_name_keys = set(current_taxes.mapped(unique_tax_name_key))
xmlid2tax = {
@@ -314,8 +323,8 @@ class AccountChartTemplate(models.AbstractModel):
account = self.ref(xmlid, raise_if_not_found=False)
if not account or (account and account.code != values['code']):
existing_account = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(company),
('code', '=', values['code']),
('company_id', '=', company.id),
])
if existing_account:
self.env['ir.model.data']._update_xmlids([{
@@ -525,12 +534,14 @@ class AccountChartTemplate(models.AbstractModel):
# Set default Purchase and Sale taxes on the company
if not company.account_sale_tax_id:
company.account_sale_tax_id = self.env['account.tax'].search([
('type_tax_use', 'in', ('sale', 'all')), ('company_id', '=', company.id)], limit=1).id
*self.env['account.tax']._check_company_domain(company),
('type_tax_use', 'in', ('sale', 'all'))], limit=1).id
if not company.account_purchase_tax_id:
company.account_purchase_tax_id = self.env['account.tax'].search([
('type_tax_use', 'in', ('purchase', 'all')), ('company_id', '=', company.id)], limit=1).id
*self.env['account.tax']._check_company_domain(company),
('type_tax_use', 'in', ('purchase', 'all'))], limit=1).id
# Display caba fields if there are caba taxes
if self.env['account.tax'].search([('tax_exigibility', '=', 'on_payment')]):
if not company.parent_id and self.env['account.tax'].search([('tax_exigibility', '=', 'on_payment')]):
company.tax_exigibility = True
for field, model in {
@@ -633,10 +644,13 @@ class AccountChartTemplate(models.AbstractModel):
for fname in list(accounts_data):
if company[fname]:
del accounts_data[fname]
accounts = self.env['account.account'].create(accounts_data.values())
for company_attr_name, account in zip(accounts_data.keys(), accounts):
company[company_attr_name] = account
if company.parent_id:
for company_attr_name in accounts_data:
company[company_attr_name] = company.parent_ids[0][company_attr_name]
else:
accounts = self.env['account.account'].create(accounts_data.values())
for company_attr_name, account in zip(accounts_data.keys(), accounts):
company[company_attr_name] = account
@api.model
def _instantiate_foreign_taxes(self, country, company):
@@ -655,8 +669,8 @@ class AccountChartTemplate(models.AbstractModel):
# - Creates tax group and taxes with their ir.model.data
taxes_in_country = self.env['account.tax'].search([
*self.env['account.tax']._check_company_domain(company),
('country_id', '=', country.id),
('company_id', '=', company.id)
])
if taxes_in_country:
return
@@ -693,6 +707,7 @@ class AccountChartTemplate(models.AbstractModel):
if account_template_xml_id in existing_accounts:
continue
local_tax_group = self.env["account.tax.group"].search([
*self.env['account.tax.group']._check_company_domain(company),
('country_id', '=', company.account_fiscal_country_id.id),
(field, '!=', False),
], limit=1)
@@ -710,7 +725,7 @@ class AccountChartTemplate(models.AbstractModel):
sign_comparator = '<' if float(foreign_tax_rep_line.get('factor_percent', 100)) < 0 else '>'
minimal_domain = [
('company_id', '=', company.id),
*self.env['account.tax.repartition.line']._check_company_domain(company),
('account_id', '!=', False),
('factor_percent', sign_comparator, 0),
]
@@ -735,6 +750,7 @@ class AccountChartTemplate(models.AbstractModel):
# Try to create cash basis account if not mapped
local_cash_basis_tax = self.env["account.tax"].search([
*self.env['account.tax']._check_company_domain(company),
('country_id', '=', company.account_fiscal_country_id.id),
('cash_basis_transition_account_id', '!=', False)
], limit=1)
@@ -900,7 +916,12 @@ class AccountChartTemplate(models.AbstractModel):
# --------------------------------------------------------------------------------
def ref(self, xmlid, raise_if_not_found=True):
return self.env.ref(f"account.{self.env.company.id}_{xmlid}" if xmlid and '.' not in xmlid else xmlid, raise_if_not_found)
if '.' in xmlid:
return self.env.ref(xmlid, raise_if_not_found)
return (
self.env.ref(f"account.{self.env.company.id}_{xmlid}", raise_if_not_found=False)
or self.env.ref(f"account.{self.env.company.parent_ids[0].id}_{xmlid}", raise_if_not_found)
)
def _get_parent_template(self, code):
parents = []
+88 -31
View File
@@ -47,22 +47,24 @@ class ResCompany(models.Model):
string="Tax Return Lock Date",
tracking=True,
help="No users can edit journal entries related to a tax prior and inclusive of this date.")
max_tax_lock_date = fields.Date(compute='_compute_max_tax_lock_date', recursive=True) # TODO maybe store
transfer_account_id = fields.Many2one('account.account',
check_company=True,
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 = 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")
default_cash_difference_expense_account_id = fields.Many2one('account.account', string="Cash Difference Expense")
account_journal_suspense_account_id = fields.Many2one('account.account', string='Journal Suspense Account')
account_journal_payment_debit_account_id = fields.Many2one('account.account', string='Journal Outstanding Receipts')
account_journal_payment_credit_account_id = fields.Many2one('account.account', string='Journal Outstanding Payments')
account_journal_early_pay_discount_gain_account_id = fields.Many2one(comodel_name='account.account', string='Cash Discount Write-Off Gain Account')
account_journal_early_pay_discount_loss_account_id = fields.Many2one(comodel_name='account.account', string='Cash Discount Write-Off Loss Account')
default_cash_difference_income_account_id = fields.Many2one('account.account', string="Cash Difference Income", check_company=True)
default_cash_difference_expense_account_id = fields.Many2one('account.account', string="Cash Difference Expense", check_company=True)
account_journal_suspense_account_id = fields.Many2one('account.account', string='Journal Suspense Account', check_company=True)
account_journal_payment_debit_account_id = fields.Many2one('account.account', string='Journal Outstanding Receipts', check_company=True)
account_journal_payment_credit_account_id = fields.Many2one('account.account', string='Journal Outstanding Payments', check_company=True)
account_journal_early_pay_discount_gain_account_id = fields.Many2one(comodel_name='account.account', string='Cash Discount Write-Off Gain Account', check_company=True)
account_journal_early_pay_discount_loss_account_id = fields.Many2one(comodel_name='account.account', string='Cash Discount Write-Off Loss Account', check_company=True)
transfer_account_code_prefix = fields.Char(string='Prefix of the transfer accounts')
account_sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax")
account_purchase_tax_id = fields.Many2one('account.tax', string="Default Purchase Tax")
account_sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax", check_company=True)
account_purchase_tax_id = fields.Many2one('account.tax', string="Default Purchase Tax", check_company=True)
tax_calculation_rounding_method = fields.Selection([
('round_per_line', 'Round per Line'),
('round_globally', 'Round Globally'),
@@ -71,12 +73,14 @@ class ResCompany(models.Model):
income_currency_exchange_account_id = fields.Many2one(
comodel_name='account.account',
string="Gain Exchange Rate Account",
domain="[('deprecated', '=', False), ('company_id', '=', id), \
check_company=True,
domain="[('deprecated', '=', False),\
('account_type', 'in', ('income', 'income_other'))]")
expense_currency_exchange_account_id = fields.Many2one(
comodel_name='account.account',
string="Loss Exchange Rate Account",
domain="[('deprecated', '=', False), ('company_id', '=', id), \
check_company=True,
domain="[('deprecated', '=', False), \
('account_type', '=', 'expense')]")
anglo_saxon_accounting = fields.Boolean(string="Use anglo-saxon accounting")
bank_journal_ids = fields.One2many('account.journal', 'company_id', domain=[('type', '=', 'bank')], string='Bank Journals')
@@ -104,16 +108,23 @@ class ResCompany(models.Model):
compute='_compute_invoice_terms_html', store=True, readonly=False)
# Needed in the Point of Sale
account_default_pos_receivable_account_id = fields.Many2one('account.account', string="Default PoS Receivable Account")
account_default_pos_receivable_account_id = fields.Many2one('account.account', string="Default PoS Receivable Account", check_company=True)
# Accrual Accounting
expense_accrual_account_id = fields.Many2one('account.account',
help="Account used to move the period of an expense",
domain="[('internal_group', '=', 'liability'), ('account_type', 'not in', ('asset_receivable', 'liability_payable')), ('company_id', '=', id)]")
check_company=True,
domain="[('internal_group', '=', 'liability'), ('account_type', 'not in', ('asset_receivable', 'liability_payable'))]")
revenue_accrual_account_id = fields.Many2one('account.account',
help="Account used to move the period of a revenue",
domain="[('internal_group', '=', 'asset'), ('account_type', 'not in', ('asset_receivable', 'liability_payable')), ('company_id', '=', id)]")
automatic_entry_default_journal_id = fields.Many2one('account.journal', help="Journal used by default for moving the period of an entry", domain="[('type', '=', 'general')]")
check_company=True,
domain="[('internal_group', '=', 'asset'), ('account_type', 'not in', ('asset_receivable', 'liability_payable'))]")
automatic_entry_default_journal_id = fields.Many2one(
'account.journal',
domain="[('type', '=', 'general')]",
check_company=True,
help="Journal used by default for moving the period of an entry",
)
# Technical field to hide country specific fields in company form view
country_code = fields.Char(related='country_id.code', depends=['country_id'])
@@ -138,9 +149,11 @@ class ResCompany(models.Model):
tax_exigibility = fields.Boolean(string='Use Cash Basis')
tax_cash_basis_journal_id = fields.Many2one(
comodel_name='account.journal',
check_company=True,
string="Cash Basis Journal")
account_cash_basis_base_account_id = fields.Many2one(
comodel_name='account.account',
check_company=True,
domain=[('deprecated', '=', False)],
string="Base Tax Received Account",
help="Account that will be set on lines created in cash basis journal entry and used to keep track of the "
@@ -166,6 +179,14 @@ class ResCompany(models.Model):
('out_and_in_invoices', 'Customer Invoices and Vendor Bills')],
string="Quick encoding")
def _get_company_root_delegated_field_names(self):
return super()._get_company_root_delegated_field_names() + [
'fiscalyear_last_day',
'fiscalyear_last_month',
'account_storno',
'tax_exigibility',
]
@api.constrains('account_opening_move_id', 'fiscalyear_last_day', 'fiscalyear_last_month')
def _check_fiscalyear_last_day(self):
# if the user explicitly chooses the 29th of February we allow it:
@@ -188,7 +209,10 @@ class ResCompany(models.Model):
company_to_foreign_vat_country = {
company.id: country_ids
for company, country_ids in self.env['account.fiscal.position']._read_group(
domain=[('company_id', 'in', self.ids), ('foreign_vat', '!=', False)],
domain=[
*self.env['account.fiscal.position']._check_company_domain(self),
('foreign_vat', '!=', False),
],
groupby=['company_id'],
aggregates=['country_id:array_agg'],
)
@@ -205,7 +229,14 @@ class ResCompany(models.Model):
@api.depends('account_fiscal_country_id')
def _compute_account_enabled_tax_country_ids(self):
for record in self:
foreign_vat_fpos = self.env['account.fiscal.position'].search([('company_id', '=', record.id), ('foreign_vat', '!=', False)])
if record not in self.env.user.company_ids:
# can have access to the company form without having access to its content (see base.res_company_rule_erp_manager)
record.account_enabled_tax_country_ids = False
continue
foreign_vat_fpos = self.env['account.fiscal.position'].search([
*self.env['account.fiscal.position']._check_company_domain(record),
('foreign_vat', '!=', False)
])
record.account_enabled_tax_country_ids = foreign_vat_fpos.country_id + record.account_fiscal_country_id
@api.depends('terms_type')
@@ -217,6 +248,25 @@ class ResCompany(models.Model):
if html:
company.invoice_terms_html = html
@api.depends('parent_id.max_tax_lock_date')
def _compute_max_tax_lock_date(self):
for company in self:
company.max_tax_lock_date = max(company.tax_lock_date or date.min, company.parent_id.max_tax_lock_date or date.min)
@api.model_create_multi
def create(self, vals_list):
companies = super().create(vals_list)
for company in companies:
if root_template := company.parent_ids[0].chart_template:
def try_loading(company=company):
self.env['account.chart.template']._load(
root_template,
company,
install_demo=False,
)
self.env.cr.precommit.add(try_loading)
return companies
def get_new_account_code(self, current_code, old_prefix, new_prefix):
digits = len(current_code)
return new_prefix + current_code.replace(old_prefix, '', 1).lstrip('0').rjust(digits-len(new_prefix), '0')
@@ -225,9 +275,9 @@ class ResCompany(models.Model):
if not old_code:
return
accounts = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(self),
('code', '=like', old_code + '%'),
('account_type', 'in', ('asset_cash', 'liability_credit_card')),
('company_id', '=', self.id)
], order='code asc')
for account in accounts:
account.write({'code': self.get_new_account_code(account.code, old_code, new_code)})
@@ -262,7 +312,7 @@ class ResCompany(models.Model):
if values.get('fiscalyear_lock_date'):
draft_entries = self.env['account.move'].search([
('company_id', 'in', self.ids),
('company_id', 'child_of', self.ids),
('state', '=', 'draft'),
('date', '<=', values['fiscalyear_lock_date'])])
if draft_entries:
@@ -279,7 +329,7 @@ class ResCompany(models.Model):
raise RedirectWarning(error_msg, action_error, _('Show unposted entries'))
unreconciled_statement_lines = self.env['account.bank.statement.line'].search([
('company_id', 'in', self.ids),
('company_id', 'child_of', self.ids),
('is_reconciled', '=', False),
('date', '<=', values['fiscalyear_lock_date']),
('move_id.state', 'in', ('draft', 'posted')),
@@ -292,12 +342,11 @@ class ResCompany(models.Model):
def _get_user_fiscal_lock_date(self):
"""Get the fiscal lock date for this company depending on the user"""
if not self:
return date.min
self.ensure_one()
lock_date = max(self.period_lock_date or date.min, self.fiscalyear_lock_date or date.min)
if self.user_has_groups('account.group_account_manager'):
lock_date = self.fiscalyear_lock_date or date.min
if self.parent_id:
lock_date = max(lock_date, self.parent_id._get_user_fiscal_lock_date())
return lock_date
def _get_violated_lock_dates(self, accounting_date, has_tax):
@@ -311,7 +360,7 @@ class ResCompany(models.Model):
user_lock_date = self._get_user_fiscal_lock_date()
if accounting_date and user_lock_date and accounting_date <= user_lock_date:
locks.append((user_lock_date, _('user')))
tax_lock_date = self.tax_lock_date
tax_lock_date = self.max_tax_lock_date
if accounting_date and tax_lock_date and has_tax and accounting_date <= tax_lock_date:
locks.append((tax_lock_date, _('tax')))
locks.sort()
@@ -359,7 +408,10 @@ class ResCompany(models.Model):
"""
self.ensure_one()
if not self.account_opening_move_id:
default_journal = self.env['account.journal'].search([('type', '=', 'general'), ('company_id', '=', self.id)], limit=1)
default_journal = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(self),
('type', '=', 'general')
], limit=1)
if not default_journal:
raise UserError(_("Please install a chart of accounts or create a miscellaneous journal before proceeding."))
@@ -382,14 +434,19 @@ class ResCompany(models.Model):
if none has yet been defined.
"""
unaffected_earnings_type = "equity_unaffected"
account = self.env['account.account'].search([('company_id', '=', self.id),
('account_type', '=', unaffected_earnings_type)])
account = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(self),
('account_type', '=', unaffected_earnings_type),
])
if account:
return account[0]
# Do not assume '999999' doesn't exist since the user might have created such an account
# manually.
code = 999999
while self.env['account.account'].search([('code', '=', str(code)), ('company_id', '=', self.id)]):
while self.env['account.account'].search([
*self.env['account.account']._check_company_domain(self),
('code', '=', str(code)),
]):
code -= 1
return self.env['account.account'].create({
'code': str(code),
@@ -454,7 +511,7 @@ class ResCompany(models.Model):
self.env['onboarding.onboarding.step'].action_validate_step('account.onboarding_onboarding_step_sales_tax')
def get_chart_of_accounts_or_fail(self):
account = self.env['account.account'].search([('company_id', '=', self.id)], limit=1)
account = self.env['account.account'].search(self.env['account.account']._check_company_domain(self), limit=1)
if len(account) == 0:
action = self.env.ref('account.action_account_config')
msg = _(
@@ -466,7 +523,7 @@ class ResCompany(models.Model):
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))
return bool(self.env['account.move.line'].search([('company_id', 'child_of', self.root_id.id)], limit=1))
def _chart_template_selection(self):
return self.env['account.chart.template']._select_chart_template(self.country_id)
@@ -485,7 +542,7 @@ class ResCompany(models.Model):
def build_move_info(move):
return(move.name, move.inalterable_hash, fields.Date.to_string(move.date))
journals = self.env['account.journal'].search([('company_id', '=', self.id)])
journals = self.env['account.journal'].search(self.env['account.journal']._check_company_domain(self))
results_by_journal = {
'results': [],
'printing_date': format_date(self.env, fields.Date.to_string(fields.Date.context_today(self)))
+7 -5
View File
@@ -63,7 +63,12 @@ class IrModule(models.Model):
res = 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.registry._auto_install_template = next(iter(self.account_templates))
def try_loading(env):
env['account.chart.template'].try_loading(
next(iter(self.account_templates)),
env.company,
)
self.env.registry._auto_install_template = try_loading
return res
def _load_module_terms(self, modules, langs, overwrite=False):
@@ -82,10 +87,7 @@ class IrModule(models.Model):
self.env.registry._delayed_account_translator(self.env)
del self.env.registry._delayed_account_translator
if hasattr(self.env.registry, '_auto_install_template'):
self.env['account.chart.template'].try_loading(
self.env.registry._auto_install_template,
self.env.company,
)
self.env.registry._auto_install_template(self.env)
del self.env.registry._auto_install_template
def module_uninstall(self):
@@ -105,7 +105,10 @@ class OnboardingStep(models.Model):
# Then, we open will open a custom tree view allowing to edit opening balances of the account
view_id = self.env.ref('account.init_accounts_tree').id
# Hide the current year earnings account as it is automatically computed
domain = [('account_type', '!=', 'equity_unaffected'), ('company_id', '=', company.id)]
domain = [
*self.env['account.account']._check_company_domain(company),
('account_type', '!=', 'equity_unaffected'),
]
return {
'type': 'ir.actions.act_window',
'name': _('Chart of Accounts'),
+28 -22
View File
@@ -19,6 +19,8 @@ class AccountFiscalPosition(models.Model):
_name = 'account.fiscal.position'
_description = 'Fiscal Position'
_order = 'sequence'
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
sequence = fields.Integer()
name = fields.Char(string='Fiscal Position', required=True, translate=True)
@@ -92,9 +94,9 @@ class AccountFiscalPosition(models.Model):
raise ValidationError(_("You cannot create a fiscal position with a foreign VAT within your fiscal country."))
similar_fpos_domain = [
*self.env['account.fiscal.position']._check_company_domain(record.company_id),
('foreign_vat', '!=', False),
('country_id', '=', record.country_id.id),
('company_id', '=', record.company_id.id),
('id', '!=', record.id),
]
if record.state_ids:
@@ -174,9 +176,9 @@ class AccountFiscalPosition(models.Model):
if not country_id:
return False
base_domain = [
*self._check_company_domain(self.env.company),
('auto_apply', '=', True),
('vat_required', '=', vat_required),
('company_id', 'in', [self.env.company.id, False]),
]
null_state_dom = state_domain = [('state_ids', '=', False)]
null_zip_dom = zip_domain = [('zip_from', '=', False), ('zip_to', '=', False)]
@@ -263,6 +265,7 @@ class AccountFiscalPositionTax(models.Model):
_description = 'Tax Mapping of Fiscal Position'
_rec_name = 'position_id'
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position',
required=True, ondelete='cascade')
@@ -282,16 +285,17 @@ class AccountFiscalPositionAccount(models.Model):
_description = 'Accounts Mapping of Fiscal Position'
_rec_name = 'position_id'
_check_company_auto = True
_check_company_domain = models.check_company_domain_parent_of
position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position',
required=True, ondelete='cascade')
company_id = fields.Many2one('res.company', string='Company', related='position_id.company_id', store=True)
account_src_id = fields.Many2one('account.account', string='Account on Product',
check_company=True, required=True,
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]")
domain="[('deprecated', '=', False)]")
account_dest_id = fields.Many2one('account.account', string='Account to Use Instead',
check_company=True, required=True,
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]")
domain="[('deprecated', '=', False)]")
_sql_constraints = [
('account_src_dest_uniq',
@@ -326,7 +330,7 @@ class ResPartner(models.Model):
def _credit_debit_get(self):
tables, where_clause, where_params = self.env['account.move.line']._where_calc([
('parent_state', '=', 'posted'),
('company_id', '=', self.env.company.id)
('company_id', 'child_of', self.env.company.root_id.id)
]).get_sql()
where_params = [tuple(self.ids)] + where_params
@@ -371,17 +375,21 @@ class ResPartner(models.Model):
sign = 1
if account_type == 'liability_payable':
sign = -1
res = self._cr.execute('''
SELECT partner.id
FROM res_partner partner
LEFT JOIN account_move_line aml ON aml.partner_id = partner.id
JOIN account_move move ON move.id = aml.move_id
RIGHT JOIN account_account acc ON aml.account_id = acc.id
WHERE acc.account_type = %s
AND NOT acc.deprecated AND acc.company_id = %s
AND move.state = 'posted'
GROUP BY partner.id
HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, self.env.company.id, sign, operand))
res = self._cr.execute(f'''
SELECT aml.partner_id
FROM res_partner partner
LEFT JOIN account_move_line aml ON aml.partner_id = partner.id
JOIN account_move move ON move.id = aml.move_id
JOIN res_company line_company ON line_company.id = line.company_id
RIGHT JOIN account_account acc ON aml.account_id = acc.id
WHERE acc.account_type = %s
AND NOT acc.deprecated
AND SPLIT_PART(line_company.parent_path, '/', 1):int == %s
AND move.state = 'posted'
GROUP BY aml.partner_id
HAVING %s * COALESCE(SUM(aml.amount_residual), 0) {operator} %s''',
(account_type, self.env.company.root_id.id, sign, operand)
)
res = self._cr.fetchall()
if not res:
return [('id', '=', '0')]
@@ -521,25 +529,22 @@ class ResPartner(models.Model):
journal_item_count = fields.Integer(compute='_compute_journal_item_count', string="Journal Items")
property_account_payable_id = fields.Many2one('account.account', company_dependent=True,
string="Account Payable",
domain="[('account_type', '=', 'liability_payable'), ('deprecated', '=', False), ('company_id', '=', current_company_id)]",
domain="[('account_type', '=', 'liability_payable'), ('deprecated', '=', False)]",
help="This account will be used instead of the default one as the payable account for the current partner",
required=True)
property_account_receivable_id = fields.Many2one('account.account', company_dependent=True,
string="Account Receivable",
domain="[('account_type', '=', 'asset_receivable'), ('deprecated', '=', False), ('company_id', '=', current_company_id)]",
domain="[('account_type', '=', 'asset_receivable'), ('deprecated', '=', False)]",
help="This account will be used instead of the default one as the receivable account for the current partner",
required=True)
property_account_position_id = fields.Many2one('account.fiscal.position', company_dependent=True,
string="Fiscal Position",
domain="[('company_id', '=', current_company_id)]",
help="The fiscal position determines the taxes/accounts used for this contact.")
property_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True,
string='Customer Payment Terms',
domain="[('company_id', 'in', [current_company_id, False])]",
help="This payment term will be used instead of the default one for sales orders and customer invoices")
property_supplier_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True,
string='Vendor Payment Terms',
domain="[('company_id', 'in', [current_company_id, False])]",
help="This payment term will be used instead of the default one for purchase orders and vendor bills")
ref_company_ids = fields.One2many('res.company', 'partner_id',
string='Companies that refers to partner')
@@ -849,8 +854,9 @@ class ResPartner(models.Model):
return None
return self.env['res.partner'].search(domain + extra_domain, limit=1)
company = company or self.env.company
for search_method in (search_with_vat, search_with_domain, search_with_phone_mail, search_with_name):
for extra_domain in ([('company_id', '=', (company or self.env.company).id)], []):
for extra_domain in (self.env['res.partner']._check_company_domain(company), []):
partner = search_method(extra_domain)
if partner:
return partner
+7 -3
View File
@@ -26,10 +26,14 @@ class ProductTemplate(models.Model):
_inherit = "product.template"
taxes_id = fields.Many2many('account.tax', 'product_taxes_rel', 'prod_id', 'tax_id', help="Default taxes used when selling the product.", string='Customer Taxes',
domain=[('type_tax_use', '=', 'sale')], default=lambda self: self.env.company.account_sale_tax_id)
domain=[('type_tax_use', '=', 'sale')],
default=lambda self: self.env.company.account_sale_tax_id or self.env.company.root_id.account_sale_tax_id,
)
tax_string = fields.Char(compute='_compute_tax_string')
supplier_taxes_id = fields.Many2many('account.tax', 'product_supplier_taxes_rel', 'prod_id', 'tax_id', string='Vendor Taxes', help='Default taxes used when buying the product.',
domain=[('type_tax_use', '=', 'purchase')], default=lambda self: self.env.company.account_purchase_tax_id)
domain=[('type_tax_use', '=', 'purchase')],
default=lambda self: self.env.company.account_purchase_tax_id or self.env.company.root_id.account_purchase_tax_id,
)
property_account_income_id = fields.Many2one('account.account', company_dependent=True,
string="Income Account",
domain=ACCOUNT_DOMAIN,
@@ -229,6 +233,6 @@ class ProductProduct(models.Model):
domain = expression.AND([
expression.OR(domains),
[('company_id', 'in', [False, company or self.env.company.id])],
self.env['product.product']._check_company_domain(company),
])
return self.env['product.product'].search(domain, limit=1)
+41 -14
View File
@@ -14,26 +14,39 @@ class ResConfigSettings(models.TransientModel):
comodel_name='account.journal',
related='company_id.currency_exchange_journal_id', readonly=False,
string="Currency Exchange Journal",
domain="[('company_id', '=', company_id), ('type', '=', 'general')]",
check_company=True,
domain="[('type', '=', 'general')]",
help='The accounting journal where automatic exchange differences will be registered')
income_currency_exchange_account_id = fields.Many2one(
comodel_name="account.account",
related="company_id.income_currency_exchange_account_id",
string="Gain Exchange Rate Account",
readonly=False,
domain="[('deprecated', '=', False), ('company_id', '=', company_id),\
('account_type', 'in', ('income', 'income_other'))]")
check_company=True,
domain="[('deprecated', '=', False), ('account_type', 'in', ('income', 'income_other'))]")
expense_currency_exchange_account_id = fields.Many2one(
comodel_name="account.account",
related="company_id.expense_currency_exchange_account_id",
string="Loss Exchange Rate Account",
readonly=False,
domain="[('deprecated', '=', False), ('company_id', '=', company_id),\
('account_type', '=', 'expense')]")
check_company=True,
domain="[('deprecated', '=', False), ('account_type', '=', 'expense')]")
has_chart_of_accounts = fields.Boolean(compute='_compute_has_chart_of_accounts', string='Company has a chart of accounts')
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)
sale_tax_id = fields.Many2one(
'account.tax',
string="Default Sale Tax",
related='company_id.account_sale_tax_id',
readonly=False,
check_company=True,
)
purchase_tax_id = fields.Many2one(
'account.tax',
string="Default Purchase Tax",
related='company_id.account_purchase_tax_id',
readonly=False,
check_company=True,
)
tax_calculation_rounding_method = fields.Selection(
related='company_id.tax_calculation_rounding_method', string='Tax calculation rounding method', readonly=False)
account_journal_suspense_account_id = fields.Many2one(
@@ -41,7 +54,8 @@ class ResConfigSettings(models.TransientModel):
string='Bank Suspense',
readonly=False,
related='company_id.account_journal_suspense_account_id',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', 'in', ('asset_current', 'liability_current'))]",
check_company=True,
domain="[('deprecated', '=', False), ('account_type', 'in', ('asset_current', 'liability_current'))]",
help='Bank Transactions are posted immediately after import or synchronization. '
'Their counterparty is the bank suspense account.\n'
'Reconciliation replaces the latter by the definitive account(s).')
@@ -49,8 +63,9 @@ class ResConfigSettings(models.TransientModel):
comodel_name='account.account',
string='Outstanding Receipts',
readonly=False,
check_company=True,
related='company_id.account_journal_payment_debit_account_id',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', '=', 'asset_current')]",
domain="[('deprecated', '=', False), ('account_type', '=', 'asset_current')]",
help='Incoming payments are posted on an Outstanding Receipts Account. '
'In the bank reconciliation widget, they appear as blue lines.\n'
'Bank transactions are then reconciled on the Outstanding Receipts Accounts rather than the Receivable '
@@ -59,13 +74,15 @@ class ResConfigSettings(models.TransientModel):
comodel_name='account.account',
string='Outstanding Payments',
readonly=False,
check_company=True,
related='company_id.account_journal_payment_credit_account_id',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', '=', 'asset_current')]",
domain="[('deprecated', '=', False), ('account_type', '=', 'asset_current')]",
help='Outgoing Payments are posted on an Outstanding Payments Account. '
'In the bank reconciliation widget, they appear as blue lines.\n'
'Bank transactions are then reconciled on the Outstanding Payments Account rather the Payable Account.')
transfer_account_id = fields.Many2one('account.account', string="Internal Transfer",
related='company_id.transfer_account_id', readonly=False,
check_company=True,
domain=[
('reconcile', '=', True),
('account_type', '=', 'asset_current'),
@@ -100,11 +117,18 @@ class ResConfigSettings(models.TransientModel):
module_account_invoice_extract = fields.Boolean(string="Document Digitization")
module_snailmail_account = fields.Boolean(string="Snailmail")
tax_exigibility = fields.Boolean(string='Cash Basis', related='company_id.tax_exigibility', readonly=False)
tax_cash_basis_journal_id = fields.Many2one('account.journal', related='company_id.tax_cash_basis_journal_id', string="Tax Cash Basis Journal", readonly=False)
tax_cash_basis_journal_id = fields.Many2one(
'account.journal',
string="Tax Cash Basis Journal",
related='company_id.tax_cash_basis_journal_id',
readonly=False,
check_company=True,
)
account_cash_basis_base_account_id = fields.Many2one(
comodel_name='account.account',
string="Base Tax Received Account",
readonly=False,
check_company=True,
related='company_id.account_cash_basis_base_account_id',
domain=[('deprecated', '=', False)])
account_fiscal_country_id = fields.Many2one(string="Fiscal Country Code", related="company_id.account_fiscal_country_id", readonly=False, store=False)
@@ -154,15 +178,17 @@ class ResConfigSettings(models.TransientModel):
help='Account for the difference amount after the expense discount has been granted',
readonly=False,
related='company_id.account_journal_early_pay_discount_loss_account_id',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', 'in', ('expense', 'income', 'income_other'))]",
check_company=True,
domain="[('deprecated', '=', False), ('account_type', 'in', ('expense', 'income', 'income_other'))]",
)
account_journal_early_pay_discount_gain_account_id = fields.Many2one(
comodel_name='account.account',
string='Cash Discount Gain',
help='Account for the difference amount after the income discount has been granted',
readonly=False,
check_company=True,
related='company_id.account_journal_early_pay_discount_gain_account_id',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', 'in', ('income', 'income_other', 'expense'))]",
domain="[('deprecated', '=', False), ('account_type', 'in', ('income', 'income_other', 'expense'))]",
)
def set_values(self):
@@ -208,7 +234,8 @@ class ResConfigSettings(models.TransientModel):
def _onchange_tax_exigibility(self):
res = {}
tax = self.env['account.tax'].search([
('company_id', '=', self.env.company.id), ('tax_exigibility', '=', 'on_payment')
*self.env['account.tax']._check_company_domain(self.env.company),
('tax_exigibility', '=', 'on_payment'),
], limit=1)
if not self.tax_exigibility and tax:
self.tax_exigibility = True
@@ -16,6 +16,7 @@ class ResPartnerBank(models.Model):
journal_id = fields.One2many(
'account.journal', 'bank_account_id', domain=[('type', '=', 'bank')], string='Account Journal', readonly=True,
check_company=True,
help="The accounting journal corresponding to this bank account.")
has_iban_warning = fields.Boolean(
compute='_compute_display_account_warning',
@@ -12,8 +12,6 @@ class AccountChartTemplate(models.AbstractModel):
: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
@@ -21,9 +19,6 @@ class AccountChartTemplate(models.AbstractModel):
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',
@@ -46,9 +41,11 @@ class AccountChartTemplate(models.AbstractModel):
"""
return {
self.env.company.id: {
'anglo_saxon_accounting': True,
'account_fiscal_country_id': 'base.us',
'bank_account_code_prefix': '1014',
'cash_account_code_prefix': '1015',
'transfer_account_code_prefix': '1017',
'account_default_pos_receivable_account_id': 'pos_receivable',
'income_currency_exchange_account_id': 'income_currency_exchange',
'expense_currency_exchange_account_id': 'expense_currency_exchange',
@@ -75,7 +75,7 @@ class AccountBankStatementLine(models.Model):
:return (list<int>): the ids of partner the company has access to.
"""
return self.env['res.partner'].search([
'|', ('company_id', '=', company_id), ('company_id', '=', False),
*self.env['res.company']._check_company_domain(company_id),
('id', 'in', self.env.registry.populated_models['res.partner']),
]).ids
@@ -128,7 +128,7 @@ class AccountBankStatementLine(models.Model):
])
journal_ids = self.env['account.journal'].search([
('company_id', 'in', company_ids.ids),
*self.env['account.journal']._check_company_domain(company_ids),
('type', 'in', ('cash', 'bank')),
]).ids
return [
+7 -4
View File
@@ -39,7 +39,10 @@ class AccountMove(models.Model):
payable, receivable, liquidity, other, False.
:return (Model<account.account>): the recordset of accounts found.
"""
domain = [('company_id', '=', company_id), ('account_type', '!=', 'off_balance')]
domain = [
*self.env['account.account']._check_company_domain(company_id),
('account_type', '!=', 'off_balance'),
]
if types:
domain += [('account_type', 'in', types)]
return self.env['account.account'].search(domain)
@@ -56,7 +59,7 @@ class AccountMove(models.Model):
:return (list<int>): the ids of the journals of a company and a certain type
"""
return self.env['account.journal'].search([
('company_id', '=', company_id),
*self.env['account.journal']._check_company_domain(company_id),
('currency_id', 'in', (False, currency_id)),
('type', '=', journal_type),
]).ids
@@ -70,7 +73,7 @@ class AccountMove(models.Model):
:return (Model<product.product>): all the products te company has access to
"""
return self.env['product.product'].search([
('company_id', 'in', (False, company_id)),
*self.env['product.product']._check_company_domain(company_id),
('id', 'in', self.env.registry.populated_models['product.product']),
])
@@ -83,7 +86,7 @@ class AccountMove(models.Model):
:return (list<int>): the ids of partner the company has access to.
"""
return self.env['res.partner'].search([
'|', ('company_id', '=', company_id), ('company_id', '=', False),
*self.env['res.partner']._check_company_domain(company_id),
('id', 'in', self.env.registry.populated_models['res.partner']),
]).ids
+2 -2
View File
@@ -34,7 +34,7 @@ class AccountPayment(models.Model):
:return (list<int>): the ids of partner the company has access to.
"""
return self.env['res.partner'].search([
'|', ('company_id', '=', company_id), ('company_id', '=', False),
*self.env['res.partner']._check_company_domain(company_id),
('id', 'in', self.env.registry.populated_models['res.partner']),
]).ids
@@ -47,7 +47,7 @@ class AccountPayment(models.Model):
:return (list<int>): the ids of the bank and cash journals of a company
"""
return self.env['account.journal'].search([
('company_id', '=', company_id),
*self.env['account.journal']._check_company_domain(company_id),
('type', 'in', ('cash', 'bank')),
]).ids
@@ -62,7 +62,7 @@ class AccountReconcileModelLine(models.Model):
asset, liability, equity, off_balance, False.
:return (Model<account.account>): the recordset of accounts found.
"""
domain = [('company_id', '=', company_id)]
domain = self.env['account.account']._check_company_domain(company_id)
if type:
domain += [('account_type', '=', type)]
if group:
+11 -11
View File
@@ -125,49 +125,49 @@
<record id="journal_group_comp_rule" model="ir.rule">
<field name="name">Journal Group multi-company</field>
<field name="model_id" ref="model_account_journal_group"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record id="journal_comp_rule" model="ir.rule">
<field name="name">Journal multi-company</field>
<field name="model_id" ref="model_account_journal"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record id="account_comp_rule" model="ir.rule">
<field name="name">Account multi-company</field>
<field name="model_id" ref="model_account_account"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record id="account_group_comp_rule" model="ir.rule">
<field name="name">Account Group multi-company</field>
<field name="model_id" ref="model_account_group"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record id="account_root_comp_rule" model="ir.rule">
<field name="name">Account Root multi-company</field>
<field name="model_id" ref="model_account_root"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
<field name="domain_force">['|',('company_id','=',False), ('company_id', 'parent_of', company_ids)]</field>
</record>
<record id="tax_group_comp_rule" model="ir.rule">
<field name="name">Tax group multi-company</field>
<field name="model_id" ref="model_account_tax_group"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record id="tax_comp_rule" model="ir.rule">
<field name="name">Tax multi-company</field>
<field name="model_id" ref="model_account_tax"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record id="tax_rep_comp_rule" model="ir.rule">
<field name="name">Tax Repartition multi-company</field>
<field name="model_id" ref="model_account_tax_repartition_line"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
<field name="domain_force">['|',('company_id','=',False), ('company_id', 'parent_of', company_ids)]</field>
</record>
<record id="invoice_analysis_comp_rule" model="ir.rule">
@@ -179,7 +179,7 @@
<record id="account_fiscal_position_comp_rule" model="ir.rule">
<field name="name">Account fiscal Mapping company rule</field>
<field name="model_id" ref="model_account_fiscal_position"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record model="ir.rule" id="account_bank_statement_comp_rule">
@@ -197,13 +197,13 @@
<record model="ir.rule" id="account_reconcile_model_template_comp_rule">
<field name="name">Account reconcile model template company rule</field>
<field name="model_id" ref="model_account_reconcile_model"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record model="ir.rule" id="account_reconcile_model_line_template_comp_rule">
<field name="name">Account reconcile model_line template company rule</field>
<field name="model_id" ref="model_account_reconcile_model_line"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
</record>
<record model="ir.rule" id="account_payment_comp_rule">
+1
View File
@@ -22,6 +22,7 @@ from . import test_account_invoice_report
from . import test_account_move_line_tax_details
from . import test_account_journal_dashboard
from . import test_chart_template
from . import test_company_branch
from . import test_digest
from . import test_fiscal_position
from . import test_sequence_mixin
+2 -1
View File
@@ -24,13 +24,14 @@ class TestAccountJournal(AccountTestInvoicingCommon):
def test_changing_journal_company(self):
''' Ensure you can't change the company of an account.journal if there are some journal entries '''
self.company_data['default_journal_sale'].code = "DIFFERENT"
self.env['account.move'].create({
'move_type': 'entry',
'date': '2019-01-01',
'journal_id': self.company_data['default_journal_sale'].id,
})
with self.assertRaises(UserError), self.cr.savepoint():
with self.assertRaisesRegex(UserError, "entries linked to it"), self.cr.savepoint():
self.company_data['default_journal_sale'].company_id = self.company_data_2['company']
def test_account_control_create_journal_entry(self):
@@ -1252,7 +1252,7 @@ class TestAccountTaxDetailsReport(AccountTestInvoicingCommon):
def test_amounts_sign(self):
for tax_sign in (1, -1):
tax = self.env['account.tax'].create({
'name': "tax",
'name': f"tax {tax_sign}",
'amount_type': 'percent',
'amount': tax_sign * 10.0,
})
@@ -4188,7 +4188,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon):
})
foreign_caba_tax = self.env['account.tax'].create({
'name': 'tax_1',
'name': 'foreign tax_1',
'amount': 33.3333,
'company_id': self.company_data['company'].id,
'cash_basis_transition_account_id': self.cash_basis_transfer_account.id,
+208
View File
@@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
from contextlib import nullcontext
from freezegun import freeze_time
from functools import partial
from odoo import Command
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.exceptions import UserError
from odoo.tests import tagged, Form
@tagged('post_install', '-at_install')
class TestCompanyBranch(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref)
cls.company_data['company'].write({
'child_ids': [
Command.create({'name': 'Branch A'}),
Command.create({'name': 'Branch B'}),
],
})
cls.cr.precommit.run() # load the CoA
cls.root_company = cls.company_data['company']
cls.branch_a, cls.branch_b = cls.root_company.child_ids
def test_chart_template_loading(self):
# Some company params have to be the same
self.assertEqual(self.root_company.currency_id, self.branch_a.currency_id)
self.assertEqual(self.root_company.fiscalyear_last_day, self.branch_a.fiscalyear_last_day)
self.assertEqual(self.root_company.fiscalyear_last_month, self.branch_a.fiscalyear_last_month)
# The accounts are shared
root_accounts = self.env['account.account'].search([('company_id', 'parent_of', self.root_company.id)])
branch_a_accounts = self.env['account.account'].search([('company_id', 'parent_of', self.branch_a.id)])
self.assertTrue(root_accounts)
self.assertEqual(root_accounts, branch_a_accounts)
# The journals are shared
root_journals = self.env['account.journal'].search([('company_id', 'parent_of', self.root_company.id)])
branch_a_journals = self.env['account.journal'].search([('company_id', 'parent_of', self.branch_a.id)])
self.assertTrue(root_journals)
self.assertEqual(root_journals, branch_a_journals)
def test_reconciliation(self):
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': '2016-01-01',
'company_id': self.branch_a.id,
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'name': 'product',
'price_unit': 1000,
})
],
})
invoice.action_post()
refund = self.env['account.move'].create({
'move_type': 'out_refund',
'invoice_date': '2017-01-01',
'company_id': self.root_company.id,
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'name': 'product',
'price_unit': 1000,
})
],
})
refund.action_post()
payment_lines = (invoice + refund).line_ids.filtered(lambda l: l.display_type == 'payment_term')
payment_lines.reconcile()
self.assertEqual(payment_lines.mapped('amount_residual'), [0, 0])
self.assertFalse(payment_lines.matched_debit_ids.exchange_move_id)
# Can still open the invoice with only it's branch accessible
self.env.invalidate_all()
with Form(invoice.with_context(allowed_company_ids=self.branch_a.ids)):
pass
def test_reconciliation_foreign_currency(self):
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': '2016-01-01',
'company_id': self.branch_a.id,
'currency_id': self.currency_data['currency'].id,
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'name': 'product',
'price_unit': 1000,
})
],
})
invoice.action_post()
refund = self.env['account.move'].create({
'move_type': 'out_refund',
'invoice_date': '2017-01-01',
'company_id': self.root_company.id,
'currency_id': self.currency_data['currency'].id,
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'name': 'product',
'price_unit': 1000,
})
],
})
refund.action_post()
payment_lines = (invoice + refund).line_ids.filtered(lambda l: l.display_type == 'payment_term')
payment_lines.reconcile()
self.assertEqual(payment_lines.mapped('amount_residual'), [0, 0])
self.assertTrue(payment_lines.matched_debit_ids.exchange_move_id)
self.assertTrue(payment_lines.matched_debit_ids.exchange_move_id.journal_id.company_id, invoice.company_id)
# Can still open the invoice with only it's branch accessible
self.env.invalidate_all()
with Form(invoice.with_context(allowed_company_ids=self.branch_a.ids)):
pass
def test_lock_dates(self):
moves = self.env['account.move'].search([])
moves.button_draft()
moves.unlink()
for lock in ['fiscalyear_lock_date', 'tax_lock_date']:
for root_lock, branch_lock, invoice_date, company, expected in (
# before both locks
('3021-01-01', '3022-01-01', '3020-01-01', self.root_company, 'fail'),
('3021-01-01', '3022-01-01', '3020-01-01', self.branch_a, 'fail'),
# between root and branch lock
('3020-01-01', '3022-01-01', '3021-01-01', self.root_company, 'success'),
('3020-01-01', '3022-01-01', '3021-01-01', self.branch_a, 'fail'),
# between branch and root lock
('3022-01-01', '3020-01-01', '3021-01-01', self.root_company, 'fail'),
('3022-01-01', '3020-01-01', '3021-01-01', self.branch_a, 'fail'),
# after both locks
('3020-01-01', '3021-01-01', '3022-01-01', self.root_company, 'success'),
('3020-01-01', '3021-01-01', '3022-01-01', self.branch_a, 'success'),
):
with self.subTest(
lock=lock,
root_lock=root_lock,
branch_lock=branch_lock,
invoice_date=invoice_date,
company=company.name,
), self.env.cr.savepoint() as sp:
with freeze_time('4000-01-01'): # ensure we don't lock in the future
self.root_company[lock] = root_lock
self.branch_a[lock] = branch_lock
check = partial(self.assertRaises, UserError) if expected == 'fail' else nullcontext
with check():
self.init_invoice(
'out_invoice', amounts=[100], taxes=self.root_company.account_sale_tax_id,
invoice_date=invoice_date, post=True, company=company,
)
sp.close()
def test_change_record_company(self):
account = self.env['account.account'].create({
'name': 'volatile',
'code': 'vola',
'account_type': 'income',
'company_id': self.branch_a.id,
})
account_lines = [Command.create({
'account_id': account.id,
'name': 'name',
})]
tax = self.env['account.tax'].create({
'name': 'volatile',
})
tax_lines = [Command.create({
'account_id': self.root_company.account_journal_suspense_account_id.id,
'tax_ids': [Command.set(tax.ids)],
'name': 'name',
})]
for record, lines in (
(account, account_lines),
(tax, tax_lines),
):
with self.subTest(model=record._name):
self.env['account.move'].create({'company_id': self.branch_a.id, 'line_ids': lines})
# Can switch to main
record.company_id = self.root_company
# Can switch back
record.company_id = self.branch_a
# Can't use in main if owned by a branch
with self.assertRaisesRegex(UserError, 'belongs to another company'):
self.env['account.move'].create({'company_id': self.root_company.id, 'line_ids': lines})
# Can still switch to main
record.company_id = self.root_company
# Can use in main now
self.env['account.move'].create({'company_id': self.root_company.id, 'line_ids': lines})
# Can't switch back to branch if used in main
with self.assertRaisesRegex(UserError, 'journal items linked'):
record.company_id = self.branch_a
@@ -60,9 +60,9 @@
<group>
<group>
<field name="account_type" widget="account_type_selection"/>
<field name="tax_ids" widget="many2many_tags" domain="[('company_id','=',company_id)]" attrs="{'invisible': [('internal_group', '=', 'off_balance')]}" options="{'no_quick_create': True}"/>
<field name="tax_ids" widget="many2many_tags" attrs="{'invisible': [('internal_group', '=', 'off_balance')]}" options="{'no_quick_create': True}"/>
<field name="tag_ids" widget="many2many_tags" domain="[('applicability', '=', 'accounts')]" context="{'default_applicability': 'accounts'}" options="{'no_create_edit': True}"/>
<field name="allowed_journal_ids" widget="many2many_tags" domain="[('company_id','=',company_id)]" options="{'no_create_edit': True}"/>
<field name="allowed_journal_ids" widget="many2many_tags" options="{'no_create_edit': True}"/>
</group>
<group>
<field name="internal_group" invisible="1" readonly="1"/>
+6 -6
View File
@@ -24,7 +24,7 @@
<page string="Information" name="information">
<group>
<group string="Amount">
<field name="account_id" options="{'no_create': True}" domain="[('company_id', '=', company_id)]" readonly="1"/>
<field name="account_id" options="{'no_create': True}" domain="[('company_id', 'parent_of', company_id), ('deprecated', '=', False)]" readonly="1"/>
<field name="debit" readonly="1"/>
<field name="credit" readonly="1"/>
<field name="balance" readonly="1"/>
@@ -170,7 +170,7 @@
<field name="company_id" groups="base.group_multi_company" readonly="1" optional="hide"/>
<field name="journal_id" readonly="1" options='{"no_open":True}' optional="hide"/>
<field name="move_name" string="Journal Entry" widget="open_move_widget"/>
<field name="account_id" options="{'no_open': True, 'no_create': True}" domain="[('company_id', '=', company_id)]" groups="account.group_account_readonly"/>
<field name="account_id" options="{'no_open': True, 'no_create': True}" domain="[('company_id', 'parent_of', company_id), ('deprecated', '=', False)]" groups="account.group_account_readonly"/>
<field name="partner_id" optional="show" attrs="{'readonly':[('move_type', '!=', 'entry')]}"/>
<field name="ref" optional="hide" readonly="False"/>
<field name="product_id" readonly="1" optional="hide"/>
@@ -909,7 +909,7 @@
context="{'partner_id': partner_id, 'move_type': parent.move_type}"
groups="account.group_account_readonly"
options="{'no_quick_create': True}"
domain="[('deprecated', '=', False), ('account_type', 'not in', ('asset_receivable', 'liability_payable', 'off_balance')), ('company_id', '=', parent.company_id)]"
domain="[('deprecated', '=', False), ('account_type', 'not in', ('asset_receivable', 'liability_payable', 'off_balance')), ('company_id', 'parent_of', parent.company_id)]"
attrs="{'required': [('display_type', 'not in', ('line_note', 'line_section'))]}"/>
<field name="analytic_distribution" widget="analytic_distribution"
groups="analytic.group_analytic_accounting"
@@ -1023,7 +1023,7 @@
<field name="discount" string="Disc.%"/>
</group>
<group>
<field name="account_id" options="{'no_create': True}" domain="[('company_id', '=', company_id)]" context="{'partner_id': partner_id, 'move_type': parent.move_type}"/>
<field name="account_id" domain="[('company_id', 'parent_of', company_id), ('deprecated', '=', False)]" options="{'no_create': True}" context="{'partner_id': partner_id, 'move_type': parent.move_type}"/>
<field name="tax_ids" widget="many2many_tags"/>
<field name="analytic_distribution" widget="analytic_distribution" groups="analytic.group_analytic_accounting"/>
</group>
@@ -1084,7 +1084,7 @@
'required': [('display_type', 'not in', ('line_section', 'line_note'))],
'invisible': [('display_type', 'in', ('line_section', 'line_note'))],
}"
domain="[('deprecated', '=', False), ('company_id', '=', parent.company_id)]" />
domain="[('company_id', 'parent_of', parent.company_id), ('deprecated', '=', False)]" />
<field name="partner_id"
optional="show"
domain="['|', ('parent_id', '=', False), ('is_company', '=', True)]"
@@ -1171,7 +1171,7 @@
<!-- Form view to cover mobile use -->
<form>
<group>
<field name="account_id" domain="[('company_id', '=', parent.company_id), ('deprecated', '=', False)]"/>
<field name="account_id" domain="[('company_id', 'parent_of', parent.company_id), ('deprecated', '=', False)]"/>
<field name="partner_id" domain="['|', ('parent_id', '=', False), ('is_company', '=', True)]"/>
<field name="name"/>
<field name="analytic_distribution" widget="analytic_distribution" groups="analytic.group_analytic_accounting"/>
@@ -18,6 +18,7 @@
<field name="date"/>
<field name="name"/>
<field name="journal_id"/>
<field name="company_id" optional="hide" groups="base.group_multi_company"/>
<field name="payment_method_line_id"/>
<field name="partner_id" string="Customer"/>
<field name="amount_signed" string="Amount in Currency" optional="hide" groups="!base.group_multi_currency"/>
@@ -208,6 +208,7 @@
<field name="partner_mapping_line_ids"
nolabel="1">
<tree editable="bottom">
<field name="company_id"/>
<field name="payment_ref_regex"
attrs="{'required': [('narration_regex', '=', False)]}"/>
<field name="narration_regex"
@@ -23,7 +23,7 @@
<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">
<block title="Fiscal Localization" name="fiscal_localization_setting_container" attrs="{'invisible': [('is_root_company', '=', False)]}">
<setting string="Fiscal Localization" company_dependent="1" help="Taxes, fiscal positions, chart of accounts &amp; legal statements for your country"
documentation="/applications/finance/fiscal_localizations.html">
<div class="content-group">
@@ -12,6 +12,7 @@ import json
class AutomaticEntryWizard(models.TransientModel):
_name = 'account.automatic.entry.wizard'
_description = 'Create Automatic Entries'
_check_company_auto = True
# General
action = fields.Selection([('change_period', 'Change Period'), ('change_account', 'Change Account')], required=True)
@@ -24,7 +25,8 @@ class AutomaticEntryWizard(models.TransientModel):
percentage = fields.Float("Percentage", compute='_compute_percentage', readonly=False, store=True, help="Percentage of each line to execute the action on.")
total_amount = fields.Monetary(compute='_compute_total_amount', store=True, readonly=False, currency_field='company_currency_id', help="Total amount impacted by the automatic entry.")
journal_id = fields.Many2one('account.journal', required=True, readonly=False, string="Journal",
domain="[('company_id', '=', company_id), ('type', '=', 'general')]",
check_company=True,
domain="[('type', '=', 'general')]",
compute="_compute_journal_id",
inverse="_inverse_journal_id",
help="Journal where to create the entry.")
@@ -32,21 +34,21 @@ class AutomaticEntryWizard(models.TransientModel):
# change period
account_type = fields.Selection([('income', 'Revenue'), ('expense', 'Expense')], compute='_compute_account_type', store=True)
expense_accrual_account = fields.Many2one('account.account', readonly=False,
domain="[('company_id', '=', company_id),"
"('account_type', 'not in', ('asset_receivable', 'liability_payable', 'off_balance'))]",
check_company=True,
domain="[('account_type', 'not in', ('asset_receivable', 'liability_payable', 'off_balance'))]",
compute="_compute_expense_accrual_account",
inverse="_inverse_expense_accrual_account",
)
revenue_accrual_account = fields.Many2one('account.account', readonly=False,
domain="[('company_id', '=', company_id),"
"('account_type', 'not in', ('asset_receivable', 'liability_payable', 'off_balance'))]",
check_company=True,
domain="[('account_type', 'not in', ('asset_receivable', 'liability_payable', 'off_balance'))]",
compute="_compute_revenue_accrual_account",
inverse="_inverse_revenue_accrual_account",
)
lock_date_message = fields.Char(string="Lock Date Message", compute="_compute_lock_date_message")
# change account
destination_account_id = fields.Many2one(string="To", comodel_name='account.account', help="Account to transfer to.")
destination_account_id = fields.Many2one(string="To", comodel_name='account.account', help="Account to transfer to.", check_company=True)
display_currency_helper = fields.Boolean(string="Currency Conversion Helper", compute='_compute_display_currency_helper')
# Technical field. Used to indicate whether or not to display the currency conversion tooltip. The tooltip informs a currency conversion will be performed with the transfer.
@@ -139,9 +141,9 @@ class AutomaticEntryWizard(models.TransientModel):
raise UserError(_('You can only change the period/account for posted journal items.'))
if any(move_line.reconciled for move_line in move_line_ids):
raise UserError(_('You can only change the period/account for items that are not yet reconciled.'))
if any(line.company_id != move_line_ids[0].company_id for line in move_line_ids):
if any(line.company_id.root_id != move_line_ids[0].company_id.root_id for line in move_line_ids):
raise UserError(_('You cannot use this wizard on journal entries belonging to different companies.'))
res['company_id'] = move_line_ids[0].company_id.id
res['company_id'] = move_line_ids[0].company_id.root_id.id
allowed_actions = set(dict(self._fields['action'].selection))
if self.env.context.get('default_action'):
@@ -50,11 +50,13 @@ class AccountMoveReversal(models.TransientModel):
for record in self:
if record.move_ids:
record.available_journal_ids = self.env['account.journal'].search([
('company_id', '=', record.company_id.id),
*self.env['account.journal']._check_company_domain(record.company_id),
('type', 'in', record.move_ids.journal_id.mapped('type')),
])
else:
record.available_journal_ids = self.env['account.journal'].search([('company_id', '=', record.company_id.id)])
record.available_journal_ids = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(record.company_id),
])
@api.constrains('journal_id', 'move_ids')
def _check_journal_type(self):
@@ -70,7 +72,7 @@ class AccountMoveReversal(models.TransientModel):
if any(move.state != "posted" for move in move_ids):
raise UserError(_('You can only reverse posted moves.'))
if 'company_id' in fields:
res['company_id'] = move_ids.company_id.id or self.env.company.id
res['company_id'] = move_ids.company_id.root_id.id or self.env.company.id
if 'move_ids' in fields:
res['move_ids'] = [(6, 0, move_ids.ids)]
return res
@@ -9,6 +9,7 @@ from odoo.tools import frozendict
class AccountPaymentRegister(models.TransientModel):
_name = 'account.payment.register'
_description = 'Register Payment'
_check_company_auto = True
# == Business fields ==
payment_date = fields.Date(string="Payment Date", required=True,
@@ -30,6 +31,7 @@ class AccountPaymentRegister(models.TransientModel):
journal_id = fields.Many2one(
comodel_name='account.journal',
compute='_compute_journal_id', store=True, readonly=False, precompute=True,
check_company=True,
domain="[('id', 'in', available_journal_ids)]")
available_journal_ids = fields.Many2many(
comodel_name='account.journal',
@@ -111,7 +113,8 @@ class AccountPaymentRegister(models.TransientModel):
comodel_name='account.account',
string="Difference Account",
copy=False,
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
domain="[('deprecated', '=', False)]",
check_company=True,
compute='_compute_writeoff_account_id',
store=True,
readonly=False,
@@ -153,7 +156,10 @@ class AccountPaymentRegister(models.TransientModel):
"""
payment_type = batch_result['payment_values']['payment_type']
company = batch_result['lines'].company_id
journals = self.env['account.journal'].search([('company_id', '=', company.id), ('type', 'in', ('bank', 'cash'))])
journals = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(company),
('type', 'in', ('bank', 'cash')),
])
if payment_type == 'inbound':
return journals.filtered('inbound_payment_method_line_ids')
else:
@@ -169,13 +175,16 @@ class AccountPaymentRegister(models.TransientModel):
payment_values = batch_result['payment_values']
foreign_currency_id = payment_values['currency_id']
partner_bank_id = payment_values['partner_bank_id']
company = batch_result['lines'].company_id
if len(company) > 1:
company = company._accessible_branches()[:1]
currency_domain = [('currency_id', '=', foreign_currency_id)]
partner_bank_domain = [('bank_account_id', '=', partner_bank_id)]
default_domain = [
*self.env['account.journal']._check_company_domain(company),
('type', 'in', ('bank', 'cash')),
('company_id', '=', batch_result['lines'].company_id.id),
('id', 'in', self.available_journal_ids.ids)
]
@@ -202,7 +211,7 @@ class AccountPaymentRegister(models.TransientModel):
@api.model
def _get_batch_available_partner_banks(self, batch_result, journal):
payment_values = batch_result['payment_values']
company = batch_result['lines'].company_id
company = batch_result['lines'].company_id._accessible_branches()[:1]
# A specific bank account is set on the journal. The user must use this one.
if payment_values['payment_type'] == 'inbound':
@@ -244,7 +253,7 @@ class AccountPaymentRegister(models.TransientModel):
lines = self.line_ids._origin
if len(lines.company_id) > 1:
if len(lines.company_id.root_id) > 1:
raise UserError(_("You can't create payments for entries belonging to different companies."))
if not lines:
raise UserError(_("You can't open the register payment wizard without at least one receivable/payable line."))
@@ -306,7 +315,7 @@ class AccountPaymentRegister(models.TransientModel):
'''
payment_values = batch_result['payment_values']
lines = batch_result['lines']
company = lines[0].company_id
company = lines[0].company_id._accessible_branches()[:1]
source_amount = abs(sum(lines.mapped('amount_residual')))
if payment_values['currency_id'] == company.currency_id.id:
@@ -369,7 +378,7 @@ class AccountPaymentRegister(models.TransientModel):
else:
# == Multiple batches: The wizard is not editable ==
wizard.update({
'company_id': batches[0]['lines'][0].company_id.id,
'company_id': batches[0]['lines'][0].company_id._accessible_branches()[:1].id,
'partner_id': False,
'partner_type': False,
'payment_type': wizard_values_from_batch['payment_type'],
@@ -414,7 +423,7 @@ class AccountPaymentRegister(models.TransientModel):
wizard.available_journal_ids = wizard._get_batch_available_journals(batch)
else:
wizard.available_journal_ids = self.env['account.journal'].search([
('company_id', '=', wizard.company_id.id),
*self.env['account.journal']._check_company_domain(wizard.company_id),
('type', 'in', ('bank', 'cash')),
])
@@ -426,8 +435,8 @@ class AccountPaymentRegister(models.TransientModel):
wizard.journal_id = wizard._get_batch_journal(batch)
else:
wizard.journal_id = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(wizard.company_id),
('type', 'in', ('bank', 'cash')),
('company_id', '=', wizard.company_id.id),
('id', 'in', self.available_journal_ids.ids)
], limit=1)
@@ -609,8 +618,10 @@ class AccountPaymentRegister(models.TransientModel):
"The register payment wizard should only be called on account.move or account.move.line records."
))
if 'journal_id' in res and not self.env['account.journal'].browse(res['journal_id'])\
.filtered_domain([('company_id', '=', lines.company_id.id), ('type', 'in', ('bank', 'cash'))]):
if 'journal_id' in res and not self.env['account.journal'].browse(res['journal_id']).filtered_domain([
*self.env['account.journal']._check_company_domain(lines.company_id),
('type', 'in', ('bank', 'cash')),
]):
# default can be inherited from the list view, should be computed instead
del res['journal_id']
@@ -633,7 +644,7 @@ class AccountPaymentRegister(models.TransientModel):
# Check.
if not available_lines:
raise UserError(_("You can't register a payment because there is nothing left to pay on the selected journal items."))
if len(lines.company_id) > 1:
if len(lines.company_id.root_id) > 1:
raise UserError(_("You can't create payments for entries belonging to different companies."))
if len(set(available_lines.mapped('account_type'))) > 1:
raise UserError(_("You can't register payments for journal items being either all inbound, either all outbound."))
@@ -654,6 +665,7 @@ class AccountPaymentRegister(models.TransientModel):
'partner_type': self.partner_type,
'ref': self.communication,
'journal_id': self.journal_id.id,
'company_id': self.company_id.id,
'currency_id': self.currency_id.id,
'partner_id': self.partner_id.id,
'partner_bank_id': self.partner_bank_id.id,
@@ -725,6 +737,7 @@ class AccountPaymentRegister(models.TransientModel):
'partner_type': batch_values['partner_type'],
'ref': self._get_batch_communication(batch_result),
'journal_id': self.journal_id.id,
'company_id': self.company_id.id,
'currency_id': batch_values['source_currency_id'],
'partner_id': batch_values['partner_id'],
'partner_bank_id': partner_bank_id,
@@ -47,8 +47,10 @@ class AccountTourUploadBill(models.TransientModel):
record.preview_invoice = html
def _selection_values(self):
journal_alias = self.env['account.journal'] \
.search([('type', '=', 'purchase'), ('company_id', '=', self.env.company.id)], limit=1)
journal_alias = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(self.env.company),
('type', '=', 'purchase'),
], limit=1)
values = [('sample', _('Try a sample vendor bill')), ('upload', _('Upload your own bill'))]
if journal_alias.alias_name and journal_alias.alias_domain:
+8 -7
View File
@@ -12,12 +12,13 @@ from odoo.tools.misc import formatLang
class AccruedExpenseRevenue(models.TransientModel):
_name = 'account.accrued.orders.wizard'
_description = 'Accrued Orders Wizard'
_check_company_auto = True
def _get_account_domain(self):
if self.env.context.get('active_model') == 'purchase.order':
return [('account_type', '=', 'liability_current'), ('company_id', '=', self._get_default_company())]
return [('account_type', '=', 'liability_current')]
else:
return [('account_type', '=', 'asset_current'), ('company_id', '=', self._get_default_company())]
return [('account_type', '=', 'asset_current')]
def _get_default_company(self):
if not self._context.get('active_model'):
@@ -32,7 +33,7 @@ class AccruedExpenseRevenue(models.TransientModel):
journal_id = fields.Many2one(
comodel_name='account.journal',
compute='_compute_journal_id', store=True, readonly=False, precompute=True,
domain="[('type', '=', 'general'), ('company_id', '=', company_id)]",
domain="[('type', '=', 'general')]",
required=True,
check_company=True,
string='Journal',
@@ -78,11 +79,11 @@ class AccruedExpenseRevenue(models.TransientModel):
@api.depends('company_id')
def _compute_journal_id(self):
journal = self.env['account.journal'].search(
[('type', '=', 'general'), ('company_id', '=', self.company_id.id)], limit=1
)
for record in self:
record.journal_id = journal
record.journal_id = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(record.company_id),
('type', '=', 'general')
], limit=1)
@api.depends('date', 'journal_id', 'account_id', 'amount')
def _compute_preview_data(self):
+2 -1
View File
@@ -74,7 +74,8 @@ class SetupBarBankConfigWizard(models.TransientModel):
linked_journal_id = fields.Many2one(string="Journal",
comodel_name='account.journal', inverse='set_linked_journal_id',
compute="_compute_linked_journal_id",
domain=lambda self: [('type', '=', 'bank'), ('bank_account_id', '=', False), ('company_id', '=', self.env.company.id)])
check_company=True,
domain=[('type', '=', 'bank'), ('bank_account_id', '=', False)])
bank_bic = fields.Char(related='bank_id.bic', readonly=False, string="Bic")
num_journals_without_account = fields.Integer(default=lambda self: self._number_unlinked_journal())
@@ -412,7 +412,7 @@ class AccountEdiCommon(models.AbstractModel):
tax_ids = []
for tax_categ_percent_el in allow_el.findall(tax_xpath):
tax = self.env['account.tax'].search([
('company_id', '=', invoice.company_id.id),
*self.env['account.tax']._check_company_domain(invoice.company_id),
('amount', '=', float(tax_categ_percent_el.text)),
('amount_type', '=', 'percent'),
('type_tax_use', '=', invoice.journal_id.type), # Journal type is ensured by _create_invoice_from_xml_tree to be either 'sale' or 'purchase'
@@ -654,7 +654,7 @@ class AccountEdiCommon(models.AbstractModel):
4. price_include matching the amount
"""
base_domain = [
('company_id', '=', invoice_line.company_id.id),
*self.env['account.journal']._check_company_domain(invoice_line.company_id),
('amount_type', '=', 'fixed'),
('amount', '=', fixed_tax_vals['tax_amount']),
]
@@ -675,7 +675,7 @@ class AccountEdiCommon(models.AbstractModel):
for tax_node in tax_nodes:
amount = float(tax_node.text)
domain = [
('company_id', '=', invoice_line.company_id.id),
*self.env['account.journal']._check_company_domain(invoice_line.company_id),
('amount_type', '=', 'percent'),
('type_tax_use', '=', invoice_line.move_id.journal_id.type),
('amount', '=', amount),
@@ -616,7 +616,7 @@ class AccountEdiXmlUBL20(models.AbstractModel):
# Product
product = self._import_retrieve_info_from_map(
tree,
self._import_retrieve_product_map(invoice_line.move_id.journal_id),
self._import_retrieve_product_map(invoice_line.move_id.company_id),
)
if product is not None:
invoice_line.product_id = product
@@ -740,9 +740,9 @@ class AccountEdiXmlUBL20(models.AbstractModel):
return self.env['product.product'].search(extra_domain + [('name', 'ilike', name_node.text)], limit=1)
return {
10: lambda tree: with_code_barcode(tree, [('company_id', '=', company.id)]),
10: lambda tree: with_code_barcode(tree, self.env['product.product']._check_company_domain(company)),
20: lambda tree: with_code_barcode(tree, []),
30: lambda tree: with_name(tree, [('company_id', '=', company.id)]),
30: lambda tree: with_name(tree, self.env['product.product']._check_company_domain(company)),
40: lambda tree: with_name(tree, []),
}
@@ -33,8 +33,8 @@ class AccountJournal(models.Model):
for journal in self:
to_remove = []
available_providers = installed_providers.filtered(
lambda p: p.company_id == journal.company_id
available_providers = installed_providers.filtered_domain(
self.env['payment.provider']._check_company_domain(journal.company_id)
).mapped('code')
available = payment_method.code in available_providers
@@ -64,7 +64,7 @@ class AccountPayment(models.Model):
for payment in self:
if payment.use_electronic_payment_method:
payment.suitable_payment_token_ids = self.env['payment.token'].sudo().search([
('company_id', '=', payment.company_id.id),
*self.env['payment.token']._check_company_domain(payment.company_id),
('provider_id.capture_manually', '=', False),
('partner_id', '=', payment.partner_id.id),
('provider_id', '=', payment.payment_method_line_id.payment_provider_id.id),
@@ -103,7 +103,7 @@ class AccountPayment(models.Model):
return
self.payment_token_id = self.env['payment.token'].search([
('company_id', '=', self.company_id.id),
*self.env['payment.token']._check_company_domain(self.company_id),
('partner_id', '=', self.partner_id.id),
('provider_id.capture_manually', '=', False),
('provider_id', '=', self.payment_method_line_id.payment_provider_id.id),
@@ -21,8 +21,8 @@ class AccountPaymentMethodLine(models.Model):
@api.depends('payment_method_id')
def _compute_payment_provider_id(self):
providers = self.env['payment.provider'].sudo().search([
*self.env['payment.provider']._check_company_domain(self.journal_id.company_id),
('code', 'in', self.mapped('code')),
('company_id', 'in', self.journal_id.company_id.ids),
])
# Make sure to pick the active provider, if any.
@@ -37,7 +37,10 @@ class AccountPaymentMethodLine(models.Model):
for line in self:
code = line.payment_method_id.code
company = line.journal_id.company_id
line.payment_provider_id = providers_map.get((code, company), False)
line.payment_provider_id = False
while not line.payment_provider_id and company:
line.payment_provider_id = providers_map.get((code, company), False)
company = company.parent_id
@api.model
def _get_payment_method_domain(self, code):
@@ -49,7 +52,7 @@ class AccountPaymentMethodLine(models.Model):
if unique:
company_ids = self.env['payment.provider'].sudo().search([('code', '=', code)]).mapped('company_id')
if company_ids:
domain = expression.AND([domain, [('company_id', 'in', company_ids.ids)]])
domain = expression.AND([domain, self.env['payment.provider']._check_company_domain(company_ids)])
return domain
@@ -12,7 +12,8 @@ class PaymentProvider(models.Model):
comodel_name='account.journal',
compute='_compute_journal_id',
inverse='_inverse_journal_id',
domain='[("type", "=", "bank"), ("company_id", "=", company_id)]',
check_company=True,
domain='[("type", "=", "bank")]',
)
#=== COMPUTE METHODS ===#
@@ -21,7 +22,7 @@ class PaymentProvider(models.Model):
def _compute_journal_id(self):
for provider in self:
payment_method = self.env['account.payment.method.line'].search([
('journal_id.company_id', '=', provider.company_id.id),
*self.env['account.payment.method.line']._check_company_domain(provider.company_id),
('code', '=', provider._get_code())
], limit=1)
if payment_method:
@@ -33,13 +34,14 @@ class PaymentProvider(models.Model):
for provider in self:
code = provider._get_code()
payment_method_line = self.env['account.payment.method.line'].search([
('journal_id.company_id', '=', provider.company_id.id),
*self.env['account.payment.method.line']._check_company_domain(provider.company_id),
('code', '=', code),
], limit=1)
if provider.journal_id:
if not payment_method_line:
default_payment_method_id = provider._get_default_payment_method_id(code)
existing_payment_method_line = self.env['account.payment.method.line'].search([
*self.env['account.payment.method.line']._check_company_domain(provider.company_id),
('payment_method_id', '=', default_payment_method_id),
('journal_id', '=', provider.journal_id.id),
], limit=1)
@@ -37,7 +37,7 @@ class AccountPaymentRegister(models.TransientModel):
for wizard in self:
if wizard.can_edit_wizard and wizard.use_electronic_payment_method:
wizard.suitable_payment_token_ids = self.env['payment.token'].sudo().search([
('company_id', '=', wizard.company_id.id),
*self.env['payment.tokeb']._check_company_domain(wizard.company_id),
('provider_id.capture_manually', '=', False),
('partner_id', '=', wizard.partner_id.id),
('provider_id', '=', wizard.payment_method_line_id.payment_provider_id.id),
@@ -63,7 +63,7 @@ class AccountPaymentRegister(models.TransientModel):
and wizard.partner_id:
wizard.payment_token_id = self.env['payment.token'].sudo().search([
('company_id', '=', wizard.company_id.id),
*self.env['payment.token']._check_company_domain(wizard.company_id),
('partner_id', '=', wizard.partner_id.id),
('provider_id.capture_manually', '=', False),
('provider_id', '=', wizard.payment_method_line_id.payment_provider_id.id),
@@ -121,10 +121,10 @@ class AccountEdiProxyClientUser(models.Model):
# use the first purchase journal if the Peppol journal is not set up
# to create the move anyway
if not journal_id:
journal_id = self.env['account.journal'].search(
[('company_id', '=', company.id), ('type', '=', 'purchase')],
limit=1,
)
journal_id = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(company),
('type', '=', 'purchase')
], limit=1)
attachment_vals = {
'name': f'{filename}.xml',
+4 -4
View File
@@ -151,10 +151,10 @@ class ResCompany(models.Model):
def _compute_peppol_purchase_journal_id(self):
for company in self:
if company.is_account_peppol_participant and not company.peppol_purchase_journal_id:
company.peppol_purchase_journal_id = self.env['account.journal'].search(
[('company_id', '=', company.id), ('type', '=', 'purchase')],
limit=1,
)
company.peppol_purchase_journal_id = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(company),
('type', '=', 'purchase'),
], limit=1)
else:
company.peppol_purchase_journal_id = False
+7 -18
View File
@@ -5,6 +5,7 @@ from collections import defaultdict
import itertools
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import groupby
class AccountAnalyticAccount(models.Model):
@@ -96,24 +97,12 @@ class AccountAnalyticAccount(models.Model):
@api.constrains('company_id')
def _check_company_consistency(self):
analytic_accounts = self.filtered('company_id')
if not analytic_accounts:
return
self.flush_recordset(['company_id'])
self.env['account.analytic.line'].flush_model(['account_id', 'company_id'])
self._cr.execute('''
SELECT line.account_id
FROM account_analytic_line line
JOIN account_analytic_account account ON line.account_id = account.id
WHERE line.company_id != account.company_id and account.company_id IS NOT NULL
AND account.id IN %s
''', [tuple(self.ids)])
if self._cr.fetchone():
raise UserError(_("You can't set a different company on your analytic account since there are some analytic items linked to it."))
for company, accounts in groupby(self, lambda account: account.company_id):
if company and self.env['account.analytic.line'].search([
('account_id', 'in', [account.id for account in accounts]),
'!', ('company_id', 'child_of', company.id),
], limit=1):
raise UserError(_("You can't set a different company on your analytic account since there are some analytic items linked to it."))
@api.depends('code', 'partner_id')
def _compute_display_name(self):
+3 -2
View File
@@ -21,7 +21,8 @@ class AccountAnalyticPlan(models.Model):
'account.analytic.plan',
string="Parent",
ondelete='cascade',
domain="[('id', '!=', id), ('company_id', 'in', [False, company_id])]",
check_company=True,
domain="[('id', '!=', id)]",
)
parent_path = fields.Char(
index='btree',
@@ -135,8 +136,8 @@ class AccountAnalyticPlan(models.Model):
company_id = kwargs.get('company_id', self.env.company.id)
record_account_ids = kwargs.get('existing_account_ids', [])
all_plans = self.search([
*self._check_company_domain(company_id),
('account_ids', '!=', False),
'|', ('company_id', '=', company_id), ('company_id', '=', False),
])
root_plans = self.browse({
int(plan.parent_path.split('/')[0])
@@ -7,6 +7,7 @@
<tree string="Analytic Distribution Model" editable="top" multi_edit="1">
<field name="partner_id" optional="show"/>
<field name="partner_category_id" optional="hide"/>
<field name="company_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
<field name="analytic_distribution" widget="analytic_distribution" optional="show"
options="{'force_applicability': 'optional', 'disable_save': true}"/>
@@ -29,6 +30,7 @@
</group>
<group>
<field name="company_id" groups="base.group_multi_company"/>
<field name="company_id" invisible="1"/>
</group>
</group>
<group string="Distribution to apply" colspan="2">
@@ -11,6 +11,7 @@ class ResConfigSettings(models.TransientModel):
company_id = fields.Many2one('res.company', string='Company', required=True,
default=lambda self: self.env.company)
is_root_company = fields.Boolean(compute='_compute_is_root_company')
user_default_rights = fields.Boolean(
"Default Access Rights",
config_parameter='base_setup.default_user_rights')
@@ -119,3 +120,8 @@ class ResConfigSettings(models.TransientModel):
for record in self:
record.company_informations = informations
@api.depends('company_id')
def _compute_is_root_company(self):
for record in self:
record.is_root_company = not record.company_id.parent_id
@@ -7,6 +7,7 @@
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<field name="is_root_company" invisible="1"/>
<app data-string="General Settings" string="General Settings" name="general_settings" logo="/base/static/description/settings.png">
<div id="invite_users">
@@ -33,9 +33,9 @@ class AccountFiscalPosition(models.Model):
# If the company has a fiscal position with a foreign vat in Europe, in the same country as the partner, then the VIES validity applies
if self.search_count([
*self._check_company_domain(company),
('foreign_vat', '!=', False),
('country_id', '=', delivery.country_id.id),
('company_id', '=', company.id)
]) or company.country_id in eu_countries:
return super()._get_vat_valid(delivery, company) and delivery.vies_valid
+1 -3
View File
@@ -110,14 +110,13 @@ class Lead(models.Model):
compute='_compute_name', readonly=False, store=True)
user_id = fields.Many2one(
'res.users', string='Salesperson', default=lambda self: self.env.user,
domain="['&', ('share', '=', False), ('company_ids', 'in', user_company_ids)]",
domain="[('share', '=', False)]",
check_company=True, index=True, tracking=True)
user_company_ids = fields.Many2many(
'res.company', compute='_compute_user_company_ids',
help='UX: Limit to lead company or all if no company')
team_id = fields.Many2one(
'crm.team', string='Sales Team', check_company=True, index=True, tracking=True,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
compute='_compute_team_id', ondelete="set null", readonly=False, store=True, precompute=True)
lead_properties = fields.Properties(
'Properties', definition='team_id.lead_properties_definition',
@@ -175,7 +174,6 @@ class Lead(models.Model):
# Customer / contact
partner_id = fields.Many2one(
'res.partner', string='Customer', check_company=True, index=True, tracking=10,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
help="Linked partner (optional). Usually created when converting the lead. You can find a partner by its Name, TIN, Email or Internal Reference.")
partner_is_blacklisted = fields.Boolean('Partner is blacklisted', related='partner_id.is_blacklisted', readonly=True)
contact_name = fields.Char(
@@ -205,6 +205,7 @@ class TestCRMLeadMultiCompany(TestCrmCommon):
# writing current user on lead would imply putting its team and team's company
# on lead (aka self.company_2), and this clashes with company restriction on
# customer
self.env.user.company_ids -= self.company_main
with self.assertRaises(UserError):
lead.write({
'user_id': self.env.user,
+1 -1
View File
@@ -7,7 +7,7 @@ from odoo.exceptions import UserError
class SaleOrder(models.Model):
_inherit = 'sale.order'
carrier_id = fields.Many2one('delivery.carrier', string="Delivery Method", domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Fill this field if you plan to invoice the shipping based on picking.")
carrier_id = fields.Many2one('delivery.carrier', string="Delivery Method", check_company=True, help="Fill this field if you plan to invoice the shipping based on picking.")
delivery_message = fields.Char(readonly=True, copy=False)
delivery_rating_success = fields.Boolean(copy=False)
delivery_set = fields.Boolean(compute='_compute_delivery_state')
@@ -61,7 +61,7 @@ class ChooseDeliveryCarrier(models.TransientModel):
@api.depends('partner_id')
def _compute_available_carrier(self):
for rec in self:
carriers = self.env['delivery.carrier'].search(['|', ('company_id', '=', False), ('company_id', '=', rec.order_id.company_id.id)])
carriers = self.env['delivery.carrier'].search(self.env['delivery.carrier']._check_company_domain(rec.order_id.company_id))
rec.available_carrier_ids = carriers.available_carriers(rec.order_id.partner_shipping_id) if rec.partner_id else carriers
def _get_shipment_rate(self):
+4 -2
View File
@@ -127,7 +127,7 @@ class EventEvent(models.Model):
organizer_id = fields.Many2one(
'res.partner', string='Organizer', tracking=True,
default=lambda self: self.env.company.partner_id,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
check_company=True)
event_type_id = fields.Many2one('event.type', string='Template', ondelete='set null')
event_mail_ids = fields.One2many(
'event.mail', 'event_id', string='Mail Schedule', copy=True,
@@ -207,7 +207,9 @@ class EventEvent(models.Model):
# Location and communication
address_id = fields.Many2one(
'res.partner', string='Venue', default=lambda self: self.env.company.partner_id.id,
tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
check_company=True,
tracking=True
)
address_search = fields.Many2one(
'res.partner', string='Address', compute='_compute_address_search', search='_search_address_search')
address_inline = fields.Char(
+1 -1
View File
@@ -93,7 +93,7 @@ class EventLeadRule(models.Model):
help='Filter the attendees to include those of this specific event category. If not set, no event category restriction will be applied.')
event_id = fields.Many2one(
'event.event', string='Event',
domain="[('company_id', 'in', [company_id or current_company_id, False])]",
check_company=True,
help='Filter the attendees to include those of this specific event. If not set, no event restriction will be applied.')
company_id = fields.Many2one(
'res.company', string='Company',
+1 -1
View File
@@ -47,7 +47,7 @@ class FleetVehicle(models.Model):
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
trailer_hook = fields.Boolean(default=False, string='Trailer Hitch', compute='_compute_model_fields', store=True, readonly=False)
driver_id = fields.Many2one('res.partner', 'Driver', tracking=True, help='Driver address of the vehicle', copy=False)
future_driver_id = fields.Many2one('res.partner', 'Future Driver', tracking=True, help='Next Driver Address of the vehicle', copy=False, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
future_driver_id = fields.Many2one('res.partner', 'Future Driver', tracking=True, help='Next Driver Address of the vehicle', copy=False, check_company=True)
model_id = fields.Many2one('fleet.vehicle.model', 'Model',
tracking=True, required=True)
+2 -2
View File
@@ -17,9 +17,9 @@ class Department(models.Model):
complete_name = fields.Char('Complete Name', compute='_compute_complete_name', recursive=True, store=True)
active = fields.Boolean('Active', default=True)
company_id = fields.Many2one('res.company', string='Company', index=True, default=lambda self: self.env.company)
parent_id = fields.Many2one('hr.department', string='Parent Department', index=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
parent_id = fields.Many2one('hr.department', string='Parent Department', index=True, check_company=True)
child_ids = fields.One2many('hr.department', 'parent_id', string='Child Departments')
manager_id = fields.Many2one('hr.employee', string='Manager', tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
manager_id = fields.Many2one('hr.employee', string='Manager', tracking=True, check_company=True)
member_ids = fields.One2many('hr.employee', 'department_id', string='Members', readonly=True)
total_employee = fields.Integer(compute='_compute_total_employee', string='Total Employee')
jobs_ids = fields.One2many('hr.job', 'department_id', string='Jobs')
+1 -1
View File
@@ -404,7 +404,7 @@ class HrEmployeePrivate(models.Model):
if vals['work_contact_id']:
self._message_subscribe([vals['work_contact_id']])
if 'user_id' in vals:
# Update the profile pictures with user, except if provided
# Update the profile pictures with user, except if provided
vals.update(self._sync_user(self.env['res.users'].browse(vals['user_id']),
(bool(self.image_1920))))
if 'work_permit_expiration_date' in vals:
+8 -7
View File
@@ -19,28 +19,29 @@ class HrEmployeeBase(models.AbstractModel):
name = fields.Char()
active = fields.Boolean("Active")
color = fields.Integer('Color Index', default=0)
department_id = fields.Many2one('hr.department', 'Department', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
department_id = fields.Many2one('hr.department', 'Department', check_company=True)
member_of_department = fields.Boolean("Member of department", compute='_compute_part_of_department', search='_search_part_of_department',
help="Whether the employee is a member of the active user's department or one of it's child department.")
job_id = fields.Many2one('hr.job', 'Job Position', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
job_id = fields.Many2one('hr.job', 'Job Position', check_company=True)
job_title = fields.Char("Job Title", compute="_compute_job_title", store=True, readonly=False)
company_id = fields.Many2one('res.company', 'Company')
address_id = fields.Many2one('res.partner', 'Work Address', compute="_compute_address_id", store=True, readonly=False,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
check_company=True)
work_phone = fields.Char('Work Phone', compute="_compute_phones", store=True, readonly=False)
mobile_phone = fields.Char('Work Mobile', compute="_compute_work_contact_details", store=True, inverse='_inverse_work_contact_details')
work_email = fields.Char('Work Email', compute="_compute_work_contact_details", store=True, inverse='_inverse_work_contact_details')
work_contact_id = fields.Many2one('res.partner', 'Work Contact', copy=False)
work_location_id = fields.Many2one('hr.work.location', 'Work Location', compute="_compute_work_location_id", store=True, readonly=False,
domain="[('address_id', '=', address_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]")
check_company=True,
domain="[('address_id', '=', address_id)]")
user_id = fields.Many2one('res.users')
resource_id = fields.Many2one('resource.resource')
resource_calendar_id = fields.Many2one('resource.calendar', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
resource_calendar_id = fields.Many2one('resource.calendar', check_company=True)
parent_id = fields.Many2one('hr.employee', 'Manager', compute="_compute_parent_id", store=True, readonly=False,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
check_company=True)
coach_id = fields.Many2one(
'hr.employee', 'Coach', compute='_compute_coach', store=True, readonly=False,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
check_company=True,
help='Select the "Employee" who is the coach of this employee.\n'
'The "Coach" has no specific rights or responsibilities by default.')
tz = fields.Selection(
+1 -1
View File
@@ -26,7 +26,7 @@ class Job(models.Model):
employee_ids = fields.One2many('hr.employee', 'job_id', string='Employees', groups='base.group_user')
description = fields.Html(string='Job Description', sanitize_attributes=False)
requirements = fields.Text('Requirements')
department_id = fields.Many2one('hr.department', string='Department', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
department_id = fields.Many2one('hr.department', string='Department', check_company=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
contract_type_id = fields.Many2one('hr.contract.type', string='Employment Type')
+1 -1
View File
@@ -15,7 +15,7 @@ class HrPlan(models.Model):
plan_activity_type_ids = fields.One2many(
'hr.plan.activity.type', 'plan_id',
string='Activities',
domain="[('company_id', '=', company_id)]")
check_company=True)
active = fields.Boolean(default=True)
steps_count = fields.Integer(compute='_compute_steps_count')
+1 -1
View File
@@ -12,5 +12,5 @@ class WorkLocation(models.Model):
active = fields.Boolean(default=True)
name = fields.Char(string="Work Location", required=True)
company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
address_id = fields.Many2one('res.partner', required=True, string="Work Address", domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
address_id = fields.Many2one('res.partner', required=True, string="Work Address", check_company=True)
location_number = fields.Char()
+4 -1
View File
@@ -242,7 +242,10 @@ class User(models.Model):
# Raise meaningful error message
raise AccessError(_("You are only allowed to update your preferences. Please contact a HR officer to update other information."))
employee_domain = [('user_id', 'in', self.ids), ('company_id', '=', self.env.company.id)]
employee_domain = [
*self.env['hr.employee']._check_company_domain(self.env.company),
('user_id', 'in', self.ids),
]
if hr_fields:
employees = self.env['hr.employee'].sudo().search(employee_domain)
get_field = self.env['ir.model.fields']._get
+1 -1
View File
@@ -8,4 +8,4 @@ class ResourceResource(models.Model):
_inherit = "resource.resource"
user_id = fields.Many2one(copy=False)
employee_id = fields.One2many('hr.employee', 'resource_id', domain="[('company_id', '=', company_id)]")
employee_id = fields.One2many('hr.employee', 'resource_id', check_company=True)
+2 -1
View File
@@ -30,7 +30,8 @@ class HrPlanWizard(models.TransientModel):
], limit=1)
plan_id = fields.Many2one('hr.plan', default=lambda self: self._default_plan_id(),
domain="[('company_id', 'in', [False, company_id]), '|', ('department_id', '=', department_id), ('department_id', '=', False)]")
check_company=True,
domain="['|', ('department_id', '=', department_id), ('department_id', '=', False)]")
department_id = fields.Many2one('hr.department', compute='_compute_department_id')
employee_ids = fields.Many2many(
'hr.employee', 'hr_employee_hr_plan_wizard_rel', 'employee_id', 'plan_wizard_id', string='Employee', required=True,
+47 -12
View File
@@ -12,7 +12,6 @@ from odoo.tools import email_split, float_repr, float_compare, is_html_empty
class HrExpense(models.Model):
_name = "hr.expense"
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'analytic.mixin']
_description = "Expense"
@@ -56,7 +55,15 @@ class HrExpense(models.Model):
states={'approved': [('readonly', True)], 'done': [('readonly', True)]},
default=_default_employee_id, domain=lambda self: self._get_employee_id_domain(), check_company=True)
# product_id not required to allow create an expense without product via mail alias, but should be required on the view.
product_id = fields.Many2one('product.product', string='Category', tracking=True, states={'done': [('readonly', True)]}, domain="[('can_be_expensed', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", ondelete='restrict')
product_id = fields.Many2one(
'product.product',
string='Category',
tracking=True,
states={'done': [('readonly', True)]},
check_company=True,
domain="[('can_be_expensed', '=', True)]",
ondelete='restrict',
)
product_description = fields.Html(compute='_compute_product_description')
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure', compute='_compute_product_uom_id',
store=True, precompute=True, copy=True, readonly=True,
@@ -68,7 +75,8 @@ class HrExpense(models.Model):
quantity = fields.Float(required=True, states={'done': [('readonly', True)]}, digits='Product Unit of Measure', default=1)
tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id',
compute='_compute_tax_ids', store=True, readonly=False, precompute=True,
domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]", string='Included taxes',
check_company=True,
domain="[('type_tax_use', '=', 'purchase')]", string='Included taxes',
help="Both price-included and price-excluded taxes will behave as price-included taxes for expenses.")
amount_tax = fields.Monetary(string='Tax amount in Currency', help="Tax amount in currency", compute='_compute_amount_tax', store=True, currency_field='currency_id')
amount_tax_company = fields.Monetary('Tax amount', help="Tax amount in company currency", compute='_compute_total_amount_company', store=True, currency_field='company_currency_id')
@@ -80,8 +88,14 @@ class HrExpense(models.Model):
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=False, store=True, states={'reported': [('readonly', True)], 'approved': [('readonly', True)], 'done': [('readonly', True)]}, compute='_compute_currency_id', default=lambda self: self.env.company.currency_id)
currency_rate = fields.Float(compute='_compute_currency_rate', tracking=True)
account_id = fields.Many2one('account.account', compute='_compute_account_id', store=True, readonly=False, precompute=True, string='Account',
domain="[('account_type', 'not in', ('asset_receivable','liability_payable','asset_cash','liability_credit_card')), ('company_id', '=', company_id)]", help="An expense account is expected")
account_id = fields.Many2one(
'account.account',
compute='_compute_account_id', store=True, readonly=False, precompute=True,
string='Account',
check_company=True,
domain="[('account_type', 'not in', ('asset_receivable','liability_payable','asset_cash','liability_credit_card'))]",
help="An expense account is expected",
)
description = fields.Text('Internal Notes', readonly=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]})
payment_mode = fields.Selection([
("own_account", "Employee (to reimburse)"),
@@ -144,7 +158,7 @@ class HrExpense(models.Model):
def _compute_product_has_cost(self):
for expense in self:
expense.product_has_cost = expense.product_id and (float_compare(expense.product_id.standard_price, 0.0, precision_digits=2) != 0)
tax_ids = expense.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == expense.company_id)
tax_ids = expense.product_id.supplier_taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(expense.company_id))
expense.product_has_tax = bool(tax_ids)
@api.depends('sheet_id', 'sheet_id.account_move_ids', 'sheet_id.state')
@@ -310,7 +324,7 @@ class HrExpense(models.Model):
def _compute_tax_ids(self):
for expense in self:
expense = expense.with_company(expense.company_id)
expense.tax_ids = expense.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == expense.company_id) # taxes only from the same company
expense.tax_ids = expense.product_id.supplier_taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(expense.company_id))
@api.depends('product_id', 'company_id')
@@ -959,12 +973,21 @@ class HrExpenseSheet(models.Model):
if company_journal_id:
return company_journal_id.id
default_company_id = self.default_get(['company_id'])['company_id']
journal = self.env['account.journal'].search([('type', '=', 'purchase'), ('company_id', '=', default_company_id)], limit=1)
journal = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(default_company_id),
('type', '=', 'purchase'),
], limit=1)
return journal.id
name = fields.Char('Expense Report Summary', required=True, tracking=True)
expense_line_ids = fields.One2many('hr.expense', 'sheet_id', string='Expense Lines', copy=False)
product_ids = fields.Many2many('product.product', compute='_compute_product_ids', search='_search_product_ids', string='Categories')
product_ids = fields.Many2many(
'product.product',
string='Categories',
compute='_compute_product_ids',
search='_search_product_ids',
check_company=True,
)
expense_number = fields.Integer(compute='_compute_expense_number', string='Number of Expenses')
nb_account_move = fields.Integer(
string='Number of Journal Entries',
@@ -1015,8 +1038,14 @@ class HrExpenseSheet(models.Model):
tracking=True,
related='expense_line_ids.payment_mode', readonly=True)
employee_journal_id = fields.Many2one(
'account.journal', string='Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, check_company=True, domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]",
default=_default_journal_id, help="The journal used when the expense is paid by employee.")
'account.journal',
string='Journal',
states={'done': [('readonly', True)], 'post': [('readonly', True)]},
check_company=True,
domain="[('type', '=', 'purchase')]",
default=_default_journal_id,
help="The journal used when the expense is paid by employee.",
)
selectable_payment_method_line_ids = fields.Many2many('account.payment.method.line', compute='_compute_selectable_payment_method_line_ids')
payment_method_line_id = fields.Many2one(
comodel_name='account.payment.method.line',
@@ -1029,7 +1058,13 @@ class HrExpenseSheet(models.Model):
)
accounting_date = fields.Date("Accounting Date")
account_move_ids = fields.One2many('account.move', 'expense_sheet_id', string='Journal Entries', readonly=True)
journal_id = fields.Many2one('account.journal', compute='_compute_journal_id', string="Expense Journal", store=True)
journal_id = fields.Many2one(
'account.journal',
compute='_compute_journal_id',
string="Expense Journal",
store=True,
check_company=True,
)
# === Security fields === #
can_reset = fields.Boolean('Can Reset', compute='_compute_can_reset')
+2 -2
View File
@@ -16,12 +16,12 @@ class ResCompany(models.Model):
"account.journal",
string="Default Expense Journal",
check_company=True,
domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]",
domain="[('type', '=', 'purchase')]",
help="The company's default journal used when an employee expense is created.",
)
company_expense_allowed_payment_method_line_ids = fields.Many2many(
"account.payment.method.line",
string="Payment methods available for expenses paid by company",
check_company=True,
domain="[('payment_type', '=', 'outbound'), ('company_id', '=', company_id),('journal_id', '!=', False)]",
domain="[('payment_type', '=', 'outbound'), ('journal_id', '!=', False)]",
)
@@ -16,7 +16,7 @@ class ResConfigSettings(models.TransientModel):
module_hr_payroll_expense = fields.Boolean(string='Reimburse Expenses in Payslip')
module_hr_expense_extract = fields.Boolean(string='Send bills to OCR to generate expenses')
expense_product_id = fields.Many2one('product.product', related='company_id.expense_product_id', readonly=False)
expense_journal_id = fields.Many2one('account.journal', related='company_id.expense_journal_id', readonly=False)
expense_journal_id = fields.Many2one('account.journal', related='company_id.expense_journal_id', readonly=False, check_company=True)
company_expense_allowed_payment_method_line_ids = fields.Many2many(
comodel_name='account.payment.method.line',
related='company_id.company_expense_allowed_payment_method_line_ids',
+9 -5
View File
@@ -6,10 +6,10 @@ from odoo.tools import float_compare
class HrExpenseSplit(models.TransientModel):
_name = 'hr.expense.split'
_inherit = ['analytic.mixin']
_description = 'Expense Split'
_check_company_auto = True
def default_get(self, fields):
result = super(HrExpenseSplit, self).default_get(fields)
@@ -28,8 +28,12 @@ class HrExpenseSplit(models.TransientModel):
name = fields.Char('Description', required=True)
wizard_id = fields.Many2one('hr.expense.split.wizard')
expense_id = fields.Many2one('hr.expense', string='Expense')
product_id = fields.Many2one('product.product', string='Product', required=True)
tax_ids = fields.Many2many('account.tax', domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]")
product_id = fields.Many2one('product.product', string='Product', required=True, check_company=True)
tax_ids = fields.Many2many(
'account.tax',
check_company=True,
domain="[('type_tax_use', '=', 'purchase')]",
)
total_amount = fields.Monetary("Total In Currency", required=True, compute='_compute_from_product_id', store=True, readonly=False)
amount_tax = fields.Monetary(string='Tax amount in Currency', compute='_compute_amount_tax')
employee_id = fields.Many2one('hr.employee', string="Employee", required=True)
@@ -57,12 +61,12 @@ class HrExpenseSplit(models.TransientModel):
In case we switch to the product without taxes defined on it, taxes should be removed.
Computed method won't be good for this purpose, as we don't want to recompute and reset taxes in case they are removed on purpose during splitting.
"""
self.tax_ids = self.tax_ids if self.product_has_tax and self.tax_ids else self.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == self.company_id)
self.tax_ids = self.tax_ids if self.product_has_tax and self.tax_ids else self.product_id.supplier_taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(self.company_id))
@api.depends('product_id')
def _compute_product_has_tax(self):
for split in self:
split.product_has_tax = split.product_id and split.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == split.company_id)
split.product_has_tax = split.product_id and split.product_id.supplier_taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(split.company_id))
def _get_values(self):
self.ensure_one()
@@ -1,5 +1,5 @@
"id","name","description","invoice_label","sequence","active","amount_type","amount","type_tax_use","tax_group_id","repartition_line_ids/repartition_type","repartition_line_ids/document_type","repartition_line_ids/account_id","description@es","invoice_label@es"
ri_tax_percepcion_iva_aplicada,"0%","Perception VAT applied","Perc VAT A","4","False","fixed","0.0","sale","tax_group_percepcion_iva","base","invoice","","Percepción IVA Aplicada","Perc IVA A"
ri_tax_percepcion_iva_aplicada,"0% EX","Perception VAT applied","Perc VAT A","4","False","fixed","0.0","sale","tax_group_percepcion_iva","base","invoice","","Percepción IVA Aplicada","Perc IVA A"
"","","","","","","","","","","tax","invoice","ri_percepcion_iva_aplicada","",""
"","","","","","","","","","","base","refund","","",""
"","","","","","","","","","","tax","refund","ri_percepcion_iva_aplicada","",""
1 id name description invoice_label sequence active amount_type amount type_tax_use tax_group_id repartition_line_ids/repartition_type repartition_line_ids/document_type repartition_line_ids/account_id description@es invoice_label@es
2 ri_tax_percepcion_iva_aplicada 0% 0% EX Perception VAT applied Perc VAT A 4 False fixed 0.0 sale tax_group_percepcion_iva base invoice Percepción IVA Aplicada Perc IVA A
3 tax invoice ri_percepcion_iva_aplicada
4 base refund
5 tax refund ri_percepcion_iva_aplicada
-1
View File
@@ -17,7 +17,6 @@
</record>
<record id="company_exento" model="res.company">
<field name="parent_id" ref="base.main_company"/>
<field name="currency_id" ref="base.ARS"/>
<field name="partner_id" ref="partner_exento"/>
<field name="name">(AR) Exento</field>
-1
View File
@@ -17,7 +17,6 @@
</record>
<record id="company_mono" model="res.company">
<field name="parent_id" ref="base.main_company"/>
<field name="currency_id" ref="base.ARS"/>
<field name="partner_id" ref="partner_mono"/>
<field name="name">(AR) Monotributista</field>
-1
View File
@@ -17,7 +17,6 @@
</record>
<record id="company_ri" model="res.company">
<field name="parent_id" ref="base.main_company"/>
<field name="currency_id" ref="base.ARS"/>
<field name="partner_id" ref="partner_ri"/>
<field name="name">(AR) Responsable Inscripto</field>
+5 -1
View File
@@ -182,7 +182,11 @@ class AccountMove(models.Model):
for rec in self.filtered(lambda x: x.company_id.account_fiscal_country_id.code == "AR" and x.journal_id.type == 'sale'
and x.l10n_latam_use_documents and x.partner_id.l10n_ar_afip_responsibility_type_id):
res_code = rec.partner_id.l10n_ar_afip_responsibility_type_id.code
domain = [('company_id', '=', rec.company_id.id), ('l10n_latam_use_documents', '=', True), ('type', '=', 'sale')]
domain = [
*self.env['account.journal']._check_company_domain(rec.company_id),
('l10n_latam_use_documents', '=', True),
('type', '=', 'sale'),
]
journal = self.env['account.journal']
msg = False
if res_code in ['9', '10'] and rec.journal_id.l10n_ar_afip_pos_system not in expo_journals:
-2
View File
@@ -17,7 +17,6 @@ class TestAr(AccountTestInvoicingCommon):
# ==== Company ====
cls.company_data['company'].write({
'parent_id': cls.env.ref('base.main_company').id,
'currency_id': cls.env.ref('base.ARS').id,
'name': '(AR) Responsable Inscripto (Unit Tests)',
"l10n_ar_afip_start_date": time.strftime('%Y-01-01'),
@@ -45,7 +44,6 @@ class TestAr(AccountTestInvoicingCommon):
# ==== Company MONO ====
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,
'name': '(AR) Monotributista (Unit Tests)',
"l10n_ar_afip_start_date": time.strftime('%Y-01-01'),
+1 -1
View File
@@ -31,8 +31,8 @@ class AccountChartTemplate(models.AbstractModel):
if company.account_fiscal_country_id.code == "CL":
foreign = ref('l10n_cl.dc_fe_dte').id
self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(company),
('type', '=', 'purchase'),
('company_id', '=', self.env.company.id),
]).l10n_latam_use_documents = False
move_data['demo_invoice_1']['l10n_latam_document_type_id'] = foreign
move_data['demo_invoice_2']['l10n_latam_document_type_id'] = foreign
+14 -6
View File
@@ -36,13 +36,21 @@ class ProductTemplate(models.Model):
company = self.env.company
if company.account_fiscal_country_id.code == "DE":
if not self.property_account_income_id:
taxes = self.taxes_id.filtered(lambda t: t.company_id == company)
taxes = self.taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(company))
if not result['income'] or (result['income'].tax_ids and taxes and taxes[0] not in result['income'].tax_ids):
result['income'] = self.env['account.account'].search([('internal_group', '=', 'income'), ('deprecated', '=', False),
('tax_ids', 'in', taxes.ids)], limit=1)
result['income'] = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(company),
('internal_group', '=', 'income'),
('deprecated', '=', False),
('tax_ids', 'in', taxes.ids)
], limit=1)
if not self.property_account_expense_id:
supplier_taxes = self.supplier_taxes_id.filtered(lambda t: t.company_id == company)
supplier_taxes = self.supplier_taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(company))
if not result['expense'] or (result['expense'].tax_ids and supplier_taxes and supplier_taxes[0] not in result['expense'].tax_ids):
result['expense'] = self.env['account.account'].search([('internal_group', '=', 'expense'), ('deprecated', '=', False),
('tax_ids', 'in', supplier_taxes.ids)], limit=1)
result['expense'] = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(company),
('internal_group', '=', 'expense'),
('deprecated', '=', False),
('tax_ids', 'in', supplier_taxes.ids),
], limit=1)
return result
@@ -139,7 +139,7 @@
"tax_group_nonformal","60","75%","Withholding from Informal Suppliers of Goods (75%)","Withholding from Informal Suppliers of Goods (75%)","18.0","group","purchase","","tax_group_ret","tax_18_purch,ret_75_tax_nonformal","","","","","","Retención a Proveedores Informales de Bienes (75%)"
"tax_group_person_construction","61","2% Mat.","Withholding to Individuals for Services with Materials (2%)","Withholding to Individuals for Services with Materials (2%)","18.0","group","purchase","","tax_group_ret","tax_18_purch,ret_100_tax_person,ret_2_income_person","","","","","","Retención a Jurídicas por Servicios Profesionales (30%)"
"tax_group_person_services","58","10% Serv.","Withholding to Individuals for Person Services","Withholding to Individuals for Person Services","18.0","group","purchase","","tax_group_ret","tax_18_purch,ret_100_tax_person,ret_10_income_person","","","","","","Retención a Físicas por Honorarios por Servicios (10%)"
"tax_group_moral_services","58","2% Mat.","Withholding to Individuals for Moral Services","Withholding to Individuals for Moral Services","18.0","group","purchase","","tax_group_ret","tax_18_purch,ret_30_tax_moral","","","","","","Retención a Jurídicas por Servicios Profesionales (30%)"
"tax_group_moral_services","58","2% Serv P.","Withholding to Individuals for Moral Services","Withholding to Individuals for Moral Services","18.0","group","purchase","","tax_group_ret","tax_18_purch,ret_30_tax_moral","","","","","","Retención a Jurídicas por Servicios Profesionales (30%)"
"tax_group_restaurant_sale","64","Restaurant","Restaurant Sales","Restaurant Sales","18.0","group","sale","","tax_group_ret","tax_18_sale,tax_tip_sale","","","","","","Ventas del Restaurante"
"tax_group_restaurant_purch","65","Restaurant","Restaurant Purchases","Restaurant Purchases","18.0","group","purchase","","tax_group_ret","tax_18_purch,tax_tip_purch","","","","","","Compras a Restaurantes"
"tax_18_10_total_mount","","18% of 10%","18% ITBIS on 10% of the Total Amount","18% ITBIS on 10% of the Total Amount","1.8","percent","purchase","False","tax_group_itbis","","","base","invoice","","","18% ITBIS sobre el 10% del Monto Total"
1 id sequence name description invoice_label amount amount_type type_tax_use price_include tax_group_id children_tax_ids active repartition_line_ids/repartition_type repartition_line_ids/document_type repartition_line_ids/tag_ids repartition_line_ids/account_id description@es
139 tax_group_nonformal 60 75% Withholding from Informal Suppliers of Goods (75%) Withholding from Informal Suppliers of Goods (75%) 18.0 group purchase tax_group_ret tax_18_purch,ret_75_tax_nonformal Retención a Proveedores Informales de Bienes (75%)
140 tax_group_person_construction 61 2% Mat. Withholding to Individuals for Services with Materials (2%) Withholding to Individuals for Services with Materials (2%) 18.0 group purchase tax_group_ret tax_18_purch,ret_100_tax_person,ret_2_income_person Retención a Jurídicas por Servicios Profesionales (30%)
141 tax_group_person_services 58 10% Serv. Withholding to Individuals for Person Services Withholding to Individuals for Person Services 18.0 group purchase tax_group_ret tax_18_purch,ret_100_tax_person,ret_10_income_person Retención a Físicas por Honorarios por Servicios (10%)
142 tax_group_moral_services 58 2% Mat. 2% Serv P. Withholding to Individuals for Moral Services Withholding to Individuals for Moral Services 18.0 group purchase tax_group_ret tax_18_purch,ret_30_tax_moral Retención a Jurídicas por Servicios Profesionales (30%)
143 tax_group_restaurant_sale 64 Restaurant Restaurant Sales Restaurant Sales 18.0 group sale tax_group_ret tax_18_sale,tax_tip_sale Ventas del Restaurante
144 tax_group_restaurant_purch 65 Restaurant Restaurant Purchases Restaurant Purchases 18.0 group purchase tax_group_ret tax_18_purch,tax_tip_purch Compras a Restaurantes
145 tax_18_10_total_mount 18% of 10% 18% ITBIS on 10% of the Total Amount 18% ITBIS on 10% of the Total Amount 1.8 percent purchase False tax_group_itbis base invoice 18% ITBIS sobre el 10% del Monto Total
+1 -1
View File
@@ -16,7 +16,7 @@ def _edit_tax_types(env, template_data):
]
if not concerned_company_ids:
return
current_taxes = env['account.tax'].search([('company_id', 'in', concerned_company_ids)])
current_taxes = env['account.tax'].search(env['account.tax']._check_company_domain(concerned_company_ids))
if not current_taxes:
return
xmlid2tax = {
+6 -5
View File
@@ -27,22 +27,21 @@ class Company(models.Model):
for company in self:
invoice_repartition_lines, refund_repartition_lines = company._get_repartition_lines_oss()
taxes = self.env['account.tax'].search([
*self.env['account.tax']._check_company_domain(company),
('type_tax_use', '=', 'sale'),
('amount_type', '=', 'percent'),
('company_id', '=', company.id),
('country_id', '=', company.account_fiscal_country_id.id),
('tax_group_id', 'not in', oss_tax_groups.mapped('res_id'))])
multi_tax_reports_countries_fpos = self.env['account.fiscal.position'].search([
('company_id', '=', company.id),
('foreign_vat', '!=', False),
])
oss_countries = eu_countries - company.account_fiscal_country_id - multi_tax_reports_countries_fpos.country_id
for destination_country in oss_countries:
mapping = []
fpos = self.env['account.fiscal.position'].search([
*self.env['account.fiscal.position']._check_company_domain(company),
('country_id', '=', destination_country.id),
('company_id', '=', company.id),
('auto_apply', '=', True),
('vat_required', '=', False),
('foreign_vat', '=', False)], limit=1)
@@ -62,7 +61,9 @@ class Company(models.Model):
if not foreign_taxes.get(tax_amount, False):
oss_tax_group_local_xml_id = f"{company.id}_oss_tax_group_{str(tax_amount).replace('.', '_')}_{company.account_fiscal_country_id.code}"
if not self.env.ref(f"account.{oss_tax_group_local_xml_id}", raise_if_not_found=False):
tg = self.env['account.tax.group'].search([('company_id', '=', company.id)])
tg = self.env['account.tax.group'].search([
*self.env['account.tax.group']._check_company_domain(company),
])
self.env['ir.model.data'].create({
'name': oss_tax_group_local_xml_id,
'module': 'account',
@@ -113,8 +114,8 @@ class Company(models.Model):
self.ensure_one()
if not self.env.ref(f'l10n_eu_oss.oss_tax_account_company_{self.id}', raise_if_not_found=False):
sales_tax_accounts = self.env['account.tax'].search([
*self.env['account.tax']._check_company_domain(self),
('type_tax_use', '=', 'sale'),
('company_id', '=', self.id)
]).invoice_repartition_line_ids.mapped('account_id')
if not sales_tax_accounts:
return False
+1 -1
View File
@@ -22,5 +22,5 @@ def _setup_inalterability(env):
fr_companies._create_secure_sequence(['l10n_fr_closing_sequence_id'])
for fr_company in fr_companies:
fr_journals = env['account.journal'].search([('company_id', '=', fr_company.id)])
fr_journals = env['account.journal'].search(env['account.journal']._check_company_domain(fr_company))
fr_journals.filtered(lambda x: not x.secure_sequence_id)._create_secure_sequence(['secure_sequence_id'])
+2 -2
View File
@@ -11,8 +11,8 @@ class ResCompany(models.Model):
siret = fields.Char(related='partner_id.siret', string='SIRET', size=14, readonly=False)
ape = fields.Char(string='APE')
l10n_fr_rounding_difference_loss_account_id = fields.Many2one('account.account')
l10n_fr_rounding_difference_profit_account_id = fields.Many2one('account.account')
l10n_fr_rounding_difference_loss_account_id = fields.Many2one('account.account', check_company=True)
l10n_fr_rounding_difference_profit_account_id = fields.Many2one('account.account', check_company=True)
@api.model
def _get_unalterable_country(self):
+5 -4
View File
@@ -26,7 +26,7 @@ class AccountFrFec(models.TransientModel):
('official', 'Official FEC report (posted entries only)'),
('nonofficial', 'Non-official FEC report (posted and unposted entries)'),
], string='Export Type', required=True, default='official')
excluded_journal_ids = fields.Many2many('account.journal', string="Excluded Journals", domain="[('company_id', '=', current_company_id)]")
excluded_journal_ids = fields.Many2many('account.journal', string="Excluded Journals", domain="[('company_id', 'parent_of', current_company_id)]")
@api.onchange('test_file')
def _onchange_export_file(self):
@@ -152,8 +152,8 @@ class AccountFrFec(models.TransientModel):
rows_to_write = [header]
# INITIAL BALANCE
unaffected_earnings_account = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(company),
('account_type', '=', 'equity_unaffected'),
('company_id', '=', company.id)
], limit=1)
unaffected_earnings_line = True # used to make sure that we add the unaffected earning initial balance only once
if unaffected_earnings_account:
@@ -236,8 +236,9 @@ class AccountFrFec(models.TransientModel):
and (unaffected_earnings_results[11] != '0,00'
or unaffected_earnings_results[12] != '0,00')):
#search an unaffected earnings account
unaffected_earnings_account = self.env['account.account'].search([('account_type', '=', 'equity_unaffected'),
('company_id', '=', company.id)], limit=1)
unaffected_earnings_account = self.env['account.account'].search([
('account_type', '=', 'equity_unaffected')
], limit=1)
if unaffected_earnings_account:
unaffected_earnings_results[4] = unaffected_earnings_account.code
unaffected_earnings_results[5] = unaffected_earnings_account.name
@@ -41,8 +41,9 @@ class AccountClosing(models.Model):
JOIN account_journal j ON aml.journal_id = j.id
JOIN account_account acc ON acc.id = aml.account_id
JOIN account_move m ON m.id = aml.move_id
JOIN res_company move_company ON move_company.id = m.company_id
WHERE j.type = 'sale'
AND aml.company_id = %(company_id)s
AND SPLIT_PART(move_company.parent_path, '/', 1)::int = %(company_id)s
AND m.state = 'posted'
AND acc.account_type = 'asset_receivable' '''
@@ -11,8 +11,9 @@ class AccountChartTemplate(models.AbstractModel):
company = company or self.env.company
"""We need to deactivate einvoice here, as we can not send e-invoice and e-waybill in the same demo company"""
if company == self.env.ref('l10n_in_edi_ewaybill.demo_company_in_ewaybill'):
val = self.env['account.journal'].search([
sales_journals = self.env['account.journal'].search([
*self.env['account.journal']._check_company_domain(company),
('type', '=', 'sale'),
('company_id', '=', company.id)])
val.write({'edi_format_ids': [Command.unlink(self.env.ref('l10n_in_edi.edi_in_einvoice_json_1_03').id)]})
])
sales_journals.write({'edi_format_ids': [Command.unlink(self.env.ref('l10n_in_edi.edi_in_einvoice_json_1_03').id)]})
return super()._get_demo_data(company)
+15 -11
View File
@@ -378,9 +378,10 @@ class AccountEdiFormat(models.Model):
def _l10n_it_get_partner_invoice(self, tree, company):
# Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2>
elements = tree.xpath('//CedentePrestatore//IdCodice')
partner = elements and self.env['res.partner'].search(
['&', ('vat', 'ilike', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)],
limit=1)
partner = elements and self.env['res.partner'].search([
*self.env['res.partner']._check_company_domain(company),
('vat', 'ilike', elements[0].text)
], limit=1)
if not partner:
elements = tree.xpath('//CedentePrestatore//CodiceFiscale')
if elements:
@@ -391,13 +392,16 @@ class AccountEdiFormat(models.Model):
elif re.match(r'^IT[0-9]{11}$', codice):
domains.append([('l10n_it_codice_fiscale', '=',
self.env['res.partner']._l10n_it_edi_normalized_codice_fiscale(codice))])
partner = elements and self.env['res.partner'].search(
AND([OR(domains), OR([[('company_id', '=', company.id)], [('company_id', '=', False)]])]), limit=1)
partner = elements and self.env['res.partner'].search(AND([
*self.env['res.partner']._check_company_domain(company),
OR(domains)
]), limit=1)
if not partner:
elements = tree.xpath('//DatiTrasmissione//Email')
partner = elements and self.env['res.partner'].search(
['&', '|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text), '|',
('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
partner = elements and self.env['res.partner'].search([
*self.env['res.partner']._check_company_domain(company),
'|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text)
], limit=1)
return partner
@@ -415,8 +419,8 @@ class AccountEdiFormat(models.Model):
def _l10n_it_edi_search_tax_for_import(self, company, percentage, extra_domain=None):
""" Returns the VAT, Withholding or Pension Fund tax that suits the conditions given
and matches the percentage found in the XML for the company. """
conditions = [
('company_id', '=', company.id),
domain = [
*self.env['account.tax']._check_company_domain(company),
('amount', '=', percentage),
('amount_type', '=', 'percent'),
('type_tax_use', '=', 'purchase'),
@@ -425,7 +429,7 @@ class AccountEdiFormat(models.Model):
# As we're importing vendor bills, we're excluding Reverse Charge Taxes
# which have a [100.0, 100.0, -100.0] repartition lines factor_percent distribution.
# We only allow for taxes that have all positive repartition lines factor_percent distribution.
taxes = self.env['account.tax'].search(conditions).filtered(
taxes = self.env['account.tax'].search(domain).filtered(
lambda tax: all([rep_line.factor_percent >= 0 for rep_line in tax.invoice_repartition_line_ids]))
return taxes[0] if taxes else taxes
+1 -1
View File
@@ -92,7 +92,7 @@ class TestItEdi(AccountEdiTestCommon):
})
cls.default_tax = cls.env['account.tax'].with_company(cls.company).create({
'name': "22%",
'name': "22% default",
'amount': 22.0,
'amount_type': 'percent',
})
@@ -7,6 +7,7 @@ from odoo.exceptions import UserError
class L10nLatamPaymentMassTransfer(models.TransientModel):
_name = 'l10n_latam.payment.mass.transfer'
_description = 'Checks Mass Transfers'
_check_company_auto = True
payment_date = fields.Date(
string="Payment Date",
@@ -16,13 +17,15 @@ class L10nLatamPaymentMassTransfer(models.TransientModel):
destination_journal_id = fields.Many2one(
comodel_name='account.journal',
string='Destination Journal',
domain="[('type', 'in', ('bank', 'cash')), ('company_id', '=', company_id), ('id', '!=', journal_id)]",
check_company=True,
domain="[('type', 'in', ('bank', 'cash')), ('id', '!=', journal_id)]",
)
communication = fields.Char(
string="Memo",
)
journal_id = fields.Many2one(
'account.journal',
check_company=True,
compute='_compute_journal_company'
)
company_id = fields.Many2one(
@@ -31,6 +34,7 @@ class L10nLatamPaymentMassTransfer(models.TransientModel):
)
check_ids = fields.Many2many(
'account.payment',
check_company=True,
)
@api.depends('check_ids')

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