Skip to content

Commit 1304d13

Browse files
committed
[MERGE] forward port branch 9.0 up to 25616b9
2 parents cb5fd87 + 25616b9 commit 1304d13

File tree

17 files changed

+135
-34
lines changed

17 files changed

+135
-34
lines changed

addons/account/models/account.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -707,17 +707,23 @@ def compute_all(self, price_unit, currency=None, quantity=1.0, product=None, par
707707

708708
if not round_tax:
709709
prec += 5
710-
total_excluded = total_included = base = round(price_unit * quantity, prec)
710+
711+
base_values = self.env.context.get('base_values')
712+
if not base_values:
713+
total_excluded = total_included = base = round(price_unit * quantity, prec)
714+
else:
715+
total_excluded, total_included, base = base_values
711716

712717
# Sorting key is mandatory in this case. When no key is provided, sorted() will perform a
713718
# search. However, the search method is overridden in account.tax in order to add a domain
714719
# depending on the context. This domain might filter out some taxes from self, e.g. in the
715720
# case of group taxes.
716721
for tax in self.sorted(key=lambda r: r.sequence):
717722
if tax.amount_type == 'group':
718-
ret = tax.children_tax_ids.compute_all(price_unit, currency, quantity, product, partner)
723+
children = tax.children_tax_ids.with_context(base_values=(total_excluded, total_included, base))
724+
ret = children.compute_all(price_unit, currency, quantity, product, partner)
719725
total_excluded = ret['total_excluded']
720-
base = ret['base']
726+
base = ret['base'] if tax.include_base_amount else base
721727
total_included = ret['total_included']
722728
tax_amount = total_included - total_excluded
723729
taxes += ret['taxes']

addons/account/models/account_bank_statement.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ def reconciliation_widget_auto_reconcile(self, num_already_reconciled_lines):
477477
'details': {
478478
'name': _("Automatically reconciled items"),
479479
'model': 'account.move',
480-
'ids': automatic_reconciliation_entries.ids
480+
'ids': automatic_reconciliation_entries.mapped('journal_entry_ids').ids
481481
}
482482
}]
483483
return {

addons/account/models/account_invoice.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,14 @@ def _prepare_tax_line_vals(self, line, tax):
563563
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
564564
'account_id': self.type in ('out_invoice', 'in_invoice') and (tax['account_id'] or line.account_id.id) or (tax['refund_account_id'] or line.account_id.id),
565565
}
566+
567+
# If the taxes generate moves on the same financial account as the invoice line,
568+
# propagate the analytic account from the invoice line to the tax line.
569+
# This is necessary in situations were (part of) the taxes cannot be reclaimed,
570+
# to ensure the tax move is allocated to the proper analytic account.
571+
if not vals.get('account_analytic_id') and line.account_analytic_id and vals['account_id'] == line.account_id.id:
572+
vals['account_analytic_id'] = line.account_analytic_id.id
573+
566574
return vals
567575

568576
@api.multi
@@ -573,14 +581,6 @@ def get_taxes_values(self):
573581
taxes = line.invoice_line_tax_ids.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id)['taxes']
574582
for tax in taxes:
575583
val = self._prepare_tax_line_vals(line, tax)
576-
577-
# If the taxes generate moves on the same financial account as the invoice line,
578-
# propagate the analytic account from the invoice line to the tax line.
579-
# This is necessary in situations were (part of) the taxes cannot be reclaimed,
580-
# to ensure the tax move is allocated to the proper analytic account.
581-
if not val.get('account_analytic_id') and line.account_analytic_id and val['account_id'] == line.account_id.id:
582-
val['account_analytic_id'] = line.account_analytic_id.id
583-
584584
key = self.env['account.tax'].browse(tax['id']).get_grouping_key(val)
585585

586586
if key not in tax_grouped:

addons/account/models/account_move.py

+3
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,9 @@ def remove_move_reconcile(self):
993993
return True
994994
rec_move_ids = self.env['account.partial.reconcile']
995995
for account_move_line in self:
996+
for invoice in account_move_line.payment_id.invoice_ids:
997+
if account_move_line in invoice.payment_move_line_ids:
998+
account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]})
996999
rec_move_ids += account_move_line.matched_debit_ids
9971000
rec_move_ids += account_move_line.matched_credit_ids
9981001
return rec_move_ids.unlink()

addons/account/tests/test_tax.py

+36
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,46 @@ def setUp(self):
4343
(4, self.percent_tax.id, 0)
4444
]
4545
})
46+
self.group_tax_bis = self.tax_model.create({
47+
'name': "Group tax bis",
48+
'amount_type': 'group',
49+
'amount': 0,
50+
'sequence': 6,
51+
'children_tax_ids': [
52+
(4, self.fixed_tax.id, 0),
53+
(4, self.percent_tax.id, 0)
54+
]
55+
})
56+
self.group_of_group_tax = self.tax_model.create({
57+
'name': "Group of group tax",
58+
'amount_type': 'group',
59+
'amount': 0,
60+
'sequence': 7,
61+
'children_tax_ids': [
62+
(4, self.group_tax.id, 0),
63+
(4, self.group_tax_bis.id, 0)
64+
]
65+
})
4666
self.bank_journal = self.env['account.journal'].search([('type', '=', 'bank'), ('company_id', '=', self.account_manager.company_id.id)])[0]
4767
self.bank_account = self.bank_journal.default_debit_account_id
4868
self.expense_account = self.env['account.account'].search([('user_type_id.type', '=', 'payable')], limit=1) #Should be done by onchange later
4969

70+
def test_tax_group_of_group_tax(self):
71+
self.fixed_tax.include_base_amount = True
72+
self.group_tax.include_base_amount = True
73+
self.group_of_group_tax.include_base_amount = True
74+
res = self.group_of_group_tax.compute_all(200.0)
75+
self.assertEquals(res['total_excluded'], 200.0)
76+
# After calculation of first group
77+
# base = 210
78+
# total_included = 231
79+
# Base of the first grouped is passed
80+
# Base after the second group (220) is dropped.
81+
# Base of the group of groups is passed out,
82+
# so we obtain base as after first group
83+
self.assertEquals(res['base'], 210.0)
84+
self.assertEquals(res['total_included'], 263.0)
85+
5086
def test_tax_group(self):
5187
res = self.group_tax.compute_all(200.0)
5288
self.assertEquals(res['total_excluded'], 200.0)

addons/board/static/src/js/dashboard.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ var DashBoard = form_common.FormWidget.extend({
139139
},
140140
on_close_action: function(e) {
141141
if (confirm(_t("Are you sure you want to remove this item ?"))) {
142-
$(e.currentTarget).parents('.oe_action:first').remove();
142+
var $container = $(e.currentTarget).parents('.oe_action:first');
143+
var am = _.findWhere(this.action_managers, { am_id: $container.data('am_id') });
144+
am.destroy();
145+
this.action_managers.splice(_.indexOf(this.action_managers, am), 1);
146+
$container.remove();
143147
this.do_save_dashboard();
144148
}
145149
},
@@ -223,7 +227,13 @@ var DashBoard = form_common.FormWidget.extend({
223227
var am = new ActionManager(this),
224228
// FIXME: ideally the dashboard view shall be refactored like kanban.
225229
$action = $('#' + this.view.element_id + '_action_' + index);
226-
$action.parent().data('action_attrs', action_attrs);
230+
var $action_container = $action.closest('.oe_action');
231+
var am_id = _.uniqueId('action_manager_');
232+
am.am_id = am_id;
233+
$action_container.data({
234+
action_attrs: action_attrs,
235+
am_id: am_id,
236+
});
227237
this.action_managers.push(am);
228238
am.appendTo($action).then(function () {
229239
am.do_action(action).then(function () {

addons/mail/static/src/js/chatter.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var utils = require('mail.utils');
99
var config = require('web.config');
1010
var core = require('web.core');
1111
var form_common = require('web.form_common');
12+
var framework = require('web.framework');
1213
var web_utils = require('web.utils');
1314

1415
var _t = core._t;
@@ -441,7 +442,14 @@ var Chatter = form_common.AbstractField.extend({
441442
},
442443

443444
load_more_messages: function () {
444-
this.fetch_and_render_thread(this.msg_ids, {force_fetch: true});
445+
var self = this;
446+
var top_msg_id = this.$('.o_thread_message').first().data('messageId');
447+
var top_msg_selector = '.o_thread_message[data-message-id="' + top_msg_id + '"]';
448+
var offset = -framework.getPosition(document.querySelector(top_msg_selector)).top;
449+
this.fetch_and_render_thread(this.msg_ids, {force_fetch: true}).then(function(){
450+
offset += framework.getPosition(document.querySelector(top_msg_selector)).top;
451+
self.thread.scroll_to({offset: offset});
452+
});
445453
},
446454

447455
/**

addons/point_of_sale/models/pos_order.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,10 @@ def _create_account_move_line(self, session=None, move=None):
213213
partner_id = ResPartner._find_accounting_partner(order.partner_id).id or False
214214
if move is None:
215215
# Create an entry for the sale
216+
journal_id = self.env['ir.config_parameter'].sudo().get_param(
217+
'pos.closing.journal_id', default=order.sale_journal.id)
216218
move = self._create_account_move(
217-
order.session_id.start_at, order.name, order.sale_journal.id, order.company_id.id)
219+
order.session_id.start_at, order.name, int(journal_id), order.company_id.id)
218220

219221
def insert_data(data_type, values):
220222
# if have_to_group_by:

addons/point_of_sale/models/pos_session.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ def _confirm_orders(self):
1818
for session in self:
1919
company_id = session.config_id.journal_id.company_id.id
2020
orders = session.order_ids.filtered(lambda order: order.state == 'paid')
21-
move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, session.config_id.journal_id.id, company_id)
21+
journal_id = self.env['ir.config_parameter'].sudo().get_param(
22+
'pos.closing.journal_id', default=session.config_id.journal_id.id)
23+
move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, int(journal_id), company_id)
2224
orders.with_context(force_company=company_id)._create_account_move_line(session, move)
2325
for order in session.order_ids.filtered(lambda o: o.state != 'done'):
2426
if order.state not in ('paid', 'invoiced'):

addons/web/static/src/js/framework/data.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -793,18 +793,19 @@ var BufferedDataSet = DataSetStatic.extend({
793793
var def = $.Deferred();
794794
this.mutex.exec(function () {
795795
var dirty = false;
796-
_.each(data, function (v, k) {
797-
if (!_.isEqual(v, cached.values[k])) {
796+
// _.each is broken if a field "length" is present
797+
for (var k in data) {
798+
if (!_.isEqual(data[k], cached.values[k])) {
798799
dirty = true;
799-
if (_.isEqual(v, cached.from_read[k])) { // clean changes
800+
if (_.isEqual(data[k], cached.from_read[k])) { // clean changes
800801
delete cached.changes[k];
801802
} else {
802-
cached.changes[k] = v;
803+
cached.changes[k] = data[k];
803804
}
804805
} else {
805806
delete data[k];
806807
}
807-
});
808+
}
808809
self._update_cache(id, options);
809810

810811
if (dirty) {

addons/web/static/src/js/views/list_view.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,12 @@ var ListView = View.extend({
470470
self.records.remove(record);
471471
return;
472472
}
473-
_.each(values, function (value, key) {
473+
// _.each is broken if a field "length" is present
474+
for (var key in values) {
474475
if (fields[key] && fields[key].type === 'many2many')
475476
record.set(key + '__display', false, {silent: true});
476-
record.set(key, value, {silent: true});
477-
});
477+
record.set(key, values[key], {silent: true});
478+
}
478479
record.trigger('change', record);
479480

480481
/* When a record is reloaded, there is a rendering lag because of the addition/suppression of

addons/web/tests/test_js.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import unittest
12
import openerp.tests
23

34
class WebSuite(openerp.tests.HttpCase):
5+
6+
@unittest.skip('Memory leak in this test lead to phantomjs crash, making it unreliable')
47
def test_01_js(self):
58
self.phantom_js('/web/tests?mod=web',"","", login='admin')

addons/website/static/src/js/website.ace.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ var ViewEditor = Widget.extend({
141141
var args = {
142142
key: $(document.documentElement).data('view-xmlid'),
143143
full: true,
144-
bundles: !!$('script[src*=".assets_common"]').length
144+
bundles: this.$('.js_include_bundles')[0].checked
145145
};
146146
return ajax
147147
.jsonRpc('/website/customize_template_get', 'call', args)

debian/control

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Depends:
1717
node-less,
1818
postgresql-client,
1919
python,
20+
python-babel,
2021
python-dateutil,
2122
python-decorator,
2223
python-docutils,
@@ -32,7 +33,6 @@ Depends:
3233
python-passlib,
3334
python-psutil,
3435
python-psycopg2,
35-
python-pybabel,
3636
python-pychart,
3737
python-pydot,
3838
python-pyparsing,

openerp/addons/base/ir/ir_model.py

+21
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ def unlink(self):
114114
if model.state != 'manual':
115115
raise UserError(_("Model '%s' contains module data and cannot be removed!") % model.name)
116116

117+
# prevent screwing up fields that depend on these models' fields
118+
for model in self:
119+
model.field_id._prepare_update()
120+
117121
self._drop_table()
118122
res = super(IrModel, self).unlink()
119123

@@ -395,13 +399,29 @@ def _drop_column(self):
395399

396400
return True
397401

402+
@api.multi
403+
def _prepare_update(self):
404+
""" Check whether the fields in ``self`` may be modified or removed.
405+
This method prevents the modification/deletion of many2one fields
406+
that have an inverse one2many, for instance.
407+
"""
408+
for record in self:
409+
model = self.env[record.model]
410+
field = model._fields[record.name]
411+
if field.type == 'many2one' and model._field_inverses.get(field):
412+
msg = _("The field '%s' cannot be removed because the field '%s' depends on it.")
413+
raise UserError(msg % (field, model._field_inverses[field][0]))
414+
398415
@api.multi
399416
def unlink(self):
400417
# Prevent manual deletion of module columns
401418
if not self._context.get(MODULE_UNINSTALL_FLAG) and \
402419
any(field.state != 'manual' for field in self):
403420
raise UserError(_("This column contains module data and cannot be removed!"))
404421

422+
# prevent screwing up fields that depend on these fields
423+
self._prepare_update()
424+
405425
model_names = self.mapped('model')
406426
self._drop_column()
407427
res = super(IrModelFields, self).unlink()
@@ -493,6 +513,7 @@ def write(self, vals):
493513

494514
if vals.get('name', item.name) != item.name:
495515
# We need to rename the column
516+
item._prepare_update()
496517
if column_rename:
497518
raise UserError(_('Can only rename one field at a time!'))
498519
if vals['name'] in obj._fields:

openerp/tools/config.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def __init__(self, fname=None):
8787
self.config_file = fname
8888

8989
self._LOGLEVELS = dict([
90-
(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
90+
(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
9191
for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')
9292
])
9393

@@ -111,7 +111,7 @@ def __init__(self, fname=None):
111111
group.add_option("--addons-path", dest="addons_path",
112112
help="specify additional addons paths (separated by commas).",
113113
action="callback", callback=self._check_addons_path, nargs=1, type="string")
114-
group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules default=web")
114+
group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules. Default is 'web,web_kanban'")
115115

116116
group.add_option("-D", "--data-dir", dest="data_dir", my_default=_get_default_datadir(),
117117
help="Directory where to store Odoo data")
@@ -395,6 +395,7 @@ def die(cond, msg):
395395
'test_file', 'test_enable', 'test_commit', 'test_report_directory',
396396
'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
397397
'data_dir',
398+
'server_wide_modules',
398399
]
399400

400401
posix_keys = [
@@ -448,14 +449,21 @@ def die(cond, msg):
448449
if len(self.options['language']) > 5:
449450
raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
450451

452+
# server_wide_modules defaults to web,web_kanban if empty or unset
453+
server_wide_modules = self.options['server_wide_modules'] = (
454+
self.options['server_wide_modules']
455+
if self.options['server_wide_modules']
456+
else 'web,web_kanban'
457+
)
458+
451459
if opt.save:
452460
self.save()
453461

454462
openerp.conf.addons_paths = self.options['addons_path'].split(',')
455-
if opt.server_wide_modules:
456-
openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))
457-
else:
458-
openerp.conf.server_wide_modules = ['web','web_kanban']
463+
464+
openerp.conf.server_wide_modules = [
465+
m.strip() for m in server_wide_modules.split(',')
466+
]
459467

460468
def _is_addons_path(self, path):
461469
for f in os.listdir(path):

setup/package.dfdebian

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ RUN apt-get update -qq && \
2121
postgresql \
2222
postgresql-client \
2323
python \
24+
python-babel \
2425
python-dateutil \
2526
python-decorator \
2627
python-docutils \
@@ -36,7 +37,6 @@ RUN apt-get update -qq && \
3637
python-passlib \
3738
python-psutil \
3839
python-psycopg2 \
39-
python-pybabel \
4040
python-pychart \
4141
python-pydot \
4242
python-pyparsing \

0 commit comments

Comments
 (0)