X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Faccount%2Faccount_move_line.py;h=0f3e1322aaff17a2e69ea91d9365d4078cc294eb;hb=f4e350ca36668d13c9eb09fbf77c85df110ce40c;hp=cb242942656913e829bf5747f0d8df714ec4144f;hpb=20be322c4571aae52863c32503c9e03da972e4b3;p=odoo%2Fodoo.git diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index cb24294..0f3e132 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -19,18 +19,16 @@ # ############################################################################## -import sys import time from datetime import datetime -from operator import itemgetter - -from lxml import etree from openerp import workflow -from openerp.osv import fields, osv, orm +from openerp.osv import fields, osv from openerp.tools.translate import _ import openerp.addons.decimal_precision as dp from openerp import tools +from openerp.report import report_sxw +import openerp class account_move_line(osv.osv): _name = "account.move.line" @@ -41,8 +39,7 @@ class account_move_line(osv.osv): fiscalperiod_obj = self.pool.get('account.period') account_obj = self.pool.get('account.account') fiscalyear_ids = [] - if context is None: - context = {} + context = dict(context or {}) initial_bal = context.get('initial_bal', False) company_clause = " " if context.get('company_id', False): @@ -127,8 +124,8 @@ class account_move_line(osv.osv): if move_line.reconcile_id: continue - if not move_line.account_id.type in ('payable', 'receivable'): - #this function does not suport to be used on move lines not related to payable or receivable accounts + if not move_line.account_id.reconcile: + #this function does not suport to be used on move lines not related to a reconcilable account continue if move_line.currency_id: @@ -216,17 +213,14 @@ class account_move_line(osv.osv): period_id = context.get('period_id') if type(period_id) == str: ids = period_obj.search(cr, uid, [('name', 'ilike', period_id)]) - context.update({ - 'period_id': ids and ids[0] or False - }) + context = dict(context, period_id=ids and ids[0] or False) return context def _default_get(self, cr, uid, fields, context=None): #default_get should only do the following: # -propose the next amount in debit/credit in order to balance the move # -propose the next account from the journal (default debit/credit account) accordingly - if context is None: - context = {} + context = dict(context or {}) account_obj = self.pool.get('account.account') period_obj = self.pool.get('account.period') journal_obj = self.pool.get('account.journal') @@ -431,8 +425,21 @@ class account_move_line(osv.osv): 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): + for line in r.line_partial_ids: + move[line.move_id.id] = True + for line in r.line_id: + move[line.move_id.id] = True + move_line_ids = [] + if move: + move_line_ids = self.pool.get('account.move.line').search(cr, uid, [('journal_id','in',move.keys())], context=context) + return move_line_ids + + _columns = { - 'name': fields.char('Name', size=64, required=True), + 'name': fields.char('Name', required=True), 'quantity': fields.float('Quantity', digits=(16,2), help="The optional quantity expressed by this line, eg: number of product sold. The quantity is not a legal requirement but is very useful for some reports."), 'product_uom_id': fields.many2one('product.uom', 'Unit of Measure'), 'product_id': fields.many2one('product.product', 'Product'), @@ -441,11 +448,12 @@ class account_move_line(osv.osv): 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade", domain=[('type','<>','view'), ('type', '<>', 'closed')], select=2), 'move_id': fields.many2one('account.move', 'Journal Entry', ondelete="cascade", help="The move of this entry line.", select=2, required=True), 'narration': fields.related('move_id','narration', type='text', relation='account.move', string='Internal Note'), - 'ref': fields.related('move_id', 'ref', string='Reference', type='char', size=64, store=True), - 'statement_id': fields.many2one('account.bank.statement', 'Statement', help="The bank statement used for bank reconciliation", select=1), - 'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', readonly=True, ondelete='set null', select=2), - 'reconcile_partial_id': fields.many2one('account.move.reconcile', 'Partial Reconcile', readonly=True, ondelete='set null', select=2), - 'reconcile': fields.function(_get_reconcile, type='char', string='Reconcile Ref'), + 'ref': fields.related('move_id', 'ref', string='Reference', type='char', store=True), + 'statement_id': fields.many2one('account.bank.statement', 'Statement', help="The bank statement used for bank reconciliation", select=1, copy=False), + 'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', readonly=True, ondelete='set null', select=2, copy=False), + 'reconcile_partial_id': fields.many2one('account.move.reconcile', 'Partial Reconcile', readonly=True, ondelete='set null', select=2, copy=False), + 'reconcile_ref': fields.function(_get_reconcile, type='char', string='Reconcile Ref', oldname='reconcile', store={ + 'account.move.line': (lambda self, cr, uid, ids, c={}: ids, ['reconcile_id','reconcile_partial_id'], 50),'account.move.reconcile': (_get_move_from_reconcile, None, 50)}), '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')), 'amount_residual_currency': fields.function(_amount_residual, string='Residual Amount in Currency', multi="residual", help="The residual amount on a receivable or payable of a journal entry expressed in its currency (maybe different of the company currency)."), 'amount_residual': fields.function(_amount_residual, string='Residual Amount', multi="residual", help="The residual amount on a receivable or payable of a journal entry expressed in the company currency."), @@ -469,15 +477,15 @@ class account_move_line(osv.osv): 'analytic_lines': fields.one2many('account.analytic.line', 'move_id', 'Analytic lines'), 'centralisation': fields.selection([('normal','Normal'),('credit','Credit Centralisation'),('debit','Debit Centralisation'),('currency','Currency Adjustment')], 'Centralisation', size=8), 'balance': fields.function(_balance, fnct_search=_balance_search, string='Balance'), - 'state': fields.selection([('draft','Unbalanced'), ('valid','Balanced')], 'Status', readonly=True), + 'state': fields.selection([('draft','Unbalanced'), ('valid','Balanced')], 'Status', readonly=True, copy=False), 'tax_code_id': fields.many2one('account.tax.code', 'Tax Account', help="The Account can either be a base tax code or a tax code account."), 'tax_amount': fields.float('Tax/Base Amount', digits_compute=dp.get_precision('Account'), select=True, help="If the Tax account is a tax code account, this field will contain the taxed amount.If the tax account is base tax code, "\ "this field will contain the basic amount(without tax)."), 'invoice': fields.function(_invoice, string='Invoice', type='many2one', relation='account.invoice', fnct_search=_invoice_search), - 'account_tax_id':fields.many2one('account.tax', 'Tax'), + 'account_tax_id':fields.many2one('account.tax', 'Tax', copy=False), '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) } @@ -534,7 +542,9 @@ class account_move_line(osv.osv): if context.get('journal_type', False): jids = journal_pool.search(cr, uid, [('type','=', context.get('journal_type'))]) if not jids: - raise osv.except_osv(_('Configuration Error!'), _('Cannot find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration/Journals/Journals.') % context.get('journal_type')) + model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_journal_form') + msg = _("""Cannot find any account journal of "%s" type for this company, You should create one.\n Please go to Journal Configuration""") % context.get('journal_type').replace('_', ' ').title() + raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel')) journal_id = jids[0] return journal_id @@ -565,12 +575,15 @@ class account_move_line(osv.osv): cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'account_move_line_journal_id_period_id_index\'') if not cr.fetchone(): cr.execute('CREATE INDEX account_move_line_journal_id_period_id_index ON account_move_line (journal_id, period_id)') + cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('account_move_line_date_id_index',)) + if not cr.fetchone(): + cr.execute('CREATE INDEX account_move_line_date_id_index ON account_move_line (date DESC, id desc)') return res def _check_no_view(self, cr, uid, ids, context=None): lines = self.browse(cr, uid, ids, context=context) for l in lines: - if l.account_id.type == 'view': + if l.account_id.type in ('view', 'consolidation'): return False return True @@ -622,13 +635,13 @@ class account_move_line(osv.osv): return True _constraints = [ - (_check_no_view, 'You cannot create journal items on an account of type view.', ['account_id']), + (_check_no_view, 'You cannot create journal items on an account of type view or consolidation.', ['account_id']), (_check_no_closed, 'You cannot create journal items on closed account.', ['account_id']), (_check_company_id, 'Account and Period must belong to the same company.', ['company_id']), (_check_date, 'The date of your Journal Entry is not in the defined period! You should change the date or remove this constraint from the journal.', ['date']), (_check_currency, 'The selected account of your Journal Entry forces to provide a secondary currency. You should remove the secondary currency on the account or select a multi-currency view on the journal.', ['currency_id']), (_check_currency_and_amount, "You cannot create journal items with a secondary currency without recording both 'currency' and 'amount currency' field.", ['currency_id','amount_currency']), - (_check_currency_amount, 'The amount expressed in the secondary currency must be positive when the journal item is a debit and negative when if it is a credit.', ['amount_currency']), + (_check_currency_amount, 'The amount expressed in the secondary currency must be positive when account is debited and negative when account is credited.', ['amount_currency']), (_check_currency_company, "You cannot provide a secondary currency if it is the same than the company one." , ['currency_id']), ] @@ -646,6 +659,7 @@ class account_move_line(osv.osv): if (amount>0) and journal: x = journal_obj.browse(cr, uid, journal).default_credit_account_id if x: acc = x + context = dict(context) context.update({ 'date': date, 'res.currency.compute.account': acc, @@ -724,6 +738,8 @@ class account_move_line(osv.osv): def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): if context is None: context = {} + if context.get('fiscalyear'): + args.append(('period_id.fiscalyear_id', '=', context.get('fiscalyear', False))) if context and context.get('next_partner_only', False): if not context.get('partner_id', False): partner = self.list_partners_to_reconcile(cr, uid, context=context) @@ -736,6 +752,74 @@ class account_move_line(osv.osv): args.append(('partner_id', '=', partner[0])) return super(account_move_line, self).search(cr, uid, args, offset, limit, order, context, count) + def prepare_move_lines_for_reconciliation_widget(self, cr, uid, lines, target_currency=False, target_date=False, context=None): + """ Returns move lines formatted for the manual/bank reconciliation widget + + :param target_currency: curreny you want the move line debit/credit converted into + :param target_date: date to use for the monetary conversion + """ + if not lines: + return [] + if context is None: + context = {} + ctx = context.copy() + currency_obj = self.pool.get('res.currency') + company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id + rml_parser = report_sxw.rml_parse(cr, uid, 'reconciliation_widget_aml', context=context) + reconcile_partial_ids = [] # for a partial reconciliation, take only one line + ret = [] + + for line in lines: + if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: + continue + if line.reconcile_partial_id: + reconcile_partial_ids.append(line.reconcile_partial_id.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, + 'partner_id': line.partner_id.id, + 'partner_name': line.partner_id.name, + } + + # Get right debit / credit: + line_currency = line.currency_id or company_currency + 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) + if target_currency and line_currency == target_currency and target_currency != company_currency: + debit = line.debit > 0 and line.amount_residual_currency or 0.0 + credit = line.credit > 0 and line.amount_residual_currency or 0.0 + amount_currency_str = rml_parser.formatLang(line.amount_residual, currency_obj=company_currency) + amount_str = rml_parser.formatLang(debit or credit, currency_obj=target_currency) + else: + debit = line.debit > 0 and line.amount_residual or 0.0 + credit = line.credit > 0 and line.amount_residual or 0.0 + amount_str = rml_parser.formatLang(debit or credit, currency_obj=company_currency) + if target_currency and target_currency != company_currency: + amount_currency_str = rml_parser.formatLang(debit or credit, currency_obj=line_currency) + ctx = context.copy() + if target_date: + ctx.update({'date': target_date}) + debit = currency_obj.compute(cr, uid, target_currency.id, company_currency.id, debit, context=ctx) + credit = currency_obj.compute(cr, uid, target_currency.id, company_currency.id, credit, context=ctx) + amount_str = rml_parser.formatLang(debit or credit, currency_obj=target_currency) + + ret_line['credit'] = credit + ret_line['debit'] = debit + ret_line['amount_str'] = amount_str + ret_line['amount_currency_str'] = amount_currency_str + ret.append(ret_line) + return ret + def list_partners_to_reconcile(self, cr, uid, context=None): cr.execute( """SELECT partner_id FROM ( @@ -751,7 +835,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 @@ -779,9 +863,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) @@ -799,12 +885,15 @@ class account_move_line(osv.osv): if self.pool.get('res.currency').is_zero(cr, uid, currency_id, total): res = self.reconcile(cr, uid, merges+unmerge, context=context, writeoff_acc_id=writeoff_acc_id, writeoff_period_id=writeoff_period_id, writeoff_journal_id=writeoff_journal_id) return res + # marking the lines as reconciled does not change their validity, so there is no need + # to revalidate their moves completely. + reconcile_context = dict(context, novalidate=True) r_id = move_rec_obj.create(cr, uid, { 'type': type, 'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge) - }, context=context) - move_rec_obj.reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context) - return True + }, context=reconcile_context) + move_rec_obj.reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=reconcile_context) + return r_id def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context=None): account_obj = self.pool.get('account.account') @@ -849,18 +938,17 @@ class account_move_line(osv.osv): (tuple(ids), )) r = cr.fetchall() #TODO: move this check to a constraint in the account_move_reconcile object + if len(r) != 1: + raise osv.except_osv(_('Error'), _('Entries are not of the same account or already reconciled ! ')) if not unrec_lines: raise osv.except_osv(_('Error!'), _('Entry is already reconciled.')) account = account_obj.browse(cr, uid, account_id, context=context) + if not account.reconcile: + raise osv.except_osv(_('Error'), _('The account is not defined to be reconciled !')) if r[0][1] != None: raise osv.except_osv(_('Error!'), _('Some entries are already reconciled.')) - if context.get('fy_closing'): - # We don't want to generate any write-off when being called from the - # wizard used to close a fiscal year (and it doesn't give us any - # writeoff_acc_id). - pass - elif (not currency_obj.is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \ + if (not currency_obj.is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \ (account.currency_id and (not currency_obj.is_zero(cr, uid, account.currency_id, currency))): if not writeoff_acc_id: raise osv.except_osv(_('Warning!'), _('You have to provide an account for the write off/exchange difference entry.')) @@ -929,11 +1017,14 @@ class account_move_line(osv.osv): writeoff_line_ids = [writeoff_line_ids[1]] ids += writeoff_line_ids + # marking the lines as reconciled does not change their validity, so there is no need + # to revalidate their moves completely. + reconcile_context = dict(context, novalidate=True) r_id = move_rec_obj.create(cr, uid, { 'type': type, 'line_id': map(lambda x: (4, x, False), ids), 'line_partial_ids': map(lambda x: (3, x, False), ids) - }) + }, context=reconcile_context) # the id of the move.reconcile is written in the move.line (self) by the create method above # because of the way the line_id are defined: (4, x, False) for id in ids: @@ -983,12 +1074,8 @@ class account_move_line(osv.osv): period_pool = self.pool.get('account.period') pids = period_pool.find(cr, user, date, context=context) if pids: - res.update({ - 'period_id':pids[0] - }) - context.update({ - 'period_id':pids[0] - }) + res.update({'period_id':pids[0]}) + context = dict(context, period_id=pids[0]) return { 'value':res, 'context':context, @@ -1021,10 +1108,16 @@ class account_move_line(osv.osv): part_rec_ids = [rec['reconcile_partial_id'][0] for rec in part_recs] unlink_ids += rec_ids unlink_ids += part_rec_ids + all_moves = obj_move_line.search(cr, uid, ['|',('reconcile_id', 'in', unlink_ids),('reconcile_partial_id', 'in', unlink_ids)]) + all_moves = list(set(all_moves) - set(move_ids)) if unlink_ids: if opening_reconciliation: + raise osv.except_osv(_('Warning!'), + _('Opening Entries have already been generated. Please run "Cancel Closing Entries" wizard to cancel those entries and then run this wizard.')) obj_move_rec.write(cr, uid, unlink_ids, {'opening_reconciliation': False}) obj_move_rec.unlink(cr, uid, unlink_ids) + if len(all_moves) >= 2: + obj_move_line.reconcile_partial(cr, uid, all_moves, 'auto',context=context) return True def unlink(self, cr, uid, ids, context=None, check=True): @@ -1102,7 +1195,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 ''), @@ -1131,15 +1224,14 @@ class account_move_line(osv.osv): move_obj = self.pool.get('account.move') cur_obj = self.pool.get('res.currency') journal_obj = self.pool.get('account.journal') - if context is None: - context = {} + context = dict(context or {}) if vals.get('move_id', False): move = self.pool.get('account.move').browse(cr, uid, vals['move_id'], context=context) if move.company_id: vals['company_id'] = move.company_id.id if move.date and not vals.get('date'): vals['date'] = move.date - if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']: + if ('account_id' in vals) and not account_obj.read(cr, uid, [vals['account_id']], ['active'])[0]['active']: raise osv.except_osv(_('Bad Account!'), _('You cannot use an inactive account.')) if 'journal_id' in vals and vals['journal_id']: context['journal_id'] = vals['journal_id'] @@ -1199,7 +1291,7 @@ class account_move_line(osv.osv): break # Automatically convert in the account's secondary currency if there is one and # the provided values were not already multi-currency - if account.currency_id and (vals.get('amount_currency', False) is False) and account.currency_id.id != account.company_id.currency_id.id: + if account.currency_id and 'amount_currency' not in vals and account.currency_id.id != account.company_id.currency_id.id: vals['currency_id'] = account.currency_id.id ctx = {} if 'date' in vals: @@ -1214,35 +1306,43 @@ class account_move_line(osv.osv): if vals.get('account_tax_id', False): tax_id = tax_obj.browse(cr, uid, vals['account_tax_id']) total = vals['debit'] - vals['credit'] - if journal.type in ('purchase_refund', 'sale_refund'): + base_code = 'base_code_id' + tax_code = 'tax_code_id' + account_id = 'account_collected_id' + base_sign = 'base_sign' + tax_sign = 'tax_sign' + if journal.type in ('purchase_refund', 'sale_refund') or (journal.type in ('cash', 'bank') and total < 0): base_code = 'ref_base_code_id' tax_code = 'ref_tax_code_id' account_id = 'account_paid_id' base_sign = 'ref_base_sign' tax_sign = 'ref_tax_sign' - else: - base_code = 'base_code_id' - tax_code = 'tax_code_id' - account_id = 'account_collected_id' - base_sign = 'base_sign' - tax_sign = 'tax_sign' tmp_cnt = 0 - for tax in tax_obj.compute_all(cr, uid, [tax_id], total, 1.00, force_excluded=True).get('taxes'): + for tax in tax_obj.compute_all(cr, uid, [tax_id], total, 1.00, force_excluded=False).get('taxes'): #create the base movement if tmp_cnt == 0: if tax[base_code]: tmp_cnt += 1 - self.write(cr, uid,[result], { + if tax_id.price_include: + total = tax['price_unit'] + newvals = { 'tax_code_id': tax[base_code], - 'tax_amount': tax[base_sign] * abs(total) - }) + 'tax_amount': tax[base_sign] * abs(total), + } + if tax_id.price_include: + if tax['price_unit'] < 0: + newvals['credit'] = abs(tax['price_unit']) + else: + newvals['debit'] = tax['price_unit'] + self.write(cr, uid, [result], newvals, context=context) else: data = { 'move_id': vals['move_id'], 'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''), 'date': vals['date'], - 'partner_id': vals.get('partner_id',False), - 'ref': vals.get('ref',False), + 'partner_id': vals.get('partner_id', False), + 'ref': vals.get('ref', False), + 'statement_id': vals.get('statement_id', False), 'account_tax_id': False, 'tax_code_id': tax[base_code], 'tax_amount': tax[base_sign] * abs(total), @@ -1259,6 +1359,7 @@ class account_move_line(osv.osv): 'date': vals['date'], 'partner_id': vals.get('partner_id',False), 'ref': vals.get('ref',False), + 'statement_id': vals.get('statement_id', False), 'account_tax_id': False, 'tax_code_id': tax[tax_code], 'tax_amount': tax[tax_sign] * abs(tax['amount']), @@ -1270,7 +1371,7 @@ class account_move_line(osv.osv): self.create(cr, uid, data, context) del vals['account_tax_id'] - if check and not context.get('novalidate') and ((not context.get('no_store_function')) or journal.entry_posted): + if check and not context.get('novalidate') and (context.get('recompute', True) or journal.entry_posted): tmp = move_obj.validate(cr, uid, [vals['move_id']], context) if journal.entry_posted and tmp: move_obj.button_validate(cr,uid, [vals['move_id']], context)