[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:
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -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=[
|
||||
|
||||
@@ -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.",
|
||||
)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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 & 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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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","",""
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user