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 2f5c1421a60..b7d8d52f096 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -441,7 +441,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
@@ -523,6 +523,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.'))
@@ -536,6 +538,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_invoice.py b/addons/account/models/account_invoice.py
index e1e3dfa0499..47d7bcaf446 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -229,12 +229,6 @@ class AccountInvoice(models.Model):
payment_lines.update(line.mapped('matched_debit_ids.debit_move_id.id'))
self.payment_move_line_ids = self.env['account.move.line'].browse(list(payment_lines))
- @api.depends('date_invoice', 'is_recurrency_enabled', 'recurrency_interval', 'recurrency_type')
- def _compute_next_recurrency_date(self):
- #initially set the recurrency_date
- for invoice in self.filtered(lambda inv: inv.is_recurrency_enabled and inv.recurrency_interval and inv.recurrency_type and inv.date_invoice and not inv.next_recurrency_date):
- invoice.next_recurrency_date = invoice._get_next_recurrency_date(invoice.date_invoice)
-
name = fields.Char(string='Reference/Description', index=True,
readonly=True, states={'draft': [('readonly', False)]}, copy=False, help='The name that will be used on account move lines')
@@ -373,18 +367,6 @@ class AccountInvoice(models.Model):
#fields use to set the sequence, on the first invoice of the journal
sequence_number_next = fields.Char(string='Next Number', compute="_get_sequence_number_next", inverse="_set_sequence_next")
sequence_number_next_prefix = fields.Char(string='Next Number Prefix', compute="_get_sequence_prefix")
- #recurrent vendor bills fields
- is_recurrency_enabled = fields.Boolean(string="Generate Recurring Document(s)", copy=False, help="Tick this box to generate recurring bills. The recurring bills will only be generated if this bill is validated.")
- recurrency_interval = fields.Integer(string='Every', default=1, copy=False, help="Define the recurrency of the vendor bill.")
- recurrency_type = fields.Selection([
- ('days', 'Day(s)'),
- ('weeks', 'Week(s)'),
- ('months', 'Month(s)'),
- ('years', 'Year(s)'),
- ], string="Recurrency Type", default='months', copy=False)
- # Technical field depicting whether the document is generated by cron or by user
- is_recurring_document = fields.Boolean(string="Recurring Document", copy=False)
- next_recurrency_date = fields.Date(compute=_compute_next_recurrency_date, _string='Next Recurrency Date', copy=False, store=True)
_sql_constraints = [
('number_uniq', 'unique(number, company_id, journal_id, type)', 'Invoice Number must be unique per Company!'),
@@ -468,52 +450,6 @@ class AccountInvoice(models.Model):
self.type == 'in_refund' and self.state == 'draft' and _('Vendor Credit Note') or \
self.type == 'in_refund' and _('Vendor Credit Note - %s') % (self.number)
- def _get_next_recurrency_date(self, current_date):
- """
- This method is used to return next date based on recurrency configuration of the record.
-
- :param current_date: Origin date from which next date is to be generated.
- :returns: future date on which a duplicated document will be generated by crons.
- """
- assert self.recurrency_type, "Invalid `Recurrency` interval! Please set it to either of these: 'Day(s)', 'Week(s)', 'Month(s)' or 'Year(s)'."
- self.ensure_one()
- recurring_next_date = fields.Date.from_string(current_date) + relativedelta(**{self.recurrency_type: self.recurrency_interval})
- # In case of monthly recurrent invoice, future invoices should always be created on same day as of it's main invoice but
- # if the reference invoice was made on last day of the month, then the recurring invoice should be created on last day of next month.
- # example, main invoice is set to 31st Jan, the next invoices should be created on 28/29th Feb, 31st Mar, 30 Apr and so on
- if self.recurrency_type == 'months':
- reference_invoice_day = fields.Date.from_string(self.date_invoice).day
- recurring_next_date += relativedelta(day=reference_invoice_day)
- if reference_invoice_day == fields.Date.from_string(self.date_invoice) + relativedelta(day=31):
- recurring_next_date += relativedelta(day=31)
- return recurring_next_date
-
- @api.model
- def _cron_generate_recurring_vendor_bill(self):
- today = fields.Date.today()
- domain = [
- ('is_recurrency_enabled', '=', True),
- ('state', 'not in', ['draft', 'cancel']),
- ('next_recurrency_date', '<=', today)
- ]
- for invoice in self.search(domain):
- while invoice.next_recurrency_date <= today:
- invoice._create_recurrent_invoice()
- invoice.next_recurrency_date = invoice._get_next_recurrency_date(invoice.next_recurrency_date)
-
- def _create_recurrent_invoice(self):
- self.ensure_one()
- invoice = self.copy({
- 'origin': '',
- 'date_invoice': self.next_recurrency_date,
- 'is_recurring_document': True,
- })
- # set the due date
- #invoice._onchange_payment_term_date_invoice()
- link = '%s' % (self.id, self.number)
- invoice.message_post(body=_('This bill has been generated by %s') % link)
- return invoice
-
@api.model
def create(self, vals):
onchanges = {
@@ -1862,6 +1798,7 @@ class AccountPaymentTermLine(models.Model):
if self.value == 'percent' and (self.value_amount < 0.0 or self.value_amount > 100.0):
raise ValidationError(_('Percentages for Payment Terms Line must be between 0 and 100.'))
+ @api.one
@api.constrains('days')
def _check_days(self):
if self.option in ('day_following_month', 'day_current_month') and self.days <= 0:
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 78e9cc0e3e9..f5196eb02d6 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',
@@ -318,7 +318,7 @@ class AccountMoveLine(models.Model):
if not cr.fetchone():
cr.execute('CREATE INDEX account_move_line_partner_id_ref_idx ON account_move_line (partner_id, ref)')
- @api.depends('debit', 'credit', 'amount_currency', 'currency_id', 'matched_debit_ids', 'matched_credit_ids', 'matched_debit_ids.amount', 'matched_credit_ids.amount', 'account_id.currency_id', 'move_id.state')
+ @api.depends('debit', 'credit', 'amount_currency', 'currency_id', 'matched_debit_ids', 'matched_credit_ids', 'matched_debit_ids.amount', 'matched_credit_ids.amount', 'move_id.state')
def _amount_residual(self):
""" Computes the residual amount of a move line from a reconcilable account in the company currency and the line's currency.
This amount will be 0 for fully reconciled lines or lines from a non-reconcilable account, the original line amount
@@ -983,7 +983,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 2090d03bc30..ca1e13511d4 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -588,9 +588,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/models/company.py b/addons/account/models/company.py
index d73dbf82c64..8ae4230a7b2 100644
--- a/addons/account/models/company.py
+++ b/addons/account/models/company.py
@@ -4,7 +4,7 @@ from datetime import timedelta, datetime
import calendar
from odoo import fields, models, api, _
-from odoo.exceptions import ValidationError
+from odoo.exceptions import ValidationError, UserError
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_round, float_is_zero
@@ -130,6 +130,12 @@ Best Regards,'''))
if values.get('cash_account_code_prefix'):
new_cash_code = values.get('cash_account_code_prefix') or company.cash_account_code_prefix
company.reflect_code_prefix_change(company.cash_account_code_prefix, new_cash_code)
+
+ #forbid the change of currency_id if there are already some accounting entries existing
+ if 'currency_id' in values and values['currency_id'] != company.currency_id.id:
+ if self.env['account.move.line'].search([('company_id', '=', company.id)]):
+ raise UserError(_('You cannot change the currency of the company since some journal items already exist'))
+
return super(ResCompany, self).write(values)
@api.model
diff --git a/addons/account/models/res_config_settings.py b/addons/account/models/res_config_settings.py
index 78a743ce9d7..68d8ef3e83b 100644
--- a/addons/account/models/res_config_settings.py
+++ b/addons/account/models/res_config_settings.py
@@ -57,12 +57,10 @@ class ResConfigSettings(models.TransientModel):
tax_exigibility = fields.Boolean(string='Cash Basis', related='company_id.tax_exigibility')
tax_cash_basis_journal_id = fields.Many2one('account.journal', related='company_id.tax_cash_basis_journal_id', string="Tax Cash Basis Journal")
account_hide_setup_bar = fields.Boolean(string='Hide Setup Bar', related='company_id.account_setup_bar_closed',help="Tick if you wish to hide the setup bar on the dashboard")
- group_allow_recurring = fields.Boolean(string="Recurring Vendor Bills", implied_group="account.group_allow_recurring")
@api.multi
def set_values(self):
super(ResConfigSettings, self).set_values()
- self.env.ref('account.recurrent_vendor_bills_cron').write({'active': self.group_allow_recurring})
if self.group_multi_currency:
self.env.ref('base.group_user').write({'implied_ids': [(4, self.env.ref('product.group_sale_pricelist').id)]})
""" install a chart of accounts for the given company (if required) """
diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml
index 1bcf45e5121..c527729765c 100644
--- a/addons/account/security/account_security.xml
+++ b/addons/account/security/account_security.xml
@@ -32,11 +32,6 @@
-
- Allow to create recurring vendor bills
-
-
-
Allow the cash rounding management
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index 8108b0efa09..9003e9e7f4d 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -426,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
+ self.model.notifyChanges(self.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);
@@ -655,7 +659,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 6a26ad45f2a..13427bb6870 100644
--- a/addons/account/static/tests/reconciliation_tests.js
+++ b/addons/account/static/tests/reconciliation_tests.js
@@ -43,18 +43,23 @@ 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'},
company_id: {string: "Company", type: 'many2one', relation: 'res.company'},
},
records: [
- {id: 282, code: 100000, display_name: "100000 Fixed Asset Account", company_id: 1},
- {id: 283, code: 101000, display_name: "101000 Current Assets", company_id: 1},
- {id: 284, code: 101110, display_name: "101110 Stock Valuation Account", company_id: 1},
- {id: 285, code: 101120, display_name: "101120 Stock Interim Account (Received)", company_id: 1},
- {id: 286, code: 101130, display_name: "101130 Stock Interim Account (Delivered)", company_id: 1},
- {id: 287, code: 101200, display_name: "101200 Account Receivable", company_id: 1},
- {id: 288, code: 101300, display_name: "101300 Tax Paid", company_id: 1},
- {id: 308, code: 101401, display_name: "101401 Bank", company_id: 1},
+ {id: 282, code: 100000, name: "100000 Fixed Asset Account", company_id: 1},
+ {id: 283, code: 101000, name: "101000 Current Assets", company_id: 1},
+ {id: 284, code: 101110, name: "101110 Stock Valuation Account", company_id: 1},
+ {id: 285, code: 101120, name: "101120 Stock Interim Account (Received)", company_id: 1},
+ {id: 286, code: 101130, name: "101130 Stock Interim Account (Delivered)", company_id: 1},
+ {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: 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},
+ {id: 503, code: 503, name: "503 Account", company_id: 1},
+ {id: 504, code: 504, name: "504 Account", company_id: 1},
],
mark_as_reconciled: function () {
return $.when();
@@ -1050,13 +1055,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": '',
+ "account.account,false,search": '',
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'name_get') {
+ return def.then(this._super.bind(this, route, args));
+ }
+ return this._super(route, args);
+ },
+ });
+
+ clientAction.prependTo($('#qunit-fixture'));
+
+ var widget = clientAction.widgets[0];
+
+ // open the first line in write-off mode
+ widget.$('.accounting_view tfoot td:first').trigger('click');
+
+ // select an account with the many2one (drop down)
+ widget.$('.create .create_account_id input').trigger('click');
+ $('.ui-autocomplete .ui-menu-item a:contains(101200)').trigger('mouseenter').trigger('click');
+ assert.strictEqual(widget.$('.create .create_account_id input').val(), "101200 Account Receivable", "Display the selected account");
+ assert.strictEqual(widget.$('tbody:first .cell_account_code').text(), "101200", "Display the code of the selected account");
+
+ // use the many2one select dialog to change the account
+ widget.$('.create .create_account_id input').trigger('click');
+ $('.ui-autocomplete .ui-menu-item a:contains(Search)').trigger('mouseenter').trigger('click');
+ // select the account who does not appear in the drop drown
+ $('.modal tr.o_data_row:contains(502)').click();
+ assert.strictEqual(widget.$('.create .create_account_id input').val(), "101200 Account Receivable", "Selected account does not change");
+ // wait the name_get to render the changes
+ def.resolve();
+ assert.strictEqual(widget.$('.create .create_account_id input').val(), "502 Account", "Display the selected account");
+ assert.strictEqual(widget.$('tbody:first .cell_account_code').text(), "502", "Display the code of the selected account");
+ clientAction.destroy();
+ });
+
QUnit.test('Reconciliation create line with taxes', function (assert) {
assert.expect(13);
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
testUtils.addMockEnvironment(clientAction, {
- 'data': this.params.data,
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$"
+ }
+ }
+ },
});
clientAction.appendTo($('#qunit-fixture'));
@@ -1068,26 +1137,26 @@ QUnit.module('account', {
widget.$('.create .create_label input').val('test1').trigger('input');
widget.$('.create .create_amount input').val('1100').trigger('input');
- assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "1100.00", "should display the value 1100.00 in left column");
+ assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "$\u00a01100.00", "should display the value 1100.00 in left column");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
- assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "75.00", "should display 'Open Balance' with 75.00 in right column");
+ assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column");
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 1, "should have 1 created reconcile lines");
widget.$('.create .create_tax_id input').trigger('click');
$('.ui-autocomplete .ui-menu-item a:contains(10.00%)').trigger('mouseenter').trigger('click');
- assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "1000.00100.00", "should have 2 created reconcile lines with right column values");
+ assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "$\u00a01000.00$\u00a0100.00", "should have 2 created reconcile lines with right column values");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
- assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "75.00", "should display 'Open Balance' with 75.00 in right column");
+ assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "", "should display 'Open Balance' without any value in left column");
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines");
widget.$('.create .create_tax_id input').trigger('click');
$('.ui-autocomplete .ui-menu-item a:contains(20.00%)').trigger('mouseenter').trigger('click');
- assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "1100.00220.00", "should have 2 created reconcile lines with right column values");
+ assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "$\u00a01100.00$\u00a0220.00", "should have 2 created reconcile lines with right column values");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Create Write-off", "should display 'Create Write-off'");
- assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "145.00", "should display 'Create Write-off' with 145.00 in right column");
+ assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "$\u00a0145.00", "should display 'Create Write-off' with 145.00 in right column");
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines");
clientAction.destroy();
@@ -1099,7 +1168,7 @@ QUnit.module('account', {
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
testUtils.addMockEnvironment(clientAction, {
- 'data': this.params.data,
+ data: this.params.data,
});
clientAction.appendTo($('#qunit-fixture'));
diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py
index 813896bdec0..d50e277028c 100644
--- a/addons/account/tests/__init__.py
+++ b/addons/account/tests/__init__.py
@@ -6,7 +6,6 @@ from . import test_account_move_closed_period
from . import test_account_supplier_invoice
from . import test_account_validate_account_move
from . import test_account_invoice_rounding
-from . import test_account_supplier_invoice_recurrent
from . import test_bank_statement_reconciliation
from . import test_fiscal_position
from . import test_reconciliation_widget
diff --git a/addons/account/tests/test_account_supplier_invoice_recurrent.py b/addons/account/tests/test_account_supplier_invoice_recurrent.py
deleted file mode 100644
index 47f681dd3cb..00000000000
--- a/addons/account/tests/test_account_supplier_invoice_recurrent.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-from datetime import datetime
-from dateutil.relativedelta import relativedelta
-
-from odoo.addons.account.tests.account_test_classes import AccountingTestCase
-from odoo.tests import tagged
-
-
-@tagged('post_install', '-at_install')
-class TestAccountRecurrent(AccountingTestCase):
-
- def apply_vendor_cron(self):
- self.env.ref('account.recurrent_vendor_bills_cron').method_direct_trigger()
-
- def test_create_recurring_vendor_bills(self):
- AccountInvoice = self.env['account.invoice']
- invoice_account_id = self.env['account.account'].search([('user_type_id', '=', self.ref('account.data_account_type_receivable'))], limit=1).id
- invoice_line_account_id = self.env['account.account'].search([('user_type_id', '=', self.ref('account.data_account_type_expenses'))], limit=1).id
- purchase_journal = self.env['account.journal'].search([('type', '=', 'purchase')], limit=1)
-
- invoice_line_data = [
- (0, 0,
- {
- 'product_id': self.ref('product.product_product_4'),
- 'name': 'product 4 that cost 100',
- 'quantity': 1.0,
- 'price_unit': 100.0,
- 'account_id': invoice_line_account_id,
- }
- ),
- (0, 0,
- {
- 'product_id': self.ref('product.product_product_5'),
- 'name': 'product 5 that cost 200',
- 'quantity': 2.0,
- 'price_unit': 200.0,
- 'account_id': invoice_line_account_id,
- }
- )
- ]
-
- # Create an invoice dated 2 months before today
- invoice = AccountInvoice.create({
- 'partner_id': self.ref('base.res_partner_2'),
- 'account_id': invoice_account_id,
- 'name': 'invoice test recurrent',
- 'type': 'in_invoice',
- 'reference_type': 'none',
- 'date_invoice': datetime.today() + relativedelta(months=-2),
- 'journal_id': purchase_journal.id,
- 'is_recurrency_enabled': True,
- 'recurrency_interval': 1,
- 'recurrency_type': 'months',
- 'invoice_line_ids': invoice_line_data
- })
- recurring_domain = [('type', '=', 'in_invoice'), ('state', '=', 'draft'), ('is_recurring_document', '=', True)]
-
- # After executing cron, verify that no bill is auto generated if the recurrent bill is in `draft` state
- self.apply_vendor_cron()
- recurring_invoice_count = AccountInvoice.search_count(recurring_domain)
- self.assertEquals(recurring_invoice_count, 0, 'Recurring bills should not be generated when reference bill is still `draft`.')
-
- # After validating bill, run cron and check that 2 recurring bill have been generated
- invoice.action_invoice_open() # Validate invoice
- self.apply_vendor_cron()
- recurring_invoice_count = previous_recurring_invoice_count = AccountInvoice.search_count(recurring_domain)
- self.assertEquals(recurring_invoice_count, 2, '2 recurring bills should be generated.')
-
- # verify that bills aren't generated if reference bill is in `cancel` state
- purchase_journal.write({'update_posted': True}) # Allow to cancel the invoice
- inv2_data = {
- 'name': 'invoice test recurrent 2',
- 'date_invoice': datetime.today() + relativedelta(months=-2),
- 'is_recurrency_enabled': True,
- 'recurrency_interval': 1,
- 'recurrency_type': 'months',
- }
- invoice2 = invoice.copy(default=inv2_data)
- invoice2.action_invoice_cancel() # Cancel the invoice
- self.apply_vendor_cron()
- recurring_invoice_count = AccountInvoice.search_count(recurring_domain)
- self.assertEquals(previous_recurring_invoice_count, recurring_invoice_count, 'Recurring bills should not be generated when reference bill is in `cancel` state.')
-
- # verify that vendor bills are generated even when invoice is in `paid` state
- inv3_data = {
- 'name': 'invoice test recurrent 3',
- 'date_invoice': datetime.today() + relativedelta(months=-2),
- 'is_recurrency_enabled': True,
- 'recurrency_interval': 1,
- 'recurrency_type': 'months',
- }
-
- invoice3 = invoice.copy(default=inv3_data)
- invoice3.action_invoice_open() # Validate invoice
- Payment = self.env['account.payment']
- payment_vals = Payment.with_context(default_invoice_ids=[(4, invoice3.id, None)]).default_get(Payment._fields.keys())
- payment_vals.update({
- 'payment_method_id': self.ref("account.account_payment_method_manual_out"),
- 'journal_id': self.env['account.journal'].search([('type', '=', 'bank')], limit=1).id
- })
- Payment.create(payment_vals).action_validate_invoice_payment() # Register the payment
- self.apply_vendor_cron()
- recurring_invoice_count = AccountInvoice.search_count(recurring_domain)
- self.assertEquals(recurring_invoice_count, 4, '4 recurring bills should be generated.')
diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py
index 3c22a127180..3d490eedc49 100644
--- a/addons/account/tests/test_payment.py
+++ b/addons/account/tests/test_payment.py
@@ -20,7 +20,9 @@ class TestPayment(AccountingTestCase):
self.currency_chf_id = self.env.ref("base.CHF").id
self.currency_usd_id = self.env.ref("base.USD").id
self.currency_eur_id = self.env.ref("base.EUR").id
- self.env.ref('base.main_company').write({'currency_id': self.currency_eur_id})
+
+ company = self.env.ref('base.main_company')
+ self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_eur_id, company.id])
self.product = self.env.ref("product.product_product_4")
self.payment_method_manual_in = self.env.ref("account.account_payment_method_manual_in")
self.payment_method_manual_out = self.env.ref("account.account_payment_method_manual_out")
@@ -361,3 +363,53 @@ class TestPayment(AccountingTestCase):
{'account_id': self.account_eur.id, 'debit': 300.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
{'account_id': inv_3.account_id.id, 'debit': 0.0, 'credit': 300.0, 'amount_currency': 0.0, 'currency_id': False},
])
+
+ def test_payment_and_writeoff_in_other_currency(self):
+ # Use case:
+ # Company is in EUR, create a customer invoice for 25 EUR and register payment of 25 USD.
+ # Mark invoice as fully paid with a write_off
+ # Check that all the aml are correctly created.
+ invoice = self.create_invoice(amount=25, type='out_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
+ # register payment on invoice
+ payment = self.payment_model.create({'payment_type': 'inbound',
+ 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
+ 'partner_type': 'customer',
+ 'partner_id': self.partner_agrolait.id,
+ 'amount': 25,
+ 'currency_id': self.currency_usd_id,
+ 'payment_date': time.strftime('%Y') + '-07-15',
+ 'payment_difference_handling': 'reconcile',
+ 'writeoff_account_id': self.account_payable.id,
+ 'journal_id': self.bank_journal_euro.id,
+ 'invoice_ids': [(4, invoice.id, None)]
+ })
+ payment.post()
+ self.check_journal_items(payment.move_line_ids, [
+ {'account_id': self.account_eur.id, 'debit': 16.35, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': self.currency_usd_id},
+ {'account_id': self.account_payable.id, 'debit': 8.65, 'credit': 0.0, 'amount_currency': 13.22, 'currency_id': self.currency_usd_id},
+ {'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 25.0, 'amount_currency': -38.22, 'currency_id': self.currency_usd_id},
+ ])
+ # Use case:
+ # Company is in EUR, create a vendor bill for 25 EUR and register payment of 25 USD.
+ # Mark invoice as fully paid with a write_off
+ # Check that all the aml are correctly created.
+ invoice = self.create_invoice(amount=25, type='in_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
+ # register payment on invoice
+ payment = self.payment_model.create({'payment_type': 'inbound',
+ 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
+ 'partner_type': 'supplier',
+ 'partner_id': self.partner_agrolait.id,
+ 'amount': 25,
+ 'currency_id': self.currency_usd_id,
+ 'payment_date': time.strftime('%Y') + '-07-15',
+ 'payment_difference_handling': 'reconcile',
+ 'writeoff_account_id': self.account_payable.id,
+ 'journal_id': self.bank_journal_euro.id,
+ 'invoice_ids': [(4, invoice.id, None)]
+ })
+ payment.post()
+ self.check_journal_items(payment.move_line_ids, [
+ {'account_id': self.account_eur.id, 'debit': 16.35, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': self.currency_usd_id},
+ {'account_id': self.account_payable.id, 'debit': 0.0, 'credit': 8.65, 'amount_currency': -13.22, 'currency_id': self.currency_usd_id},
+ {'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 7.7, 'amount_currency': -11.78, 'currency_id': self.currency_usd_id},
+ ])
diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index aa4fc950279..1180df172c9 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -27,7 +27,8 @@ class TestReconciliation(AccountingTestCase):
self.currency_swiss_id = self.env.ref("base.CHF").id
self.currency_usd_id = self.env.ref("base.USD").id
self.currency_euro_id = self.env.ref("base.EUR").id
- self.env.ref('base.main_company').write({'currency_id': self.currency_euro_id})
+ 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.account_rcv = partner_agrolait.property_account_receivable_id or self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1)
self.account_rsa = partner_agrolait.property_account_payable_id or self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_payable').id)], limit=1)
self.product = self.env.ref("product.product_product_4")
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index b75bd604ee2..30b9382e607 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -339,12 +339,6 @@
-
-
-
Record a new vendor bill
@@ -784,7 +744,7 @@
[('type','=','in_refund')]{'default_type': 'in_refund', 'type': 'in_refund', 'journal_type': 'purchase'}
-
+
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/google_calendar/README.md b/addons/google_calendar/README.md
index 47b00887fc2..2473da7b922 100644
--- a/addons/google_calendar/README.md
+++ b/addons/google_calendar/README.md
@@ -48,5 +48,5 @@ a notification in you Odoo which ever the page you are.
Google Calendar
---------------
-With the plugin Google\_calendar, you can synchronize your Odoo calendar with Google Calendar.
+With this plugin, you can synchronize your Odoo calendar with Google Calendar.
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 60b308dc36e..a97052e0445 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -416,7 +416,8 @@ class HrExpense(models.Model):
product = default_product
else:
expense_description = expense_description.replace(product_code.group(), '')
- product = self.env['product.product'].search([('default_code', 'ilike', product_code.group(1))]) or default_product
+ products = self.env['product.product'].search([('default_code', 'ilike', product_code.group(1))]) or default_product
+ product = products.filtered(lambda p: p.default_code == product_code.group(1)) or products[0]
pattern = '[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?'
# Match the last occurence of a float in the string
@@ -591,10 +592,13 @@ class HrExpenseSheet(models.Model):
MailFollowers = self.env['mail.followers']
for partner in users.mapped('partner_id'):
values['message_follower_ids'] += MailFollowers._add_follower_command(self._name, [], {partner.id: None}, {})[0]
-
- if values.get('user_id') and values.get('user_id') != employee.user_id.id:
+
+ if values.get('user_id') and values.get('user_id') != employee.user_id.id and values.get('user_id') != self.env.uid:
resp_partner = self.env['res.users'].browse(values['user_id'])
- values['message_follower_ids'] += MailFollowers._add_follower_command(self._name, [], {resp_partner.partner_id.id: None}, {})[0]
+ # Parsing ORM command to avoid adding several times the same partner
+ # TDE note: a better implementation should come in saas 11.3 or 11.4, allowing to normally remove that code
+ if resp_partner.partner_id.id not in (x[2].get('partner_id') for x in values['message_follower_ids'] if len(x) == 3):
+ values['message_follower_ids'] += MailFollowers._add_follower_command(self._name, [], {resp_partner.partner_id.id: None}, {})[0]
# --------------------------------------------
# Actions
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index 6d27b2b9cdb..3d5343ca5ec 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -8,7 +8,7 @@
-
+
@@ -35,14 +35,29 @@
-
+ hr.expense.view.formhr.expense
-