diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py index 1b378e15e93..d1726d164b6 100644 --- a/addons/account/__openerp__.py +++ b/addons/account/__openerp__.py @@ -98,6 +98,7 @@ module named account_voucher. 'wizard/account_reconcile_view.xml', 'wizard/account_reconcile_partner_process_view.xml', 'wizard/account_automatic_reconcile_view.xml', + 'wizard/account_financial_report_view.xml', 'project/wizard/project_account_analytic_line_view.xml', 'account_end_fy.xml', 'account_invoice_view.xml', diff --git a/addons/account/account.py b/addons/account/account.py index b19ad71602e..61e1fb021a9 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -431,9 +431,16 @@ class account_account(osv.osv): return False return True + def _check_account_type(self, cr, uid, ids, context=None): + for account in self.browse(cr, uid, ids, context=context): + if account.type in ('receivable', 'payable') and account.user_type.close_method != 'unreconciled': + return False + return True + _constraints = [ (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id']), - (_check_type, 'Configuration Error! \nYou cannot define children to an account with internal type different of "View"! ', ['type']), + (_check_type, 'Configuration Error! \nYou can not define children to an account with internal type different of "View"! ', ['type']), + (_check_account_type, 'Configuration Error! \nYou can not select an account type with a deferral method different of "Unreconciled" for accounts with internal type "Payable/Receivable"! ', ['user_type','type']), ] _sql_constraints = [ ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !') @@ -509,14 +516,14 @@ class account_account(osv.osv): if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]): if method == 'write': - raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.')) + raise osv.except_osv(_('Error !'), _('You can not desactivate an account that contains some journal items.')) elif method == 'unlink': - raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. ')) + raise osv.except_osv(_('Error !'), _('You can not remove an account containing journal items!. ')) #Checking whether the account is set as a property to any Partner or not value = 'account.account,' + str(ids[0]) partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context) if partner_prop_acc: - raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.')) + raise osv.except_osv(_('Warning !'), _('You can not remove/desactivate an account which is set on a customer or supplier.')) return True def _check_allow_type_change(self, cr, uid, ids, new_type, context=None): @@ -529,15 +536,20 @@ class account_account(osv.osv): if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]): #Check for 'Closed' type if old_type == 'closed' and new_type !='closed': - raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!")) + raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains journal items!")) #Check for change From group1 to group2 and vice versa if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1): - raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from '%s' to '%s' type as it contains account entries!") % (old_type,new_type,)) + raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from '%s' to '%s' type as it contains journal items!") % (old_type,new_type,)) return True def write(self, cr, uid, ids, vals, context=None): + if context is None: context = {} + if not ids: + return True + if isinstance(ids, (int, long)): + ids = [ids] # Dont allow changing the company_id when account_move_line already exist if 'company_id' in vals: @@ -650,11 +662,13 @@ class account_journal(osv.osv): def write(self, cr, uid, ids, vals, context=None): if context is None: context = {} + if isinstance(ids, (int, long)): + ids = [ids] for journal in self.browse(cr, uid, ids, context=context): if 'company_id' in vals and journal.company_id.id != vals['company_id']: move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)]) if move_lines: - raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines')) + raise osv.except_osv(_('Warning !'), _('You can not modify the company of this journal as its related record exist in journal items')) return super(account_journal, self).write(cr, uid, ids, vals, context=context) def create_sequence(self, cr, uid, vals, context=None): @@ -800,8 +814,8 @@ class account_fiscalyear(osv.osv): return True _constraints = [ - (_check_duration, 'Error! The duration of the Fiscal Year is invalid. ', ['date_stop']), - (_check_fiscal_year, 'Error! You cannot define overlapping fiscal years',['date_start', 'date_stop']) + (_check_duration, 'Error! The start date of the fiscal year must be before his end date.', ['date_start','date_stop']), + (_check_fiscal_year, 'Error! You can not define overlapping fiscal years for the same company.',['date_start', 'date_stop']) ] def create_period3(self, cr, uid, ids, context=None): @@ -879,7 +893,10 @@ class account_period(osv.osv): 'state': 'draft', } _order = "date_start, special desc" - + _sql_constraints = [ + ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'), + ] + def _check_duration(self,cr,uid,ids,context=None): obj_period = self.browse(cr, uid, ids[0], context=context) if obj_period.date_stop < obj_period.date_start: @@ -952,7 +969,7 @@ class account_period(osv.osv): if 'company_id' in vals: move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)]) if move_lines: - raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines')) + raise osv.except_osv(_('Warning !'), _('You can not modify company of this period as some journal items exists.')) return super(account_period, self).write(cr, uid, ids, vals, context=context) def build_ctx_periods(self, cr, uid, period_from_id, period_to_id): @@ -1180,10 +1197,10 @@ class account_move(osv.osv): _constraints = [ (_check_centralisation, - 'You cannot create more than one move per period on centralized journal', + 'You can not create more than one move per period on centralized journal', ['journal_id']), (_check_period_journal, - 'You cannot create entries on different periods/journals in the same move', + 'You can not create journal items on different periods/journals in the same journal entry', ['line_id']), ] @@ -1194,7 +1211,7 @@ class account_move(osv.osv): valid_moves = self.validate(cr, uid, ids, context) if not valid_moves: - raise osv.except_osv(_('Integrity Error !'), _('You cannot validate a non-balanced entry !\nMake sure you have configured Payment Term properly !\nIt should contain atleast one Payment Term Line with type "Balance" !')) + raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non-balanced entry !\nMake sure you have configured payment terms properly !\nThe latest payment term line should be of the type "Balance" !')) obj_sequence = self.pool.get('ir.sequence') for move in self.browse(cr, uid, valid_moves, context=context): if move.name =='/': @@ -1230,7 +1247,7 @@ class account_move(osv.osv): if not top: top = account2.id elif top<>account2.id: - raise osv.except_osv(_('Error !'), _('You cannot validate a Journal Entry unless all journal items are in same chart of accounts !')) + raise osv.except_osv(_('Error !'), _('You can not validate a journal entry unless all journal items belongs to the same chart of accounts !')) return self.post(cursor, user, ids, context=context) def button_cancel(self, cr, uid, ids, context=None): @@ -1317,7 +1334,7 @@ class account_move(osv.osv): for move in self.browse(cr, uid, ids, context=context): if move['state'] != 'draft': raise osv.except_osv(_('UserError'), - _('You can not delete posted movement: "%s"!') % \ + _('You can not delete a posted journal entry "%s"!') % \ move['name']) line_ids = map(lambda x: x.id, move.line_id) context['journal_id'] = move.journal_id.id @@ -1337,7 +1354,7 @@ class account_move(osv.osv): def _centralise(self, cr, uid, move, mode, context=None): assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection - currency_obj = self.pool.get('res.currency') + currency_obj = self.pool.get('res.currency') if context is None: context = {} @@ -1599,7 +1616,7 @@ class account_tax_code(osv.osv): (parent_ids,) + where_params) res=dict(cr.fetchall()) obj_precision = self.pool.get('decimal.precision') - res2 = {} + res2 = {} for record in self.browse(cr, uid, ids, context=context): def _rec_get(record): amount = res.get(record.id, 0.0) @@ -1768,6 +1785,9 @@ class account_tax(osv.osv): 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True) } + _sql_constraints = [ + ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'), + ] def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80): """ @@ -2126,7 +2146,11 @@ class account_model(osv.osv): period_id = period_id[0] for model in self.browse(cr, uid, ids, context=context): - entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')} + try: + entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')} + except: + raise osv.except_osv(_('Wrong model !'), _('You have a wrong expression "%(...)s" in your model !')) + move_id = account_move_obj.create(cr, uid, { 'ref': entry['name'], 'period_id': period_id, @@ -2198,8 +2222,8 @@ class account_model_line(osv.osv): } _order = 'sequence' _sql_constraints = [ - ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'), - ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'), + ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model, they must be positive!'), + ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'), ] account_model_line() @@ -2365,7 +2389,7 @@ class account_account_template(osv.osv): _check_recursion = check_cycle _constraints = [ (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id']), - (_check_type, 'Configuration Error! \nYou cannot define children to an account with internal type different of "View"! ', ['type']), + (_check_type, 'Configuration Error!\nYou can not define children to an account with internal type different of "View"! ', ['type']), ] @@ -2398,7 +2422,7 @@ class account_add_tmpl_wizard(osv.osv_memory): ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code']) res = None if not ptids or not ptids[0]['code']: - raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!')) + raise osv.except_osv(_('Error !'), _('I can not locate a parent code for the template account!')) res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])]) return res and res[0] or False @@ -2613,6 +2637,79 @@ class account_fiscal_position_account_template(osv.osv): account_fiscal_position_account_template() +class account_financial_report(osv.osv): + _name = "account.financial.report" + _description = "Account Report" + + def _get_level(self, cr, uid, ids, field_name, arg, context=None): + res = {} + for report in self.browse(cr, uid, ids, context=context): + level = 0 + if report.parent_id: + level = report.parent_id.level + 1 + res[report.id] = level + return res + + def _get_children_by_order(self, cr, uid, ids, context=None): + res = [] + for id in ids: + res.append(id) + ids2 = self.search(cr, uid, [('parent_id', '=', id)], order='sequence ASC', context=context) + res += self._get_children_by_order(cr, uid, ids2, context=context) + return res + + def _get_balance(self, cr, uid, ids, name, args, context=None): + res = {} + res_all = {} + for report in self.browse(cr, uid, ids, context=context): + balance = 0.0 + if report.id in res_all: + balance = res_all[report.id] + elif report.type == 'accounts': + # it's the sum of balance of the linked accounts + for a in report.account_ids: + balance += a.balance + elif report.type == 'account_report' and report.account_report_id: + # it's the amount of the linked report + res2 = self._get_balance(cr, uid, [report.account_report_id.id], 'balance', False, context=context) + res_all.update(res2) + for key, value in res2.items(): + balance += value + elif report.type == 'sum': + # it's the sum of balance of the children of this account.report + #for child in report.children_ids: + res2 = self._get_balance(cr, uid, [rec.id for rec in report.children_ids], 'balance', False, context=context) + res_all.update(res2) + for key, value in res2.items(): + balance += value + res[report.id] = balance + res_all[report.id] = balance + return res + + _columns = { + 'name': fields.char('Report Name', size=128, required=True), + 'parent_id': fields.many2one('account.financial.report', 'Parent'), + 'children_ids': fields.one2many('account.financial.report', 'parent_id', 'Account Report'), + 'sequence': fields.integer('Sequence'), + 'type': fields.selection([ + ('sum','Sum'), + ('accounts','Accounts'), + ('account_report','Account Report'), + ],'Type'), + 'account_ids': fields.many2many('account.account', 'account_account_financial_report', 'report_line_id', 'account_id', 'Accounts'), + 'note': fields.text('Notes'), + 'account_report_id': fields.many2one('account.financial.report', 'Account Report'), + 'balance': fields.function(_get_balance, 'Balance'), + 'display_detail': fields.boolean('Display the account list'), + 'level': fields.function(_get_level, string='Level', store=True, type='integer'), + } + + _defaults = { + 'type': 'sum', + } + +account_financial_report() + # Multi charts of Accounts wizard class wizard_multi_charts_accounts(osv.osv_memory): @@ -2706,7 +2803,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): unconfigured_cmp = list(set(company_ids)-set(configured_cmp)) for field in res['fields']: if field == 'company_id': - res['fields'][field]['domain'] = unconfigured_cmp + res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)] res['fields'][field]['selection'] = [('', '')] if unconfigured_cmp: cmp_select = [(line.id, line.name) for line in self.pool.get('res.company').browse(cr, uid, unconfigured_cmp)] diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index f712cc9ab0c..277e2aa04b1 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -58,13 +58,6 @@ class account_bank_statement(osv.osv): journal_id = ids[0] return journal_id - def _default_balance_start(self, cr, uid, context=None): - cr.execute('select id from account_bank_statement where journal_id=%s order by date desc limit 1', (1,)) - res = cr.fetchone() - if res: - return self.browse(cr, uid, res[0], context=context).balance_end - return 0.0 - def _end_balance(self, cursor, user, ids, name, attr, context=None): res_currency_obj = self.pool.get('res.currency') res_users_obj = self.pool.get('res.users') @@ -90,7 +83,8 @@ class account_bank_statement(osv.osv): res[statement.id] -= res_currency_obj.compute(cursor, user, company_currency_id, currency_id, line.credit, context=context) - if statement.state == 'draft': + + if statement.state in ('draft', 'open'): for line in statement.line_ids: res[statement.id] += line.amount for r in res: @@ -127,7 +121,7 @@ class account_bank_statement(osv.osv): _name = "account.bank.statement" _description = "Bank Statement" _columns = { - 'name': fields.char('Name', size=64, required=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', states={'confirm': [('readonly', True)]}), + 'name': fields.char('Name', 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 'date': fields.date('Date', required=True, states={'confirm': [('readonly', True)]}), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}), @@ -136,19 +130,21 @@ class account_bank_statement(osv.osv): 'balance_start': fields.float('Starting Balance', digits_compute=dp.get_precision('Account'), states={'confirm':[('readonly',True)]}), 'balance_end_real': fields.float('Ending Balance', digits_compute=dp.get_precision('Account'), - states={'confirm':[('readonly', True)]}), - 'balance_end': fields.function(_end_balance, string='Balance'), + states={'confirm': [('readonly', True)]}), + 'balance_end': fields.function(_end_balance, store=True, # store=True for account_cash_statement + string="Balance", help='Balance as calculated based on Starting Balance and transaction lines'), 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True), 'line_ids': fields.one2many('account.bank.statement.line', 'statement_id', 'Statement lines', states={'confirm':[('readonly', True)]}), 'move_line_ids': fields.one2many('account.move.line', 'statement_id', 'Entry lines', states={'confirm':[('readonly',True)]}), - 'state': fields.selection([('draft', 'Draft'),('confirm', 'Confirmed')], - 'State', required=True, - states={'confirm': [('readonly', True)]}, readonly="1", - help='When new statement is created the state will be \'Draft\'. \ - \n* And after getting confirmation from the bank it will be in \'Confirmed\' state.'), + 'state': fields.selection([('draft', 'Draft'), + ('open','Open'), # used by cash statements + ('confirm', 'Closed')], + 'State', required=True, readonly="1", + help='When new statement is created the state will be \'Draft\'.\n' + 'And after getting confirmation from the bank it will be in \'Confirmed\' state.'), 'currency': fields.function(_currency, string='Currency', 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.'), @@ -158,7 +154,6 @@ class account_bank_statement(osv.osv): 'name': "/", 'date': lambda *a: time.strftime('%Y-%m-%d'), 'state': 'draft', - 'balance_start': _default_balance_start, 'journal_id': _default_journal_id, 'period_id': _get_period, 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.bank.statement',context=c), @@ -386,7 +381,7 @@ class account_bank_statement(osv.osv): if t['state'] in ('draft'): unlink_ids.append(t['id']) else: - raise osv.except_osv(_('Invalid action !'), _('Cannot delete bank statement(s) which are already confirmed !')) + raise osv.except_osv(_('Invalid action !'), _('In order to delete a bank statement, you must first cancel it to delete related journal items.')) osv.osv.unlink(self, cr, uid, unlink_ids, context=context) return True diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index 982383ea2a7..3957e75c860 100644 --- a/addons/account/account_cash_statement.py +++ b/addons/account/account_cash_statement.py @@ -119,39 +119,6 @@ class account_cash_statement(osv.osv): res2[statement.id] = encoding_total return res2 - def _end_balance(self, cursor, user, ids, name, attr, context=None): - res_currency_obj = self.pool.get('res.currency') - res_users_obj = self.pool.get('res.users') - res = {} - - company_currency_id = res_users_obj.browse(cursor, user, user, - context=context).company_id.currency_id.id - - statements = self.browse(cursor, user, ids, context=context) - for statement in statements: - res[statement.id] = statement.balance_start - currency_id = statement.currency.id - for line in statement.move_line_ids: - if line.debit > 0: - if line.account_id.id == \ - statement.journal_id.default_debit_account_id.id: - res[statement.id] += res_currency_obj.compute(cursor, - user, company_currency_id, currency_id, - line.debit, context=context) - else: - if line.account_id.id == \ - statement.journal_id.default_credit_account_id.id: - res[statement.id] -= res_currency_obj.compute(cursor, - user, company_currency_id, currency_id, - line.credit, context=context) - - if statement.state in ('draft', 'open'): - for line in statement.line_ids: - res[statement.id] += line.amount - for r in res: - res[r] = round(res[r], 2) - return res - def _get_company(self, cr, uid, context=None): user_pool = self.pool.get('res.users') company_pool = self.pool.get('res.company') @@ -218,18 +185,11 @@ class account_cash_statement(osv.osv): return res _columns = { - 'balance_end_real': fields.float('Closing Balance', digits_compute=dp.get_precision('Account'), states={'confirm': [('readonly', True)]}, help="closing balance entered by the cashbox verifier"), - 'state': fields.selection( - [('draft', 'Draft'), - ('confirm', 'Closed'), - ('open','Open')], 'State', required=True, states={'confirm': [('readonly', True)]}, readonly="1"), 'total_entry_encoding': fields.function(_get_sum_entry_encoding, store=True, string="Cash Transaction", help="Total cash transactions"), 'closing_date': fields.datetime("Closed On"), - 'balance_end': fields.function(_end_balance, store=True, string='Balance', help="Closing balance based on Starting Balance and Cash Transactions"), 'balance_end_cash': fields.function(_balance_end_cash, store=True, string='Balance', help="Closing balance based on cashBox"), 'starting_details_ids': fields.one2many('account.cashbox.line', 'starting_id', string='Opening Cashbox'), 'ending_details_ids': fields.one2many('account.cashbox.line', 'ending_id', string='Closing Cashbox'), - 'name': fields.char('Name', 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'), 'user_id': fields.many2one('res.users', 'Responsible', required=False), } _defaults = { @@ -247,7 +207,7 @@ class account_cash_statement(osv.osv): ] open_jrnl = self.search(cr, uid, sql) if open_jrnl: - raise osv.except_osv(_('Error'), _('You can not have two open register for the same journal')) + raise osv.except_osv(_('Error'), _('You can not have two open register for the same journal!')) if self.pool.get('account.journal').browse(cr, uid, vals['journal_id'], context=context).type == 'cash': open_close = self._get_cash_open_close_box_lines(cr, uid, context) diff --git a/addons/account/account_installer.xml b/addons/account/account_installer.xml index 151f7bbc0f0..374304135df 100644 --- a/addons/account/account_installer.xml +++ b/addons/account/account_installer.xml @@ -23,7 +23,7 @@ 23 - + diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 545bfd41f10..97852a11b5f 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -294,7 +294,7 @@ -