diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 487477dee6a..d509f4f88a9 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -446,6 +446,7 @@ class AccountBankStatementLine(models.Model):
""" Prepare the dict of values to balance the move.
:param recordset move: the account.move to link the move line
+ :param dict move: a dict of vals of a account.move which will be created later
:param float amount: the amount of transaction that wasn't already reconciled
"""
company_currency = self.journal_id.company_id.currency_id
@@ -453,19 +454,23 @@ class AccountBankStatementLine(models.Model):
st_line_currency = self.currency_id or statement_currency
amount_currency = False
st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
+ if isinstance(move, dict):
+ amount_sum = sum(x[2].get('amount_currency', 0) for x in move['line_ids'])
+ else:
+ amount_sum = sum(x.amount_currency for x in move.line_ids)
# We have several use case here to compare the currency and amount currency of counterpart line to balance the move:
if st_line_currency != company_currency and st_line_currency == statement_currency:
# company in currency A, statement in currency B and transaction in currency B
# counterpart line must have currency B and correct amount is inverse of already existing lines
- amount_currency = -sum([x.amount_currency for x in move.line_ids])
+ amount_currency = -amount_sum
elif st_line_currency != company_currency and statement_currency == company_currency:
# company in currency A, statement in currency A and transaction in currency B
# counterpart line must have currency B and correct amount is inverse of already existing lines
- amount_currency = -sum([x.amount_currency for x in move.line_ids])
+ amount_currency = -amount_sum
elif st_line_currency != company_currency and st_line_currency != statement_currency:
# company in currency A, statement in currency B and transaction in currency C
# counterpart line must have currency B and use rate between B and C to compute correct amount
- amount_currency = -sum([x.amount_currency for x in move.line_ids])/st_line_currency_rate
+ amount_currency = -amount_sum/st_line_currency_rate
elif st_line_currency == company_currency and statement_currency != company_currency:
# company in currency A, statement in currency B and transaction in currency A
# counterpart line must have currency B and amount is computed using the rate between A and B
@@ -474,9 +479,8 @@ class AccountBankStatementLine(models.Model):
# last case is company in currency A, statement in currency A and transaction in currency A
# and in this case counterpart line does not need any second currency nor amount_currency
- return {
+ aml_dict = {
'name': self.name,
- 'move_id': move.id,
'partner_id': self.partner_id and self.partner_id.id or False,
'account_id': amount >= 0 \
and self.statement_id.journal_id.default_credit_account_id.id \
@@ -487,22 +491,72 @@ class AccountBankStatementLine(models.Model):
'currency_id': statement_currency != company_currency and statement_currency.id or (st_line_currency != company_currency and st_line_currency.id or False),
'amount_currency': amount_currency,
}
+ if isinstance(move, self.env['account.move'].__class__):
+ aml_dict['move_id'] = move.id
+ return aml_dict
+ @api.multi
def fast_counterpart_creation(self):
"""This function is called when confirming a bank statement and will allow to automatically process lines without
going in the bank reconciliation widget. By setting an account_id on bank statement lines, it will create a journal
entry using that account to counterpart the bank account
"""
+ payment_list = []
+ move_list = []
+ account_type_receivable = self.env.ref('account.data_account_type_receivable')
+ already_done_stmt_line_ids = [a['statement_line_id'][0] for a in self.env['account.move.line'].read_group([('statement_line_id', 'in', self.ids)], ['statement_line_id'], ['statement_line_id'])]
+ managed_st_line = []
for st_line in self:
# Technical functionality to automatically reconcile by creating a new move line
- if st_line.account_id and not st_line.journal_entry_ids.ids:
- vals = {
+ if st_line.account_id and not st_line.id in already_done_stmt_line_ids:
+ managed_st_line.append(st_line.id)
+ # Create payment vals
+ total = st_line.amount
+ payment_methods = (total > 0) and st_line.journal_id.inbound_payment_method_ids or st_line.journal_id.outbound_payment_method_ids
+ currency = st_line.journal_id.currency_id or st_line.company_id.currency_id
+ partner_type = 'customer' if st_line.account_id.user_type_id == account_type_receivable else 'supplier'
+ payment_list.append({
+ 'payment_method_id': payment_methods and payment_methods[0].id or False,
+ 'payment_type': total > 0 and 'inbound' or 'outbound',
+ 'partner_id': st_line.partner_id.id,
+ 'partner_type': partner_type,
+ 'journal_id': st_line.statement_id.journal_id.id,
+ 'payment_date': st_line.date,
+ 'state': 'reconciled',
+ 'currency_id': currency.id,
+ 'amount': abs(total),
+ 'communication': st_line._get_communication(payment_methods[0] if payment_methods else False),
+ 'name': st_line.statement_id.name or _("Bank Statement %s") % st_line.date,
+ })
+
+ # Create move and move line vals
+ move_vals = st_line._prepare_reconciliation_move(st_line.statement_id.name)
+ aml_dict = {
'name': st_line.name,
'debit': st_line.amount < 0 and -st_line.amount or 0.0,
'credit': st_line.amount > 0 and st_line.amount or 0.0,
'account_id': st_line.account_id.id,
+ 'partner_id': st_line.partner_id.id,
+ 'statement_line_id': st_line.id,
}
- st_line.process_reconciliation(new_aml_dicts=[vals])
+ st_line._prepare_move_line_for_currency(aml_dict, st_line.date or fields.Date.context_today())
+ move_vals['line_ids'] = [(0, 0, aml_dict)]
+ balance_line = self._prepare_reconciliation_move_line(
+ move_vals, -aml_dict['debit'] if st_line.amount < 0 else aml_dict['credit'])
+ move_vals['line_ids'].append((0, 0, balance_line))
+ move_list.append(move_vals)
+
+ # Creates
+ payment_ids = self.env['account.payment'].create(payment_list)
+ for payment_id, move_vals in zip(payment_ids, move_list):
+ for line in move_vals['line_ids']:
+ line[2]['payment_id'] = payment_id.id
+ move_ids = self.env['account.move'].create(move_list)
+ move_ids.post()
+
+ for move, st_line, payment in zip(move_ids, self.browse(managed_st_line), payment_ids):
+ st_line.write({'move_name': move.name})
+ payment.write({'payment_reference': move.name})
def _get_communication(self, payment_method_id):
return self.name or ''
@@ -630,8 +684,6 @@ class AccountBankStatementLine(models.Model):
# Create move line(s). Either matching an existing journal entry (eg. invoice), in which
# case we reconcile the existing and the new move lines together, or being a write-off.
if counterpart_aml_dicts or new_aml_dicts:
- st_line_currency = self.currency_id or statement_currency
- st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
# Create the move
self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
@@ -654,32 +706,12 @@ class AccountBankStatementLine(models.Model):
# Complete dicts to create both counterpart move lines and write-offs
to_create = (counterpart_aml_dicts + new_aml_dicts)
- company = self.company_id
date = self.date or fields.Date.today()
for aml_dict in to_create:
aml_dict['move_id'] = move.id
aml_dict['partner_id'] = self.partner_id.id
aml_dict['statement_line_id'] = self.id
- if st_line_currency.id != company_currency.id:
- aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
- aml_dict['currency_id'] = st_line_currency.id
- if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
- # Statement is in company currency but the transaction is in foreign currency
- aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
- aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
- elif self.currency_id and st_line_currency_rate:
- # Statement is in foreign currency and the transaction is in another one
- aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date)
- aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date)
- else:
- # Statement is in foreign currency and no extra currency is given for the transaction
- aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date)
- aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date)
- elif statement_currency.id != company_currency.id:
- # Statement is in foreign currency but the transaction is in company currency
- prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
- aml_dict['amount_currency'] = prorata_factor * self.amount
- aml_dict['currency_id'] = statement_currency.id
+ self._prepare_move_line_for_currency(aml_dict, date)
# Create write-offs
for aml_dict in new_aml_dicts:
@@ -729,6 +761,36 @@ class AccountBankStatementLine(models.Model):
counterpart_moves.assert_balanced()
return counterpart_moves
+ @api.multi
+ def _prepare_move_line_for_currency(self, aml_dict, date):
+ self.ensure_one()
+ company_currency = self.journal_id.company_id.currency_id
+ statement_currency = self.journal_id.currency_id or company_currency
+ st_line_currency = self.currency_id or statement_currency
+ st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
+ company = self.company_id
+
+ if st_line_currency.id != company_currency.id:
+ aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
+ aml_dict['currency_id'] = st_line_currency.id
+ if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
+ # Statement is in company currency but the transaction is in foreign currency
+ aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
+ aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
+ elif self.currency_id and st_line_currency_rate:
+ # Statement is in foreign currency and the transaction is in another one
+ aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date)
+ aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date)
+ else:
+ # Statement is in foreign currency and no extra currency is given for the transaction
+ aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date)
+ aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date)
+ elif statement_currency.id != company_currency.id:
+ # Statement is in foreign currency but the transaction is in company currency
+ prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
+ aml_dict['amount_currency'] = prorata_factor * self.amount
+ aml_dict['currency_id'] = statement_currency.id
+
def _check_invoice_state(self, invoice):
if invoice.state == 'in_payment' and all([payment.state == 'reconciled' for payment in invoice.mapped('payment_move_line_ids.payment_id')]):
invoice.write({'state': 'paid'})
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 2e325c760fe..e711f4a0dfd 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -93,11 +93,12 @@ class AccountInvoice(models.Model):
('type', 'in', [TYPE2JOURNAL[ty] for ty in inv_types if ty in TYPE2JOURNAL]),
('company_id', '=', company_id),
]
- journal_with_currency = False
- if self._context.get('default_currency_id'):
- currency_clause = [('currency_id', '=', self._context.get('default_currency_id'))]
- journal_with_currency = self.env['account.journal'].search(domain + currency_clause, limit=1)
- return journal_with_currency or self.env['account.journal'].search(domain, limit=1)
+ company_currency_id = self.env['res.company'].browse(company_id).currency_id.id
+ currency_id = self._context.get('default_currency_id') or company_currency_id
+ currency_clause = [('currency_id', '=', currency_id)]
+ if currency_id == company_currency_id:
+ currency_clause = ['|', ('currency_id', '=', False)] + currency_clause
+ return self.env['account.journal'].search(domain + currency_clause, limit=1)
@api.model
def _default_currency(self):
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index e5f4920cc3c..4e6b6adbb90 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -87,12 +87,14 @@ class AccountMove(models.Model):
if line.account_id.user_type_id.type in ('receivable', 'payable'):
amount = abs(line.debit - line.credit)
total_amount += amount
- for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
- total_reconciled += partial_line.amount
precision_currency = move.currency_id or move.company_id.currency_id
if float_is_zero(total_amount, precision_rounding=precision_currency.rounding):
move.matched_percentage = 1.0
else:
+ for line in move.line_ids:
+ if line.account_id.user_type_id.type in ('receivable', 'payable'):
+ for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
+ total_reconciled += partial_line.amount
move.matched_percentage = total_reconciled / total_amount
@api.multi
@@ -432,6 +434,7 @@ class AccountMove(models.Model):
@api.multi
def _reverse_move(self, date=None, journal_id=None):
self.ensure_one()
+ date = date or fields.Date.today()
with self.env.norecompute():
reversed_move = self.copy(default={
'date': date,
@@ -692,8 +695,8 @@ class AccountMoveLine(models.Model):
tax_audit = fields.Char(string="Tax Audit String", compute="_compute_tax_audit", store=True, help="Computed field, listing the tax grids impacted by this line, and the amount it applies to each of them.")
_sql_constraints = [
- ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'),
- ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
+ ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry! Credit or debit should be zero.'),
+ ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry! Credit and debit should be positive.'),
]
@api.model
@@ -827,22 +830,21 @@ class AccountMoveLine(models.Model):
In case of full reconciliation, all moves belonging to the reconciliation will belong to the same account_full_reconcile object.
"""
# Get first all aml involved
- part_recs = self.env['account.partial.reconcile'].search(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)])
- amls = self
- todo = set(part_recs)
+ todo = self.env['account.partial.reconcile'].search_read(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)], ['debit_move_id', 'credit_move_id'])
+ amls = set(self.ids)
seen = set()
while todo:
- partial_rec = todo.pop()
- seen.add(partial_rec)
- for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]:
- if aml not in amls:
- amls += aml
- for x in aml.matched_debit_ids | aml.matched_credit_ids:
- if x not in seen:
- todo.add(x)
- partial_rec_ids = [x.id for x in seen]
+ aml_ids = [rec['debit_move_id'][0] for rec in todo if rec['debit_move_id']] + [rec['credit_move_id'][0] for rec in todo if rec['credit_move_id']]
+ amls |= set(aml_ids)
+ seen |= set([rec['id'] for rec in todo])
+ todo = self.env['account.partial.reconcile'].search_read(['&', '|', ('credit_move_id', 'in', aml_ids), ('debit_move_id', 'in', aml_ids), '!', ('id', 'in', list(seen))], ['debit_move_id', 'credit_move_id'])
+
+ partial_rec_ids = list(seen)
if not amls:
return
+ else:
+ amls = self.browse(list(amls))
+
# If we have multiple currency, we can only base ourselve on debit-credit to see if it is fully reconciled
currency = set([a.currency_id for a in amls if a.currency_id.id != False])
multiple_currency = False
diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py
index abc17743e61..614aba000ab 100644
--- a/addons/account/report/account_invoice_report.py
+++ b/addons/account/report/account_invoice_report.py
@@ -96,7 +96,7 @@ class AccountInvoiceReport(models.Model):
sub.payment_term_id, sub.uom_name, sub.currency_id, sub.journal_id,
sub.fiscal_position_id, sub.user_id, sub.company_id, sub.nbr, sub.invoice_id, sub.type, sub.state,
sub.categ_id, sub.date_due, sub.account_id, sub.account_line_id, sub.partner_bank_id,
- sub.product_qty, sub.price_total as price_total, sub.price_average as price_average, sub.amount_total as amount_total,
+ sub.product_qty, sub.price_total as price_total, sub.price_average as price_average, sub.amount_total / COALESCE(cr.rate, 1) as amount_total,
COALESCE(cr.rate, 1) as currency_rate, sub.residual as residual, sub.commercial_partner_id as commercial_partner_id
"""
return select_str
@@ -114,8 +114,7 @@ class AccountInvoiceReport(models.Model):
ai.partner_bank_id,
SUM ((invoice_type.sign_qty * ail.quantity) / COALESCE(u.factor,1) * COALESCE(u2.factor,1)) AS product_qty,
SUM(ail.price_subtotal_signed * invoice_type.sign) AS price_total,
- ai.amount_total_signed / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
- count(*) * invoice_type.sign AS amount_total,
+ SUM(ail.price_total * invoice_type.sign_qty) AS amount_total,
SUM(ABS(ail.price_subtotal_signed)) / CASE
WHEN SUM(ail.quantity / COALESCE(u.factor,1) * COALESCE(u2.factor,1)) <> 0::numeric
THEN SUM(ail.quantity / COALESCE(u.factor,1) * COALESCE(u2.factor,1))
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index d4dce5581ca..2846e2d2e2f 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -281,7 +281,6 @@ var StatementModel = BasicModel.extend({
if (last && !this._isValid(last)) {
return Promise.resolve(false);
}
-
prop = this._formatQuickCreate(line);
line.reconciliation_proposition.push(prop);
line.createForm = _.pick(prop, this.quickCreateFields);
@@ -493,13 +492,6 @@ var StatementModel = BasicModel.extend({
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 Promise.resolve();
- }
line.reconciliation_proposition.push(focus);
if (reconcileModel.has_second_line) {
var second = {};
@@ -619,19 +611,16 @@ var StatementModel = BasicModel.extend({
var self = this;
var line = this.getLine(handle);
var prop = _.last(_.filter(line.reconciliation_proposition, '__focus'));
+ if ('to_check' in values && values.to_check === false) {
+ // check if we have another line with to_check and if yes don't change value of this proposition
+ prop.to_check = line.reconciliation_proposition.some(function(rec_prop, index) {
+ return rec_prop.id !== prop.id && rec_prop.to_check;
+ });
+ }
if (!prop) {
prop = this._formatQuickCreate(line);
line.reconciliation_proposition.push(prop);
}
- if ('to_check' in values && !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.")
- }});
- // FIXME: model should not be tied to the DOM !
- $('.create_to_check input:visible').prop('checked', !values.to_check).change();
- return Promise.resolve();
- }
_.each(values, function (value, fieldName) {
if (fieldName === 'analytic_tag_ids') {
switch (value.operation) {
@@ -683,7 +672,6 @@ var StatementModel = BasicModel.extend({
prop.__tax_to_recompute = true;
}
line.createForm = _.pick(prop, this.quickCreateFields);
-
// If you check/uncheck the force_tax_included box, reset the createForm amount.
if(prop.base_amount)
line.createForm.amount = prop.base_amount;
@@ -744,16 +732,8 @@ var StatementModel = BasicModel.extend({
"new_aml_dicts": _.map(_.filter(props, function (prop) {
return isNaN(prop.id) && prop.display;
}), self._formatToProcessReconciliation.bind(self, line)),
+ "to_check": line.to_check,
};
- 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
@@ -870,7 +850,12 @@ var StatementModel = BasicModel.extend({
var formatOptions = {
currency_id: line.st_line.currency_id,
};
+ line.to_check = false;
_.each(line.reconciliation_proposition, function (prop) {
+ if (prop.to_check) {
+ // If one of the proposition is to_check, set the global to_check flag to true
+ line.to_check = true;
+ }
if (prop.is_tax) {
if (!_.find(line.reconciliation_proposition, {'id': prop.link}).__tax_to_recompute) {
reconciliation_proposition.push(prop);
@@ -1025,6 +1010,7 @@ var StatementModel = BasicModel.extend({
prop.label = prop.name;
prop.account_id = self._formatNameGet(prop.account_id || line.account_id);
prop.is_partially_reconciled = prop.amount_str !== prop.total_amount_str;
+ prop.to_check = !!prop.to_check;
});
}
},
@@ -1160,7 +1146,7 @@ var StatementModel = BasicModel.extend({
'link': values.link,
'display': true,
'invalid': true,
- 'to_check': values.to_check,
+ 'to_check': !!values.to_check,
'__tax_to_recompute': true,
'is_tax': values.is_tax,
'__focus': '__focus' in values ? values.__focus : true,
@@ -1406,11 +1392,8 @@ var ManualModel = StatementModel.extend({
return self.loadData(lines);
});
default:
- var partner_ids = context.partner_ids;
- var account_ids = context.account_ids || self.account_ids;
- if (partner_ids && !account_ids) account_ids = [];
- if (!partner_ids && account_ids) partner_ids = [];
- account_ids = null; // TOFIX: REMOVE ME
+ var partner_ids = context.partner_ids || null;
+ var account_ids = context.account_ids || self.account_ids || null;
return self._rpc({
model: 'account.reconciliation.widget',
method: 'get_all_data_for_manual_reconciliation',
@@ -1627,6 +1610,7 @@ var ManualModel = StatementModel.extend({
prop.debit = prop.debit !== 0 ? 0 : tmp_value;
prop.amount = -prop.amount;
prop.journal_id = self._formatNameGet(prop.journal_id || line.journal_id);
+ prop.to_check = !!prop.to_check;
});
}
},
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index 53cd0b76598..01e18865480 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -346,7 +346,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
update: function (state) {
var self = this;
// isValid
- var to_check_checked = !!(state.reconciliation_proposition[0] && state.reconciliation_proposition[0].to_check);
+ var to_check_checked = !!(state.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);
@@ -476,9 +476,13 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
}
}
});
+ if (state.to_check) {
+ // Set the to_check field to true if global to_check is set
+ this.$('.create_to_check input').prop('checked', state.to_check).change();
+ }
return true;
});
- });
+ });
}
this.$('.create .add_line').toggle(!!state.balance.amount_currency);
},
diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js
index 0248d0cec7e..84f7d40e9a6 100644
--- a/addons/account/static/tests/reconciliation_tests.js
+++ b/addons/account/static/tests/reconciliation_tests.js
@@ -477,7 +477,7 @@ var auto_reconciliation = {
};
var data_for_manual_reconciliation_widget = {
- '[[],null]': {
+ '[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]': {
'customers': [
{'account_id': 287, 'partner_name': "Agrolait", 'reconciliation_proposition': [], 'currency_id': 3, 'max_date': "2017-02-14 12:30:31", 'last_time_entries_checked': null, 'account_code': "101200", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'mode': "customers"},
{'account_id': 7, 'partner_name': "Camptocamp", 'reconciliation_proposition': [], 'currency_id': 3, 'max_date': "2017-02-13 14:24:55", 'last_time_entries_checked': null, 'account_code': "101200", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'mode': "customers"}
@@ -744,7 +744,7 @@ QUnit.module('account', {
"name": "INV/2017/0003",
"analytic_tag_ids": [[6, null, []]]
}],
- payment_aml_ids: [], new_aml_dicts: []}]],
+ payment_aml_ids: [], new_aml_dicts: [], to_check: false}]],
"Should call process_bank_statement_line with ids");
var def = testUtils.makeTestPromise();
def.abort = function () {};
@@ -782,8 +782,10 @@ QUnit.module('account', {
// line is send back to server.
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: [{
+ [[5],[{partner_id: 8, to_check: false, counterpart_aml_dicts: [],
+ payment_aml_ids: [],
+ to_check: false,
+ new_aml_dicts: [{
account_id: 287,
credit: 1175,
debit: 0,
@@ -834,7 +836,7 @@ QUnit.module('account', {
// We also create a line which is the open balance.
testUtils.mock.intercept(clientAction, 'call_service', function (event) {
assert.deepEqual(event.data.args[1].args,
- [[5],[{partner_id: 8,
+ [[5],[{partner_id: 8, to_check: false,
counterpart_aml_dicts: [{
counterpart_aml_id: 109,
credit: 650,
@@ -884,6 +886,7 @@ QUnit.module('account', {
}],
payment_aml_ids: [],
new_aml_dicts: [],
+ to_check: false,
}]
], "should call process_bank_statement_line with partial reconcile values");
}
@@ -1408,6 +1411,7 @@ QUnit.module('account', {
payment_aml_ids: [],
partner_id: 8,
+ to_check: false,
new_aml_dicts: []}]
], "should call process_bank_statement_line with partial reconcile values");
}
@@ -1500,6 +1504,7 @@ QUnit.module('account', {
payment_aml_ids: [],
partner_id: 8,
+ to_check: false,
new_aml_dicts: [
{account_id: 282,
credit: 0,
@@ -1609,6 +1614,7 @@ QUnit.module('account', {
payment_aml_ids: [],
partner_id: 8,
+ to_check: false,
new_aml_dicts: []
}]
], "should call process_bank_statement_line with correct counterpart_aml_dicts");
@@ -1662,7 +1668,7 @@ QUnit.module('account', {
assert.expect(5);
// tweak the data to fit our needs
- this.params.data_for_manual_reconciliation_widget['[283, null, "", 0, 6]'] = _.extend({}, this.params.data_for_manual_reconciliation_widget['[[],null]']);
+ this.params.data_for_manual_reconciliation_widget['[283, null, "", 0, 6]'] = _.extend({}, this.params.data_for_manual_reconciliation_widget['[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]']);
this.params.data_for_manual_reconciliation_widget['[283, null, "", 0, 6]'].accounts[0].reconciliation_proposition = [
{account_id: 283, account_type: "other", amount_currency_str: "", currency_id: false, date_maturity: "2017-03-18", date: "2017-02-16",
total_amount_str: "$ 500.00", partner_id: 8, account_name: "101000 Current Assets", name: "INV/2017/0987", partner_name: "Agrolait",
@@ -1772,7 +1778,7 @@ QUnit.module('account', {
QUnit.test('Tax on account receivable', async function(assert){
assert.expect(21);
- this.params.data_for_manual_reconciliation_widget['[[],null]'].accounts = [];
+ this.params.data_for_manual_reconciliation_widget['[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]'].accounts = [];
var clientAction = new ReconciliationClientAction.ManualAction(null, this.params.options);
testUtils.mock.addMockEnvironment(clientAction, {
data: this.params.data,
diff --git a/addons/account/tests/test_bank_statement_reconciliation.py b/addons/account/tests/test_bank_statement_reconciliation.py
index 0311f278a4b..7957be0b77d 100644
--- a/addons/account/tests/test_bank_statement_reconciliation.py
+++ b/addons/account/tests/test_bank_statement_reconciliation.py
@@ -13,6 +13,8 @@ class TestBankStatementReconciliation(AccountingTestCase):
self.bsl_model = self.env['account.bank.statement.line']
self.reconciliation_widget = self.env['account.reconciliation.widget']
self.partner = self.env['res.partner'].create({'name': 'test'})
+ self.currency_usd_id = self.env.ref("base.USD").id
+ self.currency_euro_id = self.env.ref("base.EUR").id
def test_reconciliation_proposition(self):
rcv_mv_line = self.create_invoice(100)
@@ -108,3 +110,31 @@ class TestBankStatementReconciliation(AccountingTestCase):
})
return bank_stmt_line
+
+ def test_confirm_statement_usd(self):
+ company = self.env.ref('base.main_company')
+ self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_euro_id, company.id])
+ self.env['res.currency.rate'].search([]).unlink()
+ self.env['res.currency.rate'].create({
+ 'currency_id': self.currency_usd_id,
+ 'rate': 2.0,
+ 'name': '2001-01-01',
+ })
+ bank_journal_usd = self.env['account.journal'].create({
+ 'name': 'Bank US',
+ 'type': 'bank',
+ 'code': 'BNK68',
+ 'currency_id': self.currency_usd_id,
+ })
+ statement = self.bs_model.create({
+ 'journal_id': bank_journal_usd.id,
+ 'balance_end_real': 100,
+ 'line_ids': [(0, 0, {
+ 'name': '_',
+ 'partner_id': self.partner.id,
+ 'amount': 100,
+ 'account_id': bank_journal_usd.default_debit_account_id.id,
+ })],
+ })
+ statement.button_open()
+ statement.button_confirm_bank()
diff --git a/addons/account/wizard/account_invoice_send.py b/addons/account/wizard/account_invoice_send.py
index 54fbd7ff4be..e65b5f0ee89 100644
--- a/addons/account/wizard/account_invoice_send.py
+++ b/addons/account/wizard/account_invoice_send.py
@@ -11,6 +11,7 @@ class AccountInvoiceSend(models.TransientModel):
_description = 'Account Invoice Send'
is_email = fields.Boolean('Email', default=lambda self: self.env.user.company_id.invoice_is_email)
+ invoice_without_email = fields.Text(compute='_compute_invoice_without_email', string='invoice(s) that will not be sent')
is_print = fields.Boolean('Print', default=lambda self: self.env.user.company_id.invoice_is_print)
printed = fields.Boolean('Is Printed', default=False)
invoice_ids = fields.Many2many('account.invoice', 'account_invoice_account_invoice_send_rel', string='Invoices')
@@ -45,6 +46,22 @@ class AccountInvoiceSend(models.TransientModel):
self.composer_id.template_id = self.template_id.id
self.composer_id.onchange_template_id_wrapper()
+ @api.onchange('is_email')
+ def _compute_invoice_without_email(self):
+ for wizard in self:
+ if wizard.is_email and len(wizard.invoice_ids) > 1:
+ invoices = self.env['account.invoice'].search([
+ ('id', 'in', self.env.context.get('active_ids')),
+ ('partner_id.email', '=', False)
+ ])
+ if invoices:
+ wizard.invoice_without_email = "%s\n%s" % (
+ _("The following invoice(s) will not be sent by email, because the customers don't have email address."),
+ "\n".join([i.reference for i in invoices])
+ )
+ else:
+ wizard.invoice_without_email = False
+
@api.multi
def _send_email(self):
if self.is_email:
diff --git a/addons/account/wizard/account_invoice_send_views.xml b/addons/account/wizard/account_invoice_send_views.xml
index 6052c1438d9..648dcdec65d 100644
--- a/addons/account/wizard/account_invoice_send_views.xml
+++ b/addons/account/wizard/account_invoice_send_views.xml
@@ -23,6 +23,9 @@
Create a new employee
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 4e5207a385a..37e250779ec 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -800,12 +800,12 @@ class HrExpenseSheet(models.Model):
return self.employee_id.parent_id.user_id
elif self.employee_id.department_id.manager_id.user_id:
return self.employee_id.department_id.manager_id.user_id
- return self.env.user
+ return self.env['res.users']
def activity_update(self):
for expense_report in self.filtered(lambda hol: hol.state == 'submit'):
self.activity_schedule(
'hr_expense.mail_act_expense_approval',
- user_id=expense_report.sudo()._get_responsible_for_approval().id)
+ user_id=expense_report.sudo()._get_responsible_for_approval().id or self.env.user.id)
self.filtered(lambda hol: hol.state == 'approve').activity_feedback(['hr_expense.mail_act_expense_approval'])
self.filtered(lambda hol: hol.state == 'cancel').activity_unlink(['hr_expense.mail_act_expense_approval'])
diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py
index 58f9bc70284..5d3353c889b 100644
--- a/addons/hr_holidays/models/hr_leave.py
+++ b/addons/hr_holidays/models/hr_leave.py
@@ -266,6 +266,11 @@ class HolidaysRequest(models.Model):
self.request_unit_hours = False
self.request_unit_custom = False
+ @api.onchange('user_id')
+ def _onchange_user_id(self):
+ if not self.out_of_office_message:
+ self.out_of_office_message = self.user_id.out_of_office_message
+
@api.onchange('request_date_from_period', 'request_hour_from', 'request_hour_to',
'request_date_from', 'request_date_to',
'employee_id')
@@ -340,7 +345,7 @@ class HolidaysRequest(models.Model):
def _onchange_type(self):
if self.holiday_type == 'employee':
if not self.employee_id:
- self.employee_id = self.env.user.employee_ids[:1].id
+ self.employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1).id
self.mode_company_id = False
self.category_id = False
elif self.holiday_type == 'company':
@@ -353,7 +358,7 @@ class HolidaysRequest(models.Model):
self.mode_company_id = False
self.category_id = False
if not self.department_id:
- self.department_id = self.env.user.employee_ids[:1].department_id.id
+ self.department_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1).department_id.id
elif self.holiday_type == 'category':
self.employee_id = False
self.mode_company_id = False
@@ -384,11 +389,15 @@ class HolidaysRequest(models.Model):
for holiday in self:
holiday.number_of_days_display = holiday.number_of_days
+ def _get_calendar(self):
+ self.ensure_one()
+ return self.employee_id.resource_calendar_id or self.env.user.company_id.resource_calendar_id
+
@api.multi
@api.depends('number_of_days')
def _compute_number_of_hours_display(self):
for holiday in self:
- calendar = holiday.employee_id.resource_calendar_id or self.env.user.company_id.resource_calendar_id
+ calendar = holiday._get_calendar()
if holiday.date_from and holiday.date_to:
number_of_hours = calendar.get_work_hours_count(holiday.date_from, holiday.date_to)
holiday.number_of_hours_display = number_of_hours or (holiday.number_of_days * HOURS_PER_DAY)
@@ -572,6 +581,10 @@ class HolidaysRequest(models.Model):
@api.multi
def write(self, values):
+ # Allow an employee to always write his own out of office message
+ if len(self) == 1 and values.keys() == {'out_of_office_message'} and self.employee_id.user_id == self.env.user:
+ return super(HolidaysRequest, self.sudo()).write(values)
+
employee_id = values.get('employee_id', False)
if not self.env.context.get('leave_fast_create') and values.get('state'):
self._check_approval_update(values['state'])
@@ -823,7 +836,7 @@ class HolidaysRequest(models.Model):
def _get_responsible_for_approval(self):
self.ensure_one()
- responsible = self.env.user
+ responsible = self.env['res.users']
if self.validation_type == 'hr' or (self.validation_type == 'both' and self.state == 'validate1'):
if self.holiday_status_id.responsible_id:
@@ -846,12 +859,12 @@ class HolidaysRequest(models.Model):
elif holiday.state == 'confirm':
holiday.activity_schedule(
'hr_holidays.mail_act_leave_approval',
- user_id=holiday.sudo()._get_responsible_for_approval().id)
+ user_id=holiday.sudo()._get_responsible_for_approval().id or self.env.user.id)
elif holiday.state == 'validate1':
holiday.activity_feedback(['hr_holidays.mail_act_leave_approval'])
holiday.activity_schedule(
'hr_holidays.mail_act_leave_second_approval',
- user_id=holiday.sudo()._get_responsible_for_approval().id)
+ user_id=holiday.sudo()._get_responsible_for_approval().id or self.env.user.id)
elif holiday.state == 'validate':
to_do |= holiday
elif holiday.state == 'refuse':
diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py
index 352765d45cc..91bf5658093 100644
--- a/addons/hr_holidays/models/hr_leave_allocation.py
+++ b/addons/hr_holidays/models/hr_leave_allocation.py
@@ -550,7 +550,7 @@ class HolidaysAllocation(models.Model):
def _get_responsible_for_approval(self):
self.ensure_one()
- responsible = self.env.user
+ responsible = self.env['res.users']
if self.validation_type == 'hr' or (self.validation_type == 'both' and self.state == 'validate1'):
if self.holiday_status_id.responsible_id:
@@ -570,12 +570,12 @@ class HolidaysAllocation(models.Model):
elif allocation.state == 'confirm':
allocation.activity_schedule(
'hr_holidays.mail_act_leave_allocation_approval',
- user_id=allocation.sudo()._get_responsible_for_approval().id)
+ user_id=allocation.sudo()._get_responsible_for_approval().id or self.env.user.id)
elif allocation.state == 'validate1':
allocation.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval'])
allocation.activity_schedule(
'hr_holidays.mail_act_leave_allocation_second_approval',
- user_id=allocation.sudo()._get_responsible_for_approval().id)
+ user_id=allocation.sudo()._get_responsible_for_approval().id or self.env.user.id)
elif allocation.state == 'validate':
to_do |= allocation
elif allocation.state == 'refuse':
diff --git a/addons/hr_holidays/tests/test_out_of_office.py b/addons/hr_holidays/tests/test_out_of_office.py
index 05e3e480cc0..4c97f465ce1 100644
--- a/addons/hr_holidays/tests/test_out_of_office.py
+++ b/addons/hr_holidays/tests/test_out_of_office.py
@@ -4,26 +4,29 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
-from odoo.tests.common import tagged, users, warmup
+from odoo.tests.common import tagged, users, warmup, Form
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase
@tagged('out_of_office')
class TestOutOfOffice(TestHrHolidaysBase):
- def test_leave_ooo(self):
- leave_type = self.env['hr.leave.type'].create({
+ def setUp(self):
+ super().setUp()
+ self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
})
+
+ def test_leave_ooo(self):
self.assertNotEqual(self.employee_hruser.user_id.im_status, 'leave_offline', 'user should not be on leave')
self.assertNotEqual(self.employee_hruser.user_id.partner_id.im_status, 'leave_offline', 'user should not be on leave')
leave_date_end = (datetime.today() + relativedelta(days=3))
leave = self.env['hr.leave'].create({
'name': 'Christmas',
'employee_id': self.employee_hruser.id,
- 'holiday_status_id': leave_type.id,
+ 'holiday_status_id': self.leave_type.id,
'date_from': (datetime.today() - relativedelta(days=1)),
'date_to': leave_date_end,
'out_of_office_message': 'contact tde in case of problems',
@@ -51,6 +54,29 @@ class TestOutOfOffice(TestHrHolidaysBase):
self.assertEqual(infos[0]['direct_partner'][0]['out_of_office_date_end'], leave_date_end)
self.assertEqual(infos[0]['direct_partner'][0]['out_of_office_message'], 'contact tde in case of problems')
+ def test_leave_ooo_use_default(self):
+ """ Out of office message from Preferences should be used as default value """
+ self.user_hruser.out_of_office_message = 'contact xdo in case of problems'
+
+ leave_form = Form(self.env['hr.leave'].sudo(self.user_hruser), view='hr_holidays.hr_leave_view_form')
+ leave_form.date_from = datetime.today() - relativedelta(days=1)
+ leave_form.date_to = datetime.today()
+ leave_form.holiday_status_id = self.leave_type
+ leave = leave_form.save()
+ self.assertEqual(leave.out_of_office_message, 'contact xdo in case of problems')
+
+ def test_leave_ooo_overwrite_default(self):
+ """ Out of office message default value should be overwrittable """
+ self.user_hruser.out_of_office_message = 'contact xdo in case of problems'
+
+ leave_form = Form(self.env['hr.leave'].sudo(self.user_hruser), view='hr_holidays.hr_leave_view_form')
+ leave_form.date_from = datetime.today() - relativedelta(days=1)
+ leave_form.date_to = datetime.today()
+ leave_form.holiday_status_id = self.leave_type
+ leave_form.out_of_office_message = 'contact tde in case of problems'
+ leave = leave_form.save()
+ self.assertEqual(leave.out_of_office_message, 'contact tde in case of problems')
+
@tagged('out_of_office')
class TestOutOfOfficePerformance(TestHrHolidaysBase):
diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml
index e137648e758..cb8d6ec5420 100644
--- a/addons/hr_holidays/views/hr_leave_views.xml
+++ b/addons/hr_holidays/views/hr_leave_views.xml
@@ -246,6 +246,7 @@