summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2016-08-19 16:59:29 +0200
committerChristophe Simonis <chs@odoo.com>2016-08-19 16:59:29 +0200
commitbc1a0a32ca80aaadd52e66115fe9c9ad4b26c2e0 (patch)
treee2ca10dde39d78301e100c76a76f3ab86fd509f3 /addons
parent2331bbf4b05d9e9f2ad5e56814ad440683882e3d (diff)
parent58cbcbae318f613c0e8239fbcfea59d0c3c33612 (diff)
[MERGE] forward port branch 9.0 up to 58cbcba
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account.py13
-rw-r--r--addons/account/models/account_bank_statement.py280
-rw-r--r--addons/account/models/account_journal_dashboard.py5
-rw-r--r--addons/account/models/account_move.py24
-rw-r--r--addons/account/models/partner.py2
-rw-r--r--addons/account/static/src/js/account_reconciliation_widgets.js86
-rw-r--r--addons/account/static/src/xml/account_reconciliation.xml1
-rw-r--r--addons/account_asset/account_asset.py10
-rw-r--r--addons/account_asset/wizard/account_asset_change_duration.py3
-rw-r--r--addons/account_voucher/account_voucher.py4
-rw-r--r--addons/base_import/static/src/js/import.js2
-rw-r--r--addons/event/models/event.py2
-rw-r--r--addons/event_sale/wizard/event_edit_registration.py2
-rw-r--r--addons/hr/hr.py4
-rw-r--r--addons/hr_equipment/models/hr_equipment.py2
-rw-r--r--addons/hr_payroll_account/hr_payroll_account.py6
-rw-r--r--addons/hr_recruitment/models/hr_recruitment.py10
-rw-r--r--addons/lunch/models/lunch.py4
-rw-r--r--addons/mail/controllers/bus.py1
-rw-r--r--addons/mail/models/mail_channel.py2
-rw-r--r--addons/mail/models/mail_followers.py8
-rw-r--r--addons/mail/models/mail_message.py12
-rw-r--r--addons/mail/models/mail_template.py2
-rw-r--r--addons/mail/models/mail_tracking_value.py2
-rw-r--r--addons/mail/wizard/invite.py4
-rw-r--r--addons/mail/wizard/mail_compose_message.py2
-rw-r--r--addons/mrp/views/report_mrpbomstructure.xml3
-rw-r--r--addons/procurement_jit/sale.py4
-rw-r--r--addons/purchase/invoice.py18
-rw-r--r--addons/purchase/purchase.py20
-rw-r--r--addons/purchase/purchase_view.xml2
-rw-r--r--addons/purchase_requisition/purchase_requisition.py5
-rw-r--r--addons/stock/stock.py25
-rw-r--r--addons/warning/warning.py10
-rw-r--r--addons/web/controllers/main.py7
-rw-r--r--addons/web/controllers/pivot.py7
-rw-r--r--addons/web/static/src/js/framework/data.js2
-rw-r--r--addons/web/static/src/js/views/form_widgets.js14
-rw-r--r--addons/web/views/database_manager.html4
-rw-r--r--addons/web_editor/tests/test_ui.py3
-rw-r--r--addons/website/views/website_templates.xml5
-rw-r--r--addons/website_blog/controllers/main.py4
-rw-r--r--addons/website_form/controllers/main.py2
-rw-r--r--addons/website_forum/models/forum.py12
-rw-r--r--addons/website_forum/models/res_users.py2
-rw-r--r--addons/website_quote/static/src/css/website_quotation.css18
-rw-r--r--addons/website_quote/static/src/js/website_quotation.js17
-rw-r--r--addons/website_slides/controllers/main.py4
-rw-r--r--addons/website_slides/models/slides.py2
-rw-r--r--addons/website_slides/static/src/js/slides_upload.js4
50 files changed, 397 insertions, 290 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index a582d77d399..87d5d7e7f1f 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -231,7 +231,7 @@ class AccountJournal(models.Model):
#groups_id = fields.Many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', string='Groups')
currency_id = fields.Many2one('res.currency', help='The currency used to enter statement', string="Currency", oldname='currency')
- company_id = fields.Many2one('res.company', string='Company', required=True, index=1, default=lambda self: self.env.user.company_id,
+ company_id = fields.Many2one('res.company', string='Company', required=True, index=True, default=lambda self: self.env.user.company_id,
help="Company related to this journal")
refund_sequence = fields.Boolean(string='Dedicated Refund Sequence', help="Check this box if you don't want to share the same sequence for invoices and refunds made from this journal", default=False)
@@ -632,7 +632,16 @@ class AccountTax(models.Model):
if self.amount_type == 'fixed':
# Use copysign to take into account the sign of the base amount which includes the sign
# of the quantity and the sign of the price_unit
- return math.copysign(quantity, base_amount) * self.amount
+ # Amount is the fixed price for the tax, it can be negative
+ # Base amount included the sign of the quantity and the sign of the unit price and when
+ # a product is returned, it can be done either by changing the sign of quantity or by changing the
+ # sign of the price unit.
+ # When the price unit is equal to 0, the sign of the quantity is absorbed in base_amount then
+ # a "else" case is needed.
+ if base_amount:
+ return math.copysign(quantity, base_amount) * self.amount
+ else:
+ return quantity * self.amount
if (self.amount_type == 'percent' and not self.price_include) or (self.amount_type == 'division' and self.price_include):
return base_amount * self.amount / 100
if self.amount_type == 'percent' and self.price_include:
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index b33a52a7671..dcc272ad1f2 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -129,7 +129,7 @@ class AccountBankStatement(models.Model):
name = fields.Char(string='Reference', states={'open': [('readonly', False)]}, copy=False, readonly=True)
reference = fields.Char(string='External Reference', states={'open': [('readonly', False)]}, copy=False, readonly=True, help="Used to hold the reference of the external mean that created this statement (name of imported file, reference of online synchronization...)")
- date = fields.Date(required=True, states={'confirm': [('readonly', True)]}, select=True, copy=False, default=fields.Date.context_today)
+ date = fields.Date(required=True, states={'confirm': [('readonly', True)]}, index=True, copy=False, default=fields.Date.context_today)
date_done = fields.Datetime(string="Closed On")
balance_start = fields.Monetary(string='Starting Balance', states={'confirm': [('readonly', True)]}, default=_default_opening_balance)
balance_end_real = fields.Monetary('Ending Balance', states={'confirm': [('readonly', True)]})
@@ -289,58 +289,54 @@ class AccountBankStatement(models.Model):
"""
statements = self
bsl_obj = self.env['account.bank.statement.line']
-
# NB : The field account_id can be used at the statement line creation/import to avoid the reconciliation process on it later on,
# this is why we filter out statements lines where account_id is set
- st_lines_filter = [('journal_entry_ids', '=', False), ('account_id', '=', False)]
- if statements:
- st_lines_filter += [('statement_id', 'in', statements.ids)]
-
- # Try to automatically reconcile statement lines
- automatic_reconciliation_entries = []
- st_lines_left = self.env['account.bank.statement.line']
- for st_line in bsl_obj.search(st_lines_filter):
- res = st_line.auto_reconcile()
- if not res:
- st_lines_left = (st_lines_left | st_line)
- else:
- automatic_reconciliation_entries.append(res.ids)
- # Try to set statement line's partner
- for st_line in st_lines_left:
- if st_line.name and not st_line.partner_id:
- additional_domain = [('ref', '=', st_line.name)]
- match_recs = st_line.get_move_lines_for_reconciliation(limit=1, additional_domain=additional_domain, overlook_partner=True)
- if match_recs and match_recs[0].partner_id:
- st_line.write({'partner_id': match_recs[0].partner_id.id})
-
- # Collect various informations for the reconciliation widget
- notifications = []
- num_auto_reconciled = len(automatic_reconciliation_entries)
- if num_auto_reconciled > 0:
- auto_reconciled_message = num_auto_reconciled > 1 \
- and _("%d transactions were automatically reconciled.") % num_auto_reconciled \
- or _("1 transaction was automatically reconciled.")
- notifications += [{
- 'type': 'info',
- 'message': auto_reconciled_message,
- 'details': {
- 'name': _("Automatically reconciled items"),
- 'model': 'account.move',
- 'ids': automatic_reconciliation_entries
- }
- }]
-
- lines = []
- for el in statements:
- lines.extend(el.line_ids.ids)
- lines = list(set(lines))
+ sql_query = """SELECT stl.id
+ FROM account_bank_statement_line stl
+ WHERE account_id IS NULL AND not exists (select 1 from account_move m where m.statement_line_id = stl.id)
+ AND company_id = %s
+ """
+ params = (self.env.user.company_id.id,)
+ if statements:
+ sql_query += ' AND stl.statement_id IN %s'
+ params += (tuple(statements.ids),)
+ sql_query += ' ORDER BY stl.id'
+ self.env.cr.execute(sql_query, params)
+ st_lines_left = self.env['account.bank.statement.line'].browse([line.get('id') for line in self.env.cr.dictfetchall()])
+
+ #try to assign partner to bank_statement_line
+ stl_to_assign_partner = [stl.id for stl in st_lines_left if not stl.partner_id]
+ refs = list(set([st.name for st in st_lines_left if not stl.partner_id]))
+ if st_lines_left and stl_to_assign_partner and refs:
+ sql_query = """SELECT aml.partner_id, aml.ref, stl.id
+ FROM account_move_line aml
+ JOIN account_account acc ON acc.id = aml.account_id
+ JOIN account_bank_statement_line stl ON aml.ref = stl.name
+ WHERE (aml.company_id = %s
+ AND aml.partner_id IS NOT NULL)
+ AND (
+ (aml.statement_id IS NULL AND aml.account_id IN %s)
+ OR
+ (acc.internal_type IN ('payable', 'receivable') AND aml.reconciled = false)
+ )
+ AND aml.ref IN %s
+ """
+ params = (self.env.user.company_id.id, (st_lines_left[0].journal_id.default_credit_account_id.id, st_lines_left[0].journal_id.default_debit_account_id.id), tuple(refs))
+ if statements:
+ sql_query += 'AND stl.id IN %s'
+ params += (tuple(stl_to_assign_partner),)
+ self.env.cr.execute(sql_query, params)
+ results = self.env.cr.dictfetchall()
+ st_line = self.env['account.bank.statement.line']
+ for line in results:
+ st_line.browse(line.get('id')).write({'partner_id': line.get('partner_id')})
return {
'st_lines_ids': st_lines_left.ids,
- 'notifications': notifications,
+ 'notifications': [],
'statement_name': len(statements) == 1 and statements[0].name or False,
- 'num_already_reconciled_lines': statements and bsl_obj.search_count([('journal_entry_ids', '!=', False), ('id', 'in', lines)]) or 0,
+ 'num_already_reconciled_lines': 0,
}
@api.multi
@@ -450,6 +446,40 @@ class AccountBankStatementLine(models.Model):
####################################################
@api.multi
+ def reconciliation_widget_auto_reconcile(self, num_already_reconciled_lines):
+ automatic_reconciliation_entries = self.env['account.bank.statement.line']
+ unreconciled = self.env['account.bank.statement.line']
+ for stl in self:
+ res = stl.auto_reconcile()
+ if res:
+ automatic_reconciliation_entries += stl
+ else:
+ unreconciled += stl
+
+ # Collect various informations for the reconciliation widget
+ notifications = []
+ num_auto_reconciled = len(automatic_reconciliation_entries)
+ if num_auto_reconciled > 0:
+ auto_reconciled_message = num_auto_reconciled > 1 \
+ and _("%d transactions were automatically reconciled.") % num_auto_reconciled \
+ or _("1 transaction was automatically reconciled.")
+ notifications += [{
+ 'type': 'info',
+ 'message': auto_reconciled_message,
+ 'details': {
+ 'name': _("Automatically reconciled items"),
+ 'model': 'account.move',
+ 'ids': automatic_reconciliation_entries.ids
+ }
+ }]
+ return {
+ 'st_lines_ids': unreconciled.ids,
+ 'notifications': notifications,
+ 'statement_name': False,
+ 'num_already_reconciled_lines': num_auto_reconciled + num_already_reconciled_lines,
+ }
+
+ @api.multi
def get_data_for_reconciliation_widget(self, excluded_ids=None):
""" Returns the data required to display a reconciliation widget, for each statement line in self """
excluded_ids = excluded_ids or []
@@ -561,86 +591,66 @@ class AccountBankStatementLine(models.Model):
return self.env['account.move.line'].search(domain, offset=offset, limit=limit, order="date_maturity asc, id asc")
- def _get_domain_maker_move_line_amount(self):
- """ Returns a function that can create the appropriate domain to search on move.line amount based on statement.line currency/amount """
- company_currency = self.journal_id.company_id.currency_id
- st_line_currency = self.currency_id or self.journal_id.currency_id
- currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
- field = currency and 'amount_residual_currency' or 'amount_residual'
- precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
-
- def ret(comparator, amount, p=precision, f=field, c=currency):
- if comparator == '<':
- if amount < 0:
- domain = [(f, '<', 0), (f, '>', amount)]
- else:
- domain = [(f, '>', 0), (f, '<', amount)]
- elif comparator == '=':
- if f == 'amount_residual':
- liquidity_field = amount > 0 and 'debit' or 'credit'
- domain = [
- '|', (f, '=', float_round(amount, precision_digits=p)),
- '&', ('account_id.internal_type', '=', 'liquidity'),
- (liquidity_field, '=', amount),
- ]
- else:
- domain = [
- '|', (f, '=', float_round(amount, precision_digits=p)),
- '&', ('account_id.internal_type', '=', 'liquidity'),
- ('amount_currency', '=', amount),
- ]
- else:
- raise UserError(_("Programmation error : domain_maker_move_line_amount requires comparator '=' or '<'"))
- domain += [('currency_id', '=', c)]
- return domain
-
- return ret
+ def _get_common_sql_query(self, overlook_partner = False, excluded_ids = None, split = False):
+ acc_type = "acc.internal_type IN ('payable', 'receivable')" if (self.partner_id or overlook_partner) else "acc.reconcile = true"
+ select_clause = "SELECT aml.id "
+ from_clause = "FROM account_move_line aml JOIN account_account acc ON acc.id = aml.account_id "
+ where_clause = """WHERE aml.company_id = %(company_id)s
+ AND (
+ (aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s)
+ OR
+ ("""+acc_type+""" AND aml.reconciled = false)
+ )"""
+ where_clause = where_clause + ' AND aml.partner_id = %(partner_id)s' if self.partner_id else where_clause
+ where_clause = where_clause + ' AND aml.id NOT IN %(excluded_ids)s' if excluded_ids else where_clause
+ if split:
+ return select_clause, from_clause, where_clause
+ return select_clause + from_clause + where_clause
def get_reconciliation_proposition(self, excluded_ids=None):
""" Returns move lines that constitute the best guess to reconcile a statement line
Note: it only looks for move lines in the same currency as the statement line.
"""
+ self.ensure_one()
+ if not excluded_ids:
+ excluded_ids = []
+ amount = self.amount_currency or self.amount
+ company_currency = self.journal_id.company_id.currency_id
+ st_line_currency = self.currency_id or self.journal_id.currency_id
+ currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
+ precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
+ params = {'company_id': self.env.user.company_id.id,
+ 'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id),
+ 'amount': float_round(amount, precision_digits=precision),
+ 'partner_id': self.partner_id.id,
+ 'excluded_ids': tuple(excluded_ids),
+ 'ref': self.name,
+ }
# Look for structured communication match
if self.name:
- overlook_partner = not self.partner_id # If the transaction has no partner, look for match in payable and receivable account anyway
- domain = [('ref', '=', self.name)]
- match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=2, additional_domain=domain, overlook_partner=overlook_partner)
- if match_recs and len(match_recs) == 1:
- return match_recs
- elif len(match_recs) == 0:
- move = self.env['account.move'].search([('name', '=', self.name)], limit=1)
- if move:
- domain = [('move_id', '=', move.id)]
- match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=2, additional_domain=domain, overlook_partner=overlook_partner)
- if match_recs and len(match_recs) == 1:
- return match_recs
-
- # How to compare statement line amount and move lines amount
- amount_domain_maker = self._get_domain_maker_move_line_amount()
- amount = self.amount_currency or self.amount
+ add_to_select = ", CASE WHEN aml.ref = %(ref)s THEN 1 ELSE 2 END as temp_field_order "
+ add_to_from = " JOIN account_move m ON m.id = aml.move_id "
+ select_clause, from_clause, where_clause = self._get_common_sql_query(overlook_partner=True, excluded_ids=excluded_ids, split=True)
+ sql_query = select_clause + add_to_select + from_clause + add_to_from + where_clause
+ sql_query += " AND (aml.ref= %(ref)s or m.name = %(ref)s) \
+ ORDER BY temp_field_order, date_maturity asc, aml.id asc"
+ self.env.cr.execute(sql_query, params)
+ results = self.env.cr.fetchone()
+ if results:
+ return self.env['account.move.line'].browse(results[0])
# Look for a single move line with the same amount
- match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=1, additional_domain=amount_domain_maker('=', amount))
- if match_recs:
- return match_recs
-
- if not self.partner_id:
- return self.env['account.move.line']
-
- # Select move lines until their total amount is greater than the statement line amount
- domain = [('reconciled', '=', False)]
- domain += [('account_id.user_type_id.type', '=', amount > 0 and 'receivable' or 'payable')] # Make sure we can't mix receivable and payable
- domain += amount_domain_maker('<', amount) # Will also enforce > 0
- mv_lines = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=5, additional_domain=domain)
- st_line_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id
- ret = self.env['account.move.line']
- total = 0
- for line in mv_lines:
- total += line.currency_id and line.amount_residual_currency or line.amount_residual
- if float_compare(total, abs(amount), precision_digits=st_line_currency.rounding) != -1:
- break
- ret = (ret | line)
- return ret
+ field = currency and 'amount_residual_currency' or 'amount_residual'
+ liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit'
+ sql_query = self._get_common_sql_query(excluded_ids=excluded_ids) + \
+ " AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \
+ ORDER BY date_maturity asc, aml.id asc LIMIT 1"
+ self.env.cr.execute(sql_query, params)
+ results = self.env.cr.fetchone()
+ if results:
+ return self.env['account.move.line'].browse(results[0])
+
+ return self.env['account.move.line']
def _get_move_lines_for_auto_reconcile(self):
""" Returns the move lines that the method auto_reconcile can use to try to reconcile the statement line """
@@ -654,28 +664,44 @@ class AccountBankStatementLine(models.Model):
self.ensure_one()
match_recs = self.env['account.move.line']
- # How to compare statement line amount and move lines amount
- amount_domain_maker = self._get_domain_maker_move_line_amount()
- equal_amount_domain = amount_domain_maker('=', self.amount_currency or self.amount)
-
+ amount = self.amount_currency or self.amount
+ company_currency = self.journal_id.company_id.currency_id
+ st_line_currency = self.currency_id or self.journal_id.currency_id
+ currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
+ precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
+ params = {'company_id': self.env.user.company_id.id,
+ 'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id),
+ 'amount': float_round(amount, precision_digits=precision),
+ 'partner_id': self.partner_id.id,
+ 'ref': self.name,
+ }
+ field = currency and 'amount_residual_currency' or 'amount_residual'
+ liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit'
# Look for structured communication match
if self.name:
- overlook_partner = not self.partner_id # If the transaction has no partner, look for match in payable and receivable account anyway
- domain = equal_amount_domain + [('ref', '=', self.name)]
- match_recs = self.get_move_lines_for_reconciliation(limit=2, additional_domain=domain, overlook_partner=overlook_partner)
- if match_recs and len(match_recs) != 1:
+ sql_query = self._get_common_sql_query() + \
+ " AND aml.ref = %(ref)s AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \
+ ORDER BY date_maturity asc, aml.id asc"
+ self.env.cr.execute(sql_query, params)
+ match_recs = self.env.cr.dictfetchall()
+ if len(match_recs) > 1:
return False
# Look for a single move line with the same partner, the same amount
if not match_recs:
if self.partner_id:
- match_recs = self.get_move_lines_for_reconciliation(limit=2, additional_domain=equal_amount_domain)
- if match_recs and len(match_recs) != 1:
+ sql_query = self._get_common_sql_query() + \
+ " AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \
+ ORDER BY date_maturity asc, aml.id asc"
+ self.env.cr.execute(sql_query, params)
+ match_recs = self.env.cr.dictfetchall()
+ if len(match_recs) > 1:
return False
if not match_recs:
return False
+ match_recs = self.env['account.move.line'].browse([aml.get('id') for aml in match_recs])
# Now reconcile
counterpart_aml_dicts = []
payment_aml_rec = self.env['account.move.line']
diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index a4b29bedcfc..b1bf44aa4ae 100644
--- a/addons/account/models/account_journal_dashboard.py
+++ b/addons/account/models/account_journal_dashboard.py
@@ -117,7 +117,7 @@ class account_journal(models.Model):
for i in range(0,6):
if i == 0:
query += "("+select_sql_clause+" and date < '"+start_date.strftime(DF)+"')"
- elif i == 6:
+ elif i == 5:
query += " UNION ALL ("+select_sql_clause+" and date >= '"+start_date.strftime(DF)+"')"
else:
next_date = start_date + timedelta(days=7)
@@ -303,6 +303,7 @@ class account_journal(models.Model):
invoice_type = _journal_invoice_type_map[(self.type, self._context.get('invoice_type'))]
ctx = self._context.copy()
+ ctx.pop('group_by', None)
ctx.update({
'journal_type': self.type,
'default_journal_id': self.id,
@@ -336,6 +337,7 @@ class account_journal(models.Model):
'default_payment_type': payment_type,
'default_journal_id': self.id
})
+ ctx.pop('group_by', None)
action_rec = self.env['ir.model.data'].xmlid_to_object('account.action_account_payments')
if action_rec:
action = action_rec.read([])[0]
@@ -351,6 +353,7 @@ class account_journal(models.Model):
ctx = dict(self.env.context, default_journal_id=self.id)
if ctx.get('search_default_journal', False):
ctx.update(search_default_journal_id=self.id)
+ ctx.pop('group_by', None)
ir_model_obj = self.pool['ir.model.data']
model, action_id = ir_model_obj.get_object_reference(self._cr, self._uid, 'account', action_name)
action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context)
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index f8a5007c7e8..8efbd9e3c7a 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -89,7 +89,7 @@ class AccountMove(models.Model):
company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', store=True, readonly=True,
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")
- statement_line_id = fields.Many2one('account.bank.statement.line', string='Bank statement line reconciled with this entry', copy=False, readonly=True)
+ statement_line_id = fields.Many2one('account.bank.statement.line', index=True, string='Bank statement line reconciled with this entry', copy=False, readonly=True)
# 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)
@@ -230,6 +230,16 @@ class AccountMoveLine(models.Model):
_description = "Journal Item"
_order = "date desc, id desc"
+ def init(self, cr):
+ """ change index on partner_id to a multi-column index on (partner_id, ref), the new index will behave in the
+ same way when we search on partner_id, with the addition of being optimal when having a query that will
+ search on partner_id and ref at the same time (which is the case when we open the bank reconciliation widget)
+ """
+ cr.execute('DROP INDEX IF EXISTS account_move_line_partner_id_index')
+ cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('account_move_line_partner_id_ref_idx',))
+ 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')
def _amount_residual(self):
""" Computes the residual amount of a move line from a reconciliable account in the company currency and the line's currency.
@@ -363,7 +373,7 @@ class AccountMoveLine(models.Model):
move_id = fields.Many2one('account.move', string='Journal Entry', ondelete="cascade",
help="The move of this entry line.", index=True, required=True, auto_join=True)
narration = fields.Text(related='move_id.narration', string='Internal Note')
- ref = fields.Char(related='move_id.ref', string='Partner Reference', store=True, copy=False)
+ ref = fields.Char(related='move_id.ref', string='Partner Reference', store=True, copy=False, index=True)
payment_id = fields.Many2one('account.payment', string="Originator Payment", help="Payment that created this entry")
statement_id = fields.Many2one('account.bank.statement', string='Statement',
help="The bank statement used for bank reconciliation", index=True, copy=False)
@@ -389,7 +399,7 @@ class AccountMoveLine(models.Model):
# TODO: put the invoice link and partner_id on the account_move
invoice_id = fields.Many2one('account.invoice', oldname="invoice")
- partner_id = fields.Many2one('res.partner', string='Partner', index=True, ondelete='restrict')
+ partner_id = fields.Many2one('res.partner', string='Partner', ondelete='restrict')
user_type_id = fields.Many2one('account.account.type', related='account_id.user_type_id', index=True, store=True, oldname="user_type")
_sql_constraints = [
@@ -1134,8 +1144,6 @@ class AccountMoveLine(models.Model):
@api.multi
def write(self, vals):
- if vals.get('tax_line_id') or vals.get('tax_ids'):
- raise UserError(_('You cannot change the tax, you should remove and recreate lines.'))
if ('account_id' in vals) and self.env['account.account'].browse(vals['account_id']).deprecated:
raise UserError(_('You cannot use deprecated account.'))
if any(key in vals for key in ('account_id', 'journal_id', 'date', 'move_id', 'debit', 'credit')):
@@ -1420,11 +1428,11 @@ class AccountPartialReconcile(models.Model):
exchange_partial_rec_id = rate_diff_partial_rec.id
#mark the reference of the full reconciliation on the partial ones and on the entries
self.env['account.full.reconcile'].with_context(check_move_validity=False).create({
- 'partial_reconcile_ids': [(6, 0, partial_rec_ids)],
- 'reconciled_line_ids': [(6, 0, aml_ids)],
+ 'partial_reconcile_ids': [(4, p_id) for p_id in partial_rec_ids],
+ 'reconciled_line_ids': [(4, a_id) for a_id in aml_ids],
'exchange_move_id': exchange_move_id,
'exchange_partial_rec_id': exchange_partial_rec_id,
- })
+ })
return res
@api.multi
diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py
index cfa771b5b42..a4b5a0e9d13 100644
--- a/addons/account/models/partner.py
+++ b/addons/account/models/partner.py
@@ -338,7 +338,7 @@ class ResPartner(models.Model):
else:
domain += [('partner_id', 'in', self.ids)]
#adding the overdue lines
- overdue_domain = ['|', '&', ('date_maturity', '!=', False), ('date_maturity', '<=', date), '&', ('date_maturity', '=', False), ('date', '<=', date)]
+ overdue_domain = ['|', '&', ('date_maturity', '!=', False), ('date_maturity', '<', date), '&', ('date_maturity', '=', False), ('date', '<', date)]
if overdue_only:
domain += overdue_domain
return domain
diff --git a/addons/account/static/src/js/account_reconciliation_widgets.js b/addons/account/static/src/js/account_reconciliation_widgets.js
index 26657212f35..cb84ee74b23 100644
--- a/addons/account/static/src/js/account_reconciliation_widgets.js
+++ b/addons/account/static/src/js/account_reconciliation_widgets.js
@@ -1127,6 +1127,7 @@ var bankStatementReconciliation = abstractReconciliation.extend({
this.reconciliation_menu_id = false; // Used to update the needaction badge
// The same move line cannot be selected for multiple reconciliations
this.excluded_move_lines_ids = {};
+ this.widget_childrens = [];
},
serverPreprocessResultHandler: function(data) {
@@ -1178,42 +1179,62 @@ var bankStatementReconciliation = abstractReconciliation.extend({
single_statement: self.single_statement,
total_lines: self.num_already_reconciled_lines+self.lines.length
}));
- self.updateProgressbar();
- var reconciliations_to_show = self.lines.slice(0, self.num_reconciliations_fetched_in_batch);
- self.last_displayed_reconciliation_index = reconciliations_to_show.length;
- self.$(".reconciliation_lines_container").css("opacity", 0);
+ self.$el.find('.js_automatic_reconciliation').click(function() {
+ // Let odoo try to reconcile entries for the user
+ self.model_bank_statement_line
+ .call("reconciliation_widget_auto_reconcile", [self.lines || undefined, self.num_already_reconciled_lines])
+ .then(function(data){ self.serverPreprocessResultHandler(data); })
+ .then(function(){ self.$('.js_automatic_reconciliation').hide();
+ return self.display_reconciliation_propositions();
+ });
- // If everything is reconciled, show end message
- if (self.lines.length === 0) {
- if (self.notifications)
- self.displayNotifications(self.notifications, 0);
- self.displayDoneMessage(true);
- return;
- }
+ });
+ return self.display_reconciliation_propositions();
+ });
+ });
+ },
- // Display the reconciliations
- return self.model_bank_statement_line
- .call("get_data_for_reconciliation_widget", [reconciliations_to_show])
- .then(function (data) {
- var child_promises = [];
- var datum = data.shift();
- if (datum !== undefined)
- child_promises.push(self.displayReconciliation(datum.st_line.id, 'match', false, true, datum.st_line, datum.reconciliation_proposition));
- while ((datum = data.shift()) !== undefined)
- child_promises.push(self.displayReconciliation(datum.st_line.id, 'inactive', false, true, datum.st_line, datum.reconciliation_proposition));
-
- // When reconciliations are instanciated, make an entrance
- $.when.apply($, child_promises).then(function(){
- self.$(".reconciliation_lines_container").animate({opacity: 1}, self.aestetic_animation_speed, function() {
- if (self.notifications) {
- self.displayNotifications(self.notifications);
- self.updateShowMoreButton();
- }
- });
- });
+ display_reconciliation_propositions: function() {
+ var self = this;
+ self.updateProgressbar();
+ var reconciliations_to_show = self.lines.slice(0, self.num_reconciliations_fetched_in_batch);
+ self.last_displayed_reconciliation_index = reconciliations_to_show.length;
+ self.$(".reconciliation_lines_container").css("opacity", 0);
+ // Delete previous bankStatementReconciliationLine
+ $.each(self.widget_childrens, function(index, child) {
+ child.destroy();
+ });
+
+ // If everything is reconciled, show end message
+ if (self.lines.length === 0) {
+ self.$(".reconciliation_lines_container").hide();
+ if (self.notifications)
+ self.displayNotifications(self.notifications, 0);
+ self.displayDoneMessage(true);
+ return;
+ }
+
+ // Display the reconciliations
+ return self.model_bank_statement_line
+ .call("get_data_for_reconciliation_widget", [reconciliations_to_show])
+ .then(function (data) {
+ var child_promises = [];
+ var datum = data.shift();
+ if (datum !== undefined)
+ child_promises.push(self.displayReconciliation(datum.st_line.id, 'match', false, true, datum.st_line, datum.reconciliation_proposition));
+ while ((datum = data.shift()) !== undefined)
+ child_promises.push(self.displayReconciliation(datum.st_line.id, 'inactive', false, true, datum.st_line, datum.reconciliation_proposition));
+
+ // When reconciliations are instanciated, make an entrance
+ $.when.apply($, child_promises).then(function(){
+ self.$(".reconciliation_lines_container").animate({opacity: 1}, self.aestetic_animation_speed, function() {
+ if (self.notifications) {
+ self.displayNotifications(self.notifications);
+ self.updateShowMoreButton();
+ }
});
+ });
});
- });
},
statementNameClickHandler: function() {
@@ -1320,6 +1341,7 @@ var bankStatementReconciliation = abstractReconciliation.extend({
reconciliation_proposition: initial_data_provided ? reconciliation_proposition : undefined,
};
var widget = new self.children_widget(self, context);
+ this.widget_childrens.push(widget);
return widget.appendTo(self.$(".reconciliation_lines_container"));
},
diff --git a/addons/account/static/src/xml/account_reconciliation.xml b/addons/account/static/src/xml/account_reconciliation.xml
index d91f042bcda..5c5b59c5758 100644
--- a/addons/account/static/src/xml/account_reconciliation.xml
+++ b/addons/account/static/src/xml/account_reconciliation.xml
@@ -12,6 +12,7 @@
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" t-att-aria-valuemax="total_lines" style="width: 0%;">
</div>
</div>
+ <button class="btn btn-default js_automatic_reconciliation pull-right" title="Let odoo try to reconcile entries for the user">Automatic reconciliation</button>
<div class="oe_clear o_clear"></div>
<div class="notification_area"></div>
<div class="interface_options oe_form o_form"></div>
diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py
index b1429b0cc1c..76d6dcfe2d7 100644
--- a/addons/account_asset/account_asset.py
+++ b/addons/account_asset/account_asset.py
@@ -6,6 +6,7 @@ from dateutil.relativedelta import relativedelta
from openerp import api, fields, models, _
from openerp.exceptions import UserError, ValidationError
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
+from openerp.tools import float_compare
class AccountAssetCategory(models.Model):
@@ -417,11 +418,12 @@ class AccountAssetDepreciationLine(models.Model):
categ_type = line.asset_id.category_id.type
debit_account = line.asset_id.category_id.account_asset_id.id
credit_account = line.asset_id.category_id.account_depreciation_id.id
+ prec = self.env['decimal.precision'].precision_get('Account')
move_line_1 = {
'name': asset_name,
'account_id': credit_account,
- 'debit': 0.0,
- 'credit': amount,
+ 'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
+ 'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
'journal_id': journal_id,
'partner_id': partner_id,
'currency_id': company_currency != current_currency and current_currency.id or False,
@@ -432,8 +434,8 @@ class AccountAssetDepreciationLine(models.Model):
move_line_2 = {
'name': asset_name,
'account_id': debit_account,
- 'credit': 0.0,
- 'debit': amount,
+ 'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
+ 'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
'journal_id': journal_id,
'partner_id': partner_id,
'currency_id': company_currency != current_currency and current_currency.id or False,
diff --git a/addons/account_asset/wizard/account_asset_change_duration.py b/addons/account_asset/wizard/account_asset_change_duration.py
index 8564968434c..cbfc01a58bf 100644
--- a/addons/account_asset/wizard/account_asset_change_duration.py
+++ b/addons/account_asset/wizard/account_asset_change_duration.py
@@ -54,7 +54,8 @@ class AssetModify(models.TransientModel):
if 'method_end' in fields and asset.method_time == 'end':
res.update({'method_end': asset.method_end})
if self.env.context.get('active_id'):
- res['asset_method_time'] = self._get_asset_method_time()
+ asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
+ res['asset_method_time'] = asset.method_time
return res
@api.multi
diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py
index 8116df538c4..2ba2602ad25 100644
--- a/addons/account_voucher/account_voucher.py
+++ b/addons/account_voucher/account_voucher.py
@@ -102,8 +102,8 @@ class AccountVoucher(models.Model):
pay_now = fields.Selection([
('pay_now', 'Pay Directly'),
('pay_later', 'Pay Later'),
- ], 'Payment', select=True, readonly=True, states={'draft': [('readonly', False)]}, default='pay_later')
- date_due = fields.Date('Due Date', readonly=True, select=True, states={'draft': [('readonly', False)]})
+ ], 'Payment', index=True, readonly=True, states={'draft': [('readonly', False)]}, default='pay_later')
+ date_due = fields.Date('Due Date', readonly=True, index=True, states={'draft': [('readonly', False)]})
@api.onchange('partner_id', 'pay_now')
def onchange_partner_id(self):
diff --git a/addons/base_import/static/src/js/import.js b/addons/base_import/static/src/js/import.js
index 496a25bc9ab..7b5b801b8be 100644
--- a/addons/base_import/static/src/js/import.js
+++ b/addons/base_import/static/src/js/import.js
@@ -411,7 +411,7 @@ var DataImport = Widget.extend(ControlPanelMixin, {
return $.when([{
type: 'error',
record: false,
- message: error.data.arguments[1],
+ message: error.data.arguments && error.data.arguments[1] || error.message,
}]);
}) ;
},
diff --git a/addons/event/models/event.py b/addons/event/models/event.py
index 447edb098a8..f4bf2ac7491 100644
--- a/addons/event/models/event.py
+++ b/addons/event/models/event.py
@@ -291,7 +291,7 @@ class event_registration(models.Model):
string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange')
email = fields.Char(string='Email')
phone = fields.Char(string='Phone')
- name = fields.Char(string='Attendee Name', select=True)
+ name = fields.Char(string='Attendee Name', index=True)
@api.one
@api.constrains('event_id', 'state')
diff --git a/addons/event_sale/wizard/event_edit_registration.py b/addons/event_sale/wizard/event_edit_registration.py
index 98f6553f5a6..0526db91646 100644
--- a/addons/event_sale/wizard/event_edit_registration.py
+++ b/addons/event_sale/wizard/event_edit_registration.py
@@ -68,7 +68,7 @@ class RegistrationEditorLine(models.TransientModel):
event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket')
email = fields.Char(string='Email')
phone = fields.Char(string='Phone')
- name = fields.Char(string='Name', select=True)
+ name = fields.Char(string='Name', index=True)
@api.one
def get_registration_data(self):
diff --git a/addons/hr/hr.py b/addons/hr/hr.py
index 25ca70fcfb2..14ead8fd5ce 100644
--- a/addons/hr/hr.py
+++ b/addons/hr/hr.py
@@ -261,7 +261,7 @@ class hr_employee(osv.osv):
user_field_lst.append(name)
return user_field_lst
- _constraints = [(osv.osv._check_recursion, _('Error! You cannot create recursive hierarchy of Employee(s).'), ['parent_id']),]
+ _constraints = [(osv.osv._check_recursion, 'Error! You cannot create recursive hierarchy of Employee(s).', ['parent_id']),]
class hr_department(osv.osv):
@@ -291,7 +291,7 @@ class hr_department(osv.osv):
}
_constraints = [
- (osv.osv._check_recursion, _('Error! You cannot create recursive departments.'), ['parent_id'])
+ (osv.osv._check_recursion, 'Error! You cannot create recursive departments.', ['parent_id'])
]
def name_get(self, cr, uid, ids, context=None):
diff --git a/addons/hr_equipment/models/hr_equipment.py b/addons/hr_equipment/models/hr_equipment.py
index fa7ebe2e95f..0d48366224f 100644
--- a/addons/hr_equipment/models/hr_equipment.py
+++ b/addons/hr_equipment/models/hr_equipment.py
@@ -236,7 +236,7 @@ class HrEquipmentRequest(models.Model):
employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee_get)
department_id = fields.Many2one('hr.department', string='Department')
category_id = fields.Many2one('hr.equipment.category', string='Category')
- equipment_id = fields.Many2one('hr.equipment', string='Asset', select=True)
+ equipment_id = fields.Many2one('hr.equipment', string='Asset', index=True)
user_id = fields.Many2one('res.users', string='Assigned to', track_visibility='onchange')
stage_id = fields.Many2one('hr.equipment.stage', string='Stage', track_visibility='onchange', default=_default_stage)
priority = fields.Selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High')], string='Priority')
diff --git a/addons/hr_payroll_account/hr_payroll_account.py b/addons/hr_payroll_account/hr_payroll_account.py
index fa07751f290..c27df320165 100644
--- a/addons/hr_payroll_account/hr_payroll_account.py
+++ b/addons/hr_payroll_account/hr_payroll_account.py
@@ -1,7 +1,5 @@
#-*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import time
-from datetime import date, datetime, timedelta
from openerp import api
from openerp.osv import fields, osv
@@ -94,13 +92,12 @@ class hr_payslip(osv.osv):
move_pool = self.pool.get('account.move')
hr_payslip_line_pool = self.pool['hr.payslip.line']
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Payroll')
- timenow = time.strftime('%Y-%m-%d')
for slip in self.browse(cr, uid, ids, context=context):
line_ids = []
debit_sum = 0.0
credit_sum = 0.0
- date = timenow
+ date = slip.date or slip.date_to
name = _('Payslip of %s') % (slip.employee_id.name)
move = {
@@ -152,7 +149,6 @@ class hr_payslip(osv.osv):
raise UserError(_('The Expense Journal "%s" has not properly configured the Credit Account!') % (slip.journal_id.name))
adjust_credit = (0, 0, {
'name': _('Adjustment Entry'),
- 'date': timenow,
'partner_id': False,
'account_id': acc_id,
'journal_id': slip.journal_id.id,
diff --git a/addons/hr_recruitment/models/hr_recruitment.py b/addons/hr_recruitment/models/hr_recruitment.py
index c29b3cc0308..cf234b3106e 100644
--- a/addons/hr_recruitment/models/hr_recruitment.py
+++ b/addons/hr_recruitment/models/hr_recruitment.py
@@ -120,20 +120,20 @@ class Applicant(models.Model):
help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma")
probability = fields.Float("Probability")
partner_id = fields.Many2one('res.partner', "Contact")
- create_date = fields.Datetime("Creation Date", readonly=True, select=True)
+ create_date = fields.Datetime("Creation Date", readonly=True, index=True)
write_date = fields.Datetime("Update Date", readonly=True)
stage_id = fields.Many2one('hr.recruitment.stage', 'Stage', track_visibility='onchange',
domain="['|', ('job_id', '=', False), ('job_id', '=', job_id)]",
- copy=False, select=1,
+ copy=False, index=True,
default=_default_stage_id)
last_stage_id = fields.Many2one('hr.recruitment.stage', "Last Stage",
help="Stage of the applicant before being in the current stage. Used for lost cases analysis.")
categ_ids = fields.Many2many('hr.applicant.category', string="Tags")
company_id = fields.Many2one('res.company', "Company", default=_default_company_id)
user_id = fields.Many2one('res.users', "Responsible", track_visibility="onchange", default=lambda self: self.env.uid)
- date_closed = fields.Datetime("Closed", readonly=True, select=True)
- date_open = fields.Datetime("Assigned", readonly=True, select=True)
- date_last_stage_update = fields.Datetime("Last Stage Update", select=True, default=fields.Datetime.now)
+ date_closed = fields.Datetime("Closed", readonly=True, index=True)
+ date_open = fields.Datetime("Assigned", readonly=True, index=True)
+ date_last_stage_update = fields.Datetime("Last Stage Update", index=True, default=fields.Datetime.now)
date_action = fields.Date("Next Action Date")
title_action = fields.Char("Next Action", size=64)
priority = fields.Selection(AVAILABLE_PRIORITIES, "Appreciation", default='0')
diff --git a/addons/lunch/models/lunch.py b/addons/lunch/models/lunch.py
index 2f22f69659e..38b3535c163 100644
--- a/addons/lunch/models/lunch.py
+++ b/addons/lunch/models/lunch.py
@@ -147,7 +147,7 @@ class LunchOrderLine(models.Model):
('confirmed', 'Received'),
('ordered', 'Ordered'),
('cancelled', 'Cancelled')],
- 'Status', readonly=True, select=True, default='new')
+ 'Status', readonly=True, index=True, default='new')
cashmove = fields.One2many('lunch.cashmove', 'order_id', 'Cash Move')
currency_id = fields.Many2one('res.currency', related='order_id.currency_id')
@@ -235,7 +235,7 @@ class LunchAlert(models.Model):
alert_type = fields.Selection([('specific', 'Specific Day'),
('week', 'Every Week'),
('days', 'Every Day')],
- string='Recurrency', required=True, select=True, default='specific')
+ string='Recurrency', required=True, index=True, default='specific')
specific_day = fields.Date('Day', default=fields.Date.context_today)
monday = fields.Boolean('Monday')
tuesday = fields.Boolean('Tuesday')
diff --git a/addons/mail/controllers/bus.py b/addons/mail/controllers/bus.py
index 0249c823ec4..058f7e331de 100644
--- a/addons/mail/controllers/bus.py
+++ b/addons/mail/controllers/bus.py
@@ -23,6 +23,7 @@ class MailChatController(openerp.addons.bus.controllers.main.BusController):
partner_id = request.env.user.partner_id.id
if partner_id:
+ channels = list(channels) # do not alter original list
for mail_channel in request.env['mail.channel'].search([('channel_partner_ids', 'in', [partner_id])]):
channels.append((request.db, 'mail.channel', mail_channel.id))
# personal and needaction channel
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index 249cc4dc139..f73c6ad0e05 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -45,7 +45,7 @@ class Channel(models.Model):
('channel', 'Channel')],
'Channel Type', default='channel')
description = fields.Text('Description')
- uuid = fields.Char('UUID', size=50, select=True, default=lambda self: '%s' % uuid.uuid4())
+ uuid = fields.Char('UUID', size=50, index=True, default=lambda self: '%s' % uuid.uuid4())
email_send = fields.Boolean('Send messages by email', default=False)
# multi users channel
channel_last_seen_partner_ids = fields.One2many('mail.channel.partner', 'channel_id', string='Last Seen')
diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py
index 2dd62582b1d..eda2c0647a4 100644
--- a/addons/mail/models/mail_followers.py
+++ b/addons/mail/models/mail_followers.py
@@ -18,13 +18,13 @@ class Followers(models.Model):
_description = 'Document Followers'
res_model = fields.Char(
- 'Related Document Model', required=True, select=1, help='Model of the followed resource')
+ 'Related Document Model', required=True, index=True, help='Model of the followed resource')
res_id = fields.Integer(
- 'Related Document ID', select=1, help='Id of the followed resource')
+ 'Related Document ID', index=True, help='Id of the followed resource')
partner_id = fields.Many2one(
- 'res.partner', string='Related Partner', ondelete='cascade', select=1)
+ 'res.partner', string='Related Partner', ondelete='cascade', index=True)
channel_id = fields.Many2one(
- 'mail.channel', string='Listener', ondelete='cascade', select=1)
+ 'mail.channel', string='Listener', ondelete='cascade', index=True)
subtype_ids = fields.Many2many(
'mail.message.subtype', string='Subtype',
help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall.")
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 0962bd8c111..1073016a019 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -57,12 +57,12 @@ class Message(models.Model):
help='Attachments are linked to a document through model / res_id and to the message '
'through this field.')
parent_id = fields.Many2one(
- 'mail.message', 'Parent Message', select=True, ondelete='set null',
+ 'mail.message', 'Parent Message', index=True, ondelete='set null',
help="Initial thread message.")
child_ids = fields.One2many('mail.message', 'parent_id', 'Child Messages')
# related document
- model = fields.Char('Related Document Model', select=1)
- res_id = fields.Integer('Related Document ID', select=1)
+ model = fields.Char('Related Document Model', index=True)
+ res_id = fields.Integer('Related Document ID', index=True)
record_name = fields.Char('Message Record Name', help="Name get of the related document.")
# characteristics
message_type = fields.Selection([
@@ -73,13 +73,13 @@ class Message(models.Model):
help="Message type: email for email message, notification for system "
"message, comment for other messages such as user replies",
oldname='type')
- subtype_id = fields.Many2one('mail.message.subtype', 'Subtype', ondelete='set null', select=1)
+ subtype_id = fields.Many2one('mail.message.subtype', 'Subtype', ondelete='set null', index=True)
# origin
email_from = fields.Char(
'From', default=_get_default_from,
help="Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter.")
author_id = fields.Many2one(
- 'res.partner', 'Author', select=1,
+ 'res.partner', 'Author', index=True,
ondelete='set null', default=_get_default_author,
help="Author of the message. If not set, email_from may hold an email address that did not match any partner.")
author_avatar = fields.Binary("Author's avatar", related='author_id.image_small')
@@ -108,7 +108,7 @@ class Message(models.Model):
no_auto_thread = fields.Boolean(
'No threading for answers',
help='Answers do not go in the original document discussion thread. This has an impact on the generated message-id.')
- message_id = fields.Char('Message-Id', help='Message unique identifier', select=1, readonly=1, copy=False)
+ message_id = fields.Char('Message-Id', help='Message unique identifier', index=True, readonly=1, copy=False)
reply_to = fields.Char('Reply-To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.')
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index f1579e1d943..7ef203f6275 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -116,7 +116,7 @@ class MailTemplate(models.Model):
name = fields.Char('Name')
model_id = fields.Many2one('ir.model', 'Applies to', help="The type of document this template can be used with")
- model = fields.Char('Related Document Model', related='model_id.model', select=True, store=True, readonly=True)
+ model = fields.Char('Related Document Model', related='model_id.model', index=True, store=True, readonly=True)
lang = fields.Char('Language',
help="Optional translation language (ISO code) to select when sending out an email. "
"If not set, the english version will be used. "
diff --git a/addons/mail/models/mail_tracking_value.py b/addons/mail/models/mail_tracking_value.py
index 87c26590550..8619c69404d 100644
--- a/addons/mail/models/mail_tracking_value.py
+++ b/addons/mail/models/mail_tracking_value.py
@@ -25,7 +25,7 @@ class MailTracking(models.Model):
new_value_text = fields.Text('New Value Text', readonly=1)
new_value_datetime = fields.Datetime('New Value Datetime', readonly=1)
- mail_message_id = fields.Many2one('mail.message', 'Message ID', required=True, select=True, ondelete='cascade')
+ mail_message_id = fields.Many2one('mail.message', 'Message ID', required=True, index=True, ondelete='cascade')
@api.model
def create_tracking_values(self, initial_value, new_value, col_name, col_info):
diff --git a/addons/mail/wizard/invite.py b/addons/mail/wizard/invite.py
index bb6e8e9aeb4..a8bbccebfb5 100644
--- a/addons/mail/wizard/invite.py
+++ b/addons/mail/wizard/invite.py
@@ -25,8 +25,8 @@ class Invite(models.TransientModel):
result['message'] = _('<div><p>Hello,</p><p>%s invited you to follow a new document.</p></div>') % user_name
return result
- res_model = fields.Char('Related Document Model', required=True, select=1, help='Model of the followed resource')
- res_id = fields.Integer('Related Document ID', select=1, help='Id of the followed resource')
+ res_model = fields.Char('Related Document Model', required=True, index=True, help='Model of the followed resource')
+ res_id = fields.Integer('Related Document ID', index=True, help='Id of the followed resource')
partner_ids = fields.Many2many('res.partner', string='Recipients', help="List of partners that will be added as follower of the current document.")
channel_ids = fields.Many2many('mail.channel', string='Channels', help='List of channels that will be added as listeners of the current document.',
domain=[('channel_type', '=', 'channel')])
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 34d59736185..5acc2ebfa66 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -120,7 +120,7 @@ class MailComposer(models.TransientModel):
auto_delete = fields.Boolean('Delete Emails', help='Delete sent emails (mass mailing only)')
auto_delete_message = fields.Boolean('Delete Message Copy', help='Do not keep a copy of the email in the document communication history (mass mailing only)')
template_id = fields.Many2one(
- 'mail.template', 'Use template', select=True,
+ 'mail.template', 'Use template', index=True,
domain="[('model', '=', model)]")
# mail_message updated fields
message_type = fields.Selection(default="comment")
diff --git a/addons/mrp/views/report_mrpbomstructure.xml b/addons/mrp/views/report_mrpbomstructure.xml
index f1ad7090f77..a5ece4b4f2b 100644
--- a/addons/mrp/views/report_mrpbomstructure.xml
+++ b/addons/mrp/views/report_mrpbomstructure.xml
@@ -31,8 +31,7 @@
</tr>
<tr t-foreach="get_children(o.bom_line_ids)" t-as="l">
<td style="padding-left: 20px;">
- <span style="color: white;" t-esc="'... '*(l['level'])"/>[
- <span t-esc="l['pcode']"/>]
+ <span style="color: white;" t-esc="'... '*(l['level'])"/>
<span t-esc="l['pname']"/>
</td>
<td>
diff --git a/addons/procurement_jit/sale.py b/addons/procurement_jit/sale.py
index b7ab9d40433..de43c848415 100644
--- a/addons/procurement_jit/sale.py
+++ b/addons/procurement_jit/sale.py
@@ -11,8 +11,8 @@ class SaleOrderLine(models.Model):
res = super(SaleOrderLine, self)._action_procurement_create()
orders = list(set(x.order_id for x in self))
for order in orders:
- reassign = order.picking_ids.filtered(lambda x: x.state=='confirmed' or ((x.state=='partially_available') and not x.printed))
+ reassign = order.picking_ids.filtered(lambda x: x.state=='confirmed' or ((x.state in ['partially_available', 'waiting']) and not x.printed))
if reassign:
reassign.do_unreserve()
reassign.action_assign()
- return res \ No newline at end of file
+ return res
diff --git a/addons/purchase/invoice.py b/addons/purchase/invoice.py
index 8e82e290e1c..554c5a7d7da 100644
--- a/addons/purchase/invoice.py
+++ b/addons/purchase/invoice.py
@@ -87,6 +87,22 @@ class AccountInvoice(models.Model):
if purchase_ids:
self.origin = ', '.join(purchase_ids.mapped('name'))
+ @api.onchange('partner_id', 'company_id')
+ def _onchange_partner_id(self):
+ res = super(AccountInvoice, self)._onchange_partner_id()
+ if not self.env.context.get('default_journal_id') and self.partner_id and self.currency_id and\
+ self.type in ['in_invoice', 'in_refund'] and\
+ self.currency_id != self.partner_id.property_purchase_currency_id:
+ journal_domain = [
+ ('type', '=', 'purchase'),
+ ('company_id', '=', self.company_id.id),
+ ('currency_id', '=', self.partner_id.property_purchase_currency_id.id),
+ ]
+ default_journal_id = self.env['account.journal'].search(journal_domain, limit=1)
+ if default_journal_id:
+ self.journal_id = default_journal_id
+ return res
+
@api.model
def invoice_line_move_line_get(self):
res = super(AccountInvoice, self).invoice_line_move_line_get()
@@ -163,6 +179,6 @@ class AccountInvoiceLine(models.Model):
""" Override AccountInvoice_line to add the link to the purchase order line it is related to"""
_inherit = 'account.invoice.line'
- purchase_line_id = fields.Many2one('purchase.order.line', 'Purchase Order Line', ondelete='set null', select=True, readonly=True)
+ purchase_line_id = fields.Many2one('purchase.order.line', 'Purchase Order Line', ondelete='set null', index=True, readonly=True)
purchase_id = fields.Many2one('purchase.order', related='purchase_line_id.order_id', string='Purchase Order', store=False, readonly=True,
help='Associated Purchase Order. Filled in automatically when a PO is chosen on the vendor bill.')
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 407976486f0..39441246b3f 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -93,7 +93,7 @@ class PurchaseOrder(models.Model):
'cancel': [('readonly', True)],
}
- name = fields.Char('Order Reference', required=True, select=True, copy=False, default='New')
+ name = fields.Char('Order Reference', required=True, index=True, copy=False, default='New')
origin = fields.Char('Source Document', copy=False,\
help="Reference of the document that generated this purchase order "
"request (e.g. a sale order or an internal procurement request)")
@@ -102,9 +102,9 @@ class PurchaseOrder(models.Model):
"It's used to do the matching when you receive the "
"products as this reference is usually written on the "
"delivery order sent by your vendor.")
- date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, select=True, copy=False, default=fields.Datetime.now,\
+ date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, index=True, copy=False, default=fields.Datetime.now,\
help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
- date_approve = fields.Date('Approval Date', readonly=1, select=True, copy=False)
+ date_approve = fields.Date('Approval Date', readonly=1, index=True, copy=False)
partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
dest_address_id = fields.Many2one('res.partner', string='Drop Ship Address', states=READONLY_STATES,\
help="Put an address if you want to deliver directly from the vendor to the customer. "\
@@ -118,7 +118,7 @@ class PurchaseOrder(models.Model):
('purchase', 'Purchase Order'),
('done', 'Locked'),
('cancel', 'Cancelled')
- ], string='Status', readonly=True, select=True, copy=False, default='draft', track_visibility='onchange')
+ ], string='Status', readonly=True, index=True, copy=False, default='draft', track_visibility='onchange')
order_line = fields.One2many('purchase.order.line', 'order_id', string='Order Lines', states=READONLY_STATES, copy=True)
notes = fields.Text('Terms and Conditions')
@@ -133,7 +133,7 @@ class PurchaseOrder(models.Model):
picking_count = fields.Integer(compute='_compute_picking', string='Receptions', default=0)
picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False)
- date_planned = fields.Datetime(string='Scheduled Date', compute='_compute_date_planned', inverse='_inverse_date_planned', required=True, select=True, oldname='minimum_planned_date')
+ date_planned = fields.Datetime(string='Scheduled Date', compute='_compute_date_planned', inverse='_inverse_date_planned', required=True, index=True, oldname='minimum_planned_date')
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all')
@@ -145,13 +145,13 @@ class PurchaseOrder(models.Model):
product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
create_uid = fields.Many2one('res.users', 'Responsible')
- company_id = fields.Many2one('res.company', 'Company', required=True, select=1, states=READONLY_STATES, default=lambda self: self.env.user.company_id.id)
+ company_id = fields.Many2one('res.company', 'Company', required=True, index=True, states=READONLY_STATES, default=lambda self: self.env.user.company_id.id)
picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
help="This will determine picking type of incoming shipment")
default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
help="Technical field used to display the Drop Ship Address", readonly=True)
- group_id = fields.Many2one('procurement.group', string="Procurement Group")
+ group_id = fields.Many2one('procurement.group', string="Procurement Group", copy=False)
@api.model
def name_search(self, name, args=None, operator='ilike', limit=100):
@@ -281,6 +281,8 @@ class PurchaseOrder(models.Model):
@api.multi
def button_confirm(self):
for order in self:
+ if order.state not in ['draft', 'sent']:
+ continue
order._add_supplier_to_product()
# Deal with double validation process
if order.company_id.po_double_validation == 'one_step'\
@@ -516,7 +518,7 @@ class PurchaseOrderLine(models.Model):
name = fields.Text(string='Description', required=True)
product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True)
- date_planned = fields.Datetime(string='Scheduled Date', required=True, select=True)
+ date_planned = fields.Datetime(string='Scheduled Date', required=True, index=True)
taxes_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
product_uom = fields.Many2one('product.uom', string='Product Unit of Measure', required=True)
product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True, required=True)
@@ -527,7 +529,7 @@ class PurchaseOrderLine(models.Model):
price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True)
price_tax = fields.Monetary(compute='_compute_amount', string='Tax', store=True)
- order_id = fields.Many2one('purchase.order', string='Order Reference', select=True, required=True, ondelete='cascade')
+ order_id = fields.Many2one('purchase.order', string='Order Reference', index=True, required=True, ondelete='cascade')
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account', domain=[('account_type', '=', 'normal')])
company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, readonly=True)
state = fields.Selection(related='order_id.state', stored=True)
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index a9bc1fa4341..7c561c0d6d1 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -250,7 +250,7 @@
<group>
<group>
<field name="date_planned"/>
- <field name="picking_type_id" domain="[('code','=','incoming')]" options="{'no_create': True}" context="{'special_shortened_wh_name': True}" groups="stock.group_stock_multi_locations"/>
+ <field name="picking_type_id" domain="[('code','=','incoming')]" options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
<field name="dest_address_id" groups="stock.group_stock_multi_locations" attrs="{'invisible': [('default_location_dest_id_usage', '!=', 'customer')], 'required': [('default_location_dest_id_usage', '=', 'customer')]}"/>
<field name="default_location_dest_id_usage" invisible="1"/>
<field name="incoterm_id"/>
diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py
index bf491896911..49ff8ab6338 100644
--- a/addons/purchase_requisition/purchase_requisition.py
+++ b/addons/purchase_requisition/purchase_requisition.py
@@ -257,11 +257,6 @@ class purchase_requisition(osv.osv):
if not confirm:
raise UserError(_('You have no line selected for buying.'))
- #check for complete RFQ
- for quotation in tender.purchase_ids:
- if (self.check_valid_quotation(cr, uid, quotation, context=context)):
- #Set PO state to confirm
- po.button_confirm(cr, uid, [quotation.id], context=context)
#get other confirmed lines per supplier
for po_line in tender.po_line_ids:
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 7054c8add8e..b51ee81f169 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -1555,12 +1555,18 @@ class stock_picking(models.Model):
def rereserve_quants(self, cr, uid, picking, move_ids=[], context=None):
""" Unreserve quants then try to reassign quants."""
+ if context is None:
+ context = {}
stock_move_obj = self.pool.get('stock.move')
if not move_ids:
self.do_unreserve(cr, uid, [picking.id], context=context)
self.action_assign(cr, uid, [picking.id], context=context)
else:
- stock_move_obj.do_unreserve(cr, uid, move_ids, context=context)
+ if 'no_state_change' in context:
+ move = stock_move_obj.browse(cr, uid, move_ids, context=context)
+ stock_move_obj.do_unreserve(cr, uid, [m.id for m in move if m.reserved_quant_ids], context=context)
+ else:
+ stock_move_obj.do_unreserve(cr, uid, move_ids, context=context)
stock_move_obj.action_assign(cr, uid, move_ids, no_prepare=True, context=context)
def do_new_transfer(self, cr, uid, ids, context=None):
@@ -1667,6 +1673,7 @@ class stock_picking(models.Model):
if moves_reassign and (picking.location_id.usage not in ("supplier", "production", "inventory")):
ctx = dict(context)
ctx['reserve_only_ops'] = True #unnecessary to assign other quants than those involved with pack operations as they will be unreserved anyways.
+ ctx['no_state_change'] = True
self.rereserve_quants(cr, uid, picking, move_ids=picking.move_lines.ids, context=ctx)
self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context)
@@ -2047,10 +2054,11 @@ class stock_move(osv.osv):
if move.state in ('done', 'cancel'):
raise UserError(_('Cannot unreserve a done move'))
quant_obj.quants_unreserve(cr, uid, move, context=context)
- if self.find_move_ancestors(cr, uid, move, context=context):
- self.write(cr, uid, [move.id], {'state': 'waiting'}, context=context)
- else:
- self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context)
+ if not context.get('no_state_change'):
+ if self.find_move_ancestors(cr, uid, move, context=context):
+ self.write(cr, uid, [move.id], {'state': 'waiting'}, context=context)
+ else:
+ self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context)
def _prepare_procurement_from_move(self, cr, uid, move, context=None):
origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or move.origin or move.picking_id.name or "/")
@@ -2625,6 +2633,8 @@ class stock_move(osv.osv):
operations = set()
move_qty = {}
for move in self.browse(cr, uid, ids, context=context):
+ if move.picking_id:
+ pickings.add(move.picking_id.id)
move_qty[move.id] = move.product_qty
for link in move.linked_move_operation_ids:
operations.add(link.operation_id)
@@ -4890,11 +4900,6 @@ class stock_picking_type(osv.osv):
name = record.name
if record.warehouse_id:
name = record.warehouse_id.name + ': ' +name
- if context.get('special_shortened_wh_name'):
- if record.warehouse_id:
- name = record.warehouse_id.name
- else:
- name = _('Customer') + ' (' + record.name + ')'
res.append((record.id, name))
return res
diff --git a/addons/warning/warning.py b/addons/warning/warning.py
index 0c971a2a82e..bead8a1b3a0 100644
--- a/addons/warning/warning.py
+++ b/addons/warning/warning.py
@@ -233,6 +233,16 @@ class sale_order_line(osv.osv):
return result
@api.onchange('product_id')
+ def product_id_change(self):
+ warning = self.onchange_product_id_warning()
+ product_info = self.product_id
+ if product_info.sale_line_warn != 'no-message':
+ if product_info.sale_line_warn == 'block':
+ return warning
+ result = super(sale_order_line, self).product_id_change()
+ result['warning'] = warning and warning.get('warning')
+ return result
+
def onchange_product_id_warning(self):
if not self.product_id:
return
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 645c1d9b32d..616b7352053 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -25,11 +25,6 @@ import werkzeug.utils
import werkzeug.wrappers
from openerp.api import Environment
-try:
- import xlwt
-except ImportError:
- xlwt = None
-
import openerp
import openerp.modules.registry
from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
@@ -37,7 +32,7 @@ from openerp.modules import get_resource_path
from openerp.tools import topological_sort
from openerp.tools.translate import _
from openerp.tools import ustr
-from openerp.tools.misc import str2bool
+from openerp.tools.misc import str2bool, xlwt
from openerp import http
from openerp.http import request, serialize_exception as _serialize_exception
from openerp.exceptions import AccessError
diff --git a/addons/web/controllers/pivot.py b/addons/web/controllers/pivot.py
index 571cff94229..41c113bbbdd 100644
--- a/addons/web/controllers/pivot.py
+++ b/addons/web/controllers/pivot.py
@@ -1,13 +1,10 @@
from openerp import http
import json
from openerp.http import request, serialize_exception as _serialize_exception
+from openerp.tools.misc import xlwt
from cStringIO import StringIO
from collections import deque
-try:
- import xlwt
-except ImportError:
- xlwt = None
class TableExporter(http.Controller):
@@ -21,7 +18,7 @@ class TableExporter(http.Controller):
jdata = json.loads(data)
nbr_measures = jdata['nbr_measures']
workbook = xlwt.Workbook()
- worksheet = workbook.add_sheet(jdata['title'][:30])
+ worksheet = workbook.add_sheet(jdata['title'])
header_bold = xlwt.easyxf("font: bold on; pattern: pattern solid, fore_colour gray25;")
header_plain = xlwt.easyxf("pattern: pattern solid, fore_colour gray25;")
bold = xlwt.easyxf("font: bold on;")
diff --git a/addons/web/static/src/js/framework/data.js b/addons/web/static/src/js/framework/data.js
index 3e9396a4e96..60df888ea1d 100644
--- a/addons/web/static/src/js/framework/data.js
+++ b/addons/web/static/src/js/framework/data.js
@@ -688,7 +688,7 @@ var DataSetSearch = DataSet.extend({
.limit(options.limit || false);
q = q.order_by.apply(q, this._sort);
- return q.all().done(function (records) {
+ return this.orderer.add(q.all()).done(function (records) {
// FIXME: not sure about that one, *could* have discarded count
q.count().done(function (count) { self._length = count; });
self.ids = _(records).pluck('id');
diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js
index b1f9058c825..8f046859ee8 100644
--- a/addons/web/static/src/js/views/form_widgets.js
+++ b/addons/web/static/src/js/views/form_widgets.js
@@ -478,6 +478,7 @@ var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin,
this.$('.o_debug_input').val(this.get('value'));
}
} else {
+ this.$('.o_form_input').val('');
this.$('.o_count').text(_t('No selected record'));
var $arrow = this.$('button span').detach();
this.$('button').text(_('Select records ')).append($("<span/>").addClass('fa fa-arrow-right'));
@@ -1073,7 +1074,7 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, {
render_value: function () {
var self = this;
this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
- this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true);
+ this.$("input").prop("checked", false).filter(function () {return this.value == self.get_value();}).prop("checked", true);
this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
}
});
@@ -1528,13 +1529,17 @@ var FieldStatus = common.AbstractField.extend({
return fields;
});
},
- on_click_stage: function (ev) {
+ on_click_stage: _.debounce(function (ev) {
var self = this;
var $li = $(ev.currentTarget);
+ var ul = $li.closest('.oe_form_field_status');
if (this.view.is_disabled) {
return;
}
var val;
+ if (ul.attr('disabled')) {
+ return;
+ }
if (this.field.type == "many2one") {
val = parseInt($li.data("id"), 10);
}
@@ -1551,13 +1556,16 @@ var FieldStatus = common.AbstractField.extend({
this.view.recursive_save().done(function() {
var change = {};
change[self.name] = val;
+ ul.attr('disabled', true);
self.view.dataset.write(self.view.datarecord.id, change).done(function() {
self.view.reload();
+ }).always(function() {
+ ul.removeAttr('disabled');
});
});
}
}
- },
+ }, 300),
});
var FieldMonetary = FieldFloat.extend({
diff --git a/addons/web/views/database_manager.html b/addons/web/views/database_manager.html
index 8b6fd4f09e7..7bbed8f3c20 100644
--- a/addons/web/views/database_manager.html
+++ b/addons/web/views/database_manager.html
@@ -32,7 +32,9 @@
var modal = $(this).parentsUntil('body', '.modal');
if (modal.hasClass('o_database_backup')) {
$(modal).modal('hide');
- $('.list-group').before("<div class='alert alert-info'>The backup may take some time before being ready</div>");
+ if (!$('.alert-backup-long').length) {
+ $('.list-group').before("<div class='alert alert-info alert-backup-long'>The backup may take some time before being ready</div>");
+ }
}
});
});
diff --git a/addons/web_editor/tests/test_ui.py b/addons/web_editor/tests/test_ui.py
index 8fb85d6569f..f4d6d0dd2cb 100644
--- a/addons/web_editor/tests/test_ui.py
+++ b/addons/web_editor/tests/test_ui.py
@@ -1,6 +1,7 @@
import openerp.tests
-
+@openerp.tests.common.at_install(False)
+@openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase):
post_install = True
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 5a6b0d20604..7229aa310b0 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -561,6 +561,10 @@
<xpath expr="//div[@id='wrap']" position="inside">
<div class="oe_structure">
<section class="container">
+ <t t-if="not version">
+ <meta http-equiv="refresh" content="0;URL='/website/info'" />
+ </t>
+ <t t-if="version">
<h1><t t-esc="res_company.name"/>
<small>Odoo Version <t t-raw="version.get('server_version')"/></small>
</h1>
@@ -608,6 +612,7 @@
</t>
</dl>
</div>
+ </t>
</section>
</div>
</xpath>
diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py
index e59da8736b9..5c0db09e5bc 100644
--- a/addons/website_blog/controllers/main.py
+++ b/addons/website_blog/controllers/main.py
@@ -228,8 +228,8 @@ class WebsiteBlog(http.Controller):
all_post_ids = blog_post_obj.search(cr, uid, [('blog_id', '=', blog.id)], context=context)
# should always return at least the current post
current_blog_post_index = all_post_ids.index(blog_post.id)
- next_post_id = all_post_ids[0 if current_blog_post_index == len(all_post_ids) - 1 \
- else current_blog_post_index + 1]
+ nb_posts = len(all_post_ids)
+ next_post_id = all_post_ids[(current_blog_post_index + 1) % nb_posts] if nb_posts > 1 else None
next_post = next_post_id and blog_post_obj.browse(cr, uid, next_post_id, context=context) or False
values = {
diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py
index 1dddd28a23a..c816b8914ca 100644
--- a/addons/website_form/controllers/main.py
+++ b/addons/website_form/controllers/main.py
@@ -216,7 +216,7 @@ class WebsiteForm(http.Controller):
# If some attachments didn't match a field on the model,
# we create a mail.message to link them to the record
if orphan_attachment_ids:
- if model.name != 'mail.mail':
+ if model.model != 'mail.mail':
values = {
'body': _('<p>Attached files : </p>'),
'model': model.model,
diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py
index 3c0757f4793..fa23f56478d 100644
--- a/addons/website_forum/models/forum.py
+++ b/addons/website_forum/models/forum.py
@@ -209,14 +209,14 @@ class Post(models.Model):
)
# history
- create_date = fields.Datetime('Asked on', select=True, readonly=True)
- create_uid = fields.Many2one('res.users', string='Created by', select=True, readonly=True)
- write_date = fields.Datetime('Update on', select=True, readonly=True)
+ create_date = fields.Datetime('Asked on', index=True, readonly=True)
+ create_uid = fields.Many2one('res.users', string='Created by', index=True, readonly=True)
+ write_date = fields.Datetime('Update on', index=True, readonly=True)
bump_date = fields.Datetime('Bumped on', readonly=True,
help="Technical field allowing to bump a question. Writing on this field will trigger "
"a write on write_date and therefore bump the post. Directly writing on write_date "
"is currently not supported and this field is a workaround.")
- write_uid = fields.Many2one('res.users', string='Updated by', select=True, readonly=True)
+ write_uid = fields.Many2one('res.users', string='Updated by', index=True, readonly=True)
relevancy = fields.Float('Relevance', compute="_compute_relevancy", store=True)
# vote
@@ -244,7 +244,7 @@ class Post(models.Model):
# closing
closed_reason_id = fields.Many2one('forum.post.reason', string='Reason')
- closed_uid = fields.Many2one('res.users', string='Closed by', select=1)
+ closed_uid = fields.Many2one('res.users', string='Closed by', index=True)
closed_date = fields.Datetime('Closed on', readonly=True)
# karma calculation and access
@@ -812,7 +812,7 @@ class Vote(models.Model):
post_id = fields.Many2one('forum.post', string='Post', ondelete='cascade', required=True)
user_id = fields.Many2one('res.users', string='User', required=True, default=lambda self: self._uid)
vote = fields.Selection([('1', '1'), ('-1', '-1'), ('0', '0')], string='Vote', required=True, default='1')
- create_date = fields.Datetime('Create Date', select=True, readonly=True)
+ create_date = fields.Datetime('Create Date', index=True, readonly=True)
forum_id = fields.Many2one('forum.forum', string='Forum', related="post_id.forum_id", store=True)
recipient_id = fields.Many2one('res.users', string='To', related="post_id.create_uid", store=True)
diff --git a/addons/website_forum/models/res_users.py b/addons/website_forum/models/res_users.py
index 3a5d8c92395..b389dcaa264 100644
--- a/addons/website_forum/models/res_users.py
+++ b/addons/website_forum/models/res_users.py
@@ -18,7 +18,7 @@ class Users(models.Model):
['country_id', 'city', 'website', 'website_description', 'website_published']))
return init_res
- create_date = fields.Datetime('Create Date', readonly=True, copy=False, select=True)
+ create_date = fields.Datetime('Create Date', readonly=True, copy=False, index=True)
karma = fields.Integer('Karma', default=0)
badge_ids = fields.One2many('gamification.badge.user', 'user_id', string='Badges', copy=False)
gold_badge = fields.Integer('Gold badges count', compute="_get_user_badge_level")
diff --git a/addons/website_quote/static/src/css/website_quotation.css b/addons/website_quote/static/src/css/website_quotation.css
index 6d32f06ab96..30ba234d14b 100644
--- a/addons/website_quote/static/src/css/website_quotation.css
+++ b/addons/website_quote/static/src/css/website_quotation.css
@@ -1,17 +1,11 @@
.o_website_quote .bs-sidebar {
- position: fixed;
+ position: relative;
+ width: 100%;
z-index : 1;
background-color: #f7f5fa;
border-radius: 5px;
}
-/* mobile preview: the left menu overlaps the quote */
-@media (max-width: 992px) {
- .o_website_quote .bs-sidebar {
- position: relative;
- }
-}
-
.o_website_quote .bs-sidenav {
padding-top: 10px;
padding-bottom: 10px;
@@ -56,14 +50,6 @@
.o_website_quote .bs-sidebar .nav > .active > ul {
display: block;
}
- .o_website_quote .bs-sidebar {
- width: 213px;
- }
-}
-@media (min-width: 1200px) {
- .o_website_quote .bs-sidebar {
- width: 263px;
- }
}
@media print {
body {
diff --git a/addons/website_quote/static/src/js/website_quotation.js b/addons/website_quote/static/src/js/website_quotation.js
index 2ff25986179..798b1ec5a76 100644
--- a/addons/website_quote/static/src/js/website_quotation.js
+++ b/addons/website_quote/static/src/js/website_quotation.js
@@ -2,6 +2,7 @@ odoo.define('website_quote.website_quote', function (require) {
'use strict';
var ajax = require('web.ajax');
+var config = require('web.config');
var Widget = require('web.Widget');
var website = require('website.website');
@@ -175,6 +176,22 @@ if(!$('.o_website_quote').length) {
nav_menu.setElement($('[data-id="quote_sidebar"]'));
nav_menu.start($('body[data-target=".navspy"]'));
+ var $bs_sidebar = $(".o_website_quote .bs-sidebar");
+ $(window).on('resize', _.throttle(adapt_sidebar_position, 200, {leading: false}));
+ adapt_sidebar_position();
+
+ function adapt_sidebar_position() {
+ $bs_sidebar.css({
+ position: "",
+ width: "",
+ });
+ if (config.device.size_class >= config.device.SIZES.MD) {
+ $bs_sidebar.css({
+ position: "fixed",
+ width: $bs_sidebar.outerWidth(),
+ });
+ }
+ }
});
odoo.define('website_quote.payment_method', function (require) {
diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py
index 7c8593c48e7..2e0091b53f1 100644
--- a/addons/website_slides/controllers/main.py
+++ b/addons/website_slides/controllers/main.py
@@ -295,8 +295,8 @@ class website_slides(http.Controller):
payload = request.httprequest.content_length
# payload is total request content size so it's not exact size of file.
# already add client validation this is for double check if client alter.
- if (payload / 1024 / 1024 > 15):
- return {'error': _('File is too big. File size cannot exceed 15MB')}
+ if (payload / 1024 / 1024 > 25):
+ return {'error': _('File is too big. File size cannot exceed 25MB')}
values = dict((fname, post[fname]) for fname in [
'name', 'url', 'tag_ids', 'slide_type', 'channel_id',
diff --git a/addons/website_slides/models/slides.py b/addons/website_slides/models/slides.py
index 6ecc0540437..e7455ca138a 100644
--- a/addons/website_slides/models/slides.py
+++ b/addons/website_slides/models/slides.py
@@ -192,7 +192,7 @@ class EmbeddedSlide(models.Model):
_description = 'Embedded Slides View Counter'
_rec_name = 'slide_id'
- slide_id = fields.Many2one('slide.slide', string="Presentation", required=True, select=1)
+ slide_id = fields.Many2one('slide.slide', string="Presentation", required=True, index=True)
url = fields.Char('Third Party Website URL', required=True)
count_views = fields.Integer('# Views', default=1)
diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js
index ea7ec99e401..b8fbe164421 100644
--- a/addons/website_slides/static/src/js/slides_upload.js
+++ b/addons/website_slides/static/src/js/slides_upload.js
@@ -86,8 +86,8 @@ var SlideDialog = Widget.extend({
this.reset_file();
return;
}
- if (file.size / 1024 / 1024 > 15) {
- this.display_alert(_t("File is too big. File size cannot exceed 15MB"));
+ if (file.size / 1024 / 1024 > 25) {
+ this.display_alert(_t("File is too big. File size cannot exceed 25MB"));
this.reset_file();
return;
}