[ADD] account: suspense account management
Task 1930403 Purpose: A suspense account is an account in the general ledger that temporarily stores any transactions for which there is uncertainty about the account in which they should be recorded. Once the accounting staff investigates and clarifies the purpose of this type of transaction, it shifts the transaction out of the suspense account and into the correct account(s). An entry into a suspense account may be a debit or a credit. The Chosen Odoo approach (macro) for this case is 1) from bank statement -> reconciliation widget -> reconciliation model 2) create move that have a dedicated checkbox 3) be able to reopen the bank statement line "like if there is no linked account move" and finally reconcile the transaction with the good invoice/bill (we can reprocess account move "to check") 4) when we reprocess, Instead of creating an account move : Check if there is an "to check" existing linked account move and remove all the account move lines then replace them by the good ones --> we want to keep the sequence closes odoo/odoo#30486
This commit is contained in:
@@ -276,6 +276,16 @@ class AccountBankStatement(models.Model):
|
||||
st_number = SequenceObj.with_context(**context).next_by_code('account.bank.statement')
|
||||
statement.name = st_number
|
||||
statement.state = 'open'
|
||||
|
||||
@api.multi
|
||||
def action_bank_reconcile_bank_statements(self):
|
||||
self.ensure_one()
|
||||
bank_stmt_lines = self.mapped('line_ids')
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'bank_statement_reconciliation_view',
|
||||
'context': {'statement_line_ids': bank_stmt_lines.ids, 'company_ids': self.mapped('company_id').ids},
|
||||
}
|
||||
|
||||
|
||||
class AccountBankStatementLine(models.Model):
|
||||
@@ -553,6 +563,7 @@ class AccountBankStatementLine(models.Model):
|
||||
"""
|
||||
payable_account_type = self.env.ref('account.data_account_type_payable')
|
||||
receivable_account_type = self.env.ref('account.data_account_type_receivable')
|
||||
edition_mode = self._context.get('edition_mode')
|
||||
counterpart_aml_dicts = counterpart_aml_dicts or []
|
||||
payment_aml_rec = payment_aml_rec or self.env['account.move.line']
|
||||
new_aml_dicts = new_aml_dicts or []
|
||||
@@ -569,7 +580,7 @@ class AccountBankStatementLine(models.Model):
|
||||
if any(rec.statement_id for rec in payment_aml_rec):
|
||||
raise UserError(_('A selected move line was already reconciled.'))
|
||||
for aml_dict in counterpart_aml_dicts:
|
||||
if aml_dict['move_line'].reconciled:
|
||||
if aml_dict['move_line'].reconciled and not edition_mode:
|
||||
raise UserError(_('A selected move line was already reconciled.'))
|
||||
if isinstance(aml_dict['move_line'], int):
|
||||
aml_dict['move_line'] = aml_obj.browse(aml_dict['move_line'])
|
||||
@@ -583,8 +594,12 @@ class AccountBankStatementLine(models.Model):
|
||||
user_type_id = self.env['account.account'].browse(aml_dict.get('account_id')).user_type_id
|
||||
if user_type_id in [payable_account_type, receivable_account_type] and user_type_id not in account_types:
|
||||
account_types |= user_type_id
|
||||
if any(line.journal_entry_ids for line in self):
|
||||
raise UserError(_('A selected statement line was already reconciled with an account move.'))
|
||||
if edition_mode:
|
||||
if any(not line.journal_entry_ids for line in self):
|
||||
raise UserError(_('Some selected statement line were not already reconciled with an account move.'))
|
||||
else:
|
||||
if any(line.journal_entry_ids for line in self):
|
||||
raise UserError(_('A selected statement line was already reconciled with an account move.'))
|
||||
|
||||
# Fully reconciled moves are just linked to the bank statement
|
||||
total = self.amount
|
||||
@@ -611,6 +626,8 @@ class AccountBankStatementLine(models.Model):
|
||||
# Create the move
|
||||
self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
|
||||
move_vals = self._prepare_reconciliation_move(self.statement_id.name)
|
||||
if edition_mode:
|
||||
self.button_cancel_reconciliation()
|
||||
move = self.env['account.move'].create(move_vals)
|
||||
counterpart_moves = (counterpart_moves | move)
|
||||
|
||||
|
||||
@@ -128,6 +128,24 @@ class AccountInvoice(models.Model):
|
||||
else:
|
||||
self.reconciled = False
|
||||
|
||||
@api.multi
|
||||
def _get_domain_edition_mode_available(self):
|
||||
self.ensure_one()
|
||||
domain = self.env['account.move.line']._get_domain_for_edition_mode()
|
||||
domain += ['|',('move_id.partner_id', '=?', self.partner_id.id),('move_id.partner_id', '=', False)]
|
||||
if self.type in ('out_invoice', 'in_refund'):
|
||||
domain.append(('balance', '=', -self.residual))
|
||||
else:
|
||||
domain.append(('balance', '=', self.residual))
|
||||
return domain
|
||||
|
||||
@api.multi
|
||||
def _get_edition_mode_available(self):
|
||||
for r in self:
|
||||
domain = r._get_domain_edition_mode_available()
|
||||
domain2 = [('state', '=', 'open'),('residual', '=', r.residual),('type', '=', r.type)]
|
||||
r.edition_mode_available = (0 < self.env['account.move.line'].search_count(domain) < 5) and self.env['account.invoice'].search_count(domain2) < 5 and r.state == 'open'
|
||||
|
||||
@api.one
|
||||
def _get_outstanding_info_JSON(self):
|
||||
self.outstanding_credits_debits_widget = json.dumps(False)
|
||||
@@ -364,6 +382,7 @@ class AccountInvoice(models.Model):
|
||||
related='partner_id.commercial_partner_id', store=True, readonly=True,
|
||||
help="The commercial entity that will be used on Journal Entries for this invoice")
|
||||
|
||||
edition_mode_available = fields.Boolean(compute='_get_edition_mode_available', groups='account.group_account_invoice')
|
||||
outstanding_credits_debits_widget = fields.Text(compute='_get_outstanding_info_JSON', groups="account.group_account_invoice")
|
||||
payments_widget = fields.Text(compute='_get_payment_info_JSON', groups="account.group_account_invoice")
|
||||
has_outstanding = fields.Boolean(compute='_get_outstanding_info_JSON', groups="account.group_account_invoice")
|
||||
@@ -609,6 +628,22 @@ class AccountInvoice(models.Model):
|
||||
return self.env.ref('account.account_invoices').report_action(self)
|
||||
else:
|
||||
return self.env.ref('account.account_invoices_without_payment').report_action(self)
|
||||
|
||||
@api.multi
|
||||
def action_reconcile_to_check(self, params):
|
||||
self.ensure_one()
|
||||
domain = self._get_domain_edition_mode_available()
|
||||
ids = self.env['account.move.line'].search(domain).mapped('statement_line_id').ids
|
||||
action_context = {'show_mode_selector': False, 'company_ids': self.mapped('company_id').ids}
|
||||
action_context.update({'edition_mode': True})
|
||||
action_context.update({'statement_line_ids': ids})
|
||||
action_context.update({'partner_id': self.partner_id.id})
|
||||
action_context.update({'partner_name': self.partner_id.name})
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'bank_statement_reconciliation_view',
|
||||
'context': action_context,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_invoice_sent(self):
|
||||
|
||||
@@ -145,9 +145,9 @@ class account_journal(models.Model):
|
||||
@api.multi
|
||||
def get_journal_dashboard_datas(self):
|
||||
currency = self.currency_id or self.company_id.currency_id
|
||||
number_to_reconcile = last_balance = account_sum = 0
|
||||
number_to_reconcile = number_to_check = last_balance = account_sum = 0
|
||||
title = ''
|
||||
number_draft = number_waiting = number_late = 0
|
||||
number_draft = number_waiting = number_late = to_check_balance = 0
|
||||
sum_draft = sum_waiting = sum_late = 0.0
|
||||
if self.type in ['bank', 'cash']:
|
||||
last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1)
|
||||
@@ -161,6 +161,9 @@ class account_journal(models.Model):
|
||||
AND not exists (select 1 from account_move_line aml where aml.statement_line_id = line.id)
|
||||
""", (tuple(self.ids),))
|
||||
number_to_reconcile = self.env.cr.fetchone()[0]
|
||||
to_check_ids = self.to_check_ids()
|
||||
number_to_check = len(to_check_ids)
|
||||
to_check_balance = sum([r.amount for r in to_check_ids])
|
||||
# optimization to read sum of balance from account_move_line
|
||||
account_ids = tuple(ac for ac in [self.default_debit_account_id.id, self.default_credit_account_id.id] if ac)
|
||||
if account_ids:
|
||||
@@ -196,6 +199,8 @@ class account_journal(models.Model):
|
||||
|
||||
difference = currency.round(last_balance-account_sum) + 0.0
|
||||
return {
|
||||
'number_to_check': number_to_check,
|
||||
'to_check_balance': formatLang(self.env, to_check_balance, currency_obj=currency),
|
||||
'number_to_reconcile': number_to_reconcile,
|
||||
'account_balance': formatLang(self.env, currency.round(account_sum) + 0.0, currency_obj=currency),
|
||||
'last_balance': formatLang(self.env, currency.round(last_balance) + 0.0, currency_obj=currency),
|
||||
@@ -313,11 +318,11 @@ class account_journal(models.Model):
|
||||
def action_open_reconcile(self):
|
||||
if self.type in ['bank', 'cash']:
|
||||
# Open reconciliation view for bank statements belonging to this journal
|
||||
bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)])
|
||||
bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)]).mapped('line_ids')
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'bank_statement_reconciliation_view',
|
||||
'context': {'statement_ids': bank_stmt.ids, 'company_ids': self.mapped('company_id').ids},
|
||||
'context': {'statement_line_ids': bank_stmt.ids, 'company_ids': self.mapped('company_id').ids},
|
||||
}
|
||||
else:
|
||||
# Open reconciliation view for customers/suppliers
|
||||
@@ -332,6 +337,26 @@ class account_journal(models.Model):
|
||||
'context': action_context,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_open_to_check(self):
|
||||
self.ensure_one()
|
||||
ids = self.to_check_ids().ids
|
||||
action_context = {'show_mode_selector': False, 'company_ids': self.mapped('company_id').ids}
|
||||
action_context.update({'edition_mode': True})
|
||||
action_context.update({'statement_line_ids': ids})
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'bank_statement_reconciliation_view',
|
||||
'context': action_context,
|
||||
}
|
||||
|
||||
def to_check_ids(self):
|
||||
self.ensure_one()
|
||||
domain = self.env['account.move.line']._get_domain_for_edition_mode()
|
||||
domain.append(('journal_id', '=', self.id))
|
||||
statement_line_ids = self.env['account.move.line'].search(domain).mapped('statement_line_id')
|
||||
return statement_line_ids
|
||||
|
||||
@api.multi
|
||||
def open_action(self):
|
||||
"""return action based on type for related journals"""
|
||||
|
||||
@@ -135,6 +135,7 @@ class AccountMove(models.Model):
|
||||
auto_reverse = fields.Boolean(string='Reverse Automatically', default=False, help='If this checkbox is ticked, this entry will be automatically reversed at the reversal date you defined.')
|
||||
reverse_date = fields.Date(string='Reversal Date', help='Date of the reverse accounting entry.')
|
||||
reverse_entry_id = fields.Many2one('account.move', String="Reverse entry", store=True, readonly=True)
|
||||
to_check = fields.Boolean(string='To Check', default=False, help='If this checkbox is ticked, it means that the user was not sure of all the related informations at the time of the creation of the move and that the move needs to be checked again.')
|
||||
tax_type_domain = fields.Char(store=False, help='Technical field used to have a dynamic taxes domain on the form view.')
|
||||
|
||||
@api.constrains('line_ids', 'journal_id', 'auto_reverse', 'reverse_date')
|
||||
@@ -336,8 +337,14 @@ class AccountMove(models.Model):
|
||||
|
||||
@api.multi
|
||||
def button_cancel(self):
|
||||
AccountMoveLine = self.env['account.move.line']
|
||||
excluded_move_ids = []
|
||||
|
||||
if self._context.get('edition_mode'):
|
||||
excluded_move_ids = AccountMoveLine.search(AccountMoveLine._get_domain_for_edition_mode() + [('move_id', 'in', self.ids)]).mapped('move_id').ids
|
||||
|
||||
for move in self:
|
||||
if not move.journal_id.update_posted:
|
||||
if not move.journal_id.update_posted and move.id not in excluded_move_ids:
|
||||
raise UserError(_('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))
|
||||
# We remove all the analytics entries for this journal
|
||||
move.mapped('line_ids.analytic_line_ids').unlink()
|
||||
@@ -1383,6 +1390,14 @@ class AccountMoveLine(models.Model):
|
||||
action['domain'] = [('id', 'in', ids)]
|
||||
return action
|
||||
|
||||
@api.model
|
||||
def _get_domain_for_edition_mode(self):
|
||||
return [
|
||||
('move_id.to_check', '=', True),
|
||||
('full_reconcile_id', '=', False),
|
||||
('statement_line_id', '!=', False),
|
||||
]
|
||||
|
||||
|
||||
class AccountPartialReconcile(models.Model):
|
||||
_name = "account.partial.reconcile"
|
||||
|
||||
@@ -22,6 +22,7 @@ class AccountReconcileModel(models.Model):
|
||||
], string='Type', default='writeoff_button', required=True)
|
||||
auto_reconcile = fields.Boolean(string='Auto-validate',
|
||||
help='Validate the statement line automatically (reconciliation based on your rule).')
|
||||
to_check = fields.Boolean(string='To Check', default=False, help='This matching rule is used when the user is not certain of all the informations of the counterpart.')
|
||||
|
||||
# ===== Conditions =====
|
||||
match_journal_ids = fields.Many2many('account.journal', string='Journals',
|
||||
|
||||
@@ -986,6 +986,7 @@ class AccountReconcileModelTemplate(models.Model):
|
||||
], string='Type', default='writeoff_button', required=True)
|
||||
auto_reconcile = fields.Boolean(string='Auto-validate',
|
||||
help='Validate the statement line automatically (reconciliation based on your rule).')
|
||||
to_check = fields.Boolean(string='To Check', default=False, help='This matching rule is used when the user is not certain of all the informations of the counterpart.')
|
||||
|
||||
# ===== Conditions =====
|
||||
match_journal_ids = fields.Many2many('account.journal', string='Journals',
|
||||
|
||||
@@ -39,6 +39,7 @@ class AccountReconciliation(models.AbstractModel):
|
||||
if datum.get('partner_id') is not None:
|
||||
st_line.write({'partner_id': datum['partner_id']})
|
||||
|
||||
ctx['default_to_check'] = datum.get('to_check')
|
||||
st_line.with_context(ctx).process_reconciliation(
|
||||
datum.get('counterpart_aml_dicts', []),
|
||||
payment_aml_rec,
|
||||
@@ -106,10 +107,21 @@ class AccountReconciliation(models.AbstractModel):
|
||||
:param excluded_ids: optional move lines ids excluded from the
|
||||
result
|
||||
"""
|
||||
results = {
|
||||
'lines': [],
|
||||
'value_min': 0,
|
||||
'value_max': 0,
|
||||
'reconciled_aml_ids': [],
|
||||
}
|
||||
|
||||
if not st_line_ids:
|
||||
return results
|
||||
|
||||
excluded_ids = excluded_ids or []
|
||||
|
||||
# Make a search to preserve the table's order.
|
||||
bank_statement_lines = self.env['account.bank.statement.line'].search([('id', 'in', st_line_ids)])
|
||||
results['value_max'] = len(bank_statement_lines)
|
||||
reconcile_model = self.env['account.reconcile.model'].search([('rule_type', '!=', 'writeoff_button')])
|
||||
|
||||
# Search for missing partners when opening the reconciliation widget.
|
||||
@@ -117,13 +129,6 @@ class AccountReconciliation(models.AbstractModel):
|
||||
|
||||
matching_amls = reconcile_model._apply_rules(bank_statement_lines, excluded_ids=excluded_ids, partner_map=partner_map)
|
||||
|
||||
results = {
|
||||
'lines': [],
|
||||
'value_min': 0,
|
||||
'value_max': len(bank_statement_lines),
|
||||
'reconciled_aml_ids': [],
|
||||
}
|
||||
|
||||
# Iterate on st_lines to keep the same order in the results list.
|
||||
bank_statements_left = self.env['account.bank.statement']
|
||||
for line in bank_statement_lines:
|
||||
@@ -153,26 +158,41 @@ class AccountReconciliation(models.AbstractModel):
|
||||
return results
|
||||
|
||||
@api.model
|
||||
def get_bank_statement_data(self, bank_statement_ids):
|
||||
def get_bank_statement_data(self, bank_statement_line_ids, search_str=False):
|
||||
""" Get statement lines of the specified statements or all unreconciled
|
||||
statement lines and try to automatically reconcile them / find them
|
||||
a partner.
|
||||
Return ids of statement lines left to reconcile and other data for
|
||||
the reconciliation widget.
|
||||
|
||||
:param st_line_id: ids of the bank statement
|
||||
:param bank_statement_line_ids: ids of the bank statement lines
|
||||
"""
|
||||
bank_statements = self.env['account.bank.statement'].browse(bank_statement_ids)
|
||||
if not bank_statement_line_ids:
|
||||
return {}
|
||||
edition_mode = self._context.get('edition_mode')
|
||||
bank_statements = self.env['account.bank.statement.line'].browse(bank_statement_line_ids).mapped('statement_id')
|
||||
|
||||
search_sql = '''
|
||||
AND (p.name ILIKE CONCAT('%%',%(search_str)s,'%%')
|
||||
OR line.ref ILIKE CONCAT('%%',%(search_str)s,'%%')
|
||||
OR line.name ILIKE CONCAT('%%',%(search_str)s,'%%')
|
||||
OR CAST(line.amount AS TEXT) ILIKE CONCAT('%%',%(search_str)s,'%%'))
|
||||
'''
|
||||
query = '''
|
||||
SELECT line.id
|
||||
FROM account_bank_statement_line line
|
||||
WHERE account_id IS NULL
|
||||
LEFT JOIN res_partner p on p.id = line.partner_id
|
||||
WHERE line.account_id IS NULL
|
||||
AND line.amount != 0.0
|
||||
AND line.statement_id IN %s
|
||||
AND NOT EXISTS (SELECT 1 from account_move_line aml WHERE aml.statement_line_id = line.id)
|
||||
'''
|
||||
self.env.cr.execute(query, [tuple(bank_statements.ids)])
|
||||
AND line.id IN %(ids)s
|
||||
{cond}
|
||||
{srch}
|
||||
GROUP BY line.id
|
||||
'''.format(
|
||||
cond=not edition_mode and "AND NOT EXISTS (SELECT 1 from account_move_line aml WHERE aml.statement_line_id = line.id)" or "",
|
||||
srch=search_str and search_sql or "",
|
||||
)
|
||||
self.env.cr.execute(query, {'ids':tuple(bank_statement_line_ids), 'search_str':search_str})
|
||||
|
||||
bank_statement_lines = self.env['account.bank.statement.line'].browse([line.get('id') for line in self.env.cr.dictfetchall()])
|
||||
|
||||
@@ -437,7 +457,7 @@ class AccountReconciliation(models.AbstractModel):
|
||||
return str_domain
|
||||
|
||||
@api.model
|
||||
def _domain_move_lines_for_reconciliation(self, st_line, aml_accounts, partner_id, excluded_ids=None, search_str=False):
|
||||
def _domain_move_lines_for_reconciliation(self, st_line, aml_accounts, partner_id, excluded_ids=[], search_str=False):
|
||||
""" Return the domain for account.move.line records which can be used for bank statement reconciliation.
|
||||
|
||||
:param aml_accounts:
|
||||
@@ -445,6 +465,11 @@ class AccountReconciliation(models.AbstractModel):
|
||||
:param excluded_ids:
|
||||
:param search_str:
|
||||
"""
|
||||
AccountMoveLine = self.env['account.move.line']
|
||||
|
||||
#Always exclude the journal items that have been marked as 'to be checked' in a former bank statement reconciliation
|
||||
to_check_excluded = AccountMoveLine.search(AccountMoveLine._get_domain_for_edition_mode()).ids
|
||||
excluded_ids.extend(to_check_excluded)
|
||||
|
||||
domain_reconciliation = [
|
||||
'&', '&',
|
||||
|
||||
@@ -32,6 +32,9 @@ var StatementAction = AbstractAction.extend({
|
||||
load_more: '_onLoadMore',
|
||||
reload: 'reload',
|
||||
},
|
||||
events: {
|
||||
'change .reconciliation_search_input': '_onSearch',
|
||||
},
|
||||
config: _.extend({}, AbstractAction.prototype.config, {
|
||||
// used to instantiate the model
|
||||
Model: ReconciliationModel.StatementModel,
|
||||
@@ -40,7 +43,7 @@ var StatementAction = AbstractAction.extend({
|
||||
// used to instantiate each widget line
|
||||
LineRenderer: ReconciliationRenderer.LineRenderer,
|
||||
// used context params
|
||||
params: ['statement_ids'],
|
||||
params: ['statement_line_ids'],
|
||||
// number of statements/partners/accounts to display
|
||||
defaultDisplayQty: 10,
|
||||
// number of moves lines displayed in 'match' mode
|
||||
@@ -66,12 +69,12 @@ var StatementAction = AbstractAction.extend({
|
||||
// Adding values from the context is necessary to put this information in the url via the action manager so that
|
||||
// you can retrieve it if the person shares his url or presses f5
|
||||
_.each(params.params, function (value, name) {
|
||||
params.context[name] = name.indexOf('_ids') !== -1 ? _.map((value+'').split(), parseFloat) : value;
|
||||
params.context[name] = name.indexOf('_ids') !== -1 ? _.map((value+'').split(','), parseFloat) : value;
|
||||
});
|
||||
params.params = {};
|
||||
_.each(this.config.params, function (name) {
|
||||
if (params.context[name]) {
|
||||
params.params[name] = name.indexOf('_ids') !== -1 && _.isArray(params.context[name]) ? params.context[name].join() : params.context[name];
|
||||
params.params[name] = params.context[name];
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -249,6 +252,16 @@ var StatementAction = AbstractAction.extend({
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_onSearch: function (ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
this.reload();
|
||||
},
|
||||
|
||||
_onActionPartialAmount: function(event) {
|
||||
var self = this;
|
||||
|
||||
@@ -62,6 +62,7 @@ var _t = core._t;
|
||||
* }
|
||||
* [ref]: string
|
||||
* [is_partially_reconciled]: boolean
|
||||
* [to_check]: boolean
|
||||
* [amount_currency_str]: string|false (amount in record currency)
|
||||
* }
|
||||
* mv_lines: object - idem than reconciliation_proposition
|
||||
@@ -94,7 +95,7 @@ var _t = core._t;
|
||||
*/
|
||||
var StatementModel = BasicModel.extend({
|
||||
avoidCreate: false,
|
||||
quickCreateFields: ['account_id', 'amount', 'analytic_account_id', 'label', 'tax_id', 'force_tax_included', 'analytic_tag_ids'],
|
||||
quickCreateFields: ['account_id', 'amount', 'analytic_account_id', 'label', 'tax_id', 'force_tax_included', 'analytic_tag_ids', 'to_check'],
|
||||
|
||||
/**
|
||||
* @override
|
||||
@@ -339,14 +340,13 @@ var StatementModel = BasicModel.extend({
|
||||
* - 'account.reconciliation.widget' fetch each line data
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {number[]} context.statement_ids
|
||||
* @param {number[]} context.statement_line_ids
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
load: function (context) {
|
||||
var self = this;
|
||||
// var statement_ids = context.statement_ids;
|
||||
this.statement_ids = context.statement_ids;
|
||||
if (!this.statement_ids) {
|
||||
this.statement_line_ids = context.statement_line_ids;
|
||||
if (!this.statement_line_ids) {
|
||||
return $.when();
|
||||
}
|
||||
this.context = context;
|
||||
@@ -393,14 +393,16 @@ var StatementModel = BasicModel.extend({
|
||||
self.alreadyDisplayed = [];
|
||||
self.lines = {};
|
||||
self.pagerIndex = 0;
|
||||
self.search_str = $('.reconciliation_search_input').val()
|
||||
var def_statement = this._rpc({
|
||||
model: 'account.reconciliation.widget',
|
||||
method: 'get_bank_statement_data',
|
||||
args: [self.statement_ids],
|
||||
kwargs: {"bank_statement_line_ids":self.statement_line_ids, "search_str":self.search_str},
|
||||
context: self.context,
|
||||
})
|
||||
.then(function (statement) {
|
||||
self.statement = statement;
|
||||
self.bank_statement_id = self.statement_ids.length === 1 ? {id: self.statement_ids[0], display_name: statement.statement_name} : false;
|
||||
self.bank_statement_id = self.statement_line_ids.length === 1 ? {id: self.statement_line_ids[0], display_name: statement.statement_name} : false;
|
||||
self.valuenow = self.valuenow || statement.value_min;
|
||||
self.valuemax = self.valuemax || statement.value_max;
|
||||
self.context.journal_id = statement.journal_id;
|
||||
@@ -486,11 +488,18 @@ var StatementModel = BasicModel.extend({
|
||||
quickCreateProposition: function (handle, reconcileModelId) {
|
||||
var line = this.getLine(handle);
|
||||
var reconcileModel = _.find(this.reconcileModels, function (r) {return r.id === reconcileModelId;});
|
||||
var fields = ['account_id', 'amount', 'amount_type', 'analytic_account_id', 'journal_id', 'label', 'force_tax_included', 'tax_id', 'analytic_tag_ids'];
|
||||
var fields = ['account_id', 'amount', 'amount_type', 'analytic_account_id', 'journal_id', 'label', 'force_tax_included', 'tax_id', 'analytic_tag_ids', 'to_check'];
|
||||
this._blurProposition(handle);
|
||||
|
||||
var focus = this._formatQuickCreate(line, _.pick(reconcileModel, fields));
|
||||
focus.reconcileModelId = reconcileModelId;
|
||||
if (!line.reconciliation_proposition.every(function(prop) {return prop.to_check == focus.to_check})) {
|
||||
new CrashManager().show_warning({data: {
|
||||
exception_type: _t("Incorrect Operation"),
|
||||
message: _t("You cannot mix items with and without the 'To Check' checkbox ticked.")
|
||||
}});
|
||||
return $.when();
|
||||
}
|
||||
line.reconciliation_proposition.push(focus);
|
||||
|
||||
if (reconcileModel.has_second_line) {
|
||||
@@ -615,6 +624,14 @@ var StatementModel = BasicModel.extend({
|
||||
prop = this._formatQuickCreate(line);
|
||||
line.reconciliation_proposition.push(prop);
|
||||
}
|
||||
if (!line.reconciliation_proposition.slice(0,-1).every(function(prop) {return prop.to_check == values.to_check})) {
|
||||
new CrashManager().show_warning({data: {
|
||||
exception_type: _t("Incorrect Operation"),
|
||||
message: _t("You cannot mix items with and without the 'To Check' checkbox ticked.")
|
||||
}});
|
||||
$('.create_to_check input').click();
|
||||
return $.when();
|
||||
}
|
||||
_.each(values, function (value, fieldName) {
|
||||
if (fieldName === 'analytic_tag_ids') {
|
||||
switch (value.operation) {
|
||||
@@ -708,6 +725,15 @@ var StatementModel = BasicModel.extend({
|
||||
return isNaN(prop.id) && prop.display;
|
||||
}), self._formatToProcessReconciliation.bind(self, line)),
|
||||
};
|
||||
line.reconciliation_proposition.some(function(prop) {
|
||||
if (prop.to_check) {
|
||||
values_dict['to_check'] = true;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
if (line.reconciliation_proposition[0].to_check) {
|
||||
values_dict['to_check'] = true;
|
||||
}
|
||||
|
||||
// If the lines are not fully balanced, create an unreconciled amount.
|
||||
// line.st_line.currency_id is never false here because its equivalent to
|
||||
@@ -733,6 +759,7 @@ var StatementModel = BasicModel.extend({
|
||||
model: 'account.reconciliation.widget',
|
||||
method: 'process_bank_statement_line',
|
||||
args: [ids, values],
|
||||
context: self.context,
|
||||
})
|
||||
.then(function () {
|
||||
return {handles: handles};
|
||||
@@ -1019,6 +1046,16 @@ var StatementModel = BasicModel.extend({
|
||||
.then(function(){
|
||||
return data.write_off ? self.quickCreateProposition(line.handle, data.model_id) : true;
|
||||
})
|
||||
.then(function() {
|
||||
// If still no partner set, take the one from context, if it exists
|
||||
if (!line.st_line.partner_id && self.context.partner_id && self.context.partner_name) {
|
||||
return self.changePartner(line.handle, {
|
||||
'id': self.context.partner_id,
|
||||
'display_name': self.context.partner_name,
|
||||
}, true);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
);
|
||||
});
|
||||
return $.when.apply($, defs);
|
||||
@@ -1090,6 +1127,7 @@ var StatementModel = BasicModel.extend({
|
||||
'link': values.link,
|
||||
'display': true,
|
||||
'invalid': true,
|
||||
'to_check': values.to_check,
|
||||
'__tax_to_recompute': true,
|
||||
'is_tax': values.is_tax,
|
||||
'__focus': '__focus' in values ? values.__focus : true,
|
||||
@@ -1235,7 +1273,7 @@ var StatementModel = BasicModel.extend({
|
||||
* datas allowing manual reconciliation
|
||||
*/
|
||||
var ManualModel = StatementModel.extend({
|
||||
quickCreateFields: ['account_id', 'journal_id', 'amount', 'analytic_account_id', 'label', 'tax_id', 'force_tax_included', 'analytic_tag_ids', 'date'],
|
||||
quickCreateFields: ['account_id', 'journal_id', 'amount', 'analytic_account_id', 'label', 'tax_id', 'force_tax_included', 'analytic_tag_ids', 'date', 'to_check'],
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
|
||||
@@ -66,7 +66,9 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, {
|
||||
}
|
||||
|
||||
this.$('h1.statement_name').text(this._initialState.title || _t('No Title'));
|
||||
|
||||
if (this.model.context && this.model.context.args && this.model.context.args.search) {
|
||||
this.$('.reconciliation_search_input').val(self.model.context.args.search);
|
||||
}
|
||||
return $.when.apply($, defs);
|
||||
},
|
||||
/**
|
||||
@@ -335,9 +337,11 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
||||
update: function (state) {
|
||||
var self = this;
|
||||
// isValid
|
||||
this.$('caption .o_buttons button.o_validate').toggleClass('d-none', !!state.balance.type);
|
||||
this.$('caption .o_buttons button.o_reconcile').toggleClass('d-none', state.balance.type <= 0);
|
||||
var to_check_checked = !!(state.reconciliation_proposition[0] && state.reconciliation_proposition[0].to_check);
|
||||
this.$('caption .o_buttons button.o_validate').toggleClass('d-none', !!state.balance.type && !to_check_checked);
|
||||
this.$('caption .o_buttons button.o_reconcile').toggleClass('d-none', state.balance.type <= 0 || to_check_checked);
|
||||
this.$('caption .o_buttons .o_no_valid').toggleClass('d-none', state.balance.type >= 0);
|
||||
self.$('caption .o_buttons button.o_validate').toggleClass('text-warning', to_check_checked);
|
||||
|
||||
// partner_id
|
||||
this._makePartnerRecord(state.st_line.partner_id, state.st_line.partner_name).then(function (recordID) {
|
||||
@@ -545,6 +549,9 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
||||
}, {
|
||||
type: 'char', //TODO is it a bug or a feature when type date exists ?
|
||||
name: 'date',
|
||||
}, {
|
||||
type: 'boolean',
|
||||
name: 'to_check',
|
||||
}], {
|
||||
account_id: {string: _t("Account")},
|
||||
label: {string: _t("Label")},
|
||||
@@ -580,6 +587,9 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
||||
self.fields.date = new basic_fields.FieldDate(self,
|
||||
'date', record, {mode: 'edit'});
|
||||
|
||||
self.fields.to_check = new basic_fields.FieldBoolean(self,
|
||||
'to_check', record, {mode: 'edit'});
|
||||
|
||||
var $create = $(qweb.render("reconciliation.line.create", {'state': state}));
|
||||
self.fields.account_id.appendTo($create.find('.create_account_id .o_td_field'))
|
||||
.then(addRequiredStyle.bind(self, self.fields.account_id));
|
||||
@@ -593,6 +603,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
||||
self.fields.amount.appendTo($create.find('.create_amount .o_td_field'))
|
||||
.then(addRequiredStyle.bind(self, self.fields.amount));
|
||||
self.fields.date.appendTo($create.find('.create_date .o_td_field'))
|
||||
self.fields.to_check.appendTo($create.find('.create_to_check .o_td_field'))
|
||||
self.$('.create').append($create);
|
||||
|
||||
function addRequiredStyle(widget) {
|
||||
|
||||
@@ -18,6 +18,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_filter_input_wrapper {
|
||||
position: relative;
|
||||
float: right;
|
||||
width: 150px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 20px;
|
||||
float: right;
|
||||
.searchIcon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
.o_filter_input {
|
||||
border: none;
|
||||
border-bottom: 1px black solid;
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
margin: 10px 0 2px 0;
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
<t t-else="">
|
||||
<h1 class="statement_name" />
|
||||
</t>
|
||||
<div class="o_filter_input_wrapper">
|
||||
<span class="searchIcon"><i class="fa fa-search" role="img" aria-label="Search" title="Search"></i></span>
|
||||
<input class="reconciliation_search_input o_filter_input" type="text" placeholder="Search..." />
|
||||
</div>
|
||||
<h1 class="statement_name_edition" style="display: none;"/>
|
||||
<div class="progress progress-striped">
|
||||
<div class="progress-text">
|
||||
@@ -44,6 +48,7 @@
|
||||
<attribute name="class">o_manual_statement</attribute>
|
||||
</t>
|
||||
<t t-jquery=".o_view_noreconciliation p" t-operation="replace"></t>
|
||||
<t t-jquery=".o_filter_input_wrapper" t-operation="replace"></t>
|
||||
<t t-jquery=".o_view_noreconciliation" t-operation="append">
|
||||
<p><b>Good Job!</b> There is nothing to reconcile.</p>
|
||||
<p>All invoices and payments have been matched, your accounts' balances are clean.</p>
|
||||
@@ -216,6 +221,10 @@
|
||||
<td class="o_td_label">Writeoff Date</td>
|
||||
<td class="o_td_field"></td>
|
||||
</tr>
|
||||
<tr class="create_to_check"> <!--TODO d-none maybe see with avw-->
|
||||
<td class="o_td_label">To Check</td>
|
||||
<td class="o_td_field"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="add_line_container">
|
||||
|
||||
@@ -63,6 +63,7 @@ var db = {
|
||||
{id: 287, code: 101200, name: "101200 Account Receivable", company_id: 1},
|
||||
{id: 288, code: 101300, name: "101300 Tax Paid", company_id: 1},
|
||||
{id: 308, code: 101401, name: "101401 Bank", company_id: 1},
|
||||
{id: 499, code: 499001, name: "499001 Suspense Account", company_id: 1},
|
||||
{id: 500, code: 500, name: "500 Account", company_id: 1},
|
||||
{id: 501, code: 501, name: "501 Account", company_id: 1},
|
||||
{id: 502, code: 502, name: "502 Account", company_id: 1},
|
||||
@@ -553,7 +554,7 @@ var session = {
|
||||
|
||||
var options = {
|
||||
context: {
|
||||
statement_ids: [4]
|
||||
statement_line_ids: [4]
|
||||
},
|
||||
params: {
|
||||
limitMoveLines: 5,
|
||||
@@ -1857,5 +1858,65 @@ QUnit.module('account', {
|
||||
|
||||
clientAction.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Reconcile temporarily and ask to check', function(assert){
|
||||
assert.expect(4);
|
||||
this.params.options.context['to_check'] = true;
|
||||
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
|
||||
|
||||
testUtils.mock.addMockEnvironment(clientAction, {
|
||||
data: this.params.data,
|
||||
session: {
|
||||
currencies: {
|
||||
3: {
|
||||
digits: [69, 2],
|
||||
position: "before",
|
||||
symbol: "$"
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
clientAction.appendTo($('#qunit-fixture'));
|
||||
var widget = clientAction.widgets[0];
|
||||
|
||||
// Add a line as proposition
|
||||
// open the first line
|
||||
widget.$('.accounting_view tfoot td.cell_label').click()
|
||||
|
||||
var $reconcileForm = widget.$(".create");
|
||||
$reconcileForm.find('.create_account_id input').val('499001 Suspense Account').keydown().keyup()
|
||||
$reconcileForm.find('.create_account_id input').click();
|
||||
$('.ui-autocomplete .ui-menu-item a:contains(499001 Suspense Account)')
|
||||
.trigger('mouseover')
|
||||
.trigger('click');
|
||||
|
||||
assert.equal($("button.o_validate.btn.btn-secondary.text-warning:first").length, 0, "should not display reconcile button in orange");
|
||||
$reconcileForm.find('.create_to_check input').click()
|
||||
assert.equal($("button.o_validate.btn.btn-secondary.text-warning:first").length, 1, "should display reconcile button in orange");
|
||||
|
||||
testUtils.mock.intercept(clientAction, 'call_service', function (event) {
|
||||
assert.deepEqual(event.data.args[1].args,
|
||||
[[5],[{partner_id: 8,
|
||||
counterpart_aml_dicts: [],
|
||||
payment_aml_ids: [],
|
||||
new_aml_dicts: [{account_id: 499,
|
||||
credit: 1175,
|
||||
debit: 0,
|
||||
analytic_tag_ids: [[6, null, []]],
|
||||
name: 'SAJ/2014/002 and SAJ/2014/003',
|
||||
}],
|
||||
to_check: true,
|
||||
}]],
|
||||
"Should call process_bank_statement_line with to_check set to true");
|
||||
var def = $.Deferred();
|
||||
def.abort = function () {};
|
||||
event.data.callback(def);
|
||||
});
|
||||
|
||||
widget.$("button.o_reconcile.btn.btn-primary:first").click();
|
||||
assert.ok(true, "No error in reconciliation");
|
||||
|
||||
clientAction.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from odoo import api, fields
|
||||
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
import time
|
||||
import unittest
|
||||
@@ -1444,3 +1445,37 @@ class TestReconciliation(AccountingTestCase):
|
||||
expected['tax_10']
|
||||
)
|
||||
index += 1
|
||||
|
||||
def test_reconciliation_to_check(self):
|
||||
partner = self.env['res.partner'].create({'name': 'UncertainPartner'})
|
||||
currency = self.env.user.company_id.currency_id
|
||||
invoice = self.create_invoice_partner(currency_id=currency.id, partner_id=partner.id)
|
||||
journal = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'THE', 'update_posted':True})
|
||||
|
||||
statement = self.make_payment(invoice, journal, 50)
|
||||
st_line = statement.line_ids
|
||||
previous_move_lines = st_line.journal_entry_ids.ids
|
||||
previous_name = st_line.move_name
|
||||
|
||||
with self.assertRaises(UserError): #you need edition mode to be able to change it
|
||||
st_line.with_context(edition_mode=False).process_reconciliation(
|
||||
counterpart_aml_dicts=[],
|
||||
new_aml_dicts = [{
|
||||
'debit': 0,
|
||||
'credit': 50,
|
||||
'name': 'exchange difference',
|
||||
'account_id': self.diff_income_account.id
|
||||
}],
|
||||
)
|
||||
|
||||
st_line.with_context(edition_mode=True).process_reconciliation(
|
||||
counterpart_aml_dicts=[],
|
||||
new_aml_dicts = [{
|
||||
'debit': 0,
|
||||
'credit': 50,
|
||||
'name': 'exchange difference',
|
||||
'account_id': self.diff_income_account.id
|
||||
}],
|
||||
)
|
||||
self.assertEqual(previous_name, st_line.move_name) # the name of the move hasnt changed
|
||||
self.assertNotEqual(previous_move_lines, st_line.journal_entry_ids.ids) # the lines are new
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import odoo.tests
|
||||
import time
|
||||
import requests
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -12,13 +13,17 @@ class TestUi(odoo.tests.HttpCase):
|
||||
|
||||
def test_01_admin_bank_statement_reconciliation(self):
|
||||
bank_stmt_name = 'BNK/%s/0001' % time.strftime('%Y')
|
||||
bank_stmt = self.env['account.bank.statement'].search([('name', '=', bank_stmt_name)])
|
||||
if not bank_stmt:
|
||||
bank_stmt_line = self.env['account.bank.statement'].search([('name', '=', bank_stmt_name)]).mapped('line_ids')
|
||||
if not bank_stmt_line:
|
||||
_logger.exception('Could not find bank statement %s' % bank_stmt_name)
|
||||
|
||||
# To be able to test reconciliation, admin user must have access to accounting features, so we give him the right group for that
|
||||
self.env.ref('base.user_admin').write({'groups_id': [(4, self.env.ref('account.group_account_user').id)]})
|
||||
|
||||
payload = {'action':'bank_statement_reconciliation_view', 'statement_line_ids[]': bank_stmt_line.ids}
|
||||
prep = requests.models.PreparedRequest()
|
||||
prep.prepare_url(url="http://localhost/web#", params=payload)
|
||||
|
||||
self.phantom_js("/web#statement_ids=" + str(bank_stmt.id) + "&action=bank_statement_reconciliation_view",
|
||||
self.phantom_js(prep.url.replace('http://localhost','').replace('?','#'),
|
||||
"odoo.__DEBUG__.services['web_tour.tour'].run('bank_statement_reconciliation')",
|
||||
"odoo.__DEBUG__.services['web_tour.tour'].tours.bank_statement_reconciliation.ready", login="admin")
|
||||
|
||||
@@ -271,7 +271,11 @@
|
||||
<div groups="account.group_account_invoice" class="alert alert-info" role="alert" style="margin-bottom:0px;" attrs="{'invisible': [('has_outstanding','=',False)]}">
|
||||
You have <bold><a class="alert-link" href="#outstanding" role="button">outstanding debits</a></bold> for this supplier. You can allocate them to mark this bill as paid.
|
||||
</div>
|
||||
<div groups="account.group_account_invoice" class="alert alert-info" role="alert" style="margin-bottom:0px;" attrs="{'invisible': [('edition_mode_available','=',False)]}">
|
||||
You have suspense account moves that match this invoice. <bold><button class="alert-link" type="object" name="action_reconcile_to_check" role="button" string="Check them" style="padding: 0;vertical-align: baseline;"/></bold> to mark this invoice as paid.
|
||||
</div>
|
||||
<field name="has_outstanding" invisible="1"/>
|
||||
<field name="edition_mode_available" invisible="1"/>
|
||||
<sheet string="Vendor Bill">
|
||||
<div>
|
||||
<span class="o_form_label" attrs="{'invisible': ['|',('state','!=','draft'), ('type','!=','in_invoice')]}">Draft Bill</span>
|
||||
@@ -424,7 +428,11 @@
|
||||
<div groups="account.group_account_invoice" class="alert alert-info" role="alert" style="margin-bottom:0px;" attrs="{'invisible': [('has_outstanding','=',False)]}">
|
||||
You have <bold><a class="alert-link" href="#outstanding" role="button">outstanding payments</a></bold> for this customer. You can allocate them to mark this invoice as paid.
|
||||
</div>
|
||||
<div groups="account.group_account_invoice" class="alert alert-info" role="alert" style="margin-bottom:0px;" attrs="{'invisible': [('edition_mode_available','=',False)]}">
|
||||
You have suspense account moves that match this invoice. <bold><button class="alert-link" type="object" name="action_reconcile_to_check" role="button" string="Check them" style="padding: 0;vertical-align: baseline;"/></bold> to mark this invoice as paid.
|
||||
</div>
|
||||
<field name="has_outstanding" invisible="1"/>
|
||||
<field name="edition_mode_available" invisible="1"/>
|
||||
<sheet string="Invoice">
|
||||
<div class="oe_button_box" name="button_box">
|
||||
</div>
|
||||
|
||||
@@ -258,6 +258,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="dashboard.number_to_check > 0">
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<a type="object" name="action_open_to_check"><t t-esc="dashboard.number_to_check"/> moves to check</a>
|
||||
</div>
|
||||
<div class="col-5 text-right">
|
||||
<span><t t-esc="dashboard.to_check_balance"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="JournalBodySalePurchase" id="account.JournalBodySalePurchase">
|
||||
|
||||
@@ -554,12 +554,6 @@
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<record id="action_bank_reconcile_bank_statements" model="ir.actions.client">
|
||||
<field name="name">Reconciliation on Bank Statements</field>
|
||||
<field name="tag">bank_statement_reconciliation_view</field>
|
||||
<field name="context">{'statement_ids': [active_id]}</field>
|
||||
</record>
|
||||
|
||||
<record id="view_bank_statement_tree" model="ir.ui.view">
|
||||
<field name="name">account.bank.statement.tree</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
@@ -605,7 +599,7 @@
|
||||
<form string="Bank Statement">
|
||||
<header>
|
||||
<field name="all_lines_reconciled" invisible="1" />
|
||||
<button name="%(action_bank_reconcile_bank_statements)d" string="Reconcile" type="action" class="oe_highlight" attrs="{'invisible':['|','|',('all_lines_reconciled','=',True),('line_ids','=',[]),('state', '!=', 'open')]}"/>
|
||||
<button name="action_bank_reconcile_bank_statements" string="Reconcile" type="object" class="oe_highlight" attrs="{'invisible':['|','|',('all_lines_reconciled','=',True),('line_ids','=',[]),('state', '!=', 'open')]}"/>
|
||||
<button name="check_confirm_bank" string="Validate" type="object" class="oe_highlight" attrs="{'invisible':['|','|',('all_lines_reconciled','=',False),('line_ids','=',[]),('state', '!=', 'open')]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="open,confirm"/>
|
||||
</header>
|
||||
@@ -836,6 +830,7 @@
|
||||
</group>
|
||||
<group>
|
||||
<field name="auto_reconcile" attrs="{'invisible': [('rule_type', '=', 'writeoff_button')]}"/>
|
||||
<field name="to_check" attrs="{'invisible': [('rule_type', '!=', 'writeoff_button')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Conditions on Bank Statement Line">
|
||||
@@ -1647,6 +1642,7 @@
|
||||
<field name="auto_reverse" attrs="{'readonly':[('reverse_entry_id', '!=', False)]}"/>
|
||||
<field name="reverse_date" attrs="{'invisible':[('auto_reverse', '=', False)], 'required':[('auto_reverse', '=', True)],'readonly':[('reverse_entry_id', '!=', False)]}"/>
|
||||
<field name="reverse_entry_id" attrs="{'invisible':True}"/>
|
||||
<field name="to_check"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
@@ -1668,6 +1664,7 @@
|
||||
<field name="date"/>
|
||||
<filter string="Unposted" name="unposted" domain="[('state','=','draft')]" help="Unposted Journal Entries"/>
|
||||
<filter string="Posted" name="posted" domain="[('state','=','posted')]" help="Posted Journal Entries"/>
|
||||
<filter string="To Check" name="to_check" domain="[('to_check', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter string="Sales" name="sales" domain="[('journal_id.type','=','sale')]" context="{'default_journal_type': 'sale'}"/>
|
||||
<filter string="Purchases" name="purchases" domain="[('journal_id.type','=','purchase')]" context="{'default_journal_type': 'purchase'}"/>
|
||||
|
||||
@@ -48,7 +48,7 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
# Prepare statement data to be used for bank statements creation
|
||||
stmts_vals = self._complete_stmts_vals(stmts_vals, journal, account_number)
|
||||
# Create the bank statements
|
||||
statement_ids, notifications = self._create_bank_statements(stmts_vals)
|
||||
statement_line_ids, notifications = self._create_bank_statements(stmts_vals)
|
||||
# Now that the import worked out, set it as the bank_statements_source of the journal
|
||||
if journal.bank_statements_source != 'file_import':
|
||||
# Use sudo() because only 'account.group_account_manager'
|
||||
@@ -56,15 +56,13 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
# must be able to import bank statement files
|
||||
journal.sudo().bank_statements_source = 'file_import'
|
||||
# Finally dispatch to reconciliation interface
|
||||
action = self.env.ref('account.action_bank_reconcile_bank_statements')
|
||||
return {
|
||||
'name': action.name,
|
||||
'tag': action.tag,
|
||||
'context': {
|
||||
'statement_ids': statement_ids,
|
||||
'notifications': notifications
|
||||
},
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'bank_statement_reconciliation_view',
|
||||
'context': {'statement_line_ids': statement_line_ids,
|
||||
'company_ids': self.env.user.company_ids.ids,
|
||||
'notifications': notifications,
|
||||
},
|
||||
}
|
||||
|
||||
def _journal_creation_wizard(self, currency, account_number):
|
||||
@@ -204,7 +202,7 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
BankStatementLine = self.env['account.bank.statement.line']
|
||||
|
||||
# Filter out already imported transactions and create statements
|
||||
statement_ids = []
|
||||
statement_line_ids = []
|
||||
ignored_statement_lines_import_ids = []
|
||||
for st_vals in stmts_vals:
|
||||
filtered_st_lines = []
|
||||
@@ -223,8 +221,8 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
st_vals.pop('transactions', None)
|
||||
# Create the statement
|
||||
st_vals['line_ids'] = [[0, False, line] for line in filtered_st_lines]
|
||||
statement_ids.append(BankStatement.create(st_vals).id)
|
||||
if len(statement_ids) == 0:
|
||||
statement_line_ids.extend(BankStatement.create(st_vals).line_ids.ids)
|
||||
if len(statement_line_ids) == 0:
|
||||
raise UserError(_('You already have imported that file.'))
|
||||
|
||||
# Prepare import feedback
|
||||
@@ -240,4 +238,4 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
'ids': BankStatementLine.search([('unique_import_id', 'in', ignored_statement_lines_import_ids)]).ids
|
||||
}
|
||||
}]
|
||||
return statement_ids, notifications
|
||||
return statement_line_ids, notifications
|
||||
|
||||
Reference in New Issue
Block a user