Dear ${object.partner_id.name},
-Thank you for your payment.
Here is your payment receipt ${(object.name or '').replace('/','-')} amounting to ${object.amount} ${object.currency_id.name} from ${object.company_id.name}.
Thank you for your payment.
Here is your payment receipt ${(object.name or '').replace('/','-')} amounting to ${format_amount(object.amount, object.currency_id)} from ${object.company_id.name}.
If you have any questions, please do not hesitate to contact us.
Best regards,
% if user and user.signature:
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 048b5d7e937..baa0aed666e 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -443,7 +443,7 @@ class AccountJournal(models.Model):
for journal in self:
if journal.sequence_id and journal.sequence_number_next:
sequence = journal.sequence_id._get_current_sequence()
- sequence.number_next = journal.sequence_number_next
+ sequence.sudo().number_next = journal.sequence_number_next
@api.multi
# do not depend on 'refund_sequence_id.date_range_ids', because
@@ -525,6 +525,8 @@ class AccountJournal(models.Model):
if ('company_id' in vals and journal.company_id.id != vals['company_id']):
if self.env['account.move'].search([('journal_id', 'in', self.ids)], limit=1):
raise UserError(_('This journal already contains items, therefore you cannot modify its company.'))
+ if self.bank_account_id:
+ self.bank_account_id.company_id = vals['company_id']
if ('code' in vals and journal.code != vals['code']):
if self.env['account.move'].search([('journal_id', 'in', self.ids)], limit=1):
raise UserError(_('This journal already contains items, therefore you cannot modify its short name.'))
@@ -538,6 +540,8 @@ class AccountJournal(models.Model):
self.default_debit_account_id.currency_id = vals['currency_id']
if not 'default_credit_account_id' in vals and self.default_credit_account_id:
self.default_credit_account_id.currency_id = vals['currency_id']
+ if self.bank_account_id:
+ self.bank_account_id.currency_id = vals['currency_id']
if 'bank_account_id' in vals and not vals.get('bank_account_id'):
raise UserError(_('You cannot remove the bank account from the journal once set.'))
result = super(AccountJournal, self).write(vals)
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 1ed9b16669c..77823fbe8c8 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -340,7 +340,7 @@ class AccountBankStatement(models.Model):
def link_bank_to_partner(self):
for statement in self:
for st_line in statement.line_ids:
- if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id != st_line.partner_id:
+ if st_line.bank_account_id and st_line.partner_id and not st_line.bank_account_id.partner_id:
st_line.bank_account_id.partner_id = st_line.partner_id
diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index e66a6ab068e..a769a3f8d17 100644
--- a/addons/account/models/account_journal_dashboard.py
+++ b/addons/account/models/account_journal_dashboard.py
@@ -341,12 +341,13 @@ class account_journal(models.Model):
ctx.update({
'journal_type': self.type,
'default_journal_id': self.id,
- 'search_default_journal_id': self.id,
'default_type': invoice_type,
'type': invoice_type
})
[action] = self.env.ref('account.%s' % action_name).read()
+ if not self.env.context.get('use_domain'):
+ ctx['search_default_journal_id'] = self.id
action['context'] = ctx
action['domain'] = self._context.get('use_domain', [])
account_invoice_filter = self.env.ref('account.view_account_invoice_filter', False)
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 8d0c8d27bcd..c477ca91d77 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -106,7 +106,7 @@ class AccountMove(models.Model):
default=lambda self: self.env.user.company_id)
matched_percentage = fields.Float('Percentage Matched', compute='_compute_matched_percentage', digits=0, store=True, readonly=True, help="Technical field used in cash basis method")
# Dummy Account field to search on account.move by account_id
- dummy_account_id = fields.Many2one('account.account', related='line_ids.account_id', string='Account', store=False)
+ dummy_account_id = fields.Many2one('account.account', related='line_ids.account_id', string='Account', store=False, readonly=True)
tax_cash_basis_rec_id = fields.Many2one(
'account.partial.reconcile',
string='Tax Cash Basis Entry of',
@@ -1361,7 +1361,7 @@ class AccountMoveLine(models.Model):
record.payment_id.state = 'reconciled'
result = super(AccountMoveLine, self).write(vals)
- if self._context.get('check_move_validity', True):
+ if self._context.get('check_move_validity', True) and any(key in vals for key in ('account_id', 'journal_id', 'date', 'move_id', 'debit', 'credit')):
move_ids = set()
for line in self:
if line.move_id.id not in move_ids:
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index c750dbff7c6..711c62e91a6 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -576,9 +576,9 @@ class account_payment(models.Model):
writeoff_line['amount_currency'] = amount_currency_wo
writeoff_line['currency_id'] = currency_id
writeoff_line = aml_obj.create(writeoff_line)
- if counterpart_aml['debit'] or writeoff_line['credit']:
+ if counterpart_aml['debit'] or (writeoff_line['credit'] and not counterpart_aml['credit']):
counterpart_aml['debit'] += credit_wo - debit_wo
- if counterpart_aml['credit'] or writeoff_line['debit']:
+ if counterpart_aml['credit'] or (writeoff_line['debit'] and not counterpart_aml['debit']):
counterpart_aml['credit'] += debit_wo - credit_wo
counterpart_aml['amount_currency'] -= amount_currency_wo
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index 60cba94fffc..9894dc6a9b4 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -526,17 +526,37 @@ var StatementModel = BasicModel.extend({
*/
togglePartialReconcile: function (handle) {
var line = this.getLine(handle);
- var props = _.filter(line.reconciliation_proposition, {'invalid': false});
- var prop = props[0];
- if (props.length !== 1 || Math.abs(line.st_line.amount) >= Math.abs(prop.amount)) {
+
+ // Retrieve the toggle proposition
+ var selected;
+ _.each(line.reconciliation_proposition, function (prop) {
+ if (!prop.invalid) {
+ if (((line.balance.amount < 0 || !line.partial_reconcile) && prop.amount > 0 && line.st_line.amount > 0 && line.st_line.amount < prop.amount) ||
+ ((line.balance.amount > 0 || !line.partial_reconcile) && prop.amount < 0 && line.st_line.amount < 0 && line.st_line.amount > prop.amount)) {
+ selected = prop;
+ return false;
+ }
+ }
+ });
+
+ // If no toggled proposition found, reject it
+ if (selected == null)
return $.Deferred().reject();
- }
- prop.partial_reconcile = !prop.partial_reconcile;
- if (!prop.partial_reconcile) {
+
+ // Inverse partial_reconcile value
+ selected.partial_reconcile = !selected.partial_reconcile;
+ if (!selected.partial_reconcile) {
return this._computeLine(line);
}
+
+ // Compute the write_off
+ var format_options = { currency_id: line.st_line.currency_id };
+ selected.write_off_amount = selected.amount + line.balance.amount;
+ selected.write_off_amount_str = field_utils.format.monetary(Math.abs(selected.write_off_amount), {}, format_options);
+ selected.write_off_amount_str = selected.write_off_amount_str.replace(' ', ' ');
+
return this._computeLine(line).then(function () {
- if (prop.partial_reconcile) {
+ if (selected.partial_reconcile) {
line.balance.amount = 0;
line.balance.type = 1;
line.mode = 'inactive';
@@ -646,6 +666,7 @@ var StatementModel = BasicModel.extend({
return isNaN(prop.id);
}), self._formatToProcessReconciliation.bind(self, line)),
};
+
// If the lines are not fully balanced, create an unreconciled amount.
// line.st_line.currency_id is never false here because its equivalent to
// statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side).
@@ -1078,8 +1099,9 @@ var StatementModel = BasicModel.extend({
// Do not forward port in master. @CSN will change this
var amount = prop.computed_with_tax && -prop.base_amount || -prop.amount;
if (prop.partial_reconcile === true) {
- amount = -line.st_line.amount;
+ amount = -prop.write_off_amount;
}
+
var result = {
name : prop.label,
debit : amount > 0 ? amount : 0,
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index 731d70c58e3..e86b59ec88f 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -353,7 +353,21 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
// reconciliation_proposition
var $props = this.$('.accounting_view tbody').empty();
- var props = _.filter(state.reconciliation_proposition, {'display': true});
+
+ // loop state propositions
+ var props = [];
+ var nb_debit_props = 0;
+ var nb_credit_props = 0;
+ _.each(state.reconciliation_proposition, function (prop) {
+ if (prop.display) {
+ props.push(prop);
+ if (prop.amount < 0)
+ nb_debit_props += 1;
+ else if (prop.amount > 0)
+ nb_credit_props += 1;
+ }
+ });
+
_.each(props, function (line) {
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state}));
if (!isNaN(line.id)) {
@@ -361,18 +375,16 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
.appendTo($line.find('.cell_info_popover'))
.attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line}));
}
-
- if ((state.balance.amount_currency !== 0 || line.partial_reconcile) && props.length === 1 &&
- line.already_paid === false &&
- (
- (state.st_line.amount > 0 && state.st_line.amount < props[0].amount) ||
- (state.st_line.amount < 0 && state.st_line.amount > props[0].amount))
- ) {
+ if (line.already_paid === false &&
+ ((state.balance.amount_currency < 0 || line.partial_reconcile) && nb_credit_props == 1
+ && line.amount > 0 && state.st_line.amount > 0 && state.st_line.amount < line.amount) ||
+ ((state.balance.amount_currency > 0 || line.partial_reconcile) && nb_debit_props == 1
+ && line.amount < 0 && state.st_line.amount < 0 && state.st_line.amount > line.amount)) {
var $cell = $line.find(line.amount > 0 ? '.cell_right' : '.cell_left');
var text;
if (line.partial_reconcile) {
text = _t("Undo the partial reconciliation.");
- $cell.text(state.st_line.amount_str);
+ $cell.text(line.write_off_amount_str);
} else {
text = _t("This move's amount is higher than the transaction's amount. Click to register a partial payment and keep the payment balance open.");
}
@@ -414,20 +426,24 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
this._renderCreate(state);
}
var data = this.model.get(this.handleCreateRecord).data;
-
- this.model.notifyChanges(this.handleCreateRecord, state.createForm);
- this.model.notifyChanges(this.handleCreateRecord, {analytic_tag_ids: {operation: 'REPLACE_WITH', ids: []}});
- _.each(state.createForm.analytic_tag_ids, function (tag) {
- self.model.notifyChanges(self.handleCreateRecord, {analytic_tag_ids: {operation: 'ADD_M2M', ids: tag}});
- });
-
- var record = this.model.get(this.handleCreateRecord);
- _.each(this.fields, function (field, fieldName) {
- if (self._avoidFieldUpdate[fieldName]) return;
- if (fieldName === "partner_id") return;
- if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) {
- field.reset(record);
- }
+ this.model.notifyChanges(this.handleCreateRecord, state.createForm).then(function () {
+ // FIXME can't it directly written REPLACE_WITH ids=state.createForm.analytic_tag_ids
+ this.model.notifyChanges(this.handleCreateRecord, {analytic_tag_ids: {operation: 'REPLACE_WITH', ids: []}}).then(function (){
+ var defs = [];
+ _.each(state.createForm.analytic_tag_ids, function (tag) {
+ defs.push(self.model.notifyChanges(self.handleCreateRecord, {analytic_tag_ids: {operation: 'ADD_M2M', ids: tag}}));
+ });
+ $.when.apply($, defs).then(function () {
+ var record = self.model.get(self.handleCreateRecord);
+ _.each(self.fields, function (field, fieldName) {
+ if (self._avoidFieldUpdate[fieldName]) return;
+ if (fieldName === "partner_id") return;
+ if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) {
+ field.reset(record);
+ }
+ });
+ });
+ });
});
}
this.$('.create .add_line').toggle(!!state.balance.amount_currency);
@@ -639,7 +655,8 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
return;
}
if(event.keyCode === 13) {
- if (_.findWhere(this.model.lines, {mode: 'create'}).balance.amount) {
+ var created_lines = _.findWhere(this.model.lines, {mode: 'create'});
+ if (created_lines && created_lines.balance.amount) {
this._onCreateProposition();
}
return;
diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js
index 5a3e6dba6c2..dc5ec8d95aa 100644
--- a/addons/account/static/tests/reconciliation_tests.js
+++ b/addons/account/static/tests/reconciliation_tests.js
@@ -34,17 +34,22 @@ var db = {
fields: {
id: {string: "ID", type: 'integer'},
code: {string: "code", type: 'integer'},
- display_name: {string: "Displayed name", type: 'char'},
+ name: {string: "Displayed name", type: 'char'},
},
records: [
- {id: 282, code: 100000, display_name: "100000 Fixed Asset Account"},
- {id: 283, code: 101000, display_name: "101000 Current Assets"},
- {id: 284, code: 101110, display_name: "101110 Stock Valuation Account"},
- {id: 285, code: 101120, display_name: "101120 Stock Interim Account (Received)"},
- {id: 286, code: 101130, display_name: "101130 Stock Interim Account (Delivered)"},
- {id: 287, code: 101200, display_name: "101200 Account Receivable"},
- {id: 288, code: 101300, display_name: "101300 Tax Paid"},
- {id: 308, code: 101401, display_name: "101401 Bank"},
+ {id: 282, code: 100000, name: "100000 Fixed Asset Account"},
+ {id: 283, code: 101000, name: "101000 Current Assets"},
+ {id: 284, code: 101110, name: "101110 Stock Valuation Account"},
+ {id: 285, code: 101120, name: "101120 Stock Interim Account (Received)"},
+ {id: 286, code: 101130, name: "101130 Stock Interim Account (Delivered)"},
+ {id: 287, code: 101200, name: "101200 Account Receivable"},
+ {id: 288, code: 101300, name: "101300 Tax Paid"},
+ {id: 308, code: 101401, name: "101401 Bank"},
+ {id: 500, code: 500, name: "500 Account"},
+ {id: 501, code: 501, name: "501 Account"},
+ {id: 502, code: 502, name: "502 Account"},
+ {id: 503, code: 503, name: "503 Account"},
+ {id: 504, code: 504, name: "504 Account"},
],
mark_as_reconciled: function () {
return $.when();
@@ -776,13 +781,7 @@ QUnit.module('account', {
partner_id: false,
counterpart_aml_dicts:[],
payment_aml_ids: [392],
- new_aml_dicts: [
- {
- "credit": 343.42,
- "debit": 0,
- "name": "Bank fees : Open balance"
- }
- ],
+ new_aml_dicts: [],
}]
], "should call process_reconciliations with partial reconcile values");
}
@@ -811,14 +810,14 @@ QUnit.module('account', {
assert.notOk( widget.$('.cell_left .line_info_button').length, "should not display the partial reconciliation alert");
widget.$('.accounting_view thead td:first').trigger('click');
widget.$('.match .cell_account_code:first').trigger('click');
- assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 0, "should not display the partial reconciliation alert");
+ assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 1, "should display the partial reconciliation alert");
assert.ok( widget.$('button.btn-primary:not(hidden)').length, "should not display the reconcile button");
assert.ok( widget.$('.text-danger:not(hidden)').length, "should display counterpart alert");
widget.$('.accounting_view .cell_left .line_info_button').trigger('click');
- assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 0, "should not display a partial reconciliation alert");
- assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_false'), "should not display the partial reconciliation information");
+ assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 1, "should display a partial reconciliation alert");
+ assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_true'), "should display the partial reconciliation information");
assert.ok( widget.$('button.btn-default:not(hidden)').length, "should display the validate button");
- assert.strictEqual( widget.$el.data('mode'), "match", "should be inactive mode");
+ assert.strictEqual( widget.$el.data('mode'), "inactive", "should be inactive mode");
widget.$('button.btn-default:not(hidden)').trigger('click');
clientAction.destroy();
@@ -1036,13 +1035,77 @@ QUnit.module('account', {
clientAction.destroy();
});
+ QUnit.test('Reconciliation create line (many2one test)', function (assert) {
+ assert.expect(5);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
+ var def = $.Deferred();
+
+ testUtils.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$"
+ }
+ }
+ },
+ archs: {
+ "account.account,false,list": '
Create a customer invoice
@@ -706,7 +696,7 @@
Create a credit note
@@ -744,7 +734,7 @@
Record a new vendor bill
@@ -780,7 +770,7 @@
Create a vendor credit note
diff --git a/addons/account/views/account_journal_dashboard_view.xml b/addons/account/views/account_journal_dashboard_view.xml
index dfa08fbadce..9c7f1aca551 100644
--- a/addons/account/views/account_journal_dashboard_view.xml
+++ b/addons/account/views/account_journal_dashboard_view.xml
@@ -117,16 +117,12 @@
View
- Don't forget to add it to your calendar:
-
diff --git a/addons/fleet/models/fleet_vehicle.py b/addons/fleet/models/fleet_vehicle.py
index da5d9fb69b1..213c5716059 100644
--- a/addons/fleet/models/fleet_vehicle.py
+++ b/addons/fleet/models/fleet_vehicle.py
@@ -247,9 +247,11 @@ class FleetVehicle(models.Model):
@return: the costs log view
"""
self.ensure_one()
+ copy_context = dict(self.env.context)
+ copy_context.pop('group_by', None)
res = self.env['ir.actions.act_window'].for_xml_id('fleet', 'fleet_vehicle_costs_action')
res.update(
- context=dict(self.env.context, default_vehicle_id=self.id, search_default_parent_false=True),
+ context=dict(copy_context, default_vehicle_id=self.id, search_default_parent_false=True),
domain=[('vehicle_id', '=', self.id)]
)
return res
diff --git a/addons/hr_payroll/models/hr_payslip.py b/addons/hr_payroll/models/hr_payslip.py
index 726e5efe375..c9d14c47495 100644
--- a/addons/hr_payroll/models/hr_payslip.py
+++ b/addons/hr_payroll/models/hr_payslip.py
@@ -190,7 +190,8 @@ class HrPayslip(models.Model):
leave_time = (interval[1] - interval[0]).seconds / 3600
current_leave_struct['number_of_hours'] += leave_time
work_hours = contract.employee_id.get_day_work_hours_count(interval[0].date(), calendar=contract.resource_calendar_id)
- current_leave_struct['number_of_days'] += leave_time / work_hours
+ if work_hours:
+ current_leave_struct['number_of_days'] += leave_time / work_hours
# compute worked days
work_data = contract.employee_id.get_work_days_data(day_from, day_to, calendar=contract.resource_calendar_id)
diff --git a/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py b/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py
index 0d182afcc72..46c0748f5fa 100644
--- a/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py
+++ b/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py
@@ -95,7 +95,7 @@ class HrContract(models.Model):
+ 12.0 * contract.internet \
+ 12.0 * (contract.mobile + contract.mobile_plus) \
+ 12.0 * contract.transport_employer_cost \
- + (1.326 / 1.05 * 12.0) * contract.commission_on_target \
+ + contract.warrants_cost \
+ 220.0 * contract.meal_voucher_paid_by_employer
contract.final_yearly_costs = yearly_cost / (1.0 - (contract.holidays - 20.0) / 231.0)
contract.wage = contract._get_gross_from_employer_costs(contract.final_yearly_costs)
@@ -116,8 +116,8 @@ class HrContract(models.Model):
@api.depends('commission_on_target')
def _compute_warrants_cost(self):
for contract in self:
- contract.warrants_cost = contract.commission_on_target * 1.05 * 12.0
- contract.warrant_value_employee = contract.commission_on_target * (1.00 - 0.535) * 12.0
+ contract.warrants_cost = contract.commission_on_target * 1.326 * 1.05 * 12.0
+ contract.warrant_value_employee = contract.commission_on_target * 1.326 * (1.00 - 0.535) * 12.0
@api.depends('wage', 'fuel_card', 'representation_fees', 'transport_employer_cost',
'internet', 'mobile', 'mobile_plus')
diff --git a/addons/l10n_fr/data/account_chart_template_data.xml b/addons/l10n_fr/data/account_chart_template_data.xml
index db836b0fd3b..aac0137d150 100644
--- a/addons/l10n_fr/data/account_chart_template_data.xml
+++ b/addons/l10n_fr/data/account_chart_template_data.xml
@@ -2836,98 +2836,98 @@
-
-
-
-
-
- Google
-
Outlook
-
Yahoo
-
% endif
+
@@ -165,6 +153,16 @@
+
+
+
+ Add this event to your calendar
+
+ Google
+
Outlook
+
Yahoo
+