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)] + + 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: - st_lines_filter += [('statement_id', 'in', statements.ids)] + 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 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)) + #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 @@ -449,6 +445,40 @@ class AccountBankStatementLine(models.Model): # Reconciliation interface methods #################################################### + @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 """ @@ -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 + 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]) - 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 + 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,44 +1179,64 @@ 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); - - // If everything is reconciled, show end message - if (self.lines.length === 0) { - 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(); - } + 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(); }); - }); - }); + + }); + return self.display_reconciliation_propositions(); }); }); }, + 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() { if (! this.single_statement) return; this.$(".statement_name span").hide(); @@ -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 @@
+ 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'] = _('Hello,
%s invited you to follow a new document.
Attached files :
'), '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; } diff --git a/doc/_extensions/odoo/layout.html b/doc/_extensions/odoo/layout.html index a7ed5078040..000019b32cf 100644 --- a/doc/_extensions/odoo/layout.html +++ b/doc/_extensions/odoo/layout.html @@ -230,7 +230,7 @@