diff --git a/addons/.bzrignore b/addons/.bzrignore deleted file mode 100644 index 8c348710117..00000000000 --- a/addons/.bzrignore +++ /dev/null @@ -1,2 +0,0 @@ -.* -**/node_modules diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 75940d2d83d..1ddab4bece9 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -26,6 +26,9 @@ from openerp.report import report_sxw class account_bank_statement(osv.osv): def create(self, cr, uid, vals, context=None): + if vals.get('name', '/') == '/': + journal_id = vals.get('journal_id', self._default_journal_id(cr, uid, context=context)) + vals['name'] = self._compute_default_statement_name(cr, uid, journal_id, context=context) if 'line_ids' in vals: for idx, line in enumerate(vals['line_ids']): line[2]['sequence'] = idx + 1 @@ -65,17 +68,14 @@ class account_bank_statement(osv.osv): return periods[0] return False - def _compute_default_statement_name(self, cr, uid, context=None): + def _compute_default_statement_name(self, cr, uid, journal_id, context=None): if context is None: context = {} obj_seq = self.pool.get('ir.sequence') - default_journal_id = self._default_journal_id(cr, uid, context=context) - if default_journal_id != False: - period = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, context=context), context=context) - context['fiscalyear_id'] = period.fiscalyear_id.id - journal = self.pool.get('account.journal').browse(cr, uid, default_journal_id, None) - return obj_seq.next_by_id(cr, uid, journal.sequence_id.id, context=context) - return obj_seq.next_by_code(cr, uid, 'account.bank.statement', context=context) + period = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, context=context), context=context) + context['fiscalyear_id'] = period.fiscalyear_id.id + journal = self.pool.get('account.journal').browse(cr, uid, journal_id, None) + return obj_seq.next_by_id(cr, uid, journal.sequence_id.id, context=context) def _currency(self, cursor, user, ids, name, args, context=None): res = {} @@ -150,7 +150,7 @@ class account_bank_statement(osv.osv): } _defaults = { - 'name': _compute_default_statement_name, + 'name': '/', 'date': fields.date.context_today, 'state': 'draft', 'journal_id': _default_journal_id, @@ -213,11 +213,8 @@ class account_bank_statement(osv.osv): def _get_counter_part_account(sefl, cr, uid, st_line, context=None): """Retrieve the account to use in the counterpart move. - This method may be overridden to implement custom move generation (making sure to - call super() to establish a clean extension chain). - :param browse_record st_line: account.bank.statement.line record to - create the move from. + :param browse_record st_line: account.bank.statement.line record to create the move from. :return: int/long of the account.account to use as counterpart """ if st_line.amount >= 0: @@ -226,26 +223,19 @@ class account_bank_statement(osv.osv): def _get_counter_part_partner(sefl, cr, uid, st_line, context=None): """Retrieve the partner to use in the counterpart move. - This method may be overridden to implement custom move generation (making sure to - call super() to establish a clean extension chain). - :param browse_record st_line: account.bank.statement.line record to - create the move from. + :param browse_record st_line: account.bank.statement.line record to create the move from. :return: int/long of the res.partner to use as counterpart """ return st_line.partner_id and st_line.partner_id.id or False def _prepare_bank_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, context=None): """Compute the args to build the dict of values to create the counter part move line from a - statement line by calling the _prepare_move_line_vals. This method may be - overridden to implement custom move generation (making sure to call super() to - establish a clean extension chain). + statement line by calling the _prepare_move_line_vals. - :param browse_record st_line: account.bank.statement.line record to - create the move from. + :param browse_record st_line: account.bank.statement.line record to create the move from. :param int/long move_id: ID of the account.move to link the move line :param float amount: amount of the move line - :param int/long account_id: ID of account to use as counter part :param int/long company_currency_id: ID of currency of the concerned company :return: dict of value to create() the bank account.move.line """ @@ -258,7 +248,6 @@ class account_bank_statement(osv.osv): if st_line.statement_id.currency.id != company_currency_id: amt_cur = st_line.amount cur_id = st_line.currency_id or st_line.statement_id.currency.id - # TODO : FIXME the amount should be in the journal currency if st_line.currency_id and st_line.amount_currency: amt_cur = st_line.amount_currency cur_id = st_line.currency_id.id @@ -269,9 +258,7 @@ class account_bank_statement(osv.osv): def _prepare_move_line_vals(self, cr, uid, st_line, move_id, debit, credit, currency_id=False, amount_currency=False, account_id=False, partner_id=False, context=None): """Prepare the dict of values to create the move line from a - statement line. All non-mandatory args will replace the default computed one. - This method may be overridden to implement custom move generation (making sure to - call super() to establish a clean extension chain). + statement line. :param browse_record st_line: account.bank.statement.line record to create the move from. @@ -342,21 +329,29 @@ class account_bank_statement(osv.osv): move_ids.append(st_line.journal_entry_id.id) self.pool.get('account.move').post(cr, uid, move_ids, context=context) self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st.name,), context=context) + self.link_bank_to_partner(cr, uid, ids, context=context) return self.write(cr, uid, ids, {'state':'confirm'}, context=context) def button_cancel(self, cr, uid, ids, context=None): - done = [] account_move_obj = self.pool.get('account.move') + reconcile_pool = self.pool.get('account.move.reconcile') + move_line_pool = self.pool.get('account.move.line') + move_ids = [] for st in self.browse(cr, uid, ids, context=context): - if st.state=='draft': - continue - move_ids = [] for line in st.line_ids: - move_ids += [x.id for x in line.move_ids] + if line.journal_entry_id: + move_ids.append(line.journal_entry_id.id) + for aml in line.journal_entry_id.line_id: + if aml.reconcile_id: + move_lines = [l.id for l in aml.reconcile_id.line_id] + move_lines.remove(aml.id) + reconcile_pool.unlink(cr, uid, [aml.reconcile_id.id], context=context) + if len(move_lines) >= 2: + move_line_pool.reconcile_partial(cr, uid, move_lines, 'auto', context=context) + if move_ids: account_move_obj.button_cancel(cr, uid, move_ids, context=context) account_move_obj.unlink(cr, uid, move_ids, context) - done.append(st.id) - return self.write(cr, uid, done, {'state':'draft'}, context=context) + return self.write(cr, uid, ids, {'state': 'draft'}, context=context) def _compute_balance_end_real(self, cr, uid, journal_id, context=None): res = False @@ -417,18 +412,35 @@ class account_bank_statement(osv.osv): def number_of_lines_reconciled(self, cr, uid, id, context=None): bsl_obj = self.pool.get('account.bank.statement.line') - return bsl_obj.search_count(cr, uid, [('statement_id','=',id), ('journal_entry_id','!=',False)], context=context) - + return bsl_obj.search_count(cr, uid, [('statement_id', '=', id), ('journal_entry_id', '!=', False)], context=context) + def get_format_currency_js_function(self, cr, uid, id, context=None): """ Returns a string that can be used to instanciate a javascript function. - That function formats a number according to the statement's journal currency """ + That function formats a number according to the statement line's currency or the statement currency""" company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id - currency_obj = id and self.browse(cr, uid, id, context=context).journal_id.currency or company_currency + st = id and self.browse(cr, uid, id, context=context) + if not st: + return + statement_currency = st.journal_id.currency or company_currency digits = 2 # TODO : from currency_obj - if currency_obj.position == 'after': - return "return amount.toFixed("+str(digits)+") + ' "+currency_obj.symbol+"';" - elif currency_obj.position == 'before': - return "return '"+currency_obj.symbol+" ' + amount.toFixed("+str(digits)+");" + function = "" + done_currencies = [] + for st_line in st.line_ids: + st_line_currency = st_line.currency_id or statement_currency + if st_line_currency.id not in done_currencies: + if st_line_currency.position == 'after': + return_str = "return amount.toFixed(" + str(digits) + ") + ' " + st_line_currency.symbol + "';" + else: + return_str = "return '" + st_line_currency.symbol + " ' + amount.toFixed(" + str(digits) + ");" + function += "if (currency_id === " + str(st_line_currency.id) + "){ " + return_str + " }" + done_currencies.append(st_line_currency.id) + return function + + def link_bank_to_partner(self, cr, uid, ids, context=None): + for statement in self.browse(cr, uid, ids, context=context): + for st_line in statement.line_ids: + if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id.id != st_line.partner_id.id: + self.pool.get('res.partner.bank').write(cr, uid, [st_line.bank_account_id.id], {'partner_id': st_line.partner_id.id}, context=context) class account_bank_statement_line(osv.osv): @@ -444,35 +456,40 @@ class account_bank_statement_line(osv.osv): } for mv_line in reconciliation_data['reconciliation_proposition']: mv_line_ids_selected.append(mv_line['id']) - ret.append(reconciliation_data); - + ret.append(reconciliation_data) + # Check if, now that 'candidate' move lines were selected, there are moves left for statement lines - for reconciliation_data in ret: - if not reconciliation_data['st_line']['has_no_partner']: - if self.get_move_lines_counterparts(cr, uid, reconciliation_data['st_line']['id'], excluded_ids=mv_line_ids_selected, count=True, context=context) == 0: - reconciliation_data['st_line']['no_match'] = True + #for reconciliation_data in ret: + # if not reconciliation_data['st_line']['has_no_partner']: + # st_line = self.browse(cr, uid, reconciliation_data['st_line']['id'], context=context) + # if not self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=mv_line_ids_selected, count=True, context=context): + # reconciliation_data['st_line']['no_match'] = True return ret def get_statement_line_for_reconciliation(self, cr, uid, id, context=None): """ Returns the data required by the bank statement reconciliation use case """ line = self.browse(cr, uid, id, context=context) statement_currency = line.journal_id.currency or line.journal_id.company_id.currency_id + amount = line.amount rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_widget', context=context) amount_str = line.amount > 0 and line.amount or -line.amount amount_str = rml_parser.formatLang(amount_str, currency_obj=statement_currency) amount_currency_str = "" if line.amount_currency and line.currency_id: - amount_currency_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id) + amount_currency_str = amount_str + amount_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id) + amount = line.amount_currency - dict = { + data = { 'id': line.id, 'ref': line.ref, 'note': line.note or "", 'name': line.name, 'date': line.date, - 'amount': line.amount, + 'amount': amount, 'amount_str': amount_str, - 'no_match': self.get_move_lines_counterparts(cr, uid, id, count=True, context=context) == 0 and line.partner_id.id, + 'currency_id': line.currency_id.id or statement_currency.id, + 'no_match': self.get_move_lines_counterparts(cr, uid, line, count=True, context=context) == 0, 'partner_id': line.partner_id.id, 'statement_id': line.statement_id.id, 'account_code': line.journal_id.default_debit_account_id.code, @@ -482,93 +499,116 @@ class account_bank_statement_line(osv.osv): 'has_no_partner': not line.partner_id.id, } if line.partner_id.id: - if line.amount > 0: - dict['open_balance_account_id'] = line.partner_id.property_account_receivable.id - else: - dict['open_balance_account_id'] = line.partner_id.property_account_payable.id - return dict + data['open_balance_account_id'] = line.partner_id.property_account_payable.id + if amount > 0: + data['open_balance_account_id'] = line.partner_id.property_account_receivable.id + return data + + def search_structured_com(self, cr, uid, st_line, context=None): + if not st_line.ref: + return + domain = [('ref', '=', st_line.ref)] + if st_line.partner_id: + domain += [('partner_id', '=', st_line.partner_id.id)] + ids = self.pool.get('account.move.line').search(cr, uid, domain, limit=1, context=context) + return ids and ids[0] or False def get_reconciliation_proposition(self, cr, uid, id, excluded_ids=[], context=None): """ Returns move lines that constitute the best guess to reconcile a statement line. """ st_line = self.browse(cr, uid, id, context=context) company_currency = st_line.journal_id.company_id.currency_id.id statement_currency = st_line.journal_id.currency.id or company_currency - # either use the unsigned debit/credit fields or the signed amount_currency field sign = 1 if statement_currency == company_currency: + amount_field = 'credit' if st_line.amount > 0: amount_field = 'debit' - else: - amount_field = 'credit' else: amount_field = 'amount_currency' if st_line.amount < 0: sign = -1 + # look for structured communication + exact_match_id = self.search_structured_com(cr, uid, st_line, context=context) + if exact_match_id: + return self.make_counter_part_lines(cr, uid, st_line, [exact_match_id], count=False, context=context) + #we don't propose anything if there is no partner detected + if not st_line.partner_id.id: + return [] # look for exact match - exact_match_id = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=1, additional_domain=[(amount_field,'=',(sign*st_line.amount))]) + exact_match_id = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=1, additional_domain=[(amount_field, '=', (sign * st_line.amount))]) if exact_match_id: return exact_match_id # select oldest move lines if sign == -1: - mv_lines = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field,'<',0)]) + mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field, '<', 0)]) else: - mv_lines = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field,'>',0)]) + mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field, '>', 0)]) ret = [] total = 0 # get_move_lines_counterparts inverts debit and credit amount_field = 'debit' if amount_field == 'credit' else 'credit' for line in mv_lines: - if total + line[amount_field] <= st_line.amount: + if total + line[amount_field] <= abs(st_line.amount): ret.append(line) total += line[amount_field] - else: + if total >= abs(st_line.amount): break - return ret - def get_move_lines_counterparts(self, cr, uid, id, excluded_ids=[], str="", offset=0, limit=None, count=False, additional_domain=[], context=None): + def get_move_lines_counterparts_id(self, cr, uid, st_line_id, excluded_ids=[], filter_str="", offset=0, limit=None, count=False, additional_domain=[], context=None): + st_line = self.browse(cr, uid, st_line_id, context=context) + return self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids, filter_str, offset, limit, count, additional_domain, context=context) + + def get_move_lines_counterparts(self, cr, uid, st_line, excluded_ids=[], filter_str="", offset=0, limit=None, count=False, additional_domain=[], context=None): """ Find the move lines that could be used to reconcile a statement line and returns the counterpart that could be created to reconcile them If count is true, only returns the count. - :param integer id: the id of the statement line + :param st_line: the browse record of the statement line :param integers list excluded_ids: ids of move lines that should not be fetched - :param string str: string to filter lines + :param string filter_str: string to filter lines :param integer offset: offset of the request :param integer limit: number of lines to fetch :param boolean count: just return the number of records :param tuples list domain: additional domain restrictions """ - if context is None: - context = {} - - rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_counterpart_widget', context=context) - st_line = self.browse(cr, uid, id, context=context) - company_currency = st_line.journal_id.company_id.currency_id - statement_currency = st_line.journal_id.currency or company_currency mv_line_pool = self.pool.get('account.move.line') - currency_obj = self.pool.get('res.currency') domain = additional_domain + [ - ('partner_id', '=', st_line.partner_id.id), ('reconcile_id', '=', False), - ('state','=','valid'), - '|',('account_id.type', '=', 'receivable'), - ('account_id.type', '=', 'payable'), #Let the front-end warn the user if he tries to mix payable and receivable in the same reconciliation + ('state', '=', 'valid'), ] + if st_line.partner_id.id: + domain += [('partner_id', '=', st_line.partner_id.id), + '|', ('account_id.type', '=', 'receivable'), + ('account_id.type', '=', 'payable')] + else: + domain += [('account_id.reconcile', '=', True)] + #domain += [('account_id.reconcile', '=', True), ('account_id.type', '=', 'other')] if excluded_ids: domain.append(('id', 'not in', excluded_ids)) - if str: - domain += ['|', ('move_id.name', 'ilike', str), ('move_id.ref', 'ilike', str)] + if filter_str: + if not st_line.partner_id: + domain += [ '|', ('partner_id.name', 'ilike', filter_str)] + domain += ['|', ('move_id.name', 'ilike', filter_str), ('move_id.ref', 'ilike', filter_str)] + line_ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context) + return self.make_counter_part_lines(cr, uid, st_line, line_ids, count=count, context=context) + + def make_counter_part_lines(self, cr, uid, st_line, line_ids, count=False, context=None): + if context is None: + context = {} + mv_line_pool = self.pool.get('account.move.line') + currency_obj = self.pool.get('res.currency') + company_currency = st_line.journal_id.company_id.currency_id + statement_currency = st_line.journal_id.currency or company_currency + rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_counterpart_widget', context=context) #partially reconciled lines can be displayed only once reconcile_partial_ids = [] - ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context) - if count: nb_lines = 0 - for line in mv_line_pool.browse(cr, uid, ids, context=context): + for line in mv_line_pool.browse(cr, uid, line_ids, context=context): if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: continue nb_lines += 1 @@ -577,7 +617,7 @@ class account_bank_statement_line(osv.osv): return nb_lines else: ret = [] - for line in mv_line_pool.browse(cr, uid, ids, context=context): + for line in mv_line_pool.browse(cr, uid, line_ids, context=context): if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: continue amount_currency_str = "" @@ -595,8 +635,12 @@ class account_bank_statement_line(osv.osv): 'period_name': line.period_id.name, 'journal_name': line.journal_id.name, 'amount_currency_str': amount_currency_str, + 'partner_id': line.partner_id.id, + 'partner_name': line.partner_id.name, + 'has_no_partner': not bool(st_line.partner_id.id), } - if statement_currency.id != company_currency.id and line.currency_id and line.currency_id.id == statement_currency.id: + st_line_currency = st_line.currency_id or statement_currency + if st_line.currency_id and line.currency_id and line.currency_id.id == st_line.currency_id.id: if line.amount_residual_currency < 0: ret_line['debit'] = 0 ret_line['credit'] = -line.amount_residual_currency @@ -613,21 +657,46 @@ class account_bank_statement_line(osv.osv): ret_line['credit'] = line.amount_residual if line.debit != 0 else 0 ctx = context.copy() ctx.update({'date': st_line.date}) - ret_line['debit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, ret_line['debit'], context=ctx) - ret_line['credit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, ret_line['credit'], context=ctx) - ret_line['debit_str'] = rml_parser.formatLang(ret_line['debit'], currency_obj=statement_currency) - ret_line['credit_str'] = rml_parser.formatLang(ret_line['credit'], currency_obj=statement_currency) + ret_line['debit'] = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, ret_line['debit'], context=ctx) + ret_line['credit'] = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, ret_line['credit'], context=ctx) + ret_line['debit_str'] = rml_parser.formatLang(ret_line['debit'], currency_obj=st_line_currency) + ret_line['credit_str'] = rml_parser.formatLang(ret_line['credit'], currency_obj=st_line_currency) ret.append(ret_line) if line.reconcile_partial_id: reconcile_partial_ids.append(line.reconcile_partial_id.id) return ret + def get_currency_rate_line(self, cr, uid, st_line, currency_diff, move_id, context=None): + if currency_diff < 0: + account_id = st_line.company_id.expense_currency_exchange_account_id.id + if not account_id: + raise osv.except_osv(_('Insufficient Configuration!'), _("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates.")) + else: + account_id = st_line.company_id.income_currency_exchange_account_id.id + if not account_id: + raise osv.except_osv(_('Insufficient Configuration!'), _("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates.")) + return { + 'move_id': move_id, + 'name': _('change') + ': ' + (st_line.name or '/'), + 'period_id': st_line.statement_id.period_id.id, + 'journal_id': st_line.journal_id.id, + 'partner_id': st_line.partner_id.id, + 'company_id': st_line.company_id.id, + 'statement_id': st_line.statement_id.id, + 'debit': currency_diff < 0 and -currency_diff or 0, + 'credit': currency_diff > 0 and currency_diff or 0, + 'date': st_line.date, + 'account_id': account_id + } + def process_reconciliation(self, cr, uid, id, mv_line_dicts, context=None): """ Creates a move line for each item of mv_line_dicts and for the statement line. Reconcile a new move line with its counterpart_move_line_id if specified. Finally, mark the statement line as reconciled by putting the newly created move id in the column journal_entry_id. :param int id: id of the bank statement line :param list of dicts mv_line_dicts: move lines to create. If counterpart_move_line_id is specified, reconcile with it """ + if context is None: + context = {} st_line = self.browse(cr, uid, id, context=context) company_currency = st_line.journal_id.company_id.currency_id statement_currency = st_line.journal_id.currency or company_currency @@ -637,7 +706,7 @@ class account_bank_statement_line(osv.osv): currency_obj = self.pool.get('res.currency') # Checks - if st_line.journal_entry_id.id != False: + if st_line.journal_entry_id.id: raise osv.except_osv(_('Error!'), _('The bank statement line was already reconciled.')) for mv_line_dict in mv_line_dicts: for field in ['debit', 'credit', 'amount_currency']: @@ -657,37 +726,50 @@ class account_bank_statement_line(osv.osv): amount = currency_obj.compute(cr, uid, st_line.statement_id.currency.id, company_currency.id, st_line.amount, context=context) bank_st_move_vals = bs_obj._prepare_bank_move_line(cr, uid, st_line, move_id, amount, company_currency.id, context=context) aml_obj.create(cr, uid, bank_st_move_vals, context=context) - st_line_currency_rate = bank_st_move_vals['amount_currency'] and statement_currency.id == company_currency.id and (bank_st_move_vals['amount_currency'] / st_line.amount) or False - st_line_currency = bank_st_move_vals['currency_id'] # Complete the dicts - st_line_statement_id = st_line.statement_id.id - st_line_journal_id = st_line.journal_id.id - st_line_partner_id = st_line.partner_id.id - st_line_company_id = st_line.company_id.id - st_line_period_id = st_line.statement_id.period_id.id + st_line_currency = st_line.currency_id or statement_currency + st_line_currency_rate = st_line.currency_id and statement_currency.id == company_currency.id and (st_line.amount_currency / st_line.amount) or False + to_create = [] for mv_line_dict in mv_line_dicts: mv_line_dict['ref'] = move_name mv_line_dict['move_id'] = move_id - mv_line_dict['period_id'] = st_line_period_id - mv_line_dict['journal_id'] = st_line_journal_id - mv_line_dict['partner_id'] = st_line_partner_id - mv_line_dict['company_id'] = st_line_company_id - mv_line_dict['statement_id'] = st_line_statement_id + mv_line_dict['period_id'] = st_line.statement_id.period_id.id + mv_line_dict['journal_id'] = st_line.journal_id.id + mv_line_dict['company_id'] = st_line.company_id.id + mv_line_dict['statement_id'] = st_line.statement_id.id if mv_line_dict.get('counterpart_move_line_id'): mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context) mv_line_dict['account_id'] = mv_line.account_id.id - if statement_currency.id != company_currency.id: + if st_line_currency.id != company_currency.id: mv_line_dict['amount_currency'] = mv_line_dict['debit'] - mv_line_dict['credit'] - mv_line_dict['currency_id'] = statement_currency.id - mv_line_dict['debit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['debit']) - mv_line_dict['credit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['credit']) - elif st_line_currency and st_line_currency_rate: - mv_line_dict['amount_currency'] = self.pool.get('res.currency').round(cr, uid, st_line.currency_id, (mv_line_dict['debit'] - mv_line_dict['credit']) * st_line_currency_rate) - mv_line_dict['currency_id'] = st_line_currency - + mv_line_dict['currency_id'] = st_line_currency.id + if st_line.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate: + debit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['debit'] / st_line_currency_rate) + credit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['credit'] / st_line_currency_rate) + else: + debit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=context) + credit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=context) + if mv_line_dict.get('counterpart_move_line_id'): + #post an account line that use the same currency rate than the counterpart (to balance the account) and post the difference in another line + ctx = context.copy() + ctx['date'] = mv_line.date + debit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=ctx) + credit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=ctx) + mv_line_dict['credit'] = credit_at_old_rate + mv_line_dict['debit'] = debit_at_old_rate + if debit_at_old_rate - debit_at_current_rate: + currency_diff = debit_at_current_rate - debit_at_old_rate + to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context)) + if credit_at_old_rate - credit_at_current_rate: + currency_diff = credit_at_current_rate - credit_at_old_rate + to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context)) + else: + mv_line_dict['debit'] = debit_at_current_rate + mv_line_dict['credit'] = credit_at_current_rate + to_create.append(mv_line_dict) # Create move lines move_line_pairs_to_reconcile = [] - for mv_line_dict in mv_line_dicts: + for mv_line_dict in to_create: counterpart_move_line_id = None # NB : this attribute is irrelevant for aml_obj.create() and needs to be removed from the dict if mv_line_dict.get('counterpart_move_line_id'): counterpart_move_line_id = mv_line_dict['counterpart_move_line_id'] @@ -723,7 +805,7 @@ class account_bank_statement_line(osv.osv): 'bank_account_id': fields.many2one('res.partner.bank','Bank Account'), 'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='cascade'), 'journal_id': fields.related('statement_id', 'journal_id', type='many2one', relation='account.journal', string='Journal', store=True, readonly=True), - 'ref': fields.char('Reference', size=32), + 'ref': fields.char('Structured Communication'), 'note': fields.text('Notes'), 'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of bank statement lines."), 'company_id': fields.related('statement_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True), diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index eb43830f8f6..96d63c545c6 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -295,7 +295,8 @@ class account_invoice(osv.osv): }, multi='all'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'), - 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}), + 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}, + domain="[('type', 'in', {'out_invoice': ['sale'], 'out_refund': ['sale_refund'], 'in_refund': ['purchase_refund'], 'in_invoice': ['purchase']}.get(type, [])), ('company_id', '=', company_id)]"), 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}), 'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}), 'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean', diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 3d8d8302f32..eb705b42995 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -127,8 +127,8 @@ class account_move_line(osv.osv): if move_line.reconcile_id: continue - if not move_line.account_id.type in ('payable', 'receivable'): - #this function does not suport to be used on move lines not related to payable or receivable accounts + if not move_line.account_id.reconcile: + #this function does not suport to be used on move lines not related to a reconcilable account continue if move_line.currency_id: @@ -741,6 +741,8 @@ class account_move_line(osv.osv): def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): if context is None: context = {} + if context.get('fiscalyear'): + args.append(('period_id.fiscalyear_id', '=', context.get('fiscalyear', False))) if context and context.get('next_partner_only', False): if not context.get('partner_id', False): partner = self.list_partners_to_reconcile(cr, uid, context=context) @@ -823,7 +825,7 @@ class account_move_line(osv.osv): 'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge) }, context=context) move_rec_obj.reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context) - return True + return r_id def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context=None): account_obj = self.pool.get('account.account') diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 8afa19a63b0..1d340001c55 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -351,7 +351,7 @@ diff --git a/addons/account/demo/account_bank_statement.xml b/addons/account/demo/account_bank_statement.xml index 693dbc876b5..b79d8b95cd6 100644 --- a/addons/account/demo/account_bank_statement.xml +++ b/addons/account/demo/account_bank_statement.xml @@ -15,7 +15,7 @@ - 001 + @@ -26,7 +26,7 @@ - 002 + SAJ2014002 @@ -37,7 +37,7 @@ - 003 + @@ -47,7 +47,7 @@ - 004 + diff --git a/addons/account/static/src/css/account_bank_statement_reconciliation.css b/addons/account/static/src/css/account_bank_statement_reconciliation.css index 626d1f32dab..05caeab2655 100644 --- a/addons/account/static/src/css/account_bank_statement_reconciliation.css +++ b/addons/account/static/src/css/account_bank_statement_reconciliation.css @@ -120,8 +120,6 @@ cursor: pointer; } .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_match:not(.no_partner) .toggle_match { visibility: hidden !important; } - .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_partner .partner_name, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_partner .line_open_balance { - display: none !important; } .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line > table > tbody > tr:nth-child(1) > td table { margin-bottom: 10px; } .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line table.details td:first-child { @@ -202,10 +200,6 @@ cursor: pointer; } .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(6) { border-left: 1px solid black; } - .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr.initial_line > td:nth-child(5) { - border-top: 1px solid black; } - .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr.initial_line > td:nth-child(6) { - border-top: 1px solid black; } .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls { padding: 0 0 5px 18px; } .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls .filter { diff --git a/addons/account/static/src/css/account_bank_statement_reconciliation.scss b/addons/account/static/src/css/account_bank_statement_reconciliation.scss index 3b98689bcbc..248536b7a9b 100644 --- a/addons/account/static/src/css/account_bank_statement_reconciliation.scss +++ b/addons/account/static/src/css/account_bank_statement_reconciliation.scss @@ -194,12 +194,6 @@ $initialLineBackground: #f0f0f0; } } - &.no_partner { - .partner_name, .line_open_balance { - display: none !important; - } - } - /* gap between accounting_view and action view */ > table > tbody > tr:nth-child(1) > td table { margin-bottom: 10px; @@ -341,10 +335,6 @@ $initialLineBackground: #f0f0f0; // accounting "T" td:nth-child(6) { border-left: $accountingBorder; } - tr.initial_line > td { - &:nth-child(5) { border-top: $accountingBorder; } - &:nth-child(6) { border-top: $accountingBorder; } - } } diff --git a/addons/account/static/src/js/account_widgets.js b/addons/account/static/src/js/account_widgets.js index a7e83a2146b..8f5786e978f 100644 --- a/addons/account/static/src/js/account_widgets.js +++ b/addons/account/static/src/js/account_widgets.js @@ -171,7 +171,7 @@ openerp.account = function (instance) { deferred_promises.push(self.model_bank_statement .call("get_format_currency_js_function", [self.statement_id]) .then(function(data){ - self.formatCurrency = new Function("amount", data); + self.formatCurrency = new Function("amount, currency_id", data); }) ); @@ -245,7 +245,7 @@ openerp.account = function (instance) { keyboardShortcutsHandler: function(e) { var self = this; - if (e.which === 13 && (e.ctrlKey || e.metaKey)) { + if ((e.which === 13 || e.which === 10) && (e.ctrlKey || e.metaKey)) { $.each(self.getChildren(), function(i, o){ if (o.is_valid && o.persistAndDestroy()) { self.lines_reconciled_with_ctrl_enter++; @@ -789,6 +789,9 @@ openerp.account = function (instance) { line.q_amount = (line.debit !== 0 ? "- "+line.q_debit : "") + (line.credit !== 0 ? line.q_credit : ""); line.q_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line}); line.q_label = line.name; + if (line.has_no_partner){ + line.q_label = line.partner_name + ': ' +line.q_label; + } // WARNING : pretty much of a ugly hack // The value of account_move.ref is either the move's communication or it's name without the slashes @@ -981,6 +984,7 @@ openerp.account = function (instance) { lineOpenBalanceClickHandler: function() { var self = this; if (self.get("mode") === "create") { + self.addLineBeingEdited(); self.set("mode", "match"); } else { self.set("mode", "create"); @@ -1038,7 +1042,8 @@ openerp.account = function (instance) { var slice_start = self.get("pager_index") * self.max_move_lines_displayed; var slice_end = (self.get("pager_index")+1) * self.max_move_lines_displayed; _( _.filter(self.mv_lines_deselected, function(o){ - return o.name.indexOf(self.filter) !== -1 || o.ref.indexOf(self.filter) !== -1 }) + return o.q_label.indexOf(self.filter) !== -1 || (o.ref && o.ref.indexOf(self.filter) !== -1) + }) .slice(slice_start, slice_end)).each(function(line){ var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false})); self.bindPopoverTo($line.find(".line_info_button")); @@ -1057,7 +1062,6 @@ openerp.account = function (instance) { updatePagerControls: function() { var self = this; - if (self.get("pager_index") === 0) self.$(".pager_control_left").addClass("disabled"); else @@ -1075,7 +1079,7 @@ openerp.account = function (instance) { balanceChanged: function() { var self = this; var balance = self.get("balance"); - + self.$(".tbody_open_balance").empty(); // Special case hack : no identified partner if (self.st_line.has_no_partner) { if (Math.abs(balance).toFixed(3) === "0.000") { @@ -1088,19 +1092,23 @@ openerp.account = function (instance) { self.$(".button_ok").attr("disabled", "disabled"); self.$(".button_ok").text("OK"); self.is_valid = false; + var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : ""); + var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : ""); + var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]})); + $line.find('.js_open_balance')[0].innerHTML = "Choose counterpart"; + self.$(".tbody_open_balance").append($line); } return; } - self.$(".tbody_open_balance").empty(); if (Math.abs(balance).toFixed(3) === "0.000") { self.$(".button_ok").addClass("oe_highlight"); self.$(".button_ok").text("OK"); } else { self.$(".button_ok").removeClass("oe_highlight"); self.$(".button_ok").text("Keep open"); - var debit = (balance > 0 ? self.formatCurrency(balance) : ""); - var credit = (balance < 0 ? self.formatCurrency(-1*balance) : ""); + var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : ""); + var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : ""); var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]})); self.$(".tbody_open_balance").append($line); } @@ -1111,21 +1119,15 @@ openerp.account = function (instance) { self.$(".action_pane.active").removeClass("active"); - // Special case hack : if no_partner, either inactive or create + // Special case hack : if no_partner and mode == inactive if (self.st_line.has_no_partner) { if (self.get("mode") === "inactive") { self.$(".match").slideUp(self.animation_speed); self.$(".create").slideUp(self.animation_speed); self.$(".toggle_match").removeClass("visible_toggle"); self.el.dataset.mode = "inactive"; - } else { - self.initializeCreateForm(); - self.$(".match").slideUp(self.animation_speed); - self.$(".create").slideDown(self.animation_speed); - self.$(".toggle_match").addClass("visible_toggle"); - self.el.dataset.mode = "create"; - } - return; + return; + } } if (self.get("mode") === "inactive") { @@ -1198,6 +1200,8 @@ openerp.account = function (instance) { var self = this; var line_created_being_edited = self.get("line_created_being_edited"); line_created_being_edited[0][elt.corresponding_property] = val.newValue; + + line_created_being_edited[0].currency_id = self.st_line.currency_id; // Specific cases if (elt === self.account_id_field) @@ -1215,7 +1219,7 @@ openerp.account = function (instance) { var tax = data.taxes[0]; var tax_account_id = (amount > 0 ? tax.account_collected_id : tax.account_paid_id) line_created_being_edited[0].amount = (data.total.toFixed(3) === amount.toFixed(3) ? amount : data.total); - line_created_being_edited[1] = {id: line_created_being_edited[0].id, account_id: tax_account_id, account_num: self.map_account_id_code[tax_account_id], label: tax.name, amount: tax.amount, no_remove_action: true}; + line_created_being_edited[1] = {id: line_created_being_edited[0].id, account_id: tax_account_id, account_num: self.map_account_id_code[tax_account_id], label: tax.name, amount: tax.amount, no_remove_action: true, currency_id: self.st_line.currency_id}; } ); } else { @@ -1228,10 +1232,9 @@ openerp.account = function (instance) { $.when(deferred_tax).then(function(){ // Format amounts if (line_created_being_edited[0].amount) - line_created_being_edited[0].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[0].amount)); + line_created_being_edited[0].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[0].amount), line_created_being_edited[0].currency_id); if (line_created_being_edited[1] && line_created_being_edited[1].amount) - line_created_being_edited[1].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[1].amount)); - + line_created_being_edited[1].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[1].amount), line_created_being_edited[0].currency_id); self.set("line_created_being_edited", line_created_being_edited); self.createdLinesChanged(); // TODO For some reason, previous line doesn't trigger change handler }); @@ -1268,10 +1271,10 @@ openerp.account = function (instance) { line.initial_amount = line.debit !== 0 ? line.debit : -1 * line.credit; if (balance < 0) { line.debit -= balance; - line.debit_str = self.formatCurrency(line.debit); + line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id); } else { line.credit -= balance; - line.credit_str = self.formatCurrency(line.credit); + line.credit_str = self.formatCurrency(line.credit, self.st_line.currency_id); } line.propose_partial_reconcile = false; line.partial_reconcile = true; @@ -1291,12 +1294,13 @@ openerp.account = function (instance) { }, unpartialReconcileLine: function(line) { + var self = this; if (line.initial_amount > 0) { line.debit = line.initial_amount; - line.debit_str = this.formatCurrency(line.debit); + line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id); } else { line.credit = -1 * line.initial_amount; - line.credit_str = this.formatCurrency(line.credit); + line.credit_str = self.formatCurrency(line.credit, self.st_line.currency_id); } line.propose_partial_reconcile = true; line.partial_reconcile = false; @@ -1359,16 +1363,15 @@ openerp.account = function (instance) { if (limit > 0) { // Load move lines deferred_move_lines = self.model_bank_statement_line - .call("get_move_lines_counterparts", [self.st_line.id, excluded_ids, self.filter, offset, limit]) + .call("get_move_lines_counterparts_id", [self.st_line.id, excluded_ids, self.filter, offset, limit]) .then(function (lines) { _(lines).each(self.decorateMoveLine.bind(self)); move_lines = lines; }); } - // Fetch the number of move lines corresponding to this statement line and this filter var deferred_total_move_lines_num = self.model_bank_statement_line - .call("get_move_lines_counterparts", [self.st_line.id, excluded_ids, self.filter, offset, limit, true]) + .call("get_move_lines_counterparts_id", [self.st_line.id, excluded_ids, self.filter, 0, undefined, true]) .then(function(num){ move_lines_num = num; }); diff --git a/addons/account/static/src/xml/account_bank_statement_reconciliation.xml b/addons/account/static/src/xml/account_bank_statement_reconciliation.xml index f311207ad8c..b5a8492545d 100644 --- a/addons/account/static/src/xml/account_bank_statement_reconciliation.xml +++ b/addons/account/static/src/xml/account_bank_statement_reconciliation.xml @@ -185,7 +185,7 @@ - Open balance + Open balance diff --git a/addons/account/views/report_agedpartnerbalance.xml b/addons/account/views/report_agedpartnerbalance.xml index 019f32a35de..c1f9047e330 100644 --- a/addons/account/views/report_agedpartnerbalance.xml +++ b/addons/account/views/report_agedpartnerbalance.xml @@ -99,7 +99,7 @@ - + diff --git a/addons/account/wizard/account_chart.py b/addons/account/wizard/account_chart.py index 38df2f7484d..de652a947f0 100644 --- a/addons/account/wizard/account_chart.py +++ b/addons/account/wizard/account_chart.py @@ -62,9 +62,10 @@ class account_chart(osv.osv_memory): ORDER BY p.date_stop DESC LIMIT 1) AS period_stop''', (fiscalyear_id, fiscalyear_id)) periods = [i[0] for i in cr.fetchall()] - if periods and len(periods) > 1: + if periods: start_period = periods[0] - end_period = periods[1] + if len(periods) > 1: + end_period = periods[1] res['value'] = {'period_from': start_period, 'period_to': end_period} else: res['value'] = {'period_from': False, 'period_to': False} diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 519660fb708..9550f77bcab 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -22,7 +22,6 @@ from dateutil.relativedelta import relativedelta import datetime import logging import time -import traceback from openerp.osv import osv, fields from openerp.osv.orm import intersect, except_orm @@ -73,6 +72,7 @@ class account_analytic_invoice_line(osv.osv): result = {} res = self.pool.get('product.product').browse(cr, uid, product, context=local_context) + price = False if price_unit is not False: price = price_unit elif pricelist_id: @@ -746,29 +746,32 @@ class account_analytic_account(osv.osv): contract_ids = ids else: contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')]) - for contract in self.browse(cr, uid, contract_ids, context=context): - try: - invoice_values = self._prepare_invoice(cr, uid, contract, context=context) - invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context)) - next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") - interval = contract.recurring_interval - if contract.recurring_rule_type == 'daily': - new_date = next_date+relativedelta(days=+interval) - elif contract.recurring_rule_type == 'weekly': - new_date = next_date+relativedelta(weeks=+interval) - elif contract.recurring_rule_type == 'monthly': - new_date = next_date+relativedelta(months=+interval) - else: - new_date = next_date+relativedelta(years=+interval) - self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context) - if automatic: - cr.commit() - except Exception: - if automatic: - cr.rollback() - _logger.error(traceback.format_exc()) - else: - raise + if contract_ids: + cr.execute('SELECT company_id, array_agg(id) as ids FROM account_analytic_account WHERE id IN %s GROUP BY company_id', (tuple(contract_ids),)) + for company_id, ids in cr.fetchall(): + for contract in self.browse(cr, uid, ids, context=dict(context, company_id=company_id, force_company=company_id)): + try: + invoice_values = self._prepare_invoice(cr, uid, contract, context=context) + invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context)) + next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") + interval = contract.recurring_interval + if contract.recurring_rule_type == 'daily': + new_date = next_date+relativedelta(days=+interval) + elif contract.recurring_rule_type == 'weekly': + new_date = next_date+relativedelta(weeks=+interval) + elif contract.recurring_rule_type == 'monthly': + new_date = next_date+relativedelta(months=+interval) + else: + new_date = next_date+relativedelta(years=+interval) + self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context) + if automatic: + cr.commit() + except Exception: + if automatic: + cr.rollback() + _logger.exception('Fail to create recurring invoice for contract %s', contract.code) + else: + raise return invoice_ids class account_analytic_account_summary_user(osv.osv): diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml index d0064d6336c..f8c2d65f63b 100644 --- a/addons/account_voucher/voucher_sales_purchase_view.xml +++ b/addons/account_voucher/voucher_sales_purchase_view.xml @@ -248,7 +248,7 @@ - + diff --git a/addons/auth_crypt/auth_crypt.py b/addons/auth_crypt/auth_crypt.py index 4651d27fe7d..6c9deb51e92 100644 --- a/addons/auth_crypt/auth_crypt.py +++ b/addons/auth_crypt/auth_crypt.py @@ -1,137 +1,41 @@ -# -# Implements encrypting functions. -# -# Copyright (c) 2008, F S 3 Consulting Inc. -# -# Maintainer: -# Alec Joseph Rivera (agifs3.ph) -# refactored by Antony Lesuisse openerp.com> -# - -import hashlib -import hmac import logging -from random import sample -from string import ascii_letters, digits + +from passlib.context import CryptContext import openerp from openerp.osv import fields, osv _logger = logging.getLogger(__name__) -magic_md5 = '$1$' -magic_sha256 = '$5$' - -def gen_salt(length=8, symbols=None): - if symbols is None: - symbols = ascii_letters + digits - return ''.join(sample(symbols, length)) - -def md5crypt( raw_pw, salt, magic=magic_md5 ): - """ md5crypt FreeBSD crypt(3) based on but different from md5 - - The md5crypt is based on Mark Johnson's md5crypt.py, which in turn is - based on FreeBSD src/lib/libcrypt/crypt.c (1.2) by Poul-Henning Kamp. - Mark's port can be found in ActiveState ASPN Python Cookbook. Kudos to - Poul and Mark. -agi - - Original license: - - * "THE BEER-WARE LICENSE" (Revision 42): - * - * wrote this file. As long as you retain this - * notice you can do whatever you want with this stuff. If we meet some - * day, and you think this stuff is worth it, you can buy me a beer in - * return. - * - * Poul-Henning Kamp - """ - raw_pw = raw_pw.encode('utf-8') - salt = salt.encode('utf-8') - hash = hashlib.md5() - hash.update( raw_pw + magic + salt ) - st = hashlib.md5() - st.update( raw_pw + salt + raw_pw) - stretch = st.digest() - - for i in range( 0, len( raw_pw ) ): - hash.update( stretch[i % 16] ) - - i = len( raw_pw ) - - while i: - if i & 1: - hash.update('\x00') - else: - hash.update( raw_pw[0] ) - i >>= 1 - - saltedmd5 = hash.digest() - - for i in range( 1000 ): - hash = hashlib.md5() - - if i & 1: - hash.update( raw_pw ) - else: - hash.update( saltedmd5 ) - - if i % 3: - hash.update( salt ) - if i % 7: - hash.update( raw_pw ) - if i & 1: - hash.update( saltedmd5 ) - else: - hash.update( raw_pw ) - - saltedmd5 = hash.digest() - - itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' - - rearranged = '' - for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5)): - v = ord( saltedmd5[a] ) << 16 | ord( saltedmd5[b] ) << 8 | ord( saltedmd5[c] ) - - for i in range(4): - rearranged += itoa64[v & 0x3f] - v >>= 6 - - v = ord( saltedmd5[11] ) - - for i in range( 2 ): - rearranged += itoa64[v & 0x3f] - v >>= 6 - - return magic + salt + '$' + rearranged - -def sh256crypt(cls, password, salt, magic=magic_sha256): - iterations = 1000 - # see http://en.wikipedia.org/wiki/PBKDF2 - result = password.encode('utf8') - for i in xrange(cls.iterations): - result = hmac.HMAC(result, salt, hashlib.sha256).digest() # uses HMAC (RFC 2104) to apply salt - result = result.encode('base64') # doesnt seem to be crypt(3) compatible - return '%s%s$%s' % (magic_sha256, salt, result) +default_crypt_context = CryptContext( + # kdf which can be verified by the context. The default encryption kdf is + # the first of the list + ['pbkdf2_sha512', 'md5_crypt'], + # deprecated algorithms are still verified as usual, but ``needs_update`` + # will indicate that the stored hash should be replaced by a more recent + # algorithm. Passlib 1.6 supports an `auto` value which deprecates any + # algorithm but the default, but Debian only provides 1.5 so... + deprecated=['md5_crypt'], +) class res_users(osv.osv): _inherit = "res.users" + def init(self, cr): + _logger.info("Hashing passwords, may be slow for databases with many users...") + cr.execute("SELECT id, password FROM res_users" + " WHERE password IS NOT NULL" + " AND password != ''") + for uid, pwd in cr.fetchall(): + self._set_password(cr, openerp.SUPERUSER_ID, uid, pwd) + def set_pw(self, cr, uid, id, name, value, args, context): if value: - encrypted = md5crypt(value, gen_salt()) - cr.execute("update res_users set password='', password_crypt=%s where id=%s", (encrypted, id)) - del value + self._set_password(cr, uid, id, value, context=context) def get_pw( self, cr, uid, ids, name, args, context ): cr.execute('select id, password from res_users where id in %s', (tuple(map(int, ids)),)) - stored_pws = cr.fetchall() - res = {} - - for id, stored_pw in stored_pws: - res[id] = stored_pw - - return res + return dict(cr.fetchall()) _columns = { 'password': fields.function(get_pw, fnct_inv=set_pw, type='char', string='Password', invisible=True, store=True), @@ -141,27 +45,51 @@ class res_users(osv.osv): def check_credentials(self, cr, uid, password): # convert to base_crypt if needed cr.execute('SELECT password, password_crypt FROM res_users WHERE id=%s AND active', (uid,)) + encrypted = None if cr.rowcount: - stored_password, stored_password_crypt = cr.fetchone() - if stored_password and not stored_password_crypt: - salt = gen_salt() - stored_password_crypt = md5crypt(stored_password, salt) - cr.execute("UPDATE res_users SET password='', password_crypt=%s WHERE id=%s", (stored_password_crypt, uid)) + stored, encrypted = cr.fetchone() + if stored and not encrypted: + self._set_password(cr, uid, uid, stored) try: return super(res_users, self).check_credentials(cr, uid, password) except openerp.exceptions.AccessDenied: - # check md5crypt - if stored_password_crypt: - if stored_password_crypt[:len(magic_md5)] == magic_md5: - salt = stored_password_crypt[len(magic_md5):11] - if stored_password_crypt == md5crypt(password, salt): - return - elif stored_password_crypt[:len(magic_md5)] == magic_sha256: - salt = stored_password_crypt[len(magic_md5):11] - if stored_password_crypt == md5crypt(password, salt): - return - # Reraise password incorrect + if encrypted: + valid_pass, replacement = self._crypt_context(cr, uid, uid)\ + .verify_and_update(password, encrypted) + if replacement is not None: + self._set_encrypted_password(cr, uid, uid, replacement) + if valid_pass: + return + raise + def _set_password(self, cr, uid, id, password, context=None): + """ Encrypts then stores the provided plaintext password for the user + ``id`` + """ + encrypted = self._crypt_context(cr, uid, id, context=context).encrypt(password) + self._set_encrypted_password(cr, uid, id, encrypted, context=context) + + def _set_encrypted_password(self, cr, uid, id, encrypted, context=None): + """ Store the provided encrypted password to the database, and clears + any plaintext password + + :param uid: id of the current user + :param id: id of the user on which the password should be set + """ + cr.execute( + "UPDATE res_users SET password='', password_crypt=%s WHERE id=%s", + (encrypted, id)) + + def _crypt_context(self, cr, uid, id, context=None): + """ Passlib CryptContext instance used to encrypt and verify + passwords. Can be overridden if technical, legal or political matters + require different kdfs than the provided default. + + Requires a CryptContext as deprecation and upgrade notices are used + internally + """ + return default_crypt_context + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 99f134a5cbd..7652420d7c6 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -74,7 +74,7 @@ class OAuthLogin(Home): state = dict( d=request.session.db, p=provider['id'], - r=redirect, + r=werkzeug.url_quote_plus(redirect), ) token = request.params.get('token') if token: @@ -143,7 +143,7 @@ class OAuthController(http.Controller): cr.commit() action = state.get('a') menu = state.get('m') - redirect = state.get('r') + redirect = werkzeug.url_unquote_plus(state['r']) if state.get('r') else False url = '/web' if redirect: url = redirect diff --git a/addons/calendar/static/src/js/base_calendar.js b/addons/calendar/static/src/js/base_calendar.js index eed958e20d9..37669840818 100644 --- a/addons/calendar/static/src/js/base_calendar.js +++ b/addons/calendar/static/src/js/base_calendar.js @@ -102,7 +102,7 @@ openerp.calendar = function(instance) { var self = this; var action_url = ''; - action_url = _.str.sprintf('/?db=%s#id=%s&view_type=form&model=calendar.event', db, meeting_id); + action_url = _.str.sprintf('/web?db=%s#id=%s&view_type=form&model=calendar.event', db, meeting_id); var reload_page = function(){ return location.replace(action_url); diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py index 641c490cabb..e38bf973cdf 100644 --- a/addons/crm_claim/crm_claim.py +++ b/addons/crm_claim/crm_claim.py @@ -45,13 +45,10 @@ class crm_claim_stage(osv.osv): help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."), 'case_default': fields.boolean('Common to All Teams', help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."), - 'fold': fields.boolean('Hide in Views when Empty', - help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."), } _defaults = { 'sequence': lambda *args: 1, - 'fold': False, } class crm_claim(osv.osv): diff --git a/addons/crm_claim/crm_claim_data.xml b/addons/crm_claim/crm_claim_data.xml index fca7ac51178..86cf988ff45 100644 --- a/addons/crm_claim/crm_claim_data.xml +++ b/addons/crm_claim/crm_claim_data.xml @@ -61,7 +61,6 @@ Rejected 29 - diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml index 43df3d069bf..6180b85660d 100644 --- a/addons/crm_claim/crm_claim_view.xml +++ b/addons/crm_claim/crm_claim_view.xml @@ -51,7 +51,6 @@ - diff --git a/addons/event/report/report_event_registration.py b/addons/event/report/report_event_registration.py index e76b5c60d1f..c4a4dfd6b2d 100644 --- a/addons/event/report/report_event_registration.py +++ b/addons/event/report/report_event_registration.py @@ -32,7 +32,7 @@ class report_event_registration(osv.osv): 'draft_state': fields.integer(' # No of Draft Registrations', size=20), 'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20), 'seats_max': fields.integer('Max Seats'), - 'nbevent': fields.integer('Number Of Events'), + 'nbevent': fields.integer('Number of Registrations'), 'event_type': fields.many2one('event.type', 'Event Type'), 'registration_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Attended'), ('cancel', 'Cancelled')], 'Registration State', readonly=True, required=True), 'event_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Event State', readonly=True, required=True), @@ -59,7 +59,7 @@ class report_event_registration(osv.osv): r.name AS name_registration, e.company_id AS company_id, e.date_begin AS event_date, - count(e.id) AS nbevent, + count(r.id) AS nbevent, CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state, CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state, e.type AS event_type, diff --git a/addons/event/static/src/css/event.css b/addons/event/static/src/css/event.css index ff5bfaba19b..53c03cc03f0 100644 --- a/addons/event/static/src/css/event.css +++ b/addons/event/static/src/css/event.css @@ -1,7 +1,7 @@ .oe_event_date{ border-top-left-radius:3px; border-top-right-radius:3px; - font-size: 48px; + font-size: 36px; height: auto; font-weight: bold; text-align: center; diff --git a/addons/event_sale/event_sale.py b/addons/event_sale/event_sale.py index ed99e6a39cc..69ad0cb6af6 100644 --- a/addons/event_sale/event_sale.py +++ b/addons/event_sale/event_sale.py @@ -245,7 +245,8 @@ class event_ticket(osv.osv): ] def onchange_product_id(self, cr, uid, ids, product_id=False, context=None): - return {'value': {'price': self.pool.get("product.product").browse(cr, uid, product_id).list_price or 0}} + price = self.pool.get("product.product").browse(cr, uid, product_id).list_price if product_id else 0 + return {'value': {'price': price}} class event_registration(osv.osv): diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py index ddc08336293..faed62f8da9 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/challenge.py @@ -58,7 +58,7 @@ def start_end_date_for_period(period, default_start_date=False, default_end_date end_date = default_end_date if start_date and end_date: - return (start_date.strftime(DF), end_date.strftime(DF)) + return (datetime.strftime(start_date, DF), datetime.strftime(end_date, DF)) else: return (start_date, end_date) diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py index 543c81977aa..dd1d45c05fe 100644 --- a/addons/google_calendar/google_calendar.py +++ b/addons/google_calendar/google_calendar.py @@ -699,7 +699,7 @@ class google_calendar(osv.AbstractModel): for att in att_obj.browse(cr, uid, my_att_ids, context=context): event = att.event_id - base_event_id = att.google_internal_event_id.split('_')[0] + base_event_id = att.google_internal_event_id.rsplit('_', 1)[0] if base_event_id not in event_to_synchronize: event_to_synchronize[base_event_id] = {} @@ -721,7 +721,7 @@ class google_calendar(osv.AbstractModel): for event in all_event_from_google.values(): event_id = event.get('id') - base_event_id = event_id.split('_')[0] + base_event_id = event_id.rsplit('_', 1)[0] if base_event_id not in event_to_synchronize: event_to_synchronize[base_event_id] = {} @@ -786,7 +786,7 @@ class google_calendar(osv.AbstractModel): if actSrc == 'OE': self.delete_an_event(cr, uid, current_event[0], context=context) elif actSrc == 'GG': - new_google_event_id = event.GG.event['id'].split('_')[1] + new_google_event_id = event.GG.event['id'].rsplit('_', 1)[1] if 'T' in new_google_event_id: new_google_event_id = new_google_event_id.replace('T', '')[:-1] else: @@ -795,7 +795,8 @@ class google_calendar(osv.AbstractModel): if event.GG.status: parent_event = {} if not event_to_synchronize[base_event][0][1].OE.event_id: - event_to_synchronize[base_event][0][1].OE.event_id = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].split('_')[0])], ['event_id'], context=context_novirtual)[0].get('event_id')[0] + main_ev = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].rsplit('_', 1)[0])], fields=['event_id'], context=context_novirtual) + event_to_synchronize[base_event][0][1].OE.event_id = main_ev[0].get('event_id')[0] parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id) res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context) diff --git a/addons/hr/hr.py b/addons/hr/hr.py index 85366adfe3b..f4719f572f0 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -225,7 +225,7 @@ class hr_employee(osv.osv): "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views."), 'image_small': fields.function(_get_image, fnct_inv=_set_image, - string="Smal-sized photo", type="binary", multi="_get_image", + string="Small-sized photo", type="binary", multi="_get_image", store = { 'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), }, diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml index 09e7373384e..0c356706407 100644 --- a/addons/hr/hr_view.xml +++ b/addons/hr/hr_view.xml @@ -40,7 +40,9 @@ - + @@ -68,7 +70,9 @@ - + diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index 3d467e0b065..ed25b75aa83 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -99,7 +99,7 @@ class hr_holidays_status(osv.osv): for record in self.browse(cr, uid, ids, context=context): name = record.name if not record.limit: - name = name + (' (%d/%d)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0)) + name = name + (' (%g/%g)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0)) res.append((record.id, name)) return res diff --git a/addons/l10n_be_coda/__openerp__.py b/addons/l10n_be_coda/__openerp__.py index ea1ce59eb88..6ecdb04f1d6 100644 --- a/addons/l10n_be_coda/__openerp__.py +++ b/addons/l10n_be_coda/__openerp__.py @@ -59,25 +59,7 @@ A removal of one object in the CODA processing results in the removal of the associated objects. The removal of a CODA File containing multiple Bank Statements will also remove those associated statements. -The following reconciliation logic has been implemented in the CODA processing: -------------------------------------------------------------------------------- - 1) The Company's Bank Account Number of the CODA statement is compared against - the Bank Account Number field of the Company's CODA Bank Account - configuration records (whereby bank accounts defined in type='info' - configuration records are ignored). If this is the case an 'internal transfer' - transaction is generated using the 'Internal Transfer Account' field of the - CODA File Import wizard. - 2) As a second step the 'Structured Communication' field of the CODA transaction - line is matched against the reference field of in- and outgoing invoices - (supported : Belgian Structured Communication Type). - 3) When the previous step doesn't find a match, the transaction counterparty is - located via the Bank Account Number configured on the OpenERP Customer and - Supplier records. - 4) In case the previous steps are not successful, the transaction is generated - by using the 'Default Account for Unrecognized Movement' field of the CODA - File Import wizard in order to allow further manual processing. - -In stead of a manual adjustment of the generated Bank Statements, you can also +Instead of a manual adjustment of the generated Bank Statements, you can also re-import the CODA after updating the OpenERP database with the information that was missing to allow automatic reconciliation. diff --git a/addons/l10n_be_coda/l10n_be_coda.py b/addons/l10n_be_coda/l10n_be_coda.py index 0dfa9bf9db4..c4cd8ab7c2b 100644 --- a/addons/l10n_be_coda/l10n_be_coda.py +++ b/addons/l10n_be_coda/l10n_be_coda.py @@ -28,46 +28,4 @@ class account_bank_statement(osv.osv): } -class account_bank_statement_line(osv.osv): - _inherit = 'account.bank.statement.line' - _columns = { - 'coda_account_number': fields.char('Account Number', help="The Counter Party Account Number") - } - - def create(self, cr, uid, data, context=None): - """ - This function creates a Bank Account Number if, for a bank statement line, - the partner_id field and the coda_account_number field are set, - and the account number does not exist in the database - """ - if 'partner_id' in data and data['partner_id'] and 'coda_account_number' in data and data['coda_account_number']: - acc_number_ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', data['coda_account_number'])]) - if len(acc_number_ids) == 0: - try: - type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal') - type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context) - self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': data['coda_account_number'], 'partner_id': data['partner_id'], 'state': type_id.code}, context=context) - except ValueError: - pass - return super(account_bank_statement_line, self).create(cr, uid, data, context=context) - - def write(self, cr, uid, ids, vals, context=None): - super(account_bank_statement_line, self).write(cr, uid, ids, vals, context) - """ - Same as create function above, but for write function - """ - if 'partner_id' in vals: - for line in self.pool.get('account.bank.statement.line').browse(cr, uid, ids, context=context): - if line.coda_account_number: - acc_number_ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', line.coda_account_number)]) - if len(acc_number_ids) == 0: - try: - type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal') - type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context) - self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': line.coda_account_number, 'partner_id': vals['partner_id'], 'state': type_id.code}, context=context) - except ValueError: - pass - return True - - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/l10n_be_coda/l10n_be_coda_demo.xml b/addons/l10n_be_coda/l10n_be_coda_demo.xml index 63436a60a92..ce44539a715 100644 --- a/addons/l10n_be_coda/l10n_be_coda_demo.xml +++ b/addons/l10n_be_coda/l10n_be_coda_demo.xml @@ -32,5 +32,27 @@ + + + + + + + + draft + out_invoice + + + bba + +++240/2838/42818+++ + + + Otpez Laptop without OS + + 608.89 + 10 + + + diff --git a/addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2011-01-11-18.59.15.txt b/addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2011-01-11-18.59.15.txt index bc3af59e322..d0add859663 100644 --- a/addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2011-01-11-18.59.15.txt +++ b/addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2011-01-11-18.59.15.txt @@ -4,7 +4,7 @@ 2200010000 GKCCBEBB 1 0 2300010000BE41063012345610 PARTNER 1 0 1 3100010001OL44483FW SCTOFBIONLO001010001001PARTNER 1 0 0 -2100020000OL4414AC8BOVSOVSOVERS00000000030444501101110015000002010237 11011113501 0 +2100020000OL4414AC8BOVSOVSOVERS0000000003044450110111001500001101240283842818 11011113501 0 2200020000 BBRUBEBB 1 0 2300020000BE61310126985517 PARTNER 2 0 1 3100020001OL4414AC8BOVSOVSOVERS001500001001PARTNER 2 1 0 diff --git a/addons/l10n_be_coda/wizard/account_coda_import.py b/addons/l10n_be_coda/wizard/account_coda_import.py index e50f8537881..cac4cce452c 100644 --- a/addons/l10n_be_coda/wizard/account_coda_import.py +++ b/addons/l10n_be_coda/wizard/account_coda_import.py @@ -291,79 +291,38 @@ class account_coda_import(osv.osv_memory): if 'counterpartyAddress' in line and line['counterpartyAddress'] != '': note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress']) line['name'] = "\n".join(filter(None, [line['counterpartyName'], line['communication']])) - partner = None partner_id = None - invoice = False + structured_com = "" + bank_account_id = False if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101': - ids = self.pool.get('account.invoice').search(cr, uid, [('reference', '=', line['communication']), ('reference_type', '=', 'bba')]) - -# Gère les communications structurées -# TODO : à faire primer sur resolution_proposition : si la communication indique une facture, on la sélectionne - -# if ids: -# invoice = self.pool.get('account.invoice').browse(cr, uid, ids[0]) -# partner = invoice.partner_id -# partner_id = partner.id -# if invoice.type in ['in_invoice', 'in_refund'] and line['debit'] == '1': -# line['transaction_type'] = 'supplier' -# elif invoice.type in ['out_invoice', 'out_refund'] and line['debit'] == '0': -# line['transaction_type'] = 'customer' -# line['account'] = invoice.account_id.id -# line['reconcile'] = False -# if invoice.type in ['in_invoice', 'out_invoice']: -# iml_ids = self.pool.get('account.move.line').search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)]) -# if iml_ids: -# line['reconcile'] = iml_ids[0] -# if line['reconcile']: -# voucher_vals = { -# 'type': line['transaction_type'] == 'supplier' and 'payment' or 'receipt', -# 'name': line['name'], -# 'partner_id': partner_id, -# 'journal_id': statement['journal_id'].id, -# 'account_id': statement['journal_id'].default_credit_account_id.id, -# 'company_id': statement['journal_id'].company_id.id, -# 'currency_id': statement['journal_id'].company_id.currency_id.id, -# 'date': line['entryDate'], -# 'amount': abs(line['amount']), -# 'period_id': statement['period_id'], -# 'invoice_id': invoice.id, -# } -# context['invoice_id'] = invoice.id -# voucher_vals.update(self.pool.get('account.voucher').onchange_partner_id(cr, uid, [], -# partner_id=partner_id, -# journal_id=statement['journal_id'].id, -# amount=abs(line['amount']), -# currency_id=statement['journal_id'].company_id.currency_id.id, -# ttype=line['transaction_type'] == 'supplier' and 'payment' or 'receipt', -# date=line['transactionDate'], -# context=context -# )['value']) -# line_drs = [] -# for line_dr in voucher_vals['line_dr_ids']: -# line_drs.append((0, 0, line_dr)) -# voucher_vals['line_dr_ids'] = line_drs -# line_crs = [] -# for line_cr in voucher_vals['line_cr_ids']: -# line_crs.append((0, 0, line_cr)) -# voucher_vals['line_cr_ids'] = line_crs -# line['voucher_id'] = self.pool.get('account.voucher').create(cr, uid, voucher_vals, context=context) + structured_com = line['communication'] if 'counterpartyNumber' in line and line['counterpartyNumber']: ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', str(line['counterpartyNumber']))]) - if ids and len(ids) > 0: - partner = self.pool.get('res.partner.bank').browse(cr, uid, ids[0], context=context).partner_id - partner_id = partner.id + if ids: + bank_account_id = ids[0] + partner_id = self.pool.get('res.partner.bank').browse(cr, uid, bank_account_id, context=context).partner_id.id + else: + #create the bank account, not linked to any partner. The reconciliation will link the partner manually + #chosen at the bank statement final confirmation time. + try: + type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal') + type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context) + bank_code = type_id.code + except ValueError: + bank_code = 'bank' + bank_account_id = self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': str(line['counterpartyNumber']), 'state': bank_code}, context=context) if 'communication' in line and line['communication'] != '': note.append(_('Communication') + ': ' + line['communication']) data = { 'name': line['name'], - 'note': "\n".join(note), + 'note': "\n".join(note), 'date': line['entryDate'], 'amount': line['amount'], 'partner_id': partner_id, 'statement_id': statement['id'], - 'ref': line['ref'], + 'ref': structured_com, 'sequence': line['sequence'], - 'coda_account_number': line['counterpartyNumber'], + 'bank_account_id': bank_account_id, } self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context) if statement['coda_note'] != '': diff --git a/addons/l10n_be_invoice_bba/invoice.py b/addons/l10n_be_invoice_bba/invoice.py index 854593a87cc..499e6e6c4ac 100644 --- a/addons/l10n_be_invoice_bba/invoice.py +++ b/addons/l10n_be_invoice_bba/invoice.py @@ -141,7 +141,7 @@ class account_invoice(osv.osv): elif algorithm == 'random': if not self.check_bbacomm(reference): base = random.randint(1, 9999999999) - bbacomm = str(base).rjust(7, '0') + bbacomm = str(base).rjust(10, '0') base = int(bbacomm) mod = base % 97 or 97 mod = str(mod).rjust(2, '0') diff --git a/addons/l10n_hn/l10n_hn_base.xml b/addons/l10n_hn/l10n_hn_base.xml index 8b9acf5c50a..2ea77baab1b 100644 --- a/addons/l10n_hn/l10n_hn_base.xml +++ b/addons/l10n_hn/l10n_hn_base.xml @@ -25,7 +25,7 @@ ISV por Cobrar - + percent @@ -42,7 +42,7 @@ ISV por Pagar - + percent diff --git a/addons/l10n_uk/__openerp__.py b/addons/l10n_uk/__openerp__.py index 0590224c364..0556535296f 100644 --- a/addons/l10n_uk/__openerp__.py +++ b/addons/l10n_uk/__openerp__.py @@ -32,7 +32,7 @@ This is the latest UK OpenERP localisation necessary to run OpenERP accounting f - a few other adaptations""", 'author': 'SmartMode LTD', 'website': 'http://www.smartmode.co.uk', - 'depends': ['base_iban', 'base_vat', 'account_chart'], + 'depends': ['base_iban', 'base_vat', 'account_chart', 'account_anglo_saxon'], 'data': [ 'data/account.account.type.csv', 'data/account.account.template.csv', diff --git a/addons/l10n_us/__openerp__.py b/addons/l10n_us/__openerp__.py index 90c0e6fd4ad..ea5792c1dca 100644 --- a/addons/l10n_us/__openerp__.py +++ b/addons/l10n_us/__openerp__.py @@ -28,7 +28,7 @@ United States - Chart of accounts. ================================== """, 'website': 'http://www.openerp.com', - 'depends': ['account_chart'], + 'depends': ['account_chart', 'account_anglo_saxon'], 'data': [ 'l10n_us_account_type.xml', 'account_chart_template.xml', diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index ffc49414b2e..e4028111a73 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -176,7 +176,7 @@ class mail_notification(osv.Model): references = message.parent_id.message_id if message.parent_id else False # create email values - max_recipients = 100 + max_recipients = 50 chunks = [email_pids[x:x + max_recipients] for x in xrange(0, len(email_pids), max_recipients)] email_ids = [] for chunk in chunks: @@ -188,7 +188,7 @@ class mail_notification(osv.Model): 'references': references, } email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)) - if force_send and len(chunks) < 6: # for more than 500 followers, use the queue system + if force_send and len(chunks) < 2: # for more than 50 followers, use the queue system self.pool.get('mail.mail').send(cr, uid, email_ids, context=context) return True diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index 4ae47a96774..186787c121a 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -211,3 +211,16 @@ class mail_group(osv.Model): return [] else: return super(mail_group, self).get_suggested_thread(cr, uid, removed_suggested_threads, context) + + def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None): + res = super(mail_group, self).message_get_email_values(cr, uid, id, notif_mail=notif_mail, context=context) + group = self.browse(cr, uid, id, context=context) + res.update({ + 'headers': { + 'Precedence': 'list', + } + }) + if group.alias_domain: + res['headers']['List-Id'] = '%s.%s' % (group.alias_name, group.alias_domain) + res['headers']['List-Post'] = '' % (group.alias_name, group.alias_domain) + return res diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index a4814140479..cc12bde2436 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -204,12 +204,15 @@ class mail_mail(osv.Model): """ body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context) body_alternative = tools.html2plaintext(body) - return { + res = { 'body': body, 'body_alternative': body_alternative, 'subject': self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context), 'email_to': self.send_get_mail_to(cr, uid, mail, partner=partner, context=context), } + if mail.model and mail.res_id and self.pool.get(mail.model) and hasattr(self.pool[mail.model], 'message_get_email_values'): + res.update(self.pool[mail.model].message_get_email_values(cr, uid, mail.res_id, mail, context=context)) + return res def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): """ Sends the selected emails immediately, ignoring their current @@ -268,6 +271,9 @@ class mail_mail(osv.Model): # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: + email_headers = dict(headers) + if email.get('headers'): + email_headers.update(email['headers']) msg = ir_mail_server.build_email( email_from=mail.email_from, email_to=email.get('email_to'), @@ -282,7 +288,7 @@ class mail_mail(osv.Model): object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', - headers=headers) + headers=email_headers) res = ir_mail_server.send_email(cr, uid, msg, mail_server_id=mail.mail_server_id.id, context=context) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index dc2b98fdc93..76d3ab744d5 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -34,6 +34,7 @@ import pytz import socket import time import xmlrpclib +import re from email.message import Message from urllib import urlencode @@ -48,6 +49,8 @@ from openerp.tools.translate import _ _logger = logging.getLogger(__name__) +mail_header_msgid_re = re.compile('<[^<>]+>') + def decode_header(message, header, separator=' '): return separator.join(map(decode, filter(None, message.get_all(header, [])))) @@ -694,6 +697,16 @@ class mail_thread(osv.AbstractModel): if record.alias_domain and record.alias_name else False for record in self.browse(cr, SUPERUSER_ID, ids, context=context)] + def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None): + """ Temporary method to create custom notification email values for a given + model and document. This should be better to have a headers field on + the mail.mail model, computed when creating the notification email, but + this cannot be done in a stable version. + + TDE FIXME: rethink this ulgy thing. """ + res = dict() + return res + #------------------------------------------------------ # Mail gateway #------------------------------------------------------ @@ -1301,13 +1314,13 @@ class mail_thread(osv.AbstractModel): msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) if message.get('In-Reply-To'): - parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))]) + parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To'].strip()))]) if parent_ids: msg_dict['parent_id'] = parent_ids[0] if message.get('References') and 'parent_id' not in msg_dict: - parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', - [x.strip() for x in decode(message['References']).split()])]) + msg_list = mail_header_msgid_re.findall(decode(message['References'])) + parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', [x.strip() for x in msg_list])]) if parent_ids: msg_dict['parent_id'] = parent_ids[0] diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index cb068d566c1..e14a8f3dfe5 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -267,10 +267,7 @@ class mail_compose_message(osv.TransientModel): # mass mailing: rendering override wizard static values if mass_mail_mode and wizard.model: # always keep a copy, reset record name (avoid browsing records) - mail_values.update(notification=True, record_name=False) - if hasattr(self.pool[wizard.model], 'message_new'): - mail_values['model'] = wizard.model - mail_values['res_id'] = res_id + mail_values.update(notification=True, model=wizard.model, res_id=res_id, record_name=False) # auto deletion of mail_mail if 'mail_auto_delete' in context: mail_values['auto_delete'] = context.get('mail_auto_delete') diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py index a67f88a2a28..0e44399a256 100644 --- a/addons/mass_mailing/models/mail_mail.py +++ b/addons/mass_mailing/models/mail_mail.py @@ -84,7 +84,8 @@ class MailMail(osv.Model): def send_get_email_dict(self, cr, uid, mail, partner=None, context=None): res = super(MailMail, self).send_get_email_dict(cr, uid, mail, partner, context=context) if mail.mailing_id and res.get('body') and res.get('email_to'): - email_to = tools.email_split(res.get('email_to')[0]) + emails = tools.email_split(res.get('email_to')[0]) + email_to = emails and emails[0] or False unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context) if unsubscribe_url: res['body'] = tools.append_content_to_html(res['body'], unsubscribe_url, plaintext=False, container_tag='p') diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml index 9806022498a..2939ebc3711 100644 --- a/addons/mass_mailing/views/mass_mailing.xml +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -591,6 +591,7 @@ + diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index f5b6cf466a5..ca4109c1b9a 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1071,8 +1071,8 @@ class mrp_production(osv.osv): return False # Take routing location as a Source Location. source_location_id = production.location_src_id.id - if production.bom_id.routing_id and production.bom_id.routing_id.location_id: - source_location_id = production.bom_id.routing_id.location_id.id + if production.routing_id and production.routing_id.location_id: + source_location_id = production.routing_id.location_id.id destination_location_id = production.product_id.property_stock_production.id if not source_location_id: diff --git a/addons/product/product.py b/addons/product/product.py index eaed55c0b41..6fa0627db2c 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -678,6 +678,17 @@ class product_template(osv.osv): if not context or "create_product_product" not in context: self.create_variant_ids(cr, uid, [product_template_id], context=context) self._set_standard_price(cr, uid, product_template_id, vals.get('standard_price', 0.0), context=context) + + # TODO: this is needed to set given values to first variant after creation + # these fields should be moved to product as lead to confusion + related_vals = {} + if vals.get('ean13'): + related_vals['ean13'] = vals['ean13'] + if vals.get('default_code'): + related_vals['default_code'] = vals['default_code'] + if related_vals: + self.write(cr, uid, product_template_id, related_vals, context=context) + return product_template_id def write(self, cr, uid, ids, vals, context=None): diff --git a/addons/product_email_template/models/invoice.py b/addons/product_email_template/models/invoice.py index d9e82e42012..d5876b510f1 100644 --- a/addons/product_email_template/models/invoice.py +++ b/addons/product_email_template/models/invoice.py @@ -27,7 +27,7 @@ class account_invoice(osv.Model): template_values = Composer.onchange_template_id( cr, uid, composer_id, line.product_id.email_template_id.id, 'comment', 'account.invoice', invoice.id )['value'] - template_values['attachment_ids'] = [(4, id) for id in template_values.get('attachment_ids', '[]')] + template_values['attachment_ids'] = [(4, id) for id in template_values.get('attachment_ids', [])] Composer.write(cr, uid, [composer_id], template_values, context=context) Composer.send_mail(cr, uid, [composer_id], context=context) return True diff --git a/addons/project/project_demo.xml b/addons/project/project_demo.xml index 54f78ba8c8b..695aa80af2a 100644 --- a/addons/project/project_demo.xml +++ b/addons/project/project_demo.xml @@ -50,8 +50,8 @@ project.task + ref('base.partner_root'), + ref('base.partner_demo')])]"/> diff --git a/addons/project/res_config.py b/addons/project/res_config.py index e7699a61ec8..187ad0327d8 100644 --- a/addons/project/res_config.py +++ b/addons/project/res_config.py @@ -27,11 +27,11 @@ class project_configuration(osv.osv_memory): _inherit = 'res.config.settings' _columns = { - 'module_project_mrp': fields.boolean('Generate tasks from sale orders', + 'module_sale_service': fields.boolean('Generate tasks from sale orders', help='This feature automatically creates project tasks from service products in sale orders. ' 'More precisely, tasks are created for procurement lines with product of type \'Service\', ' 'procurement method \'Make to Order\', and supply method \'Manufacture\'.\n' - '-This installs the module project_mrp.'), + '-This installs the module sale_service.'), 'module_pad': fields.boolean("Use integrated collaborative note pads on task", help='Lets the company customize which Pad installation should be used to link to new pads ' '(for example: http://ietherpad.com/).\n' diff --git a/addons/project/res_config_view.xml b/addons/project/res_config_view.xml index f190fba3b3a..16dce54d88b 100644 --- a/addons/project/res_config_view.xml +++ b/addons/project/res_config_view.xml @@ -33,8 +33,8 @@