diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py index 5ab8d385943..b5aa1cbb20d 100644 --- a/addons/account/__openerp__.py +++ b/addons/account/__openerp__.py @@ -65,6 +65,7 @@ for a particular financial year and for preparation of vouchers there is a modul 'wizard/account_period_close_view.xml', 'wizard/account_reconcile_view.xml', 'wizard/account_unreconcile_view.xml', + 'wizard/account_statement_from_invoice_view.xml', 'account_view.xml', 'account_report.xml', 'account_financial_report_data.xml', @@ -144,6 +145,7 @@ for a particular financial year and for preparation of vouchers there is a modul 'qweb' : [ "static/src/xml/account_move_reconciliation.xml", "static/src/xml/account_move_line_quickadd.xml", + "static/src/xml/account_bank_statement_reconciliation.xml", ], 'demo': [ 'demo/account_demo.xml', @@ -151,6 +153,7 @@ for a particular financial year and for preparation of vouchers there is a modul 'project/analytic_account_demo.xml', 'demo/account_minimal.xml', 'demo/account_invoice_demo.xml', + 'demo/account_bank_statement.xml', 'account_unit_test.xml', ], 'test': [ diff --git a/addons/account/account.py b/addons/account/account.py index 51700d30731..4e075be9cbf 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -2075,6 +2075,11 @@ class account_tax(osv.osv): cur_price_unit+=amount2 return res + def compute_for_bank_reconciliation(self, cr, uid, tax_id, amount, context=None): + """ Called by RPC by the bank statement reconciliation widget """ + tax = self.browse(cr, uid, tax_id, context=context) + return self.compute_all(cr, uid, [tax], amount, 1) # TOCHECK may use force_exclude parameter + def compute_all(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, force_excluded=False): """ :param force_excluded: boolean used to say that we don't want to consider the value of field price_include of diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index cf8bb5133fd..75940d2d83d 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -19,11 +19,10 @@ # ############################################################################## -import time - from openerp.osv import fields, osv from openerp.tools.translate import _ import openerp.addons.decimal_precision as dp +from openerp.report import report_sxw class account_bank_statement(osv.osv): def create(self, cr, uid, vals, context=None): @@ -66,6 +65,18 @@ class account_bank_statement(osv.osv): return periods[0] return False + def _compute_default_statement_name(self, cr, uid, 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) + def _currency(self, cursor, user, ids, name, args, context=None): res = {} res_currency_obj = self.pool.get('res.currency') @@ -92,12 +103,18 @@ class account_bank_statement(osv.osv): result[line.statement_id.id] = True return result.keys() + def _all_lines_reconciled(self, cr, uid, ids, name, args, context=None): + res = {} + for statement in self.browse(cr, uid, ids, context=context): + res[statement.id] = all([line.journal_entry_id.id for line in statement.line_ids]) + return res + _order = "date desc, id desc" _name = "account.bank.statement" _description = "Bank Statement" _inherit = ['mail.thread'] _columns = { - 'name': fields.char('Reference', size=64, required=True, states={'draft': [('readonly', False)]}, readonly=True, help='if you give the Name other then /, its created Accounting Entries Move will be with same name as statement name. This allows the statement entries to have the same references than the statement itself'), # readonly for account_cash_statement + 'name': fields.char('Reference', size=64, states={'draft': [('readonly', False)]}, readonly=True, help='if you give the Name other then /, its created Accounting Entries Move will be with same name as statement name. This allows the statement entries to have the same references than the statement itself'), # readonly for account_cash_statement 'date': fields.date('Date', required=True, states={'confirm': [('readonly', True)]}, select=True), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}), @@ -129,10 +146,11 @@ class account_bank_statement(osv.osv): type='many2one', relation='res.currency'), 'account_id': fields.related('journal_id', 'default_debit_account_id', type='many2one', relation='account.account', string='Account used in this journal', readonly=True, help='used in statement reconciliation domain, but shouldn\'t be used elswhere.'), 'cash_control': fields.related('journal_id', 'cash_control' , type='boolean', relation='account.journal',string='Cash control'), + 'all_lines_reconciled': fields.function(_all_lines_reconciled, string='All lines reconciled', type='boolean'), } _defaults = { - 'name': "/", + 'name': _compute_default_statement_name, 'date': fields.date.context_today, 'state': 'draft', 'journal_id': _default_journal_id, @@ -193,37 +211,6 @@ class account_bank_statement(osv.osv): 'ref': st_line.ref, } - 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 bank 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). - - :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 company_currency_id: ID of currency of the concerned company - :return: dict of value to create() the bank account.move.line - """ - anl_id = st_line.analytic_account_id and st_line.analytic_account_id.id or False - debit = ((amount<0) and -amount) or 0.0 - credit = ((amount>0) and amount) or 0.0 - cur_id = False - amt_cur = False - if st_line.statement_id.currency.id <> company_currency_id: - cur_id = st_line.statement_id.currency.id - if st_line.account_id and st_line.account_id.currency_id and st_line.account_id.currency_id.id <> company_currency_id: - cur_id = st_line.account_id.currency_id.id - if cur_id: - res_currency_obj = self.pool.get('res.currency') - amt_cur = -res_currency_obj.compute(cr, uid, company_currency_id, cur_id, amount, context=context) - - res = self._prepare_move_line_vals(cr, uid, st_line, move_id, debit, credit, - amount_currency=amt_cur, currency_id=cur_id, analytic_id=anl_id, context=context) - return res - 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 @@ -248,8 +235,7 @@ class account_bank_statement(osv.osv): """ return st_line.partner_id and st_line.partner_id.id or False - def _prepare_counterpart_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, - context=None): + 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 @@ -266,19 +252,22 @@ class account_bank_statement(osv.osv): account_id = self._get_counter_part_account(cr, uid, st_line, context=context) partner_id = self._get_counter_part_partner(cr, uid, st_line, context=context) debit = ((amount > 0) and amount) or 0.0 - credit = ((amount < 0) and -amount) or 0.0 + credit = ((amount < 0) and -amount) or 0.0 cur_id = False amt_cur = False - if st_line.statement_id.currency.id <> company_currency_id: + if st_line.statement_id.currency.id != company_currency_id: amt_cur = st_line.amount - cur_id = st_line.statement_id.currency.id + 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 return self._prepare_move_line_vals(cr, uid, st_line, move_id, debit, credit, - amount_currency = amt_cur, currency_id = cur_id, account_id = account_id, - partner_id = partner_id, context=context) + amount_currency=amt_cur, currency_id=cur_id, account_id=account_id, + partner_id=partner_id, context=context) - def _prepare_move_line_vals(self, cr, uid, st_line, move_id, debit, credit, currency_id = False, - amount_currency= False, account_id = False, analytic_id = False, - partner_id = False, context=None): + 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 @@ -293,7 +282,6 @@ class account_bank_statement(osv.osv): :param float amount_currency: amount of the debit/credit expressed in the currency_id :param int/long account_id: ID of the account to use in the move line if different from the statement line account ID - :param int/long analytic_id: ID of analytic account to put on the move line :param int/long partner_id: ID of the partner to put on the move line :return: dict of value to create() the account.move.line """ @@ -314,67 +302,8 @@ class account_bank_statement(osv.osv): 'period_id': st_line.statement_id.period_id.id, 'currency_id': amount_currency and cur_id, 'amount_currency': amount_currency, - 'analytic_account_id': analytic_id, } - def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, st_line_number, context=None): - """Create the account move from the statement line. - - :param int/long st_line_id: ID of the account.bank.statement.line to create the move from. - :param int/long company_currency_id: ID of the res.currency of the company - :param char st_line_number: will be used as the name of the generated account move - :return: ID of the account.move created - """ - - if context is None: - context = {} - res_currency_obj = self.pool.get('res.currency') - account_move_obj = self.pool.get('account.move') - account_move_line_obj = self.pool.get('account.move.line') - account_bank_statement_line_obj = self.pool.get('account.bank.statement.line') - st_line = account_bank_statement_line_obj.browse(cr, uid, st_line_id, context=context) - st = st_line.statement_id - - context.update({'date': st_line.date}) - - move_vals = self._prepare_move(cr, uid, st_line, st_line_number, context=context) - move_id = account_move_obj.create(cr, uid, move_vals, context=context) - account_bank_statement_line_obj.write(cr, uid, [st_line.id], { - 'move_ids': [(4, move_id, False)] - }) - torec = [] - acc_cur = ((st_line.amount<=0) and st.journal_id.default_debit_account_id) or st_line.account_id - - context.update({ - 'res.currency.compute.account': acc_cur, - }) - amount = res_currency_obj.compute(cr, uid, st.currency.id, - company_currency_id, st_line.amount, context=context) - - bank_move_vals = self._prepare_bank_move_line(cr, uid, st_line, move_id, amount, - company_currency_id, context=context) - move_line_id = account_move_line_obj.create(cr, uid, bank_move_vals, context=context) - torec.append(move_line_id) - - counterpart_move_vals = self._prepare_counterpart_move_line(cr, uid, st_line, move_id, - amount, company_currency_id, context=context) - account_move_line_obj.create(cr, uid, counterpart_move_vals, context=context) - - for line in account_move_line_obj.browse(cr, uid, [x.id for x in - account_move_obj.browse(cr, uid, move_id, - context=context).line_id], - context=context): - if line.state <> 'valid': - raise osv.except_osv(_('Error!'), - _('Journal item "%s" is not valid.') % line.name) - - # Bank statements will not consider boolean on journal entry_posted - account_move_obj.post(cr, uid, [move_id], context=context) - return move_id - - def get_next_st_line_number(self, cr, uid, st_number, st_line, context=None): - return st_number + '/' + str(st_line.sequence) - def balance_check(self, cr, uid, st_id, journal_type='bank', context=None): st = self.browse(cr, uid, st_id, context=context) if not ((abs((st.balance_end or 0.0) - st.balance_end_real) < 0.0001) or (abs((st.balance_end or 0.0) - st.balance_end_real) < 0.0001)): @@ -389,49 +318,30 @@ class account_bank_statement(osv.osv): return state in ('draft','open') def button_confirm_bank(self, cr, uid, ids, context=None): - obj_seq = self.pool.get('ir.sequence') if context is None: context = {} for st in self.browse(cr, uid, ids, context=context): j_type = st.journal_id.type - company_currency_id = st.journal_id.company_id.currency_id.id if not self.check_status_condition(cr, uid, st.state, journal_type=j_type): continue self.balance_check(cr, uid, st.id, journal_type=j_type, context=context) if (not st.journal_id.default_credit_account_id) \ or (not st.journal_id.default_debit_account_id): - raise osv.except_osv(_('Configuration Error!'), - _('Please verify that an account is defined in the journal.')) - - if not st.name == '/': - st_number = st.name - else: - c = {'fiscalyear_id': st.period_id.fiscalyear_id.id} - if st.journal_id.sequence_id: - st_number = obj_seq.next_by_id(cr, uid, st.journal_id.sequence_id.id, context=c) - else: - st_number = obj_seq.next_by_code(cr, uid, 'account.bank.statement', context=c) - + raise osv.except_osv(_('Configuration Error!'), _('Please verify that an account is defined in the journal.')) for line in st.move_line_ids: if line.state <> 'valid': - raise osv.except_osv(_('Error!'), - _('The account entries lines are not in valid state.')) + raise osv.except_osv(_('Error!'), _('The account entries lines are not in valid state.')) + move_ids = [] for st_line in st.line_ids: - if st_line.analytic_account_id: - if not st.journal_id.analytic_journal_id: - raise osv.except_osv(_('No Analytic Journal!'),_("You have to assign an analytic journal on the '%s' journal!") % (st.journal_id.name,)) if not st_line.amount: continue - st_line_number = self.get_next_st_line_number(cr, uid, st_number, st_line, context) - self.create_move_from_st_line(cr, uid, st_line.id, company_currency_id, st_line_number, context) - - self.write(cr, uid, [st.id], { - 'name': st_number, - 'balance_end_real': st.balance_end - }, context=context) - self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context) + if not st_line.journal_entry_id.id: + raise osv.except_osv(_('Error!'), _('All the account entries lines must be processed in order to close the statement.')) + 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) return self.write(cr, uid, ids, {'state':'confirm'}, context=context) def button_cancel(self, cr, uid, ids, context=None): @@ -492,93 +402,356 @@ class account_bank_statement(osv.osv): return super(account_bank_statement, self).copy(cr, uid, id, default, context=context) def button_journal_entries(self, cr, uid, ids, context=None): - ctx = (context or {}).copy() - ctx['journal_id'] = self.browse(cr, uid, ids[0], context=context).journal_id.id - return { - 'name': _('Journal Items'), - 'view_type':'form', - 'view_mode':'tree', - 'res_model':'account.move.line', - 'view_id':False, - 'type':'ir.actions.act_window', - 'domain':[('statement_id','in',ids)], - 'context':ctx, - } + ctx = (context or {}).copy() + ctx['journal_id'] = self.browse(cr, uid, ids[0], context=context).journal_id.id + return { + 'name': _('Journal Items'), + 'view_type':'form', + 'view_mode':'tree', + 'res_model':'account.move.line', + 'view_id':False, + 'type':'ir.actions.act_window', + 'domain':[('statement_id','in',ids)], + 'context':ctx, + } + 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) + + 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 """ + 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 + 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)+");" class account_bank_statement_line(osv.osv): - def onchange_partner_id(self, cr, uid, ids, partner_id, context=None): - obj_partner = self.pool.get('res.partner') - if context is None: - context = {} - if not partner_id: - return {} - part = obj_partner.browse(cr, uid, partner_id, context=context) - if not part.supplier and not part.customer: - type = 'general' - elif part.supplier and part.customer: - type = 'general' - else: - if part.supplier == True: - type = 'supplier' - if part.customer == True: - type = 'customer' - res_type = self.onchange_type(cr, uid, ids, partner_id=partner_id, type=type, context=context) - if res_type['value'] and res_type['value'].get('account_id', False): - return {'value': {'type': type, 'account_id': res_type['value']['account_id']}} - return {'value': {'type': type}} + def get_data_for_reconciliations(self, cr, uid, ids, context=None): + """ Used to instanciate a batch of reconciliations in a single request """ + # Build a list of reconciliations data + ret = [] + mv_line_ids_selected = [] + for st_line_id in ids: + reconciliation_data = { + 'st_line': self.get_statement_line_for_reconciliation(cr, uid, st_line_id, context), + 'reconciliation_proposition': self.get_reconciliation_proposition(cr, uid, st_line_id, mv_line_ids_selected, context) + } + for mv_line in reconciliation_data['reconciliation_proposition']: + mv_line_ids_selected.append(mv_line['id']) + 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 + return ret - def onchange_type(self, cr, uid, line_id, partner_id, type, context=None): - res = {'value': {}} - obj_partner = self.pool.get('res.partner') + 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 + 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) + + dict = { + 'id': line.id, + 'ref': line.ref, + 'note': line.note or "", + 'name': line.name, + 'date': line.date, + 'amount': line.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, + 'partner_id': line.partner_id.id, + 'statement_id': line.statement_id.id, + 'account_code': line.journal_id.default_debit_account_id.code, + 'account_name': line.journal_id.default_debit_account_id.name, + 'partner_name': line.partner_id.name, + 'amount_currency_str': amount_currency_str, + '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 + + 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: + 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 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))]) + 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)]) + else: + mv_lines = self.get_move_lines_counterparts(cr, uid, id, 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: + ret.append(line) + total += line[amount_field] + else: + 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): + """ 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 integers list excluded_ids: ids of move lines that should not be fetched + :param string 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 = {} - if not partner_id: - return res - account_id = False - line = self.browse(cr, uid, line_id, context=context) - if not line or (line and not line[0].account_id): - part = obj_partner.browse(cr, uid, partner_id, context=context) - if type == 'supplier': - account_id = part.property_account_payable.id - else: - account_id = part.property_account_receivable.id - res['value']['account_id'] = account_id - return res + + 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 + ] + if excluded_ids: + domain.append(('id', 'not in', excluded_ids)) + if str: + domain += ['|', ('move_id.name', 'ilike', str), ('move_id.ref', 'ilike', str)] + #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): + if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: + continue + nb_lines += 1 + if line.reconcile_partial_id: + reconcile_partial_ids.append(line.reconcile_partial_id.id) + return nb_lines + else: + ret = [] + for line in mv_line_pool.browse(cr, uid, ids, context=context): + if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: + continue + amount_currency_str = "" + if line.currency_id and line.amount_currency: + amount_currency_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id) + ret_line = { + 'id': line.id, + 'name': line.move_id.name, + 'ref': line.move_id.ref, + 'account_code': line.account_id.code, + 'account_name': line.account_id.name, + 'account_type': line.account_id.type, + 'date_maturity': line.date_maturity, + 'date': line.date, + 'period_name': line.period_id.name, + 'journal_name': line.journal_id.name, + 'amount_currency_str': amount_currency_str, + } + if statement_currency.id != company_currency.id and line.currency_id and line.currency_id.id == statement_currency.id: + if line.amount_residual_currency < 0: + ret_line['debit'] = 0 + ret_line['credit'] = -line.amount_residual_currency + else: + ret_line['debit'] = line.amount_residual_currency if line.credit != 0 else 0 + ret_line['credit'] = line.amount_residual_currency if line.debit != 0 else 0 + ret_line['amount_currency_str'] = rml_parser.formatLang(line.amount_residual, currency_obj=company_currency) + else: + if line.amount_residual < 0: + ret_line['debit'] = 0 + ret_line['credit'] = -line.amount_residual + else: + ret_line['debit'] = line.amount_residual if line.credit != 0 else 0 + 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.append(ret_line) + if line.reconcile_partial_id: + reconcile_partial_ids.append(line.reconcile_partial_id.id) + return ret + + 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 + """ + 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 + bs_obj = self.pool.get('account.bank.statement') + am_obj = self.pool.get('account.move') + aml_obj = self.pool.get('account.move.line') + currency_obj = self.pool.get('res.currency') + + # Checks + if st_line.journal_entry_id.id != False: + 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']: + if field not in mv_line_dict: + mv_line_dict[field] = 0.0 + if mv_line_dict.get('counterpart_move_line_id'): + mv_line = aml_obj.browse(cr, uid, mv_line_dict.get('counterpart_move_line_id'), context=context) + if mv_line.reconcile_id: + raise osv.except_osv(_('Error!'), _('A selected move line was already reconciled.')) + + # Create the move + move_name = st_line.statement_id.name + "/" + str(st_line.sequence) + move_vals = bs_obj._prepare_move(cr, uid, st_line, move_name, context=context) + move_id = am_obj.create(cr, uid, move_vals, context=context) + + # Create the move line for the statement line + 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 + 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 + 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: + 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 + + # Create move lines + move_line_pairs_to_reconcile = [] + for mv_line_dict in mv_line_dicts: + 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'] + del mv_line_dict['counterpart_move_line_id'] + new_aml_id = aml_obj.create(cr, uid, mv_line_dict, context=context) + if counterpart_move_line_id != None: + move_line_pairs_to_reconcile.append([new_aml_id, counterpart_move_line_id]) + + # Reconcile + for pair in move_line_pairs_to_reconcile: + # TODO : too slow + aml_obj.reconcile_partial(cr, uid, pair, context=context) + + # Mark the statement line as reconciled + self.write(cr, uid, id, {'journal_entry_id': move_id}, context=context) + + # FIXME : if it wasn't for the multicompany security settings in account_security.xml, the method would just + # return [('journal_entry_id', '=', False)] + # Unfortunately, that spawns a "no access rights" error ; it shouldn't. + def _needaction_domain_get(self, cr, uid, context=None): + user = self.pool.get("res.users").browse(cr, uid, uid) + return ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id]),('journal_entry_id', '=', False)] _order = "statement_id desc, sequence" _name = "account.bank.statement.line" _description = "Bank Statement Line" + _inherit = ['ir.needaction_mixin'] _columns = { 'name': fields.char('Description', required=True), 'date': fields.date('Date', required=True), 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')), - 'type': fields.selection([ - ('supplier','Supplier'), - ('customer','Customer'), - ('general','General') - ], 'Type', required=True), 'partner_id': fields.many2one('res.partner', 'Partner'), - 'account_id': fields.many2one('account.account','Account', - required=True), - 'statement_id': fields.many2one('account.bank.statement', 'Statement', - select=True, required=True, ondelete='cascade'), + '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), - 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'), - 'move_ids': fields.many2many('account.move', - 'account_bank_statement_line_move_rel', 'statement_line_id','move_id', - 'Moves'), 'ref': fields.char('Reference', size=32), '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), + 'journal_entry_id': fields.many2one('account.move', 'Journal Entry'), + 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency if it is a multi-currency entry.", digits_compute=dp.get_precision('Account')), + 'currency_id': fields.many2one('res.currency', 'Currency', help="The optional other currency if it is a multi-currency entry."), } _defaults = { 'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement.line'), 'date': lambda self,cr,uid,context={}: context.get('date', fields.date.context_today(self,cr,uid,context=context)), - 'type': 'general', } +class account_statement_operation_template(osv.osv): + _name = "account.statement.operation.template" + _description = "Preset for the lines that can be created in a bank statement reconciliation" + _columns = { + 'name': fields.char('Button Label', required=True), + 'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type','!=','view')]), + 'label': fields.char('Label'), + 'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')], + 'Amount type', required=True), + 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), help="Leave to 0 to ignore."), + 'tax_id': fields.many2one('account.tax', 'Tax', ondelete='cascade'), + 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='cascade'), + } + _defaults = { + 'amount_type': 'fixed', + 'amount': 0.0 + } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 5b09f815f54..9d4654c9eee 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -216,7 +216,7 @@ class account_invoice(osv.osv): _name = "account.invoice" _inherit = ['mail.thread'] _description = 'Invoice' - _order = "id desc" + _order = "number desc, id desc" _track = { 'type': { }, diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 628cc7229e5..a37ca6acfd5 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -430,7 +430,7 @@ class account_move_line(osv.osv): elif line.reconcile_partial_id: res[line.id] = str(line.reconcile_partial_id.name) return res - + def _get_move_from_reconcile(self, cr, uid, ids, context=None): move = {} for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids, context=context): @@ -491,7 +491,7 @@ class account_move_line(osv.osv): type='many2one', relation='account.invoice', fnct_search=_invoice_search), 'account_tax_id':fields.many2one('account.tax', 'Tax'), 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'), - 'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', + 'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True) } @@ -765,7 +765,7 @@ class account_move_line(osv.osv): WHERE debit > 0 AND credit > 0 AND (last_reconciliation_date IS NULL OR max_date > last_reconciliation_date) ORDER BY last_reconciliation_date""") ids = [x[0] for x in cr.fetchall()] - if not ids: + if not ids: return [] # To apply the ir_rules @@ -793,9 +793,11 @@ class account_move_line(osv.osv): else: currency_id = line.company_id.currency_id if line.reconcile_id: - raise osv.except_osv(_('Warning'), _("Journal Item '%s' (id: %s), Move '%s' is already reconciled!") % (line.name, line.id, line.move_id.name)) + raise osv.except_osv(_('Warning'), _("Journal Item '%s' (id: %s), Move '%s' is already reconciled!") % (line.name, line.id, line.move_id.name)) if line.reconcile_partial_id: for line2 in line.reconcile_partial_id.line_partial_ids: + if line2.state != 'valid': + raise osv.except_osv(_('Warning'), _("Journal Item '%s' (id: %s) cannot be used in a reconciliation as it is not balanced!") % (line2.name, line2.id)) if not line2.reconcile_id: if line2.id not in merges: merges.append(line2.id) @@ -1119,7 +1121,7 @@ class account_move_line(osv.osv): period = period_obj.browse(cr, uid, period_id, context=context) for (state,) in result: if state == 'done': - raise osv.except_osv(_('Error!'), _('You can not add/modify entries in a closed period %s of journal %s.' % (period.name,journal.name))) + raise osv.except_osv(_('Error!'), _('You can not add/modify entries in a closed period %s of journal %s.' % (period.name,journal.name))) if not result: jour_period_obj.create(cr, uid, { 'name': (journal.code or journal.name)+':'+(period.name or ''), diff --git a/addons/account/account_unit_test.xml b/addons/account/account_unit_test.xml index 5fb1c55b669..04b242b037c 100644 --- a/addons/account/account_unit_test.xml +++ b/addons/account/account_unit_test.xml @@ -6,10 +6,10 @@ - + draft in_invoice - + Test invoice 1 diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index a993c0708bc..032c94258c2 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -489,6 +489,13 @@ src_model="account.journal"/> + + + Reconciliation on Bank Statements + bank_statement_reconciliation_view + {'statement_id': active_id} + + account.cash.statement.select account.bank.statement @@ -525,6 +532,7 @@ + account.bank.statement.search account.bank.statement @@ -544,6 +552,7 @@ + account.bank.statement.form account.bank.statement @@ -551,14 +560,21 @@ - - + + + + + + + - + @@ -581,41 +597,30 @@ - + - - - - + + + + - - - - + '|',('customer','=',True),('supplier','=',True)]" + context="{'default_supplier': 1}" + attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"/> + + + + - - - - - - - - - - - - - - - @@ -682,6 +687,85 @@ [('state','=','draft')] + + + Reconciliation on Bank Statements + account.bank.statement.line + bank_statement_reconciliation_view + + + + + + + account.statement.operation.template.form + account.statement.operation.template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + account.statement.operation.template.tree + account.statement.operation.template + + + + + + + + + + account.statement.operation.template.search + account.statement.operation.template + + + + + + + + + Statement Operation Templates + account.statement.operation.template + form + tree,form + + + + Click to create a statement operation template. + + Those can be used to quickly create a move line when reconciling + your bank statements. + + + + @@ -1602,11 +1686,11 @@ - - - - - + + + + + @@ -2258,7 +2342,13 @@ - + + + + + + + @@ -2288,10 +2378,7 @@ - - - - + @@ -2299,10 +2386,7 @@ - - - - + diff --git a/addons/account/demo/account_invoice_demo.xml b/addons/account/demo/account_invoice_demo.xml index 280eeeb9e94..4f329eb32d1 100644 --- a/addons/account/demo/account_invoice_demo.xml +++ b/addons/account/demo/account_invoice_demo.xml @@ -44,5 +44,135 @@ Zed+ Antivirus + + + + + + + draft + out_invoice + + + + + Otpez Laptop without OS + + 642 + 5 + + + + Linutop + + 280 + 5 + + + + + + + + + draft + out_invoice + + + + + + 8-port Switch + + 50 + 3 + + + + 30m RJ45 wire + + 25 + 20 + + + + + + + + + draft + out_invoice + + + + + + TypeMatrix Dvorak Keyboard + + 90 + 5 + + + + Ergonomic Mouse + + 15 + 5 + + + + + + + + + draft + out_invoice + + + + + + Desktop Computer Table + + 80 + 5 + + + + Desktop Lamp + + 20 + 5 + + + + + + + + + draft + out_invoice + + + + + + TypeMatrix Dvorak Keyboard + + 90 + 5 + + + + Ergonomic Mouse + + 15 + 5 + + + diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv index d1f0bbab6b5..3183cb2c979 100644 --- a/addons/account/security/ir.model.access.csv +++ b/addons/account/security/ir.model.access.csv @@ -1,100 +1,101 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_product_product_account_user,product.product.account.user,product.model_product_product,group_account_user,1,0,0,0 -access_account_payment_term,account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0 -access_account_payment_term_line,account.payment.term.line,model_account_payment_term_line,account.group_account_user,1,0,0,0 -access_account_account_type,account.account.type,model_account_account_type,account.group_account_user,1,0,0,0 -access_account_tax_internal_user,account.tax internal user,model_account_tax,base.group_user,1,0,0,0 -access_account_account,account.account,model_account_account,account.group_account_user,1,0,0,0 -access_account_account_user,account.account user,model_account_account,base.group_user,1,0,0,0 -access_account_account_partner_manager,account.account partner manager,model_account_account,base.group_partner_manager,1,0,0,0 -access_account_journal_period_manager,account.journal.period manager,model_account_journal_period,account.group_account_manager,1,0,0,0 -access_account_tax_code,account.tax.code,model_account_tax_code,account.group_account_invoice,1,0,0,0 -access_account_tax,account.tax,model_account_tax,account.group_account_invoice,1,0,0,0 -access_account_model,account.model,model_account_model,account.group_account_user,1,1,1,1 -access_account_model_line,account.model.line,model_account_model_line,account.group_account_user,1,1,1,1 -access_account_subscription,account.subscription,model_account_subscription,account.group_account_user,1,1,1,1 -access_account_subscription_line,account.subscription.line,model_account_subscription_line,account.group_account_user,1,1,1,1 -access_account_subscription_manager,account.subscription manager,model_account_subscription,account.group_account_manager,1,0,0,0 -access_account_subscription_line_manager,account.subscription.line manager,model_account_subscription_line,account.group_account_manager,1,0,0,0 -access_account_account_template,account.account.template,model_account_account_template,account.group_account_manager,1,1,1,1 -access_account_tax_code_template,account.tax.code.template,model_account_tax_code_template,account.group_account_manager,1,1,1,1 -access_account_chart_template,account.chart.template,model_account_chart_template,account.group_account_manager,1,1,1,1 -access_account_tax_template,account.tax.template,model_account_tax_template,account.group_account_manager,1,1,1,1 -access_account_bank_statement,account.bank.statement,model_account_bank_statement,account.group_account_user,1,1,1,1 -access_account_bank_statement_line,account.bank.statement.line,model_account_bank_statement_line,account.group_account_user,1,1,1,1 -access_account_analytic_line_manager,account.analytic.line manager,model_account_analytic_line,account.group_account_manager,1,0,0,0 -access_account_analytic_account,account.analytic.account,analytic.model_account_analytic_account,base.group_user,1,0,0,0 -access_account_analytic_journal,account.analytic.journal,model_account_analytic_journal,account.group_account_user,1,0,0,0 -access_account_analytic_journal_user,account.analytic.journal,model_account_analytic_journal,base.group_user,1,1,1,0 -access_account_invoice_uinvoice,account.invoice,model_account_invoice,account.group_account_invoice,1,1,1,1 -access_account_invoice_line_uinvoice,account.invoice.line,model_account_invoice_line,account.group_account_invoice,1,1,1,1 -access_account_invoice_tax_uinvoice,account.invoice.tax,model_account_invoice_tax,account.group_account_invoice,1,1,1,1 -access_account_move_uinvoice,account.move,model_account_move,account.group_account_invoice,1,1,1,1 -access_account_move_line_uinvoice,account.move.line invoice,model_account_move_line,account.group_account_invoice,1,1,1,1 -access_account_move_reconcile_uinvoice,account.move.reconcile,model_account_move_reconcile,account.group_account_invoice,1,1,1,1 -access_account_journal_period_uinvoice,account.journal.period,model_account_journal_period,account.group_account_invoice,1,1,1,1 -access_account_payment_term_manager,account.payment.term,model_account_payment_term,account.group_account_manager,1,1,1,1 -access_account_payment_term_line_manager,account.payment.term.line,model_account_payment_term_line,account.group_account_manager,1,1,1,1 -access_account_tax_manager,account.tax,model_account_tax,account.group_account_manager,1,1,1,1 -access_account_journal_manager,account.journal,model_account_journal,account.group_account_manager,1,1,1,1 -access_account_journal_invoice,account.journal invoice,model_account_journal,account.group_account_invoice,1,0,0,0 -access_account_period_manager,account.period,model_account_period,account.group_account_manager,1,1,1,1 -access_account_period_invoice,account.period invoice,model_account_period,account.group_account_invoice,1,0,0,0 -access_account_invoice_group_invoice,account.invoice group invoice,model_account_invoice,account.group_account_invoice,1,1,1,1 -access_account_analytic_journal_manager,account.analytic.journal,model_account_analytic_journal,account.group_account_manager,1,1,1,1 -access_account_fiscalyear,account.fiscalyear,model_account_fiscalyear,account.group_account_manager,1,1,1,1 -access_account_fiscalyear_invoice,account.fiscalyear.invoice,model_account_fiscalyear,account.group_account_invoice,1,0,0,0 -access_account_fiscalyear_partner_manager,account.fiscalyear.partnermanager,model_account_fiscalyear,base.group_partner_manager,1,0,0,0 -access_account_fiscalyear_employee,account.fiscalyear employee,model_account_fiscalyear,base.group_user,1,0,0,0 -access_res_currency_account_manager,res.currency account manager,base.model_res_currency,group_account_manager,1,1,1,1 -access_res_currency_rate_account_manager,res.currency.rate account manager,base.model_res_currency_rate,group_account_manager,1,1,1,1 -access_res_currency_rate_type_account_manager,res.currency.rate.type account manager,base.model_res_currency_rate_type,group_account_manager,1,1,1,1 -access_account_invoice_user,account.invoice user,model_account_invoice,base.group_user,1,0,0,0 -access_account_invoice_user,account.invoice.line user,model_account_invoice_line,base.group_user,1,0,0,0 -access_account_payment_term_partner_manager,account.payment.term partner manager,model_account_payment_term,base.group_user,1,0,0,0 -access_account_payment_term_line_partner_manager,account.payment.term.line partner manager,model_account_payment_term_line,base.group_user,1,0,0,0 -access_account_account_sale_manager,account.account sale manager,model_account_account,base.group_sale_manager,1,0,0,0 -access_account_fiscal_position_product_manager,account.fiscal.position account.manager,model_account_fiscal_position,account.group_account_manager,1,1,1,1 -access_account_fiscal_position_tax_product_manager,account.fiscal.position.tax account.manager,model_account_fiscal_position_tax,account.group_account_manager,1,1,1,1 -access_account_fiscal_position_account_product_manager,account.fiscal.position account.manager,model_account_fiscal_position_account,account.group_account_manager,1,1,1,1 -access_account_fiscal_position,account.fiscal.position all,model_account_fiscal_position,base.group_user,1,0,0,0 -access_account_fiscal_position_tax,account.fiscal.position.tax all,model_account_fiscal_position_tax,base.group_user,1,0,0,0 -access_account_fiscal_position_account,account.fiscal.position all,model_account_fiscal_position_account,base.group_user,1,0,0,0 -access_account_fiscal_position_template,account.fiscal.position.template,model_account_fiscal_position_template,account.group_account_manager,1,1,1,1 -access_account_fiscal_position_tax_template,account.fiscal.position.tax.template,model_account_fiscal_position_tax_template,account.group_account_manager,1,1,1,1 -access_account_fiscal_position_account_template,account.fiscal.position.account.template,model_account_fiscal_position_account_template,account.group_account_manager,1,1,1,1 -access_account_sequence_fiscal_year_user,account.sequence.fiscalyear user,model_account_sequence_fiscalyear,base.group_user,1,0,0,0 -access_temp_range,temp.range,model_temp_range,account.group_account_manager,1,0,0,0 -access_report_aged_receivable,report.aged.receivable,model_report_aged_receivable,account.group_account_manager,1,1,1,1 -access_report_invoice_created,report.invoice.created,model_report_invoice_created,account.group_account_manager,1,1,1,1 -access_report_account_type_sales,report.account_type.sales,model_report_account_type_sales,account.group_account_manager,1,1,1,1 -access_report_account_sales,report.account.sales,model_report_account_sales,account.group_account_manager,1,1,1,1 -access_account_invoice_report,account.invoice.report,model_account_invoice_report,account.group_account_manager,1,1,1,1 -access_res_partner_group_account_manager,res_partner group_account_manager,model_res_partner,account.group_account_manager,1,0,0,0 -access_account_invoice_accountant,account.invoice accountant,model_account_invoice,account.group_account_user,1,0,0,0 -access_account_tax_code_accountant,account.tax.code accountant,model_account_tax_code,account.group_account_user,1,1,1,1 -access_account_move_line_manager,account.move.line manager,model_account_move_line,account.group_account_manager,1,0,0,0 -access_account_move_manager,account.move manager,model_account_move,account.group_account_manager,1,0,0,0 -access_account_entries_report_manager,account.entries.report,model_account_entries_report,account.group_account_manager,1,1,1,1 -access_account_entries_report_invoice,account.entries.report,model_account_entries_report,account.group_account_invoice,1,0,0,0 -access_account_entries_report_employee,account.entries.report employee,model_account_entries_report,base.group_user,1,0,0,0 -access_analytic_entries_report_manager,analytic.entries.report,model_analytic_entries_report,account.group_account_manager,1,0,0,0 -access_account_cashbox_line,account.cashbox.line,model_account_cashbox_line,account.group_account_user,1,1,1,1 -access_account_journal_cashbox_line,account.journal.cashbox.line,model_account_journal_cashbox_line,account.group_account_user,1,1,1,0 -access_account_invoice_tax_accountant,account.invoice.tax accountant,model_account_invoice_tax,account.group_account_user,1,0,0,0 -access_account_move_reconcile_manager,account.move.reconcile manager,model_account_move_reconcile,account.group_account_manager,1,0,0,0 -access_account_analytic_line_invoice,account.analytic.line invoice,model_account_analytic_line,account.group_account_invoice,1,1,1,1 -access_account_invoice_line_accountant,account.invoice.line accountant,model_account_invoice_line,account.group_account_user,1,0,0,0 -access_account_account_invoice,account.account invoice,model_account_account,account.group_account_invoice,1,1,1,1 -access_account_analytic_accountant,account.analytic.account accountant,analytic.model_account_analytic_account,account.group_account_user,1,1,1,1 -access_account_account_type_invoice,account.account.type invoice,model_account_account_type,account.group_account_invoice,1,1,1,1 -access_report_account_receivable_invoice,report.account.receivable.invoice,model_report_account_receivable,account.group_account_invoice,1,1,1,1 -access_account_sequence_fiscal_year_invoice,account.sequence.fiscalyear invoice,model_account_sequence_fiscalyear,account.group_account_invoice,1,1,1,1 -access_account_tax_sale_manager,account.tax sale manager,model_account_tax,base.group_sale_salesman,1,0,0,0 -access_account_journal_sale_manager,account.journal sale manager,model_account_journal,base.group_sale_salesman,1,0,0,0 -access_account_invoice_tax_sale_manager,account.invoice.tax sale manager,model_account_invoice_tax,base.group_sale_salesman,1,0,0,0 -access_account_sequence_fiscal_year_sale_user,account.sequence.fiscalyear.sale.user,model_account_sequence_fiscalyear,base.group_sale_salesman,1,1,1,0 -access_account_sequence_fiscal_year_sale_manager,account.sequence.fiscalyear.sale.manager,model_account_sequence_fiscalyear,base.group_sale_manager,1,1,1,1 -access_account_treasury_report_manager,account.treasury.report.manager,model_account_treasury_report,account.group_account_manager,1,0,0,0 -access_account_financial_report,account.financial.report,model_account_financial_report,account.group_account_user,1,1,1,1 -access_account_financial_report_invoice,account.financial.report invoice,model_account_financial_report,account.group_account_invoice,1,0,0,0 +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_product_product_account_user,product.product.account.user,product.model_product_product,group_account_user,1,0,0,0 +access_account_payment_term,account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0 +access_account_payment_term_line,account.payment.term.line,model_account_payment_term_line,account.group_account_user,1,0,0,0 +access_account_account_type,account.account.type,model_account_account_type,account.group_account_user,1,0,0,0 +access_account_tax_internal_user,account.tax internal user,model_account_tax,base.group_user,1,0,0,0 +access_account_account,account.account,model_account_account,account.group_account_user,1,0,0,0 +access_account_account_user,account.account user,model_account_account,base.group_user,1,0,0,0 +access_account_account_partner_manager,account.account partner manager,model_account_account,base.group_partner_manager,1,0,0,0 +access_account_journal_period_manager,account.journal.period manager,model_account_journal_period,account.group_account_manager,1,0,0,0 +access_account_tax_code,account.tax.code,model_account_tax_code,account.group_account_invoice,1,0,0,0 +access_account_tax,account.tax,model_account_tax,account.group_account_invoice,1,0,0,0 +access_account_model,account.model,model_account_model,account.group_account_user,1,1,1,1 +access_account_model_line,account.model.line,model_account_model_line,account.group_account_user,1,1,1,1 +access_account_subscription,account.subscription,model_account_subscription,account.group_account_user,1,1,1,1 +access_account_subscription_line,account.subscription.line,model_account_subscription_line,account.group_account_user,1,1,1,1 +access_account_subscription_manager,account.subscription manager,model_account_subscription,account.group_account_manager,1,0,0,0 +access_account_subscription_line_manager,account.subscription.line manager,model_account_subscription_line,account.group_account_manager,1,0,0,0 +access_account_account_template,account.account.template,model_account_account_template,account.group_account_manager,1,1,1,1 +access_account_tax_code_template,account.tax.code.template,model_account_tax_code_template,account.group_account_manager,1,1,1,1 +access_account_chart_template,account.chart.template,model_account_chart_template,account.group_account_manager,1,1,1,1 +access_account_tax_template,account.tax.template,model_account_tax_template,account.group_account_manager,1,1,1,1 +access_account_bank_statement,account.bank.statement,model_account_bank_statement,account.group_account_user,1,1,1,1 +access_account_bank_statement_line,account.bank.statement.line,model_account_bank_statement_line,account.group_account_user,1,1,1,1 +access_account_analytic_line_manager,account.analytic.line manager,model_account_analytic_line,account.group_account_manager,1,0,0,0 +access_account_analytic_account,account.analytic.account,analytic.model_account_analytic_account,base.group_user,1,0,0,0 +access_account_analytic_journal,account.analytic.journal,model_account_analytic_journal,account.group_account_user,1,0,0,0 +access_account_analytic_journal_user,account.analytic.journal,model_account_analytic_journal,base.group_user,1,1,1,0 +access_account_invoice_uinvoice,account.invoice,model_account_invoice,account.group_account_invoice,1,1,1,1 +access_account_invoice_line_uinvoice,account.invoice.line,model_account_invoice_line,account.group_account_invoice,1,1,1,1 +access_account_invoice_tax_uinvoice,account.invoice.tax,model_account_invoice_tax,account.group_account_invoice,1,1,1,1 +access_account_move_uinvoice,account.move,model_account_move,account.group_account_invoice,1,1,1,1 +access_account_move_line_uinvoice,account.move.line invoice,model_account_move_line,account.group_account_invoice,1,1,1,1 +access_account_move_reconcile_uinvoice,account.move.reconcile,model_account_move_reconcile,account.group_account_invoice,1,1,1,1 +access_account_journal_period_uinvoice,account.journal.period,model_account_journal_period,account.group_account_invoice,1,1,1,1 +access_account_payment_term_manager,account.payment.term,model_account_payment_term,account.group_account_manager,1,1,1,1 +access_account_payment_term_line_manager,account.payment.term.line,model_account_payment_term_line,account.group_account_manager,1,1,1,1 +access_account_tax_manager,account.tax,model_account_tax,account.group_account_manager,1,1,1,1 +access_account_journal_manager,account.journal,model_account_journal,account.group_account_manager,1,1,1,1 +access_account_journal_invoice,account.journal invoice,model_account_journal,account.group_account_invoice,1,0,0,0 +access_account_period_manager,account.period,model_account_period,account.group_account_manager,1,1,1,1 +access_account_period_invoice,account.period invoice,model_account_period,account.group_account_invoice,1,0,0,0 +access_account_invoice_group_invoice,account.invoice group invoice,model_account_invoice,account.group_account_invoice,1,1,1,1 +access_account_analytic_journal_manager,account.analytic.journal,model_account_analytic_journal,account.group_account_manager,1,1,1,1 +access_account_fiscalyear,account.fiscalyear,model_account_fiscalyear,account.group_account_manager,1,1,1,1 +access_account_fiscalyear_invoice,account.fiscalyear.invoice,model_account_fiscalyear,account.group_account_invoice,1,0,0,0 +access_account_fiscalyear_partner_manager,account.fiscalyear.partnermanager,model_account_fiscalyear,base.group_partner_manager,1,0,0,0 +access_account_fiscalyear_employee,account.fiscalyear employee,model_account_fiscalyear,base.group_user,1,0,0,0 +access_res_currency_account_manager,res.currency account manager,base.model_res_currency,group_account_manager,1,1,1,1 +access_res_currency_rate_account_manager,res.currency.rate account manager,base.model_res_currency_rate,group_account_manager,1,1,1,1 +access_res_currency_rate_type_account_manager,res.currency.rate.type account manager,base.model_res_currency_rate_type,group_account_manager,1,1,1,1 +access_account_invoice_user,account.invoice user,model_account_invoice,base.group_user,1,0,0,0 +access_account_invoice_user,account.invoice.line user,model_account_invoice_line,base.group_user,1,0,0,0 +access_account_payment_term_partner_manager,account.payment.term partner manager,model_account_payment_term,base.group_user,1,0,0,0 +access_account_payment_term_line_partner_manager,account.payment.term.line partner manager,model_account_payment_term_line,base.group_user,1,0,0,0 +access_account_account_sale_manager,account.account sale manager,model_account_account,base.group_sale_manager,1,0,0,0 +access_account_fiscal_position_product_manager,account.fiscal.position account.manager,model_account_fiscal_position,account.group_account_manager,1,1,1,1 +access_account_fiscal_position_tax_product_manager,account.fiscal.position.tax account.manager,model_account_fiscal_position_tax,account.group_account_manager,1,1,1,1 +access_account_fiscal_position_account_product_manager,account.fiscal.position account.manager,model_account_fiscal_position_account,account.group_account_manager,1,1,1,1 +access_account_fiscal_position,account.fiscal.position all,model_account_fiscal_position,base.group_user,1,0,0,0 +access_account_fiscal_position_tax,account.fiscal.position.tax all,model_account_fiscal_position_tax,base.group_user,1,0,0,0 +access_account_fiscal_position_account,account.fiscal.position all,model_account_fiscal_position_account,base.group_user,1,0,0,0 +access_account_fiscal_position_template,account.fiscal.position.template,model_account_fiscal_position_template,account.group_account_manager,1,1,1,1 +access_account_fiscal_position_tax_template,account.fiscal.position.tax.template,model_account_fiscal_position_tax_template,account.group_account_manager,1,1,1,1 +access_account_fiscal_position_account_template,account.fiscal.position.account.template,model_account_fiscal_position_account_template,account.group_account_manager,1,1,1,1 +access_account_sequence_fiscal_year_user,account.sequence.fiscalyear user,model_account_sequence_fiscalyear,base.group_user,1,0,0,0 +access_temp_range,temp.range,model_temp_range,account.group_account_manager,1,0,0,0 +access_report_aged_receivable,report.aged.receivable,model_report_aged_receivable,account.group_account_manager,1,1,1,1 +access_report_invoice_created,report.invoice.created,model_report_invoice_created,account.group_account_manager,1,1,1,1 +access_report_account_type_sales,report.account_type.sales,model_report_account_type_sales,account.group_account_manager,1,1,1,1 +access_report_account_sales,report.account.sales,model_report_account_sales,account.group_account_manager,1,1,1,1 +access_account_invoice_report,account.invoice.report,model_account_invoice_report,account.group_account_manager,1,1,1,1 +access_res_partner_group_account_manager,res_partner group_account_manager,model_res_partner,account.group_account_manager,1,0,0,0 +access_account_invoice_accountant,account.invoice accountant,model_account_invoice,account.group_account_user,1,0,0,0 +access_account_tax_code_accountant,account.tax.code accountant,model_account_tax_code,account.group_account_user,1,1,1,1 +access_account_move_line_manager,account.move.line manager,model_account_move_line,account.group_account_manager,1,0,0,0 +access_account_move_manager,account.move manager,model_account_move,account.group_account_manager,1,0,0,0 +access_account_entries_report_manager,account.entries.report,model_account_entries_report,account.group_account_manager,1,1,1,1 +access_account_entries_report_invoice,account.entries.report,model_account_entries_report,account.group_account_invoice,1,0,0,0 +access_account_entries_report_employee,account.entries.report employee,model_account_entries_report,base.group_user,1,0,0,0 +access_analytic_entries_report_manager,analytic.entries.report,model_analytic_entries_report,account.group_account_manager,1,0,0,0 +access_account_cashbox_line,account.cashbox.line,model_account_cashbox_line,account.group_account_user,1,1,1,1 +access_account_journal_cashbox_line,account.journal.cashbox.line,model_account_journal_cashbox_line,account.group_account_user,1,1,1,0 +access_account_invoice_tax_accountant,account.invoice.tax accountant,model_account_invoice_tax,account.group_account_user,1,0,0,0 +access_account_move_reconcile_manager,account.move.reconcile manager,model_account_move_reconcile,account.group_account_manager,1,0,0,0 +access_account_analytic_line_invoice,account.analytic.line invoice,model_account_analytic_line,account.group_account_invoice,1,1,1,1 +access_account_invoice_line_accountant,account.invoice.line accountant,model_account_invoice_line,account.group_account_user,1,0,0,0 +access_account_account_invoice,account.account invoice,model_account_account,account.group_account_invoice,1,1,1,1 +access_account_analytic_accountant,account.analytic.account accountant,analytic.model_account_analytic_account,account.group_account_user,1,1,1,1 +access_account_account_type_invoice,account.account.type invoice,model_account_account_type,account.group_account_invoice,1,1,1,1 +access_report_account_receivable_invoice,report.account.receivable.invoice,model_report_account_receivable,account.group_account_invoice,1,1,1,1 +access_account_sequence_fiscal_year_invoice,account.sequence.fiscalyear invoice,model_account_sequence_fiscalyear,account.group_account_invoice,1,1,1,1 +access_account_tax_sale_manager,account.tax sale manager,model_account_tax,base.group_sale_salesman,1,0,0,0 +access_account_journal_sale_manager,account.journal sale manager,model_account_journal,base.group_sale_salesman,1,0,0,0 +access_account_invoice_tax_sale_manager,account.invoice.tax sale manager,model_account_invoice_tax,base.group_sale_salesman,1,0,0,0 +access_account_sequence_fiscal_year_sale_user,account.sequence.fiscalyear.sale.user,model_account_sequence_fiscalyear,base.group_sale_salesman,1,1,1,0 +access_account_sequence_fiscal_year_sale_manager,account.sequence.fiscalyear.sale.manager,model_account_sequence_fiscalyear,base.group_sale_manager,1,1,1,1 +access_account_treasury_report_manager,account.treasury.report.manager,model_account_treasury_report,account.group_account_manager,1,0,0,0 +access_account_financial_report,account.financial.report,model_account_financial_report,account.group_account_user,1,1,1,1 +access_account_financial_report_invoice,account.financial.report invoice,model_account_financial_report,account.group_account_invoice,1,0,0,0 +access_account_statement_operation_template,account.statement.operation.template,model_account_statement_operation_template,account.group_account_user,1,1,1,1 diff --git a/addons/account/static/src/js/account_move_reconciliation.js b/addons/account/static/src/js/account_move_reconciliation.js index 1040e9d9b5c..a7e83a2146b 100644 --- a/addons/account/static/src/js/account_move_reconciliation.js +++ b/addons/account/static/src/js/account_move_reconciliation.js @@ -6,6 +6,1474 @@ openerp.account = function (instance) { instance.web.account = instance.web.account || {}; + instance.web.client_actions.add('bank_statement_reconciliation_view', 'instance.web.account.bankStatementReconciliation'); + instance.web.account.bankStatementReconciliation = instance.web.Widget.extend({ + className: 'oe_bank_statement_reconciliation', + + init: function(parent, context) { + this._super(parent); + this.max_reconciliations_displayed = 10; + this.statement_id = context.context.statement_id; + this.title = context.context.title || _t("Reconciliation"); + this.st_lines = []; + this.last_displayed_reconciliation_index = undefined; // Flow control + this.reconciled_lines = 0; // idem + this.already_reconciled_lines = 0; // Number of lines of the statement which were already reconciled + this.model_bank_statement = new instance.web.Model("account.bank.statement"); + this.model_bank_statement_line = new instance.web.Model("account.bank.statement.line"); + this.reconciliation_menu_id = false; // Used to update the needaction badge + this.formatCurrency; // Method that formats the currency ; loaded from the server + + // Only for statistical purposes + this.lines_reconciled_with_ctrl_enter = 0; + this.time_widget_loaded = Date.now(); + + // Stuff used by the children bankStatementReconciliationLine + this.max_move_lines_displayed = 5; + this.animation_speed = 100; // "Blocking" animations + this.aestetic_animation_speed = 300; // eye candy + this.map_tax_id_amount = {}; + this.presets = {}; + // We'll need to get the code of an account selected in a many2one (whose value is the id) + this.map_account_id_code = {}; + // The same move line cannot be selected for multiple resolutions + this.excluded_move_lines_ids = {}; + // Description of the fields to initialize in the "create new line" form + // NB : for presets to work correctly, a field id must be the same string as a preset field + this.create_form_fields = { + account_id: { + id: "account_id", + index: 0, + corresponding_property: "account_id", // a account.move field name + label: _t("Account"), + required: true, + tabindex: 10, + constructor: instance.web.form.FieldMany2One, + field_properties: { + relation: "account.account", + string: _t("Account"), + type: "many2one", + domain: [['type','!=','view']], + }, + }, + label: { + id: "label", + index: 1, + corresponding_property: "label", + label: _t("Label"), + required: true, + tabindex: 11, + constructor: instance.web.form.FieldChar, + field_properties: { + string: _t("Label"), + type: "char", + }, + }, + tax_id: { + id: "tax_id", + index: 2, + corresponding_property: "tax_id", + label: _t("Tax"), + required: false, + tabindex: 12, + constructor: instance.web.form.FieldMany2One, + field_properties: { + relation: "account.tax", + string: _t("Tax"), + type: "many2one", + }, + }, + amount: { + id: "amount", + index: 3, + corresponding_property: "amount", + label: _t("Amount"), + required: true, + tabindex: 13, + constructor: instance.web.form.FieldFloat, + field_properties: { + string: _t("Amount"), + type: "float", + }, + }, + analytic_account_id: { + id: "analytic_account_id", + index: 4, + corresponding_property: "analytic_account_id", + label: _t("Analytic Acc."), + required: false, + tabindex: 14, + group:"analytic.group_analytic_accounting", + constructor: instance.web.form.FieldMany2One, + field_properties: { + relation: "account.analytic.account", + string: _t("Analytic Acc."), + type: "many2one", + }, + }, + }; + }, + + start: function() { + this._super(); + var self = this; + + // Inject variable styles + var style = document.createElement("style"); + style.appendChild(document.createTextNode("")); + document.head.appendChild(style); + var css_selector = ".oe_bank_statement_reconciliation_line .toggle_match, .oe_bank_statement_reconciliation_line .toggle_create, .oe_bank_statement_reconciliation_line .initial_line > td"; + if(style.sheet.insertRule) { + style.sheet.insertRule(css_selector + " { -webkit-transition-duration: "+self.aestetic_animation_speed+"ms; }", 0); + style.sheet.insertRule(css_selector + " { -moz-transition-duration: "+self.aestetic_animation_speed+"ms; }", 0); + style.sheet.insertRule(css_selector + " { -ms-transition-duration: "+self.aestetic_animation_speed+"ms; }", 0); + style.sheet.insertRule(css_selector + " { -o-transition-duration: "+self.aestetic_animation_speed+"ms; }", 0); + style.sheet.insertRule(css_selector + " { transition-duration: "+self.aestetic_animation_speed+"ms; }", 0); + } else { + style.sheet.addRule(css_selector, "-webkit-transition-duration: "+self.aestetic_animation_speed+"ms;"); + style.sheet.addRule(css_selector, "-moz-transition-duration: "+self.aestetic_animation_speed+"ms;"); + style.sheet.addRule(css_selector, "-ms-transition-duration: "+self.aestetic_animation_speed+"ms;"); + style.sheet.addRule(css_selector, "-o-transition-duration: "+self.aestetic_animation_speed+"ms;"); + style.sheet.addRule(css_selector, "-webkit-transition-duration: "+self.aestetic_animation_speed+"ms;"); + } + + // Retreive statement infos and reconciliation data from the model + var lines_filter = [['journal_entry_id', '=', false]]; + var deferred_promises = []; + + if (self.statement_id) { + lines_filter.push(['statement_id', '=', self.statement_id]); + deferred_promises.push(self.model_bank_statement + .query(["name"]) + .filter([['id', '=', self.statement_id]]) + .first() + .then(function(title){ + self.title = title.name; + }) + ); + deferred_promises.push(self.model_bank_statement + .call("number_of_lines_reconciled", [self.statement_id]) + .then(function(num) { + self.already_reconciled_lines = num; + }) + ); + } + + deferred_promises.push(new instance.web.Model("account.statement.operation.template") + .query(['id','name','account_id','label','amount_type','amount','tax_id','analytic_account_id']) + .all().then(function (data) { + _(data).each(function(preset){ + self.presets[preset.id] = preset; + }); + }) + ); + + 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); + }) + ); + + deferred_promises.push(self.model_bank_statement_line + .query(['id']) + .filter(lines_filter) + .order_by('id') + .all().then(function (data) { + self.st_lines = _(data).map(function(o){ return o.id }); + }) + ); + + // When queries are done, render template and reconciliation lines + return $.when.apply($, deferred_promises).then(function(){ + + // If there is no statement line to reconcile, stop here + if (self.st_lines.length === 0) { + self.$el.prepend(QWeb.render("bank_statement_nothing_to_reconcile")); + return; + } + + // Create a dict account id -> account code for display facilities + new instance.web.Model("account.account") + .query(['id', 'code']) + .all().then(function(data) { + _.each(data, function(o) { self.map_account_id_code[o.id] = o.code }); + }); + + // Create a dict tax id -> amount + new instance.web.Model("account.tax") + .query(['id', 'amount']) + .all().then(function(data) { + _.each(data, function(o) { self.map_tax_id_amount[o.id] = o.amount }); + }); + + new instance.web.Model("ir.model.data") + .call("xmlid_to_res_id", ["account.menu_bank_reconcile_bank_statements"]) + .then(function(data) { + self.reconciliation_menu_id = data; + self.doReloadMenuReconciliation(); + }); + + // Bind keyboard events TODO : méthode standard ? + $("body").on("keypress", function (e) { + self.keyboardShortcutsHandler(e); + }); + + // Render and display + self.$el.prepend(QWeb.render("bank_statement_reconciliation", {title: self.title, total_lines: self.already_reconciled_lines+self.st_lines.length})); + self.updateProgressbar(); + var reconciliations_to_show = self.st_lines.slice(0, self.max_reconciliations_displayed); + self.last_displayed_reconciliation_index = reconciliations_to_show.length; + self.$(".reconciliation_lines_container").css("opacity", 0); + + // Display the reconciliations + return self.model_bank_statement_line + .call("get_data_for_reconciliations", [reconciliations_to_show]) + .then(function (data) { + var child_promises = []; + _.each(reconciliations_to_show, function(st_line_id){ + var datum = data.shift(); + child_promises.push(self.displayReconciliation(st_line_id, 'inactive', false, true, datum.st_line, datum.reconciliation_proposition)); + }); + $.when.apply($, child_promises).then(function(){ + self.getChildren()[0].set("mode", "match"); + self.$(".reconciliation_lines_container").animate({opacity: 1}, self.aestetic_animation_speed); + }); + }); + }); + }, + + keyboardShortcutsHandler: function(e) { + var self = this; + if (e.which === 13 && (e.ctrlKey || e.metaKey)) { + $.each(self.getChildren(), function(i, o){ + if (o.is_valid && o.persistAndDestroy()) { + self.lines_reconciled_with_ctrl_enter++; + } + }); + } + }, + + // Adds move line ids to the list of move lines not to fetch for a given partner + // This is required because the same move line cannot be selected for multiple reconciliation + excludeMoveLines: function(source_child, partner_id, line_ids) { + var self = this; + + var excluded_ids = this.excluded_move_lines_ids[partner_id]; + var excluded_move_lines_changed = false; + _.each(line_ids, function(line_id){ + if (excluded_ids.indexOf(line_id) === -1) { + excluded_ids.push(line_id); + excluded_move_lines_changed = true; + } + }); + if (! excluded_move_lines_changed) + return; + + // Function that finds if an array of line objects contains at least a line identified by its id + var contains_lines = function(lines_array, line_ids) { + for (var i = 0; i < lines_array.length; i++) + for (var j = 0; j < line_ids.length; j++) + if (lines_array[i].id === line_ids[j]) + return true; + return false; + }; + + // Update children if needed + _.each(self.getChildren(), function(child){ + if (child.partner_id === partner_id && child !== source_child) { + if (contains_lines(child.get("mv_lines_selected"), line_ids)) { + child.set("mv_lines_selected", _.filter(child.get("mv_lines_selected"), function(o){ return line_ids.indexOf(o.id) === -1 })); + } else if (contains_lines(child.mv_lines_deselected, line_ids)) { + child.mv_lines_deselected = _.filter(child.mv_lines_deselected, function(o){ return line_ids.indexOf(o.id) === -1 }); + child.updateMatches(); + } else if (contains_lines(child.get("mv_lines"), line_ids)) { + child.updateMatches(); + } + } + }); + }, + + unexcludeMoveLines: function(source_child, partner_id, line_ids) { + var self = this; + + var initial_excluded_lines_num = this.excluded_move_lines_ids[partner_id].length; + this.excluded_move_lines_ids[partner_id] = _.difference(this.excluded_move_lines_ids[partner_id], line_ids); + if (this.excluded_move_lines_ids[partner_id].length === initial_excluded_lines_num) + return; + + // Update children if needed + _.each(self.getChildren(), function(child){ + if (child.partner_id === partner_id && child !== source_child && (child.get("mode") === "match" || child.$el.hasClass("no_match"))) + child.updateMatches(); + }); + }, + + displayReconciliation: function(st_line_id, mode, animate_entrance, initial_data_provided, st_line, reconciliation_proposition) { + var self = this; + animate_entrance = (animate_entrance === undefined ? true : animate_entrance); + initial_data_provided = (initial_data_provided === undefined ? false : initial_data_provided); + + var context = { + st_line_id: st_line_id, + mode: mode, + animate_entrance: animate_entrance, + initial_data_provided: initial_data_provided, + st_line: initial_data_provided ? st_line : undefined, + reconciliation_proposition: initial_data_provided ? reconciliation_proposition : undefined, + }; + var widget = new instance.web.account.bankStatementReconciliationLine(self, context); + return widget.appendTo(self.$(".reconciliation_lines_container")); + }, + + childValidated: function(child) { + var self = this; + + self.reconciled_lines++; + self.updateProgressbar(); + self.doReloadMenuReconciliation(); + + // Display new line if there are left + if (self.last_displayed_reconciliation_index < self.st_lines.length) { + self.displayReconciliation(self.st_lines[self.last_displayed_reconciliation_index++], 'inactive'); + } + // Put the first line in match mode + if (self.reconciled_lines !== self.st_lines.length) { + var first_child = self.getChildren()[0]; + if (first_child.get("mode") === "inactive") { + first_child.set("mode", "match"); + } + } + // Congratulate the user if the work is done + if (self.reconciled_lines === self.st_lines.length) { + self.displayDoneMessage(); + } + }, + + displayDoneMessage: function() { + var self = this; + + var sec_taken = Math.round((Date.now()-self.time_widget_loaded)/1000); + var sec_per_item = Math.round(sec_taken/self.reconciled_lines); + var achievements = []; + + var time_taken; + if (sec_taken/60 >= 1) time_taken = Math.floor(sec_taken/60) +"' "+ sec_taken%60 +"''"; + else time_taken = sec_taken%60 +" seconds"; + + var title; + if (sec_per_item < 5) title = _t("Whew, that was fast !") + " "; + else title = _t("Congrats, you're all done !") + " "; + + if (self.lines_reconciled_with_ctrl_enter === self.reconciled_lines) + achievements.push({ + title: _t("Efficiency at its finest"), + desc: _t("Only use the ctrl-enter shortcut to validate reconciliations."), + icon: "fa-keyboard-o"} + ); + + if (sec_per_item < 5) + achievements.push({ + title: _t("Fast reconciler"), + desc: _t("Take on average less than 5 seconds to reconcile a transaction."), + icon: "fa-bolt"} + ); + + // Render it + self.$(".protip").hide(); + self.$(".oe_form_sheet").append(QWeb.render("bank_statement_reconciliation_done_message", { + title: title, + time_taken: time_taken, + sec_per_item: sec_per_item, + transactions_done: self.reconciled_lines, + done_with_ctrl_enter: self.lines_reconciled_with_ctrl_enter, + achievements: achievements, + has_statement_id: self.statement_id !== undefined, + })); + + // Animate it + var container = $(""); + self.$(".done_message").wrap(container).css("opacity", 0).css("position", "relative").css("left", "-50%"); + self.$(".done_message").animate({opacity: 1, left: 0}, self.aestetic_animation_speed*2, "easeOutCubic"); + self.$(".done_message").animate({opacity: 1}, self.aestetic_animation_speed*3, "easeOutCubic"); + + // Make it interactive + self.$(".achievement").popover({'placement': 'top', 'container': self.el, 'trigger': 'hover'}); + + self.$(".button_back_to_statement").click(function() { + self.do_action({ + type: 'ir.actions.client', + tag: 'history_back', + }); + }); + + if (self.$(".button_close_statement").length !== 0) { + self.$(".button_close_statement").hide(); + self.model_bank_statement + .query(["balance_end_real", "balance_end"]) + .filter([['id', '=', self.statement_id]]) + .first() + .then(function(data){ + if (data.balance_end_real === data.balance_end) { + self.$(".button_close_statement").show(); + self.$(".button_close_statement").click(function() { + self.$(".button_close_statement").attr("disabled", "disabled"); + self.model_bank_statement + .call("button_confirm_bank", [[self.statement_id]]) + .then(function () { + self.do_action({ + type: 'ir.actions.client', + tag: 'history_back', + }); + }, function() { + self.$(".button_close_statement").removeAttr("disabled"); + }); + }); + } + }); + } + }, + + updateProgressbar: function() { + var self = this; + var done = self.already_reconciled_lines + self.reconciled_lines; + var total = self.already_reconciled_lines + self.st_lines.length; + var prog_bar = self.$(".progress .progress-bar"); + prog_bar.attr("aria-valuenow", done); + prog_bar.css("width", (done/total*100)+"%"); + self.$(".progress .progress-text .valuenow").text(done); + }, + + /* reloads the needaction badge */ + doReloadMenuReconciliation: function () { + var menu = instance.webclient.menu; + if (!menu || !this.reconciliation_menu_id) { + return $.when(); + } + return menu.rpc("/web/menu/load_needaction", {'menu_ids': [this.reconciliation_menu_id]}).done(function(r) { + menu.on_needaction_loaded(r); + }).then(function () { + menu.trigger("need_action_reloaded"); + }); + }, + }); + + instance.web.account.bankStatementReconciliationLine = instance.web.Widget.extend({ + className: 'oe_bank_statement_reconciliation_line', + + events: { + "click .partner_name": "partnerNameClickHandler", + "click .button_ok": "persistAndDestroy", + "click .mv_line": "moveLineClickHandler", + "click .initial_line": "initialLineClickHandler", + "click .line_open_balance": "lineOpenBalanceClickHandler", + "click .pager_control_left:not(.disabled)": "pagerControlLeftHandler", + "click .pager_control_right:not(.disabled)": "pagerControlRightHandler", + "keyup .filter": "filterHandler", + "click .line_info_button": function(e){e.stopPropagation()}, // small usability hack + "click .add_line": "addLineBeingEdited", + "click .preset": "presetClickHandler", + "click .do_partial_reconcile_button": "doPartialReconcileButtonClickHandler", + "click .undo_partial_reconcile_button": "undoPartialReconcileButtonClickHandler", + }, + + init: function(parent, context) { + this._super(parent); + + if (context.initial_data_provided) { + // Process data + _(context.reconciliation_proposition).each(this.decorateMoveLine.bind(this)); + this.set("mv_lines_selected", context.reconciliation_proposition); + this.st_line = context.st_line; + this.partner_id = context.st_line.partner_id; + this.decorateStatementLine(this.st_line); + + // Exclude selected move lines + var selected_line_ids = _(context.reconciliation_proposition).map(function(o){ return o.id }); + if (this.getParent().excluded_move_lines_ids[this.partner_id] === undefined) + this.getParent().excluded_move_lines_ids[this.partner_id] = []; + this.getParent().excludeMoveLines(this, this.partner_id, selected_line_ids); + } else { + this.set("mv_lines_selected", []); + this.st_line = undefined; + this.partner_id = undefined; + } + + this.context = context; + this.st_line_id = context.st_line_id; + this.max_move_lines_displayed = this.getParent().max_move_lines_displayed; + this.animation_speed = this.getParent().animation_speed; + this.aestetic_animation_speed = this.getParent().aestetic_animation_speed; + this.model_bank_statement_line = new instance.web.Model("account.bank.statement.line"); + this.model_res_users = new instance.web.Model("res.users"); + this.model_tax = new instance.web.Model("account.tax"); + this.map_account_id_code = this.getParent().map_account_id_code; + this.map_tax_id_amount = this.getParent().map_tax_id_amount; + this.formatCurrency = this.getParent().formatCurrency; + this.presets = this.getParent().presets; + this.is_valid = true; + this.is_consistent = true; // Used to prevent bad server requests + this.total_move_lines_num = undefined; // Used for pagers + this.filter = ""; + + this.set("balance", undefined); // Debit is +, credit is - + this.on("change:balance", this, this.balanceChanged); + this.set("mode", undefined); + this.on("change:mode", this, this.modeChanged); + this.set("pager_index", 0); + this.on("change:pager_index", this, this.pagerChanged); + // NB : mv_lines represent the counterpart that will be created to reconcile existing move lines, so debit and credit are inverted + this.set("mv_lines", []); + this.on("change:mv_lines", this, this.mvLinesChanged); + this.mv_lines_deselected = []; // deselected lines are displayed on top of the match table + this.on("change:mv_lines_selected", this, this.mvLinesSelectedChanged); + this.set("lines_created", []); + this.set("line_created_being_edited", [{'id': 0}]); + this.on("change:lines_created", this, this.createdLinesChanged); + this.on("change:line_created_being_edited", this, this.createdLinesChanged); + }, + + start: function() { + var self = this; + return self._super().then(function() { + // no animation while loading + self.animation_speed = 0; + self.aestetic_animation_speed = 0; + + self.is_consistent = false; + if (self.context.animate_entrance) self.$el.css("opacity", "0"); + + // Fetch data + var deferred_fetch_data = new $.Deferred(); + if (! self.context.initial_data_provided) { + // Load statement line + self.model_bank_statement_line + .call("get_statement_line_for_reconciliation", [self.st_line_id]) + .then(function (data) { + self.st_line = data; + self.decorateStatementLine(self.st_line); + self.partner_id = data.partner_id; + if (self.getParent().excluded_move_lines_ids[self.partner_id] === undefined) + self.getParent().excluded_move_lines_ids[self.partner_id] = []; + // load and display move lines + $.when(self.loadReconciliationProposition()).then(function(){ + deferred_fetch_data.resolve(); + }); + }); + } else { + deferred_fetch_data.resolve(); + } + + // Display the widget + return $.when(deferred_fetch_data).then(function(){ + // Render template + var presets_array = []; + for (var id in self.presets) + if (self.presets.hasOwnProperty(id)) + presets_array.push(self.presets[id]); + self.$el.prepend(QWeb.render("bank_statement_reconciliation_line", {line: self.st_line, mode: self.context.mode, presets: presets_array})); + + // Stuff that require the template to be rendered + self.$(".match").slideUp(0); + self.$(".create").slideUp(0); + if (self.st_line.no_match) self.$el.addClass("no_match"); + if (self.context.mode !== "match") self.updateMatches(); + self.bindPopoverTo(self.$(".line_info_button")); + self.createFormWidgets(); + + // Special case hack : no identified partner + if (self.st_line.has_no_partner) { + self.$el.css("opacity", "0"); + self.updateBalance(); + self.$(".change_partner_container").show(0); + self.change_partner_field.$el.find("input").attr("placeholder", _t("Select Partner")); + self.$(".match").slideUp(0); + self.$el.addClass("no_partner"); + self.set("mode", self.context.mode); + self.animation_speed = self.getParent().animation_speed; + self.aestetic_animation_speed = self.getParent().aestetic_animation_speed; + self.$el.animate({opacity: 1}, self.aestetic_animation_speed); + self.is_consistent = true; + return; + } + + // TODO : the .on handler's returned deferred is lost + return $.when(self.set("mode", self.context.mode)).then(function(){ + self.is_consistent = true; + + // Make sure the display is OK + self.balanceChanged(); + self.createdLinesChanged(); + self.updateAccountingViewMatchedLines(); + + // Make an entrance + self.animation_speed = self.getParent().animation_speed; + self.aestetic_animation_speed = self.getParent().aestetic_animation_speed; + if (self.context.animate_entrance) return self.$el.animate({opacity: 1}, self.aestetic_animation_speed); + }); + }); + }); + }, + + restart: function(mode) { + var self = this; + mode = (mode === undefined ? 'inactive' : mode); + self.$el.css("height", self.$el.outerHeight()); + // Destroy everything + _.each(self.getChildren(), function(o){ o.destroy() }); + self.is_consistent = false; + return $.when(self.$el.animate({opacity: 0}, self.animation_speed)).then(function() { + self.getParent().unexcludeMoveLines(self, self.partner_id, _.map(self.get("mv_lines_selected"), function(o){ return o.id })); + $.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') }); + self.$el.empty(); + self.$el.removeClass("no_partner"); + self.context.mode = mode; + self.context.initial_data_provided = false; + self.is_valid = true; + self.is_consistent = true; + self.filter = ""; + self.set("balance", undefined, {silent: true}); + self.set("mode", undefined, {silent: true}); + self.set("pager_index", 0, {silent: true}); + self.set("mv_lines", [], {silent: true}); + self.set("mv_lines_selected", [], {silent: true}); + self.mv_lines_deselected = []; + self.set("lines_created", [], {silent: true}); + self.set("line_created_being_edited", [{'id': 0}], {silent: true}); + // Rebirth + return $.when(self.start()).then(function() { + self.$el.css("height", "auto"); + self.is_consistent = true; + self.$el.animate({opacity: 1}, self.animation_speed); + }); + }); + }, + + /* create form widgets, append them to the dom and bind their events handlers */ + createFormWidgets: function() { + var self = this; + var create_form_fields = self.getParent().create_form_fields; + var create_form_fields_arr = []; + for (var key in create_form_fields) + if (create_form_fields.hasOwnProperty(key)) + create_form_fields_arr.push(create_form_fields[key]); + create_form_fields_arr.sort(function(a, b){ return b.index - a.index }); + + // field_manager + var dataset = new instance.web.DataSet(this, "account.account", self.context); + dataset.ids = []; + dataset.arch = { + attrs: { string: "Stéphanie de Monaco", version: "7.0", class: "oe_form_container" }, + children: [], + tag: "form" + }; + + var field_manager = new instance.web.FormView ( + this, dataset, false, { + initial_mode: 'edit', + disable_autofocus: false, + $buttons: $(), + $pager: $() + }); + + field_manager.load_form(dataset); + + // fields default properties + var Default_field = function() { + this.context = {}; + this.domain = []; + this.help = ""; + this.readonly = false; + this.required = true; + this.selectable = true; + this.states = {}; + this.views = {}; + }; + var Default_node = function(field_name) { + this.tag = "field"; + this.children = []; + this.required = true; + this.attrs = { + invisible: "False", + modifiers: '{"required":true}', + name: field_name, + nolabel: "True", + }; + }; + + // Append fields to the field_manager + field_manager.fields_view.fields = {}; + for (var i=0; i= self.total_move_lines_num) { return; } + self.set("pager_index", new_index ); + }, + + filterHandler: function() { + var self = this; + self.set("pager_index", 0); + self.filter = self.$(".filter").val(); + window.clearTimeout(self.apply_filter_timeout); + self.apply_filter_timeout = window.setTimeout(self.proxy('updateMatches'), 200); + }, + + + /** Creating */ + + initializeCreateForm: function() { + var self = this; + + _.each(self.create_form, function(field) { + field.set("value", false); + }); + self.amount_field.set("value", -1*self.get("balance")); + self.account_id_field.focus(); + }, + + addLineBeingEdited: function() { + var self = this; + if (! self.islineCreatedBeingEditedValid()) return; + + self.set("lines_created", self.get("lines_created").concat(self.get("line_created_being_edited"))); + // Add empty created line + var new_id = self.get("line_created_being_edited")[0].id + 1; + self.set("line_created_being_edited", [{'id': new_id}]); + + self.initializeCreateForm(); + }, + + removeLine: function($line) { + var self = this; + var line_id = $line.data("lineid"); + + // if deleting the created line that is being edited, validate it before + if (line_id === self.get("line_created_being_edited")[0].id) { + self.addLineBeingEdited(); + } + self.set("lines_created", _.filter(self.get("lines_created"), function(o) { return o.id != line_id })); + self.amount_field.set("value", -1*self.get("balance")); + }, + + presetClickHandler: function(e) { + var self = this; + self.initializeCreateForm(); + var preset = self.presets[e.currentTarget.dataset.presetid]; + for (var key in preset) { + if (! preset.hasOwnProperty(key) || key === "amount") continue; + if (self.hasOwnProperty(key+"_field")) + self[key+"_field"].set_value(preset[key]); + } + var sign = self.amount_field.get_value() < 0 ? -1 : 1; + if (preset.amount && self.amount_field) { + if (preset.amount_type === "fixed") + self.amount_field.set_value(sign * preset.amount); + else if (preset.amount_type === "percentage_of_total") + self.amount_field.set_value(sign * self.st_line.amount * preset.amount / 100); + else if (preset.amount_type === "percentage_of_balance") { + self.amount_field.set_value(0); + self.updateBalance(); + self.amount_field.set_value(sign * Math.abs(self.get("balance")) * preset.amount / 100); + } + } + }, + + + /** Display */ + + initialLineClickHandler: function() { + var self = this; + if (self.get("mode") === "match") { + self.set("mode", "inactive"); + } else { + self.set("mode", "match"); + } + }, + + lineOpenBalanceClickHandler: function() { + var self = this; + if (self.get("mode") === "create") { + self.set("mode", "match"); + } else { + self.set("mode", "create"); + } + }, + + partnerNameClickHandler: function() { + var self = this; + self.$(".partner_name").hide(); + self.change_partner_field.$el.find("input").attr("placeholder", self.st_line.partner_name); + self.$(".change_partner_container").show(); + }, + + + /** Views updating */ + + updateAccountingViewMatchedLines: function() { + var self = this; + $.each(self.$(".tbody_matched_lines .bootstrap_popover"), function(){ $(this).popover('destroy') }); + self.$(".tbody_matched_lines").empty(); + + _(self.get("mv_lines_selected")).each(function(line){ + var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: true})); + self.bindPopoverTo($line.find(".line_info_button")); + if (line.propose_partial_reconcile) self.bindPopoverTo($line.find(".do_partial_reconcile_button")); + if (line.partial_reconcile) self.bindPopoverTo($line.find(".undo_partial_reconcile_button")); + self.$(".tbody_matched_lines").append($line); + }); + }, + + updateAccountingViewCreatedLines: function() { + var self = this; + $.each(self.$(".tbody_created_lines .bootstrap_popover"), function(){ $(this).popover('destroy') }); + self.$(".tbody_created_lines").empty(); + + _(self.getCreatedLines()).each(function(line){ + var $line = $(QWeb.render("bank_statement_reconciliation_created_line", {line: line})); + $line.find(".line_remove_button").click(function(){ self.removeLine($(this).closest(".created_line")) }); + self.$(".tbody_created_lines").append($line); + if (line.no_remove_action) { + // Then the previous line's remove button deletes this line too + $line.hover(function(){ $(this).prev().addClass("active") },function(){ $(this).prev().removeClass("active") }); + } + }); + }, + + updateMatchView: function() { + var self = this; + var table = self.$(".match table"); + var nothing_displayed = true; + + // Display move lines + $.each(self.$(".match table .bootstrap_popover"), function(){ $(this).popover('destroy') }); + table.empty(); + 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 }) + .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")); + table.append($line); + nothing_displayed = false; + }); + _(self.get("mv_lines")).each(function(line){ + var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false})); + self.bindPopoverTo($line.find(".line_info_button")); + table.append($line); + nothing_displayed = false; + }); + if (nothing_displayed) + table.append(QWeb.render("filter_no_match", {filter_str: self.filter})); + }, + + updatePagerControls: function() { + var self = this; + + if (self.get("pager_index") === 0) + self.$(".pager_control_left").addClass("disabled"); + else + self.$(".pager_control_left").removeClass("disabled"); + if (self.total_move_lines_num <= ((self.get("pager_index")+1) * self.max_move_lines_displayed)) + self.$(".pager_control_right").addClass("disabled"); + else + self.$(".pager_control_right").removeClass("disabled"); + }, + + + /** Properties changed */ + + // Updates the validation button and the "open balance" line + balanceChanged: function() { + var self = this; + var balance = self.get("balance"); + + // Special case hack : no identified partner + if (self.st_line.has_no_partner) { + if (Math.abs(balance).toFixed(3) === "0.000") { + self.$(".button_ok").addClass("oe_highlight"); + self.$(".button_ok").removeAttr("disabled"); + self.$(".button_ok").text("OK"); + self.is_valid = true; + } else { + self.$(".button_ok").removeClass("oe_highlight"); + self.$(".button_ok").attr("disabled", "disabled"); + self.$(".button_ok").text("OK"); + self.is_valid = false; + } + 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 $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); + } + }, + + modeChanged: function() { + var self = this; + + self.$(".action_pane.active").removeClass("active"); + + // Special case hack : if no_partner, either inactive or create + 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; + } + + if (self.get("mode") === "inactive") { + self.$(".match").slideUp(self.animation_speed); + self.$(".create").slideUp(self.animation_speed); + self.el.dataset.mode = "inactive"; + + } else if (self.get("mode") === "match") { + return $.when(self.updateMatches()).then(function() { + if (self.$el.hasClass("no_match")) { + self.set("mode", "inactive"); + return; + } + self.$(".match").slideDown(self.animation_speed); + self.$(".create").slideUp(self.animation_speed); + self.el.dataset.mode = "match"; + }); + + } else if (self.get("mode") === "create") { + self.initializeCreateForm(); + self.$(".match").slideUp(self.animation_speed); + self.$(".create").slideDown(self.animation_speed); + self.el.dataset.mode = "create"; + } + }, + + pagerChanged: function() { + var self = this; + self.updateMatches(); + }, + + mvLinesChanged: function() { + var self = this; + // If pager_index is out of range, set it to display the last page + if (self.get("pager_index") !== 0 && self.total_move_lines_num <= (self.get("pager_index") * self.max_move_lines_displayed)) { + self.set("pager_index", Math.ceil(self.total_move_lines_num/self.max_move_lines_displayed)-1); + } + + // If there is no match to display, disable match view and pass in mode inactive + if (self.total_move_lines_num + self.mv_lines_deselected.length === 0 && self.filter === "") { + self.$el.addClass("no_match"); + if (self.get("mode") === "match") { + self.set("mode", "inactive"); + } + } else { + self.$el.removeClass("no_match"); + } + + self.updateMatchView(); + self.updatePagerControls(); + }, + + mvLinesSelectedChanged: function(elt, val) { + var self = this; + + var added_lines_ids = _.map(_.difference(val.newValue, val.oldValue), function(o){ return o.id }); + var removed_lines_ids = _.map(_.difference(val.oldValue, val.newValue), function(o){ return o.id }); + + self.getParent().excludeMoveLines(self, self.partner_id, added_lines_ids); + self.getParent().unexcludeMoveLines(self, self.partner_id, removed_lines_ids); + + $.when(self.updateMatches()).then(function(){ + self.updateAccountingViewMatchedLines(); + self.updateBalance(); + }); + }, + + // Generic function for updating the line_created_being_edited + formCreateInputChanged: function(elt, val) { + var self = this; + var line_created_being_edited = self.get("line_created_being_edited"); + line_created_being_edited[0][elt.corresponding_property] = val.newValue; + + // Specific cases + if (elt === self.account_id_field) + line_created_being_edited[0].account_num = self.map_account_id_code[elt.get("value")]; + + // Update tax line + var deferred_tax = new $.Deferred(); + if (elt === self.tax_id_field || elt === self.amount_field) { + var amount = self.amount_field.get("value"); + var tax = self.map_tax_id_amount[self.tax_id_field.get("value")]; + if (amount && tax) { + deferred_tax = $.when(self.model_tax + .call("compute_for_bank_reconciliation", [self.tax_id_field.get("value"), amount])) + .then(function(data){ + 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}; + } + ); + } else { + line_created_being_edited[0].amount = amount; + delete line_created_being_edited[1]; + deferred_tax.resolve(); + } + } else { deferred_tax.resolve(); } + + $.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)); + 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)); + + self.set("line_created_being_edited", line_created_being_edited); + self.createdLinesChanged(); // TODO For some reason, previous line doesn't trigger change handler + }); + }, + + createdLinesChanged: function() { + var self = this; + self.updateAccountingViewCreatedLines(); + self.updateBalance(); + + if (self.islineCreatedBeingEditedValid()) self.$(".add_line").show(); + else self.$(".add_line").hide(); + }, + + + /** Model */ + + doPartialReconcileButtonClickHandler: function(e) { + var self = this; + + var line_id = $(e.currentTarget).closest("tr").data("lineid"); + var line = _.find(self.get("mv_lines_selected"), function(o) { return o.id == line_id }); + self.partialReconcileLine(line); + + $(e.currentTarget).popover('destroy'); + self.updateAccountingViewMatchedLines(); + self.updateBalance(); + e.stopPropagation(); + }, + + partialReconcileLine: function(line) { + var self = this; + var balance = self.get("balance"); + line.initial_amount = line.debit !== 0 ? line.debit : -1 * line.credit; + if (balance < 0) { + line.debit -= balance; + line.debit_str = self.formatCurrency(line.debit); + } else { + line.credit -= balance; + line.credit_str = self.formatCurrency(line.credit); + } + line.propose_partial_reconcile = false; + line.partial_reconcile = true; + }, + + undoPartialReconcileButtonClickHandler: function(e) { + var self = this; + + var line_id = $(e.currentTarget).closest("tr").data("lineid"); + var line = _.find(self.get("mv_lines_selected"), function(o) { return o.id == line_id }); + self.unpartialReconcileLine(line); + + $(e.currentTarget).popover('destroy'); + self.updateAccountingViewMatchedLines(); + self.updateBalance(); + e.stopPropagation(); + }, + + unpartialReconcileLine: function(line) { + if (line.initial_amount > 0) { + line.debit = line.initial_amount; + line.debit_str = this.formatCurrency(line.debit); + } else { + line.credit = -1 * line.initial_amount; + line.credit_str = this.formatCurrency(line.credit); + } + line.propose_partial_reconcile = true; + line.partial_reconcile = false; + }, + + updateBalance: function() { + var self = this; + var mv_lines_selected = self.get("mv_lines_selected"); + var balance = 0; + balance -= self.st_line.amount; + _.each(mv_lines_selected, function(o) { + balance = balance - o.debit + o.credit; + }); + _.each(self.getCreatedLines(), function(o) { + balance += o.amount; + }); + self.set("balance", balance); + + // Propose partial reconciliation if necessary + var lines_selected_num = mv_lines_selected.length; + var lines_created_num = self.getCreatedLines().length; + if (lines_selected_num === 1 && lines_created_num === 0 && self.st_line.amount * balance > 0) { + mv_lines_selected[0].propose_partial_reconcile = true; + self.updateAccountingViewMatchedLines(); + } + if (lines_selected_num !== 1 || lines_created_num !== 0) { + // remove partial reconciliation stuff if necessary + _.each(mv_lines_selected, function(line) { + if (line.partial_reconcile === true) self.unpartialReconcileLine(line); + if (line.propose_partial_reconcile === true) line.propose_partial_reconcile = false; + }); + self.updateAccountingViewMatchedLines(); + } + }, + + loadReconciliationProposition: function() { + var self = this; + return self.model_bank_statement_line + .call("get_reconciliation_proposition", [self.st_line.id, self.getParent().excluded_move_lines_ids[self.partner_id]]) + .then(function (lines) { + _(lines).each(self.decorateMoveLine.bind(self)); + self.set("mv_lines_selected", self.get("mv_lines_selected").concat(lines)); + }); + }, + + // Loads move lines according to the widget's state + updateMatches: function() { + var self = this; + var deselected_lines_num = self.mv_lines_deselected.length; + var move_lines = {}; + var move_lines_num = 0; + var offset = self.get("pager_index") * self.max_move_lines_displayed - deselected_lines_num; + if (offset < 0) offset = 0; + var limit = (self.get("pager_index")+1) * self.max_move_lines_displayed - deselected_lines_num; + if (limit > self.max_move_lines_displayed) limit = self.max_move_lines_displayed; + var excluded_ids = _.collect(self.get("mv_lines_selected").concat(self.mv_lines_deselected), function(o){ return o.id }); + excluded_ids = excluded_ids.concat(self.getParent().excluded_move_lines_ids[self.partner_id]); + + var deferred_move_lines; + 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]) + .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]) + .then(function(num){ + move_lines_num = num; + }); + + return $.when(deferred_move_lines, deferred_total_move_lines_num).then(function(){ + self.total_move_lines_num = move_lines_num + deselected_lines_num; + self.set("mv_lines", move_lines); + }); + }, + + // Changes the partner_id of the statement_line in the DB and reloads the widget + changePartner: function(partner_id) { + var self = this; + self.is_consistent = false; + return self.model_bank_statement_line + // Update model + .call("write", [[self.st_line_id], {'partner_id': partner_id}]) + .then(function () { + return $.when(self.restart(self.get("mode"))).then(function(){ + self.is_consistent = true; + }); + }); + }, + + // Returns an object that can be passed to process_reconciliation() + prepareSelectedMoveLineForPersisting: function(line) { + return { + name: line.name, + debit: line.debit, + credit: line.credit, + counterpart_move_line_id: line.id, + }; + }, + + // idem + prepareCreatedMoveLineForPersisting: function(line) { + var dict = {}; + + if (dict['account_id'] === undefined) + dict['account_id'] = line.account_id; + dict['name'] = line.label; + if (line.amount > 0) dict['credit'] = line.amount; + if (line.amount < 0) dict['debit'] = -1*line.amount; + if (line.tax_id) dict['tax_code_id'] = line.tax_id; + if (line.analytic_account_id) dict['analytic_account_id'] = line.analytic_account_id; + + return dict; + }, + + // idem + prepareOpenBalanceForPersisting: function() { + var balance = this.get("balance"); + var dict = {}; + + dict['account_id'] = this.st_line.open_balance_account_id; + dict['name'] = _t("Open balance"); + if (balance > 0) dict['debit'] = balance; + if (balance < 0) dict['credit'] = -1*balance; + + return dict; + }, + + // Persist data, notify parent view and terminate widget + persistAndDestroy: function() { + var self = this; + if (! self.is_consistent) return; + + self.getParent().unexcludeMoveLines(self, self.partner_id, _.map(self.get("mv_lines_selected"), function(o){ return o.id })); + + // Prepare data + var mv_line_dicts = []; + _.each(self.get("mv_lines_selected"), function(o) { mv_line_dicts.push(self.prepareSelectedMoveLineForPersisting(o)) }); + _.each(self.getCreatedLines(), function(o) { mv_line_dicts.push(self.prepareCreatedMoveLineForPersisting(o)) }); + if (Math.abs(self.get("balance")).toFixed(3) !== "0.000") mv_line_dicts.push(self.prepareOpenBalanceForPersisting()); + + // Sliding animation + var height = self.$el.outerHeight(); + var container = $(""); + container.css("height", height) + .css("marginTop", self.$el.css("marginTop")) + .css("marginBottom", self.$el.css("marginBottom")); + self.$el.wrap(container); + var deferred_animation = self.$el.parent().slideUp(self.animation_speed*height/150); + + // RPC + return self.model_bank_statement_line + .call("process_reconciliation", [self.st_line_id, mv_line_dicts]) + .then(function () { + $.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') }); + return $.when(deferred_animation).then(function(){ + self.$el.parent().remove(); + var parent = self.getParent(); + return $.when(self.destroy()).then(function() { + parent.childValidated(self); + }); + }); + }, function(){ + self.$el.parent().slideDown(self.animation_speed*height/150, function(){ + self.$el.unwrap(); + }); + }); + + }, + }); + instance.web.views.add('tree_account_reconciliation', 'instance.web.account.ReconciliationListView'); instance.web.account.ReconciliationListView = instance.web.ListView.extend({ init: function() { diff --git a/addons/account/views/account.xml b/addons/account/views/account.xml index 15df140c75e..8a9c209baa0 100644 --- a/addons/account/views/account.xml +++ b/addons/account/views/account.xml @@ -8,7 +8,8 @@ - + + diff --git a/addons/account/wizard/__init__.py b/addons/account/wizard/__init__.py index 839e490ca66..45038ee1554 100644 --- a/addons/account/wizard/__init__.py +++ b/addons/account/wizard/__init__.py @@ -63,8 +63,6 @@ import account_report_account_balance import account_change_currency -import pos_box; - +import pos_box +import account_statement_from_invoice # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - - diff --git a/addons/account/wizard/pos_box.py b/addons/account/wizard/pos_box.py index 4b7e63585d8..ba9b3bd2980 100644 --- a/addons/account/wizard/pos_box.py +++ b/addons/account/wizard/pos_box.py @@ -56,7 +56,6 @@ class CashBoxIn(CashBox): return { 'statement_id' : record.id, 'journal_id' : record.journal_id.id, - 'account_id' : record.journal_id.internal_account_id.id, 'amount' : box.amount or 0.0, 'ref' : '%s' % (box.ref or ''), 'name' : box.name, @@ -73,7 +72,6 @@ class CashBoxOut(CashBox): return { 'statement_id' : record.id, 'journal_id' : record.journal_id.id, - 'account_id' : record.journal_id.internal_account_id.id, 'amount' : -amount if amount > 0.0 else amount, 'name' : box.name, } diff --git a/addons/account_analytic_plans/__openerp__.py b/addons/account_analytic_plans/__openerp__.py index 7ca7c00e6c0..19f3788910d 100644 --- a/addons/account_analytic_plans/__openerp__.py +++ b/addons/account_analytic_plans/__openerp__.py @@ -74,6 +74,7 @@ The analytic plan validates the minimum and maximum percentage at the time of cr 'wizard/analytic_plan_create_model_view.xml', 'wizard/account_crossovered_analytic_view.xml', 'views/report_crossoveredanalyticplans.xml', + 'views/account_analytic_plans.xml', ], 'demo': [], 'test': ['test/acount_analytic_plans_report.yml'], diff --git a/addons/account_analytic_plans/account_analytic_plans_view.xml b/addons/account_analytic_plans/account_analytic_plans_view.xml index 69646faf8a0..f9a257c6fc7 100644 --- a/addons/account_analytic_plans/account_analytic_plans_view.xml +++ b/addons/account_analytic_plans/account_analytic_plans_view.xml @@ -233,57 +233,25 @@ - - account.analytic.default.form.plans - account.analytic.default - - - - - + + account.analytic.default.form.plans + account.analytic.default + + + + - - - account.analytic.default.tree.plans - account.analytic.default - - - - 1 - - - - - - - - - account.bank.statement.form.inherit - account.bank.statement - - - - - - - - - - - - - account.bank.statement.form.inherit - account.bank.statement - - - - - - - - - - - + + + + account.analytic.default.tree.plans + account.analytic.default + + + + + + + diff --git a/addons/account_bank_statement_extensions/account_bank_statement_view.xml b/addons/account_bank_statement_extensions/account_bank_statement_view.xml index d64eb68bc99..70bf0292800 100644 --- a/addons/account_bank_statement_extensions/account_bank_statement_view.xml +++ b/addons/account_bank_statement_extensions/account_bank_statement_view.xml @@ -74,10 +74,7 @@ - - - - + @@ -98,10 +95,7 @@ - - - - + @@ -129,7 +123,6 @@ - @@ -138,7 +131,6 @@ - diff --git a/addons/account_payment/wizard/account_payment_populate_statement.py b/addons/account_payment/wizard/account_payment_populate_statement.py index 92b1b1452f5..ef068a55b2c 100644 --- a/addons/account_payment/wizard/account_payment_populate_statement.py +++ b/addons/account_payment/wizard/account_payment_populate_statement.py @@ -107,12 +107,9 @@ class account_payment_populate_statement(osv.osv_memory): st_line_id = statement_line_obj.create(cr, uid, { 'name': line.order_id.reference or '?', 'amount': - amount, - 'type': 'supplier', 'partner_id': line.partner_id.id, - 'account_id': line.move_line_id.account_id.id, 'statement_id': statement.id, 'ref': line.communication, - 'voucher_id': voucher_id, }, context=context) line_obj.write(cr, uid, [line.id], {'bank_statement_line_id': st_line_id}) diff --git a/addons/account_voucher/__init__.py b/addons/account_voucher/__init__.py index 2b1478d9fa0..0acbb258b4f 100644 --- a/addons/account_voucher/__init__.py +++ b/addons/account_voucher/__init__.py @@ -22,6 +22,5 @@ import account_voucher import invoice import report -import wizard # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_voucher/__openerp__.py b/addons/account_voucher/__openerp__.py index dc8102d6647..073128c14d3 100644 --- a/addons/account_voucher/__openerp__.py +++ b/addons/account_voucher/__openerp__.py @@ -49,7 +49,6 @@ This module manages: 'security/ir.model.access.csv', 'account_voucher_sequence.xml', 'account_voucher_workflow.xml', - 'wizard/account_statement_from_invoice_view.xml', 'account_voucher_view.xml', 'voucher_payment_receipt_view.xml', 'voucher_sales_purchase_view.xml', diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index c1424079542..6adae402e0c 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -327,7 +327,6 @@ class account_voucher(osv.osv): } _columns = { - 'active': fields.boolean('Active', help="By default, reconciliation vouchers made on draft bank statements are set as inactive, which allow to hide the customer/supplier payment while the bank statement isn't confirmed."), 'type':fields.selection([ ('sale','Sale'), ('purchase','Purchase'), @@ -389,7 +388,6 @@ class account_voucher(osv.osv): 'currency_help_label': fields.function(_fnct_currency_help_label, type='text', string="Helping Sentence", help="This sentence helps you to know how to specify the payment rate by giving you the direct effect it has"), } _defaults = { - 'active': True, 'period_id': _get_period, 'partner_id': _get_partner, 'journal_id':_get_journal, @@ -1583,114 +1581,6 @@ class account_voucher_line(osv.osv): }) return values -class account_bank_statement(osv.osv): - _inherit = 'account.bank.statement' - - def button_confirm_bank(self, cr, uid, ids, context=None): - voucher_obj = self.pool.get('account.voucher') - voucher_ids = [] - for statement in self.browse(cr, uid, ids, context=context): - voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id] - if voucher_ids: - voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context) - return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context) - - def button_cancel(self, cr, uid, ids, context=None): - voucher_obj = self.pool.get('account.voucher') - for st in self.browse(cr, uid, ids, context=context): - voucher_ids = [] - for line in st.line_ids: - if line.voucher_id: - voucher_ids.append(line.voucher_id.id) - voucher_obj.cancel_voucher(cr, uid, voucher_ids, context) - return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context) - - def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None): - voucher_obj = self.pool.get('account.voucher') - move_line_obj = self.pool.get('account.move.line') - bank_st_line_obj = self.pool.get('account.bank.statement.line') - st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context) - if st_line.voucher_id: - voucher_obj.write(cr, uid, [st_line.voucher_id.id], - {'number': next_number, - 'date': st_line.date, - 'period_id': st_line.statement_id.period_id.id}, - context=context) - if st_line.voucher_id.state == 'cancel': - voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context) - voucher_obj.signal_proforma_voucher(cr, uid, [st_line.voucher_id.id]) - - v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context) - bank_st_line_obj.write(cr, uid, [st_line_id], { - 'move_ids': [(4, v.move_id.id, False)] - }) - - return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context) - return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context) - - def write(self, cr, uid, ids, vals, context=None): - # Restrict to modify the journal if we already have some voucher of reconciliation created/generated. - # Because the voucher keeps in memory the journal it was created with. - for bk_st in self.browse(cr, uid, ids, context=context): - if vals.get('journal_id') and bk_st.line_ids: - if any([x.voucher_id and True or False for x in bk_st.line_ids]): - raise osv.except_osv(_('Unable to Change Journal!'), _('You can not change the journal as you already reconciled some statement lines!')) - return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context) - - -class account_bank_statement_line(osv.osv): - _inherit = 'account.bank.statement.line' - - def onchange_partner_id(self, cr, uid, ids, partner_id, context=None): - res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context) - if 'value' not in res: - res['value'] = {} - res['value'].update({'voucher_id' : False}) - return res - - def onchange_amount(self, cr, uid, ids, amount, context=None): - return {'value' : {'voucher_id' : False}} - - def _amount_reconciled(self, cursor, user, ids, name, args, context=None): - if not ids: - return {} - res = {} - for line in self.browse(cursor, user, ids, context=context): - if line.voucher_id: - res[line.id] = line.voucher_id.amount# - else: - res[line.id] = 0.0 - return res - - def _check_amount(self, cr, uid, ids, context=None): - for obj in self.browse(cr, uid, ids, context=context): - if obj.voucher_id: - diff = abs(obj.amount) - abs(obj.voucher_id.amount) - if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff): - return False - return True - - _constraints = [ - (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']), - ] - - _columns = { - 'amount_reconciled': fields.function(_amount_reconciled, - string='Amount reconciled', type='float'), - 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'), - } - - def unlink(self, cr, uid, ids, context=None): - voucher_obj = self.pool.get('account.voucher') - statement_line = self.browse(cr, uid, ids, context=context) - unlink_ids = [] - for st_line in statement_line: - if st_line.voucher_id: - unlink_ids.append(st_line.voucher_id.id) - voucher_obj.unlink(cr, uid, unlink_ids, context=context) - return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context) - - def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context): results = [] for operation in operations: diff --git a/addons/account_voucher/account_voucher_view.xml b/addons/account_voucher/account_voucher_view.xml index a5885156442..a6f3a6cd277 100644 --- a/addons/account_voucher/account_voucher_view.xml +++ b/addons/account_voucher/account_voucher_view.xml @@ -193,59 +193,7 @@ {'state':'posted'} - - - account.bank.statement.invoice.form.inherit - account.bank.statement - - - - - - - - - - account.bank.statement.voucher.tree.inherit - account.bank.statement - - - - - - - - - - onchange_amount(amount) - - - - - - account.cash.statement.voucher.tree.inherit - account.bank.statement - - - - - - - - - - account.cash.statement.voucher.form.inherit - account.bank.statement - - - - - - - - + account.config.settings.inherit diff --git a/addons/account_voucher/wizard/account_statement_from_invoice.py b/addons/account_voucher/wizard/account_statement_from_invoice.py index 059989801d1..734a2d8ec66 100644 --- a/addons/account_voucher/wizard/account_statement_from_invoice.py +++ b/addons/account_voucher/wizard/account_statement_from_invoice.py @@ -22,7 +22,6 @@ import time from openerp.osv import fields, osv -from openerp.tools.translate import _ class account_statement_from_invoice_lines(osv.osv_memory): """ @@ -35,12 +34,13 @@ class account_statement_from_invoice_lines(osv.osv_memory): } def populate_statement(self, cr, uid, ids, context=None): + #TODO: can be moved in account module if context is None: context = {} statement_id = context.get('statement_id', False) if not statement_id: return {'type': 'ir.actions.act_window_close'} - data = self.read(cr, uid, ids, context=context)[0] + data = self.read(cr, uid, ids, context=context)[0] line_ids = data['line_ids'] if not line_ids: return {'type': 'ir.actions.act_window_close'} @@ -49,14 +49,11 @@ class account_statement_from_invoice_lines(osv.osv_memory): statement_obj = self.pool.get('account.bank.statement') statement_line_obj = self.pool.get('account.bank.statement.line') currency_obj = self.pool.get('res.currency') - voucher_obj = self.pool.get('account.voucher') - voucher_line_obj = self.pool.get('account.voucher.line') line_date = time.strftime('%Y-%m-%d') statement = statement_obj.browse(cr, uid, statement_id, context=context) # for each selected move lines for line in line_obj.browse(cr, uid, line_ids, context=context): - voucher_res = {} ctx = context.copy() # take the date for computation of currency => use payment date ctx['date'] = line_date @@ -70,55 +67,19 @@ class account_statement_from_invoice_lines(osv.osv_memory): if line.amount_currency: amount = currency_obj.compute(cr, uid, line.currency_id.id, statement.currency.id, line.amount_currency, context=ctx) - elif (line.invoice and line.invoice.currency_id.id <> statement.currency.id): + elif (line.invoice and line.invoice.currency_id.id != statement.currency.id): amount = currency_obj.compute(cr, uid, line.invoice.currency_id.id, statement.currency.id, amount, context=ctx) context.update({'move_line_ids': [line.id], 'invoice_id': line.invoice.id}) - type = 'general' - ttype = amount < 0 and 'payment' or 'receipt' - sign = 1 - if line.journal_id.type in ('sale', 'sale_refund'): - type = 'customer' - ttype = 'receipt' - elif line.journal_id.type in ('purchase', 'purhcase_refund'): - type = 'supplier' - ttype = 'payment' - sign = -1 - result = voucher_obj.onchange_partner_id(cr, uid, [], partner_id=line.partner_id.id, journal_id=statement.journal_id.id, amount=sign*amount, currency_id= statement.currency.id, ttype=ttype, date=line_date, context=context) - voucher_res = { 'type': ttype, - 'name': line.name, - 'partner_id': line.partner_id.id, - 'journal_id': statement.journal_id.id, - 'account_id': result['value'].get('account_id', statement.journal_id.default_credit_account_id.id), - 'company_id': statement.company_id.id, - 'currency_id': statement.currency.id, - 'date': statement.date, - 'amount': sign*amount, - 'payment_rate': result['value']['payment_rate'], - 'payment_rate_currency_id': result['value']['payment_rate_currency_id'], - 'period_id':statement.period_id.id} - voucher_id = voucher_obj.create(cr, uid, voucher_res, context=context) - voucher_line_dict = {} - for line_dict in result['value']['line_cr_ids'] + result['value']['line_dr_ids']: - move_line = line_obj.browse(cr, uid, line_dict['move_line_id'], context) - if line.move_id.id == move_line.move_id.id: - voucher_line_dict = line_dict - - if voucher_line_dict: - voucher_line_dict.update({'voucher_id': voucher_id}) - voucher_line_obj.create(cr, uid, voucher_line_dict, context=context) statement_line_obj.create(cr, uid, { 'name': line.name or '?', 'amount': amount, - 'type': type, 'partner_id': line.partner_id.id, - 'account_id': line.account_id.id, 'statement_id': statement_id, 'ref': line.ref, - 'voucher_id': voucher_id, 'date': statement.date, }, context=context) return {'type': 'ir.actions.act_window_close'} diff --git a/addons/l10n_be_coda/__openerp__.py b/addons/l10n_be_coda/__openerp__.py index 0cb29bcc002..ea1ce59eb88 100644 --- a/addons/l10n_be_coda/__openerp__.py +++ b/addons/l10n_be_coda/__openerp__.py @@ -2,9 +2,9 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# +# # Copyright (c) 2011 Noviat nv/sa (www.noviat.be). All rights reserved. -# +# # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the @@ -34,64 +34,64 @@ Supported are CODA flat files in V2 format from Belgian bank accounts. * CODA v2.2 support. * Foreign Currency support. * Support for all data record types (0, 1, 2, 3, 4, 8, 9). - * Parsing & logging of all Transaction Codes and Structured Format + * Parsing & logging of all Transaction Codes and Structured Format Communications. * Automatic Financial Journal assignment via CODA configuration parameters. * Support for multiple Journals per Bank Account Number. - * Support for multiple statements from different bank accounts in a single + * Support for multiple statements from different bank accounts in a single CODA file. - * Support for 'parsing only' CODA Bank Accounts (defined as type='info' in + * Support for 'parsing only' CODA Bank Accounts (defined as type='info' in the CODA Bank Account configuration records). - * Multi-language CODA parsing, parsing configuration data provided for EN, + * Multi-language CODA parsing, parsing configuration data provided for EN, NL, FR. -The machine readable CODA Files are parsed and stored in human readable format in -CODA Bank Statements. Also Bank Statements are generated containing a subset of -the CODA information (only those transaction lines that are required for the -creation of the Financial Accounting records). The CODA Bank Statement is a +The machine readable CODA Files are parsed and stored in human readable format in +CODA Bank Statements. Also Bank Statements are generated containing a subset of +the CODA information (only those transaction lines that are required for the +creation of the Financial Accounting records). The CODA Bank Statement is a 'read-only' object, hence remaining a reliable representation of the original -CODA file whereas the Bank Statement will get modified as required by accounting +CODA file whereas the Bank Statement will get modified as required by accounting business processes. CODA Bank Accounts configured as type 'Info' will only generate CODA Bank Statements. -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 +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' + 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 + 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 + 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 + 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 + 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 -re-import the CODA after updating the OpenERP database with the information that +In stead 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. Remark on CODA V1 support: ~~~~~~~~~~~~~~~~~~~~~~~~~~ -In some cases a transaction code, transaction category or structured +In some cases a transaction code, transaction category or structured communication code has been given a new or clearer description in CODA V2.The -description provided by the CODA configuration tables is based upon the CODA +description provided by the CODA configuration tables is based upon the CODA V2.2 specifications. If required, you can manually adjust the descriptions via the CODA configuration menu. ''', - 'images' : ['images/coda_logs.jpeg','images/import_coda_logs.jpeg'], - 'depends': ['account_voucher','base_iban', 'l10n_be_invoice_bba',], - 'demo': [], + 'images': ['images/coda_logs.jpeg', 'images/import_coda_logs.jpeg'], + 'depends': ['account_voucher', 'base_iban', 'l10n_be_invoice_bba'], + 'demo': ['l10n_be_coda_demo.xml'], 'data': [ 'l10n_be_coda_wizard.xml', 'l10n_be_coda_view.xml', diff --git a/addons/l10n_be_coda/l10n_be_coda_view.xml b/addons/l10n_be_coda/l10n_be_coda_view.xml index e7b4c02ef70..4b49b6ba2a3 100644 --- a/addons/l10n_be_coda/l10n_be_coda_view.xml +++ b/addons/l10n_be_coda/l10n_be_coda_view.xml @@ -25,8 +25,6 @@ - - @@ -46,10 +44,7 @@ - - - - + @@ -66,17 +61,14 @@ - - - diff --git a/addons/l10n_be_coda/l10n_be_coda_wizard.xml b/addons/l10n_be_coda/l10n_be_coda_wizard.xml index 2fb2651ce37..6e53a4d2ff0 100644 --- a/addons/l10n_be_coda/l10n_be_coda_wizard.xml +++ b/addons/l10n_be_coda/l10n_be_coda_wizard.xml @@ -10,7 +10,6 @@ -
+ Click to create a statement operation template. +
+ Those can be used to quickly create a move line when reconciling + your bank statements. +