diff options
| author | qdp-odoo <qdp@openerp.com> | 2014-05-30 18:47:50 +0200 |
|---|---|---|
| committer | qdp-odoo <qdp@openerp.com> | 2014-05-30 18:47:50 +0200 |
| commit | e3491ccb7109d40965fca25c2e0c373409d0631d (patch) | |
| tree | df72957f65fb649012dd660ff81159b80a5178e9 | |
| parent | 7580b6510b571246d5a492bf9db7c41176fd4092 (diff) | |
[IMP] account: bank statement reconciliation widget (part 2)
29 files changed, 2337 insertions, 778 deletions
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' + 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 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: - 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 onchange_type(self, cr, uid, line_id, partner_id, type, context=None): - res = {'value': {}} - obj_partner = self.pool.get('res.partner') + 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 @@ <field name="currency_id" ref="base.EUR"/> <field name="company_id" ref="base.main_company"/> <field name="partner_id" ref="base.res_partner_1"/> - <field name="journal_id" ref="account.sales_journal"/> + <field name="journal_id" ref="account.expenses_journal"/> <field name="state">draft</field> <field name="type">in_invoice</field> - <field name="account_id" ref="account.a_recv"/> + <field name="account_id" ref="account.a_pay"/> <field name="name">Test invoice 1</field> </record> <record id="test_tax_line" model="account.invoice.tax"> 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"/> <!-- Bank statement --> + + <record id="action_bank_reconcile_bank_statements" model="ir.actions.client"> + <field name="name">Reconciliation on Bank Statements</field> + <field name="tag">bank_statement_reconciliation_view</field> + <field name="context">{'statement_id': active_id}</field> + </record> + <record id="view_account_bank_statement_filter" model="ir.ui.view"> <field name="name">account.cash.statement.select</field> <field name="model">account.bank.statement</field> @@ -525,6 +532,7 @@ </tree> </field> </record> + <record id="view_bank_statement_search" model="ir.ui.view"> <field name="name">account.bank.statement.search</field> <field name="model">account.bank.statement</field> @@ -544,6 +552,7 @@ </search> </field> </record> + <record id="view_bank_statement_form" model="ir.ui.view"> <field name="name">account.bank.statement.form</field> <field name="model">account.bank.statement</field> @@ -551,14 +560,21 @@ <field name="arch" type="xml"> <form string="Bank Statement" version="7.0"> <header> - <button name="button_confirm_bank" states="draft" string="Confirm" type="object" class="oe_highlight"/> - <button name="button_dummy" states="draft" string="Compute" type="object" class="oe_highlight"/> + <field name="all_lines_reconciled" invisible="1" /> + <span attrs="{'invisible':['|',('all_lines_reconciled','=',True),('line_ids','=',[])]}"> + <button name="%(action_bank_reconcile_bank_statements)d" states="draft" string="Reconcile" type="action" class="oe_highlight"/> + </span> + <span attrs="{'invisible':[('all_lines_reconciled','=',False)]}"> + <button name="button_confirm_bank" states="draft" string="Close" type="object" class="oe_highlight"/> + </span> <button name="button_cancel" states="confirm" string="Cancel Statement" type="object"/> <field name="state" widget="statusbar" statusbar_visible="draft,confirm"/> </header> <sheet> <div class="oe_right oe_button_box" name="import_buttons"> - <!-- Put here related buttons --> + <button class="oe_inline oe_stat_button" name="%(action_view_account_statement_from_invoice_lines)d" + string="Import Invoice" type="action" + attrs="{'invisible':[('state','=','confirm')]}" widget="statinfo" icon="fa-pencil-square-o"/> </div> <label for="name" class="oe_edit_only"/> <h1><field name="name"/></h1> @@ -581,41 +597,30 @@ <notebook> <page string="Transactions" name="statement_line_ids"> <field name="line_ids" context="{'date':date}"> - <tree editable="bottom" string="Statement lines"> + <tree editable="bottom" string="Statement lines" colors="grey:journal_entry_id!=False"> <field name="sequence" readonly="1" invisible="1"/> - <field name="date"/> - <field name="name"/> - <field name="ref"/> - <field name="partner_id" on_change="onchange_partner_id(partner_id)" domain="[ + <field name="journal_entry_id" invisible="1"/> + <field name="date" attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"/> + <field name="name" attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"/> + <field name="ref" attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"/> + <field name="partner_id" domain="[ '&', '|',('parent_id','=',False),('is_company','=',True), - '|',('customer','=',True),('supplier','=',True)]" context="{'default_supplier': 1}"/> - <field name="type" on_change="onchange_type(partner_id, type)"/> - <field name="account_id" options='{"no_open":True}' domain="[('journal_id','=',parent.journal_id), ('company_id', '=', parent.company_id)]"/> - <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('company_id', '=', parent.company_id), ('type', '<>', 'view')]"/> - <field name="amount"/> + '|',('customer','=',True),('supplier','=',True)]" + context="{'default_supplier': 1}" + attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"/> + <field name="amount" attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"/> + <field name="amount_currency" groups="base.group_multi_currency" attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"/> + <field name="currency_id" groups="base.group_multi_currency" attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"/> + <field name="bank_account_id" groups="base.group_no_one"/> </tree> - <form string="Statement lines" version="7.0"> - <group col="4"> - <field name="date"/> - <field name="name"/> - <field name="ref"/> - <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> - <field name="type" on_change="onchange_type(partner_id, type)"/> - <field name="account_id" domain="[('journal_id', '=', parent.journal_id), ('type', '<>', 'view'), ('company_id', '=', parent.company_id)]"/> - <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('company_id', '=', parent.company_id), ('type', '<>', 'view')]"/> - <field name="amount"/> - <field name="sequence" readonly="0"/> - </group> - <separator string="Notes"/> - <field name="note"/> - </form> </field> </page> </notebook> <group class="oe_subtotal_footer oe_right" colspan="2" name="sale_total"> <div class="oe_subtotal_footer_separator oe_inline"> <label for="balance_end" /> + <button name="button_dummy" states="draft" string="(update)" type="object" class="oe_edit_only oe_link"/> </div> <field name="balance_end" nolabel="1" class="oe_subtotal_footer_separator" widget='monetary' options="{'currency_field': 'currency_id'}"/> </group> @@ -682,6 +687,85 @@ <field name="domain">[('state','=','draft')]</field> <field name="filter" eval="True"/> </record> + + <record id="action_bank_reconcile" model="ir.actions.client"> + <field name="name">Reconciliation on Bank Statements</field> + <field name="res_model">account.bank.statement.line</field> + <field name="tag">bank_statement_reconciliation_view</field> + </record> + + <!-- because of the needaction badge, groups needs to be specified --> + <menuitem id="menu_bank_reconcile_bank_statements" name="Reconciliation on Bank Statements" parent="account.periodical_processing_reconciliation" groups="group_account_user" action="action_bank_reconcile" sequence="20"/> + + <record id="view_account_statement_operation_template_form" model="ir.ui.view"> + <field name="name">account.statement.operation.template.form</field> + <field name="model">account.statement.operation.template</field> + <field name="arch" type="xml"> + <form string="Statement Operation Templates" version="7.0"> + <sheet> + <div class="oe_title"> + <label for="name" class="oe_edit_only"/> + <h1> + <field name="name"/> + </h1> + </div> + <group> + <group> + <field name="account_id"/> + <field name="amount_type"/> + <field name="tax_id"/> + </group> + <group> + <field name="label"/> + <label for="amount"/> + <div> + <field name="amount" class="oe_inline" /> + <label string="%" class="oe_inline" attrs="{'invisible':[('amount_type','not in',('percentage_of_total', 'percentage_of_balance'))]}" /> + </div> + <field name="analytic_account_id" groups="analytic.group_analytic_accounting"/> + </group> + </group> + </sheet> + </form> + </field> + </record> + <record id="view_account_statement_operation_template_tree" model="ir.ui.view"> + <field name="name">account.statement.operation.template.tree</field> + <field name="model">account.statement.operation.template</field> + <field name="arch" type="xml"> + <tree string="Bank Reconciliation Move Presets"> + <field name="name"/> + <field name="account_id"/> + <field name="amount_type"/> + </tree> + </field> + </record> + <record id="view_account_statement_operation_template_search" model="ir.ui.view"> + <field name="name">account.statement.operation.template.search</field> + <field name="model">account.statement.operation.template</field> + <field name="arch" type="xml"> + <search string="Bank Reconciliation Move preset"> + <filter string="With tax" domain="[('tax_id','!=',False)]"/> + <field name="amount_type"/> + </search> + </field> + </record> + <record id="action_account_statement_operation_template" model="ir.actions.act_window"> + <field name="name">Statement Operation Templates</field> + <field name="res_model">account.statement.operation.template</field> + <field name="view_type">form</field> + <field name="view_mode">tree,form</field> + <field name="search_view_id" ref="view_account_statement_operation_template_search"/> + <field name="help" type="html"> + <p class="oe_view_nocontent_create"> + Click to create a statement operation template. + </p><p> + Those can be used to quickly create a move line when reconciling + your bank statements. + </p> + </field> + </record> + <menuitem action="action_account_statement_operation_template" id="menu_action_account_statement_operation_template" parent="menu_configuration_misc" name="Statement Operation Templates" sequence="22"/> <!-- Account Types --> <record id="view_account_type_search" model="ir.ui.view"> @@ -1602,11 +1686,11 @@ <form string="Payment Term" version="7.0"> <group> <group string="Amount Computation"> - <field name="value"/> - <label for="value_amount" string="Amount To Pay" attrs="{'invisible':[('value','=','balance')]}"/> - <div attrs="{'invisible':[('value','=','balance')]}"> - <field name="value_amount" class="oe_inline"/> - </div> + <field name="value"/> + <label for="value_amount" string="Amount To Pay" attrs="{'invisible':[('value','=','balance')]}"/> + <div attrs="{'invisible':[('value','=','balance')]}"> + <field name="value_amount" class="oe_inline"/> + </div> </group> <group string="Due Date Computation"> <field name="days"/> @@ -2258,7 +2342,13 @@ <field name="arch" type="xml"> <form string="Statement" version="7.0"> <header> - <button name="button_confirm_cash" states="open" string="Close CashBox" type="object" class="oe_highlight"/> + <field name="all_lines_reconciled" invisible="1" /> + <span attrs="{'invisible':['|',('all_lines_reconciled','=',True),('line_ids','=',[])]}"> + <button name="%(action_bank_reconcile_bank_statements)d" states="open" string="Reconcile" type="action" class="oe_highlight"/> + </span> + <span attrs="{'invisible':[('all_lines_reconciled','=',False)]}"> + <button name="button_confirm_cash" states="open" string="Close CashBox" type="object" class="oe_highlight"/> + </span> <button name="button_open" states="draft" string="Open CashBox" type="object" class="oe_highlight"/> <button name="button_cancel" states="confirm,open" string="Cancel CashBox" type="object"/> <field name="state" widget="statusbar" nolabel="1" statusbar_visible="draft,confirm"/> @@ -2288,10 +2378,7 @@ <field name="date"/> <field name="name"/> <field name="ref"/> - <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> - <field name="type" on_change="onchange_type(partner_id, type)"/> - <field domain="[('journal_id','=',parent.journal_id), ('company_id', '=', parent.company_id)]" name="account_id"/> - <field name="analytic_account_id" domain="[('company_id', '=', parent.company_id), ('type', '<>', 'view')]" groups="analytic.group_analytic_accounting" /> + <field name="partner_id"/> <field name="amount"/> </tree> <form string="Statement lines" version="7.0"> @@ -2299,10 +2386,7 @@ <field name="date"/> <field name="name"/> <field name="ref"/> - <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> - <field name="type" on_change="onchange_type(partner_id, type)"/> - <field domain="[('journal_id', '=', parent.journal_id), ('type', '<>', 'view'), ('company_id', '=', parent.company_id)]" name="account_id"/> - <field name="analytic_account_id" domain="[('company_id', '=', parent.company_id), ('type', '<>', 'view')]" groups="analytic.group_analytic_accounting" /> + <field name="partner_id"/> <field name="amount"/> <field name="sequence"/> </group> 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 @@ <field name="partner_id" ref="base.res_partner_17"/> <field name="name">Zed+ Antivirus</field> </record> + + <!-- Some customer invoices used to show the reconciliation process on the bank statement --> + <record id="invoice_1" model="account.invoice"> + <field name="currency_id" ref="base.EUR"/> + <field name="company_id" ref="base.main_company"/> + <field name="journal_id" ref="account.sales_journal"/> + <field name="state">draft</field> + <field name="type">out_invoice</field> + <field name="account_id" ref="a_recv"/> + <field name="partner_id" ref="base.res_partner_9"/> + </record> + <record id="invoice_1_line_1" model="account.invoice.line"> + <field name="name">Otpez Laptop without OS</field> + <field name="invoice_id" ref="invoice_1"/> + <field name="price_unit">642</field> + <field name="quantity">5</field> + <field name="account_id" ref="a_sale"/> + </record> + <record id="invoice_1_line_2" model="account.invoice.line"> + <field name="name">Linutop</field> + <field name="invoice_id" ref="invoice_1"/> + <field name="price_unit">280</field> + <field name="quantity">5</field> + <field name="account_id" ref="a_sale"/> + </record> + <workflow action="invoice_open" model="account.invoice" ref="invoice_1"/> + + <record id="invoice_2" model="account.invoice"> + <field name="currency_id" ref="base.EUR"/> + <field name="company_id" ref="base.main_company"/> + <field name="journal_id" ref="account.sales_journal"/> + <field name="state">draft</field> + <field name="type">out_invoice</field> + <field name="account_id" ref="a_recv"/> + <field name="partner_id" ref="base.res_partner_9"/> + <field eval="time.strftime('%Y-%m') + '-01'" name="date_invoice"/> + </record> + <record id="invoice_2_line_1" model="account.invoice.line"> + <field name="name">8-port Switch</field> + <field name="invoice_id" ref="invoice_2"/> + <field name="price_unit">50</field> + <field name="quantity">3</field> + <field name="account_id" ref="a_sale"/> + </record> + <record id="invoice_2_line_2" model="account.invoice.line"> + <field name="name">30m RJ45 wire</field> + <field name="invoice_id" ref="invoice_2"/> + <field name="price_unit">25</field> + <field name="quantity">20</field> + <field name="account_id" ref="a_sale"/> + </record> + <workflow action="invoice_open" model="account.invoice" ref="invoice_2"/> + + <record id="invoice_3" model="account.invoice"> + <field name="currency_id" ref="base.EUR"/> + <field name="company_id" ref="base.main_company"/> + <field name="journal_id" ref="account.sales_journal"/> + <field name="state">draft</field> + <field name="type">out_invoice</field> + <field name="account_id" ref="a_recv"/> + <field name="partner_id" ref="base.res_partner_2"/> + <field eval="time.strftime('%Y-%m') + '-08'" name="date_invoice"/> + </record> + <record id="invoice_3_line_1" model="account.invoice.line"> + <field name="name">TypeMatrix Dvorak Keyboard</field> + <field name="invoice_id" ref="invoice_3"/> + <field name="price_unit">90</field> + <field name="quantity">5</field> + <field name="account_id" ref="a_sale"/> + </record> + <record id="invoice_3_line_2" model="account.invoice.line"> + <field name="name">Ergonomic Mouse</field> + <field name="invoice_id" ref="invoice_3"/> + <field name="price_unit">15</field> + <field name="quantity">5</field> + <field name="account_id" ref="a_sale"/> + </record> + <workflow action="invoice_open" model="account.invoice" ref="invoice_3"/> + + <record id="invoice_4" model="account.invoice"> + <field name="currency_id" ref="base.EUR"/> + <field name="company_id" ref="base.main_company"/> + <field name="journal_id" ref="account.sales_journal"/> + <field name="state">draft</field> + <field name="type">out_invoice</field> + <field name="account_id" ref="a_recv"/> + <field name="partner_id" ref="base.res_partner_2"/> + <field eval="time.strftime('%Y-%m') + '-15'" name="date_invoice"/> + </record> + <record id="invoice_4_line_1" model="account.invoice.line"> + <field name="name">Desktop Computer Table</field> + <field name="invoice_id" ref="invoice_4"/> + <field name="price_unit">80</field> + <field name="quantity">5</field> + <field name="account_id" ref="a_sale"/> + </record> + <record id="invoice_4_line_2" model="account.invoice.line"> + <field name="name">Desktop Lamp</field> + <field name="invoice_id" ref="invoice_4"/> + <field name="price_unit">20</field> + <field name="quantity">5</field> + <field name="account_id" ref="a_sale"/> + </record> + <workflow action="invoice_open" model="account.invoice" ref="invoice_4"/> + + <record id="invoice_5" model="account.invoice"> + <field name="currency_id" ref="base.EUR"/> + <field name="company_id" ref="base.main_company"/> + <field name="journal_id" ref="account.sales_journal"/> + <field name="state">draft</field> + <field name="type">out_invoice</field> + <field name="account_id" ref="a_recv"/> + <field name="partner_id" ref="base.res_partner_9"/> + <field eval="time.strftime('%Y-%m') + '-08'" name="date_invoice"/> + </record> + <record id="invoice_5_line_1" model="account.invoice.line"> + <field name="name">TypeMatrix Dvorak Keyboard</field> + <field name="invoice_id" ref="invoice_5"/> + <field name="price_unit">90</field> + <field name="quantity">5</field> + <field name="account_id" ref="a_sale"/> + </record> + <record id="invoice_5_line_2" model="account.invoice.line"> + <field name="name">Ergonomic Mouse</field> + <field name="invoice_id" ref="invoice_5"/> + <field name="price_unit">15</field> + <field name="quantity">5</field> + <field name="account_id" ref="a_sale"/> + </record> + <workflow action="invoice_open" model="account.invoice" ref="invoice_5"/> </data> </openerp> 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 !") + " <i class='fa fa-trophy congrats_icon'></i>"; + else title = _t("Congrats, you're all done !") + " <i class='fa fa-thumbs-o-up congrats_icon'></i>"; + + 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 = $("<div style='overflow: hidden;' />"); + 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<create_form_fields_arr.length; i++) { + field_manager.fields_view.fields[create_form_fields_arr[i].id] = _.extend(new Default_field(), create_form_fields_arr[i].field_properties); + } + field_manager.fields_view.fields["change_partner"] = _.extend(new Default_field(), { + relation: "res.partner", + string: _t("Partner"), + type: "many2one", + domain: [['parent_id','=',false], '|', ['customer','=',true], ['supplier','=',true]], + }); + + // Returns a function that serves as a xhr response handler + var hideGroupResponseClosureFactory = function(field_widget, $container, obj_key){ + return function(has_group){ + if (has_group) $container.show(); + else { + field_widget.destroy(); + $container.remove(); + delete self[obj_key]; + } + }; + }; + + // generate the create "form" + self.create_form = []; + for (var i=0; i<create_form_fields_arr.length; i++) { + var field_data = create_form_fields_arr[i]; + + // create widgets + var node = new Default_node(field_data.id); + if (! field_data.required) node.attrs.modifiers = ""; + var field = new field_data.constructor(field_manager, node); + self[field_data.id+"_field"] = field; + self.create_form.push(field); + + // on update : change the last created line + field.corresponding_property = field_data.corresponding_property; + field.on("change:value", self, self.formCreateInputChanged); + + // append to DOM + var $field_container = $(QWeb.render("form_create_field", {id: field_data.id, label: field_data.label})); + field.appendTo($field_container.find("td")); + self.$(".create_form").prepend($field_container); + + // now that widget's dom has been created (appendTo does that), bind events and adds tabindex + if (field_data.field_properties.type != "many2one") { + // Triggers change:value TODO : moche bind ? + field.$el.find("input").keyup(function(e, field){ field.commit_value(); }.bind(null, null, field)); + } + field.$el.find("input").attr("tabindex", field_data.tabindex); + + // Hide the field if group not OK + if (field_data.group !== undefined) { + var target = $field_container; + target.hide(); + self.model_res_users + .call("has_group", [field_data.group]) + .then(hideGroupResponseClosureFactory(field, target, (field_data.id+"_field"))); + } + } + + // generate the change partner "form" + var change_partner_node = new Default_node("change_partner"); change_partner_node.attrs.modifiers = ""; + self.change_partner_field = new instance.web.form.FieldMany2One(field_manager, change_partner_node); + self.change_partner_field.appendTo(self.$(".change_partner_container")); + self.change_partner_field.on("change:value", self.change_partner_field, function() { + self.changePartner(this.get_value()); + }); + + field_manager.do_show(); + }, + + /** Utils */ + + /* TODO : if t-call for attr, all in qweb */ + decorateStatementLine: function(line){ + line.q_popover = QWeb.render("bank_statement_reconciliation_line_details", {line: line}); + }, + + // adds fields, prefixed with q_, to the move line for qweb rendering + decorateMoveLine: function(line){ + line.partial_reconcile = false; + line.propose_partial_reconcile = false; + line.q_due_date = (line.date_maturity === false ? line.date : line.date_maturity); + line.q_amount = (line.debit !== 0 ? "- "+line.q_debit : "") + (line.credit !== 0 ? line.q_credit : ""); + line.q_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line}); + line.q_label = line.name; + + // WARNING : pretty much of a ugly hack + // The value of account_move.ref is either the move's communication or it's name without the slashes + if (line.ref && line.ref !== line.name.replace(/\//g,'')) + line.q_label += " : " + line.ref; + }, + + bindPopoverTo: function(el) { + var self = this; + $(el).addClass("bootstrap_popover"); + el.popover({ + 'placement': 'left', + 'container': self.el, + 'html': true, + 'trigger': 'hover', + 'animation': false, + 'toggle': 'popover' + }); + }, + + islineCreatedBeingEditedValid: function() { + var line = this.get("line_created_being_edited")[0]; + return line.amount // must be defined and not 0 + && line.account_id // must be defined (and will never be 0) + && line.label; // must be defined and not empty + }, + + /* returns the created lines, plus the ones being edited if valid */ + getCreatedLines: function() { + var self = this; + var created_lines = self.get("lines_created").slice(); + if (self.islineCreatedBeingEditedValid()) + return created_lines.concat(self.get("line_created_being_edited")); + else + return created_lines; + }, + + /** Matching */ + + moveLineClickHandler: function(e) { + var self = this; + if (e.currentTarget.dataset.selected === "true") self.deselectMoveLine(e.currentTarget); + else self.selectMoveLine(e.currentTarget); + }, + + // Takes a move line from the match view and adds it to the mv_lines_selected array + selectMoveLine: function(mv_line) { + var self = this; + var line_id = mv_line.dataset.lineid; + + // find the line in mv_lines or mv_lines_deselected + var line = _.find(self.get("mv_lines"), function(o){ return o.id == line_id }); + if (! line) { + line = _.find(self.mv_lines_deselected, function(o){ return o.id == line_id }); + self.mv_lines_deselected = _.filter(self.mv_lines_deselected, function(o) { return o.id != line_id }); + } + // If no line found, the view is probably not up to date to the model (asynchronous fun) + if (! line) return; + + // Warn the user if he's selecting lines from both a payable and a receivable account + var last_selected_line = _.last(self.get("mv_lines_selected")); + if (last_selected_line && last_selected_line.account_type != line.account_type) { + // TODO : web client API + alert(_.str.sprintf(_t("You are selecting transactions from both a payable and a receivable account.\n\nIn order to proceed, you first need to deselect the %s transactions."), last_selected_line.account_type)); + return; + } + + self.set("mv_lines_selected", self.get("mv_lines_selected").concat(line)); + }, + + // Removes a move line from the mv_lines_selected array + deselectMoveLine: function(mv_line) { + var self = this; + var line_id = mv_line.dataset.lineid; + var line = _.find(self.get("mv_lines_selected"), function(o) { return o.id == line_id }); + // If no line found, the view is probably not up to date to the model (asynchronous fun) + if (! line) return; + + // add the line to mv_lines_deselected and remove it from mv_lines_selected + self.mv_lines_deselected.unshift(line); + var mv_lines_selected = _.filter(self.get("mv_lines_selected"), function(o) { return o.id != line_id }); + + // remove partial reconciliation stuff if necessary + if (line.partial_reconcile === true) self.unpartialReconcileLine(line); + if (line.propose_partial_reconcile === true) line.propose_partial_reconcile = false; + + self.$el.removeClass("no_match"); + self.set("mode", "match"); + self.set("mv_lines_selected", mv_lines_selected); + }, + + + /** Matches pagination */ + + pagerControlLeftHandler: function() { + var self = this; + if (self.$(".pager_control_left").hasClass("disabled")) { return; /* shouldn't happen, anyway*/ } + if (self.total_move_lines_num < 0) { return; } + self.set("pager_index", self.get("pager_index")-1 ); + }, + + pagerControlRightHandler: function() { + var self = this; + var new_index = self.get("pager_index")+1; + if (self.$(".pager_control_right").hasClass("disabled")) { return; /* shouldn't happen, anyway*/ } + if ((new_index * self.max_move_lines_displayed) >= 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 = $("<div />"); + 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 @@ <link rel="stylesheet" href="/account/static/src/css/account_move_reconciliation.css"/> <link rel="stylesheet" href="/account/static/src/css/account_move_line_quickadd.css"/> <link rel="stylesheet" href="/account/static/src/css/account_bank_and_cash.css"/> - <script type="text/javascript" src="/account/static/src/js/account_move_reconciliation.js"></script> + <link rel="stylesheet" href="/account/static/src/css/account_bank_statement_reconciliation.css"/> + <script type="text/javascript" src="/account/static/src/js/account_widgets.js"></script> <script type="text/javascript" src="/account/static/src/js/account_move_line_quickadd.js"></script> </xpath> </template> 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 @@ <!-- add property field on default analytic account--> - <record model="ir.ui.view" id="view_default_inherit_form"> - <field name="name">account.analytic.default.form.plans</field> - <field name="model">account.analytic.default</field> - <field name="inherit_id" ref="account_analytic_default.view_account_analytic_default_form"/> - <field name="arch" type="xml"> - <field name="analytic_id" required="1" position="replace"> - <field name="analytics_id" required="1"/> - </field> - </field> - </record> - <record model="ir.ui.view" id="view_default_inherit_tree"> - <field name="name">account.analytic.default.tree.plans</field> - <field name="model">account.analytic.default</field> - <field name="inherit_id" ref="account_analytic_default.view_account_analytic_default_tree"/> - <field name="arch" type="xml"> - <xpath expr="//field[@name='analytic_id']" position="attributes"> - <attribute name="invisible">1</attribute> - </xpath> - <xpath expr="//field[@name='analytic_id']" position="after"> - <field name="analytics_id" required="1"/> - </xpath> - </field> - </record> - - <record id="view_bank_statement_inherit_form" model="ir.ui.view"> - <field name="name">account.bank.statement.form.inherit</field> - <field name="model">account.bank.statement</field> - <field name="inherit_id" ref="account.view_bank_statement_form"/> - <field name="arch" type="xml"> - <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='analytic_account_id']" position="replace"> - <field name="analytics_id" groups="analytic.group_analytic_accounting"/> - </xpath> - <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='analytic_account_id']" position="replace"> - <field name="analytics_id" groups="analytic.group_analytic_accounting"/> - </xpath> - </field> - </record> - - <record id="view_bank_statement_inherit_form2" model="ir.ui.view"> - <field name="name">account.bank.statement.form.inherit</field> - <field name="model">account.bank.statement</field> - <field name="inherit_id" ref="account.view_bank_statement_form2"/> - <field name="arch" type="xml"> - <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='analytic_account_id']" position="replace"> - <field name="analytics_id" groups="analytic.group_analytic_accounting"/> - </xpath> - <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='analytic_account_id']" position="replace"> - <field name="analytics_id" groups="analytic.group_analytic_accounting"/> - </xpath> + <record model="ir.ui.view" id="view_default_inherit_form"> + <field name="name">account.analytic.default.form.plans</field> + <field name="model">account.analytic.default</field> + <field name="inherit_id" ref="account_analytic_default.view_account_analytic_default_form"/> + <field name="arch" type="xml"> + <field name="analytic_id" required="1" position="replace"> + <field name="analytics_id" required="1"/> </field> - </record> - + </field> + </record> + <record model="ir.ui.view" id="view_default_inherit_tree"> + <field name="name">account.analytic.default.tree.plans</field> + <field name="model">account.analytic.default</field> + <field name="inherit_id" ref="account_analytic_default.view_account_analytic_default_tree"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='analytic_id']" position="replace"> + <field name="analytics_id" required="1"/> + </xpath> + </field> + </record> </data> </openerp> 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 @@ <field name="name"/> <field name="statement_id"/> <field name="ref" readonly="1"/> - <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> - <field name="type" on_change="onchange_type(partner_id, type)"/> - <field name="account_id"/> - <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('type', '<>', 'view')]"/> + <field name="partner_id"/> <field name="amount" readonly="1" sum="Total Amount"/> <field name="globalisation_id" string="Glob. Id"/> <field name="globalisation_amount" string="Glob. Am."/> @@ -98,10 +95,7 @@ <field name="val_date"/> <field name="name"/> <field name="ref" readonly="0"/> - <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> - <field name="type" on_change="onchange_type(partner_id, type)"/> - <field domain="[('type', '<>', 'view')]" name="account_id"/> - <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('type', '<>', 'view')]"/> + <field name="partner_id"/> <field name="amount"/> <field name="globalisation_id"/> <field name="sequence" readonly="0"/> @@ -129,7 +123,6 @@ <field name="statement_id"/> <field name="globalisation_id" string="Glob. Id"/> <group string="Extended Filters..." expand="0"> - <field name="account_id"/> <field name="partner_id"/> <field name="amount"/> <field name="globalisation_amount" string="Glob. Amount"/> @@ -138,7 +131,6 @@ <group string="Group By..." expand="1"> <filter string="Journal" context="{'group_by':'journal_id'}" icon="terp-folder-green"/> <filter string="Statement" context="{'group_by':'statement_id'}" icon="terp-folder-orange"/> - <filter string="Fin.Account" context="{'group_by':'account_id'}" icon="terp-folder-yellow"/> </group> </search> </field> 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 @@ <field name="context">{'state':'posted'}</field> <field name="search_view_id" ref="view_voucher_filter"/> </record> - - <record id="view_bank_statement_form_invoice" model="ir.ui.view"> - <field name="name">account.bank.statement.invoice.form.inherit</field> - <field name="model">account.bank.statement</field> - <field name="inherit_id" ref="account.view_bank_statement_form"/> - <field name="arch" type="xml"> - <xpath expr="//div[@name='import_buttons']" position="inside"> - <button class="oe_inline oe_stat_button" name="%(action_view_account_statement_from_invoice_lines)d" - string="Import Invoice" type="action" - attrs="{'invisible':[('state','=','confirm')]}" widget="statinfo" icon="fa-pencil-square-o"/> - </xpath> - </field> - </record> - - <record id="view_bank_statement_form_voucher" model="ir.ui.view"> - <field name="name">account.bank.statement.voucher.tree.inherit</field> - <field name="model">account.bank.statement</field> - <field name="inherit_id" ref="account.view_bank_statement_form"/> - <field name="arch" type="xml"> - <xpath expr="//page[@name='statement_line_ids']/field[@name='line_ids']/tree/field[@name='amount']" position="after"> - <field name="voucher_id" string="" widget="many2onebutton" options="{'label':{'create':'Reconcile','edit':'Edit Reconciliation'}}" context="{'line_type': type, 'default_type': amount < 0 and 'payment' or 'receipt', 'type': amount < 0 and 'payment' or 'receipt', 'default_partner_id': partner_id, 'default_journal_id': parent.journal_id, 'default_amount': abs(amount), 'default_reference': ref, 'default_date': date, 'default_name': name, 'default_active': False, 'account_id': account_id}"/> - </xpath> - <xpath expr="//page[@name='statement_line_ids']/field[@name='line_ids']/form/group/field[@name='sequence']" position="before"> - <field name="voucher_id" widget="many2onebutton" options="{'label':{'create':'Reconcile','edit':'Edit Reconciliation'}}" context="{'line_type': type, 'default_type': amount < 0 and 'payment' or 'receipt', 'type': amount < 0 and 'payment' or 'receipt', 'default_partner_id': partner_id, 'default_journal_id': parent.journal_id, 'default_amount': abs(amount), 'default_reference': ref, 'default_date': date, 'default_name': name, 'default_active': False, 'account_id': account_id}"/> - </xpath> - <field name="amount" position="attributes"> - <attribute name="on_change">onchange_amount(amount)</attribute> - </field> - </field> - </record> - - <record id="view_cash_statement_tree_voucher" model="ir.ui.view"> - <field name="name">account.cash.statement.voucher.tree.inherit</field> - <field name="model">account.bank.statement</field> - <field name="inherit_id" ref="account.view_bank_statement_form2"/> - <field name="arch" type="xml"> - <xpath expr="//page/field[@name='line_ids']/tree/field[@name='amount']" position="after"> - <field name="voucher_id" context="{'line_type': type, 'default_type': amount < 0 and 'payment' or 'receipt', 'type': amount < 0 and 'payment' or 'receipt', 'default_partner_id': partner_id, 'default_journal_id': parent.journal_id, 'default_amount': abs(amount), 'default_reference': ref, 'default_date': date, 'default_name': name, 'account_id': account_id}"/> - </xpath> - </field> - </record> - - <record id="view_cash_statement_form_voucher" model="ir.ui.view"> - <field name="name">account.cash.statement.voucher.form.inherit</field> - <field name="model">account.bank.statement</field> - <field name="inherit_id" ref="account.view_bank_statement_form2"/> - <field name="arch" type="xml"> - <xpath expr="//page/field[@name='line_ids']/form/group/field[@name='amount']" position="after"> - <field name="voucher_id" context="{'line_type': type, 'default_type': amount < 0 and 'payment' or 'receipt', 'type': amount < 0 and 'payment' or 'receipt', 'default_partner_id': partner_id, 'default_journal_id': parent.journal_id, 'default_amount': abs(amount), 'default_reference': ref, 'default_date': date, 'default_name': name, 'account_id': account_id}"/> - </xpath> - </field> - </record> - + <!-- res.config form view --> <record model="ir.ui.view" id="view_account_settings_currency_xchange_form"> <field name="name">account.config.settings.inherit</field> 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 @@ <field name="name"/> <field name="ref" readonly="0"/> <field name="partner_id"/> - <field name="type" /> - <field domain="[('type', '<>', 'view')]" name="account_id"/> <field name="amount"/> <field name="sequence" readonly="0"/> </group> @@ -46,10 +44,7 @@ <field name="date"/> <field name="name"/> <field name="ref"/> - <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> - <field name="type" on_change="onchange_type(partner_id, type)"/> - <field name="account_id" options='{"no_open":True}' domain="[('type', '<>', 'view')]"/> - <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('type', '<>', 'view')]"/> + <field name="partner_id"/> <field name="amount"/> <field name="note"/> </tree> @@ -66,17 +61,14 @@ <filter name="credit" string="Credit" domain="[('amount','<',0)]" icon="terp-folder-orange" help="Credit Transactions."/> <field name="statement_id"/> <group expand="0" string="Extended Filters..."> - <field name="account_id"/> <field name="partner_id"/> <field name="amount"/> - <field name="type"/> <field name="note"/> </group> <newline/> <group string="Group By..." expand="0"> <filter string="Partner" context="{'group_by':'partner_id'}" icon="terp-folder-green"/> <filter string="Statement" context="{'group_by':'statement_id'}" icon="terp-folder-orange"/> - <filter string="Fin.Account" context="{'group_by':'account_id'}" icon="terp-folder-yellow"/> </group> </search> </field> 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 @@ <form string="Import CODA File" version="7.0"> <group col="2"> <field name="coda_data" filename="coda_fname"/> - <field name="temporary_account_id" /> </group> <footer> <button name="coda_parsing" string="_Import" type="object" class="oe_highlight"/> diff --git a/addons/l10n_be_coda/wizard/account_coda_import.py b/addons/l10n_be_coda/wizard/account_coda_import.py index aa9bf49b5a1..e50f8537881 100644 --- a/addons/l10n_be_coda/wizard/account_coda_import.py +++ b/addons/l10n_be_coda/wizard/account_coda_import.py @@ -37,20 +37,10 @@ class account_coda_import(osv.osv_memory): 'coda_data': fields.binary('CODA File', required=True), 'coda_fname': fields.char('CODA Filename', size=128, required=True), 'note': fields.text('Log'), - 'temporary_account_id': fields.many2one('account.account', 'Temporary Account', domain="[('type','!=','view')]", help="It acts as a temporary account for general amount", required=True), } - def _get_default_tmp_account(self, cr, uid, context): - tmp_accounts = self.pool.get('account.account').search(cr, uid, [('code', '=', '490000')]) - if tmp_accounts and len(tmp_accounts) > 0: - tmp_account_id = tmp_accounts[0] - else: - tmp_account_id = False - return tmp_account_id - _defaults = { 'coda_fname': lambda *a: '', - 'temporary_account_id': _get_default_tmp_account, } def coda_parsing(self, cr, uid, ids, context=None, batch=False, codafile=None, codafilename=None): @@ -64,7 +54,6 @@ class account_coda_import(osv.osv_memory): try: codafile = data.coda_data codafilename = data.coda_fname - temporaryaccount = data.temporary_account_id.id except: raise osv.except_osv(_('Error'), _('Wizard in incorrect state. Please hit the Cancel button')) return {} @@ -149,7 +138,6 @@ class account_coda_import(osv.osv_memory): statementLine['amount'] = float(rmspaces(line[32:47])) / 1000 if statementLine['debit'] == '1': statementLine['amount'] = - statementLine['amount'] - statementLine['transaction_type'] = line[53] statementLine['transactionDate'] = time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT, time.strptime(rmspaces(line[47:53]), '%d%m%y')) statementLine['transaction_family'] = rmspaces(line[54:56]) statementLine['transaction_code'] = rmspaces(line[56:58]) @@ -211,7 +199,6 @@ class account_coda_import(osv.osv_memory): infoLine['sequence'] = len(statement['lines']) + 1 infoLine['ref'] = rmspaces(line[2:10]) infoLine['transactionRef'] = rmspaces(line[10:31]) - infoLine['transaction_type'] = line[31] infoLine['transaction_family'] = rmspaces(line[32:34]) infoLine['transaction_code'] = rmspaces(line[34:36]) infoLine['transaction_category'] = rmspaces(line[36:39]) @@ -304,91 +291,78 @@ class account_coda_import(osv.osv_memory): if 'counterpartyAddress' in line and line['counterpartyAddress'] != '': note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress']) line['name'] = "\n".join(filter(None, [line['counterpartyName'], line['communication']])) - line['transaction_type'] = 'general' partner = None partner_id = None invoice = False if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101': ids = self.pool.get('account.invoice').search(cr, uid, [('reference', '=', line['communication']), ('reference_type', '=', 'bba')]) - if ids: - invoice = self.pool.get('account.invoice').browse(cr, uid, ids[0]) - partner = invoice.partner_id - partner_id = partner.id - if invoice.type in ['in_invoice', 'in_refund'] and line['debit'] == '1': - line['transaction_type'] = 'supplier' - elif invoice.type in ['out_invoice', 'out_refund'] and line['debit'] == '0': - line['transaction_type'] = 'customer' - line['account'] = invoice.account_id.id - line['reconcile'] = False - if invoice.type in ['in_invoice', 'out_invoice']: - iml_ids = self.pool.get('account.move.line').search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)]) - if iml_ids: - line['reconcile'] = iml_ids[0] - if line['reconcile']: - voucher_vals = { - 'type': line['transaction_type'] == 'supplier' and 'payment' or 'receipt', - 'name': line['name'], - 'partner_id': partner_id, - 'journal_id': statement['journal_id'].id, - 'account_id': statement['journal_id'].default_credit_account_id.id, - 'company_id': statement['journal_id'].company_id.id, - 'currency_id': statement['journal_id'].company_id.currency_id.id, - 'date': line['entryDate'], - 'amount': abs(line['amount']), - 'period_id': statement['period_id'], - 'invoice_id': invoice.id, - } - context['invoice_id'] = invoice.id - voucher_vals.update(self.pool.get('account.voucher').onchange_partner_id(cr, uid, [], - partner_id=partner_id, - journal_id=statement['journal_id'].id, - amount=abs(line['amount']), - currency_id=statement['journal_id'].company_id.currency_id.id, - ttype=line['transaction_type'] == 'supplier' and 'payment' or 'receipt', - date=line['transactionDate'], - context=context - )['value']) - line_drs = [] - for line_dr in voucher_vals['line_dr_ids']: - line_drs.append((0, 0, line_dr)) - voucher_vals['line_dr_ids'] = line_drs - line_crs = [] - for line_cr in voucher_vals['line_cr_ids']: - line_crs.append((0, 0, line_cr)) - voucher_vals['line_cr_ids'] = line_crs - line['voucher_id'] = self.pool.get('account.voucher').create(cr, uid, voucher_vals, context=context) + +# Gère les communications structurées +# TODO : à faire primer sur resolution_proposition : si la communication indique une facture, on la sélectionne + +# if ids: +# invoice = self.pool.get('account.invoice').browse(cr, uid, ids[0]) +# partner = invoice.partner_id +# partner_id = partner.id +# if invoice.type in ['in_invoice', 'in_refund'] and line['debit'] == '1': +# line['transaction_type'] = 'supplier' +# elif invoice.type in ['out_invoice', 'out_refund'] and line['debit'] == '0': +# line['transaction_type'] = 'customer' +# line['account'] = invoice.account_id.id +# line['reconcile'] = False +# if invoice.type in ['in_invoice', 'out_invoice']: +# iml_ids = self.pool.get('account.move.line').search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)]) +# if iml_ids: +# line['reconcile'] = iml_ids[0] +# if line['reconcile']: +# voucher_vals = { +# 'type': line['transaction_type'] == 'supplier' and 'payment' or 'receipt', +# 'name': line['name'], +# 'partner_id': partner_id, +# 'journal_id': statement['journal_id'].id, +# 'account_id': statement['journal_id'].default_credit_account_id.id, +# 'company_id': statement['journal_id'].company_id.id, +# 'currency_id': statement['journal_id'].company_id.currency_id.id, +# 'date': line['entryDate'], +# 'amount': abs(line['amount']), +# 'period_id': statement['period_id'], +# 'invoice_id': invoice.id, +# } +# context['invoice_id'] = invoice.id +# voucher_vals.update(self.pool.get('account.voucher').onchange_partner_id(cr, uid, [], +# partner_id=partner_id, +# journal_id=statement['journal_id'].id, +# amount=abs(line['amount']), +# currency_id=statement['journal_id'].company_id.currency_id.id, +# ttype=line['transaction_type'] == 'supplier' and 'payment' or 'receipt', +# date=line['transactionDate'], +# context=context +# )['value']) +# line_drs = [] +# for line_dr in voucher_vals['line_dr_ids']: +# line_drs.append((0, 0, line_dr)) +# voucher_vals['line_dr_ids'] = line_drs +# line_crs = [] +# for line_cr in voucher_vals['line_cr_ids']: +# line_crs.append((0, 0, line_cr)) +# voucher_vals['line_cr_ids'] = line_crs +# line['voucher_id'] = self.pool.get('account.voucher').create(cr, uid, voucher_vals, context=context) if 'counterpartyNumber' in line and line['counterpartyNumber']: ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', str(line['counterpartyNumber']))]) if ids and len(ids) > 0: partner = self.pool.get('res.partner.bank').browse(cr, uid, ids[0], context=context).partner_id partner_id = partner.id - if not invoice: - if line['debit'] == '0': - line['account'] = partner.property_account_receivable.id - if partner.customer: - line['transaction_type'] = 'customer' - elif line['debit'] == '1': - line['account'] = partner.property_account_payable.id - if partner.supplier: - line['transaction_type'] = 'supplier' - if not partner and not invoice: - line['account'] = temporaryaccount if 'communication' in line and line['communication'] != '': note.append(_('Communication') + ': ' + line['communication']) - if 'voucher_id' not in line: - line['voucher_id'] = None data = { 'name': line['name'], 'note': "\n".join(note), 'date': line['entryDate'], 'amount': line['amount'], - 'type': line['transaction_type'], 'partner_id': partner_id, - 'account_id': line['account'], 'statement_id': statement['id'], 'ref': line['ref'], 'sequence': line['sequence'], - 'voucher_id': line['voucher_id'], 'coda_account_number': line['counterpartyNumber'], } self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index c1172c0cc6d..c8cf81d062c 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -457,19 +457,13 @@ class pos_session(osv.osv): if st.difference and st.journal_id.cash_control == True: if st.difference > 0.0: name= _('Point of Sale Profit') - account_id = st.journal_id.profit_account_id.id else: - account_id = st.journal_id.loss_account_id.id name= _('Point of Sale Loss') - if not account_id: - raise osv.except_osv( _('Error!'), - _("Please set your profit and loss accounts on your payment method '%s'. This will allow OpenERP to post the difference of %.2f in your ending balance. To close this session, you can update the 'Closing Cash Control' to avoid any difference.") % (st.journal_id.name,st.difference)) bsl.create(cr, uid, { 'statement_id': st.id, 'amount': st.difference, 'ref': record.name, 'name': name, - 'account_id': account_id }, context=context) if st.journal_id.type == 'bank': @@ -820,20 +814,9 @@ class pos_order(osv.osv): 'amount': data['amount'], 'date': data.get('payment_date', time.strftime('%Y-%m-%d')), 'name': order.name + ': ' + (data.get('payment_name', '') or ''), + 'partner_id': order.partner_id and order.partner_id.id or None, } - account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context) - args['account_id'] = (order.partner_id and order.partner_id.property_account_receivable \ - and order.partner_id.property_account_receivable.id) or (account_def and account_def.id) or False - args['partner_id'] = order.partner_id and order.partner_id.id or None - - if not args['account_id']: - if not args['partner_id']: - msg = _('There is no receivable account defined to make payment.') - else: - msg = _('There is no receivable account defined to make payment for the partner: "%s" (id:%d).') % (order.partner_id.name, order.partner_id.id,) - raise osv.except_osv(_('Configuration Error!'), msg) - context.pop('pos_session_id', False) journal_id = data.get('journal', False) @@ -855,7 +838,6 @@ class pos_order(osv.osv): 'statement_id' : statement_id, 'pos_statement_id' : order_id, 'journal_id' : journal_id, - 'type' : 'customer', 'ref' : order.session_id.name, }) diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 6d19d8b4d6d..559fff5d7fc 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -68,13 +68,6 @@ <field name="statement_id"/> <field name="amount"/> </tree> - <form string="Statement lines" version="7.0"> - <group col="4"> - <field name="account_id"/> - <field name="amount"/> - <field name="statement_id" domain="[('company_id','=',parent.company_id),('state','=','open')]"/> - </group> - </form> </field> </page> <page string="Extra Info"> diff --git a/addons/point_of_sale/test/account_statement_reports.yml b/addons/point_of_sale/test/account_statement_reports.yml index 99a11a7b3e8..d93fdaa8b72 100644 --- a/addons/point_of_sale/test/account_statement_reports.yml +++ b/addons/point_of_sale/test/account_statement_reports.yml @@ -1,5 +1,5 @@ - - In order to test Bank Statement feature of account I create a bank statement line and confirm it and check it's move created + In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports - I select the period and journal for the bank statement - @@ -19,30 +19,31 @@ - I create bank statement line - - !python {model: account.bank.statement.line}: | - partner = self.onchange_partner_id(cr, uid, [], ref('base.res_partner_4'), context=None) - vals = { - 'account_id': partner['value']['account_id'], - 'amount': 1000.0, - 'partner_id': ref('base.res_partner_4'), - 'statement_id': ref('account_bank_statement_0'), - 'name': 'EXT001' - } - vals.update(partner.get('value',{})) - line_id = self.create(cr, uid, vals) - assert line_id, "Account bank statement line has not been created" + !record {model: account.bank.statement.line, id: account_bank_statement_line_0}: + amount: 1000.0 + partner_id: base.res_partner_4 + statement_id: account_bank_statement_0 + name: 'EXT001' - I compute bank statement using Compute button - !python {model: account.bank.statement}: | self.button_dummy(cr, uid, [ref("account_bank_statement_0")]) - - I modify the bank statement and set the Closing Balance. - !record {model: account.bank.statement, id: account_bank_statement_0}: balance_end_real: 1000.0 - +- + I reconcile the bank statement. +- + !python {model: account.bank.statement.line}: | + counterpart_line_dict = { + 'account_id': self.pool.get('res.partner').browse(cr, uid, ref('base.res_partner_4')).property_account_receivable.id, + 'name': "EXT001", + 'credit': 1000.0 + } + self.process_reconciliation(cr, uid, ref("account_bank_statement_line_0"), [counterpart_line_dict]) - I confirm the bank statement using Confirm button - |
