diff --git a/addons/account/__init__.py b/addons/account/__init__.py index 52c65f0c3da..a243b94c2da 100644 --- a/addons/account/__init__.py +++ b/addons/account/__init__.py @@ -23,7 +23,7 @@ import account import installer import project import partner -import invoice +import account_invoice import account_bank_statement import account_bank import account_cash_statement @@ -32,8 +32,8 @@ import account_analytic_line import wizard import report import product -import sequence +import ir_sequence import company import res_currency - +import edi # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py index a0ff63ba0d3..e9cd0ed2254 100644 --- a/addons/account/__openerp__.py +++ b/addons/account/__openerp__.py @@ -22,7 +22,7 @@ "name" : "Accounting and Financial Management", "version" : "1.1", "author" : "OpenERP SA", - "category": 'Finance', + "category": 'Accounting & Finance', 'complexity': "normal", "description": """ Accounting and Financial Management. @@ -53,7 +53,7 @@ module named account_voucher. 'website': 'http://www.openerp.com', 'images' : ['images/accounts.jpeg','images/bank_statement.jpeg','images/cash_register.jpeg','images/chart_of_accounts.jpeg','images/customer_invoice.jpeg','images/journal_entries.jpeg'], 'init_xml': [], - "depends" : ["base_setup", "product", "analytic", "process","board"], + "depends" : ["base_setup", "product", "analytic", "process", "board", "edi"], 'update_xml': [ 'security/account_security.xml', 'security/ir.model.access.csv', @@ -99,12 +99,12 @@ module named account_voucher. 'wizard/account_reconcile_partner_process_view.xml', 'wizard/account_automatic_reconcile_view.xml', 'wizard/account_tax_generate_view.xml', + 'wizard/account_financial_report_view.xml', 'project/wizard/project_account_analytic_line_view.xml', 'account_end_fy.xml', 'account_invoice_view.xml', 'partner_view.xml', - 'data/account_invoice.xml', - 'data/account_data2.xml', + 'data/account_data.xml', 'account_invoice_workflow.xml', 'project/project_view.xml', 'project/project_report.xml', @@ -119,12 +119,14 @@ module named account_voucher. 'process/statement_process.xml', 'process/customer_invoice_process.xml', 'process/supplier_invoice_process.xml', - 'sequence_view.xml', + 'ir_sequence_view.xml', 'company_view.xml', 'board_account_view.xml', "wizard/account_report_profit_loss_view.xml", "wizard/account_report_balance_sheet_view.xml", - "account_bank_view.xml" + "edi/invoice_action_data.xml", + "account_bank_view.xml", + "account_pre_install.yml" ], 'demo_xml': [ 'demo/account_demo.xml', @@ -146,10 +148,9 @@ module named account_voucher. 'test/account_fiscalyear_close.yml', 'test/account_bank_statement.yml', 'test/account_cash_statement.yml', + 'test/test_edi_invoice.yml', 'test/account_report.yml', - - - ], + ], 'installable': True, 'active': False, 'certificate': '0080331923549', diff --git a/addons/account/account.py b/addons/account/account.py index cf290f2ad36..a347f5bcce2 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -102,7 +102,7 @@ class account_payment_term_line(osv.osv): ('fixed', 'Fixed Amount')], 'Valuation', required=True, help="""Select here the kind of valuation related to this payment term line. Note that you should have your last line with the type 'Balance' to ensure that the whole amount will be threated."""), - 'value_amount': fields.float('Value Amount', digits_compute=dp.get_precision('Payment Term'), help="For Value percent enter % ratio between 0-1."), + 'value_amount': fields.float('Amount To Pay', digits_compute=dp.get_precision('Payment Term'), help="For percent enter a ratio between 0-1."), 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \ "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."), 'days2': fields.integer('Day of the Month', required=True, help="Day of the month, set -1 for the last day of the current month. If it's positive, it gives the day of the next month. Set 0 for net days (otherwise it's based on the beginning of the month)."), @@ -131,7 +131,7 @@ class account_account_type(osv.osv): _name = "account.account.type" _description = "Account Type" _columns = { - 'name': fields.char('Acc. Type Name', size=64, required=True), + 'name': fields.char('Account Type', size=64, required=True), 'code': fields.char('Code', size=32, required=True), 'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True, help="""Set here the method that will be used to generate the end of year journal entries for all the accounts of this type. @@ -139,14 +139,14 @@ class account_account_type(osv.osv): 'Balance' will generally be used for cash accounts. 'Detail' will copy each existing journal item of the previous year, even the reconciled ones. 'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""), - 'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign on Reports', required=True, help='Allows you to change the sign of the balance amount displayed in the reports, so that you can see positive figures instead of negative ones in expenses accounts.'), + 'sign': fields.selection([(-1, 'Reverse balance sign'), (1, 'Preserve balance sign')], 'Sign on Reports', required=True, help='For accounts that are typically more debited than credited and that you would like to print as negative amounts in your reports, you should reverse the sign of the balance; e.g.: Expense account. The same applies for accounts that are typically more credited than debited and that you would like to print as positive amounts in your reports; e.g.: Income account.'), 'report_type':fields.selection([ ('none','/'), ('income','Profit & Loss (Income Accounts)'), ('expense','Profit & Loss (Expense Accounts)'), - ('asset','Balance Sheet (Assets Accounts)'), + ('asset','Balance Sheet (Asset Accounts)'), ('liability','Balance Sheet (Liability Accounts)') - ],'P&L / BS Category', select=True, readonly=False, help="According value related accounts will be display on respective reports (Balance Sheet Profit & Loss Account)", required=True), + ],'P&L / BS Category', select=True, readonly=False, help="This field is used to generate legal reports: profit and loss, balance sheet.", required=True), 'note': fields.text('Description'), } _defaults = { @@ -243,13 +243,15 @@ class account_account(osv.osv): 'balance': "COALESCE(SUM(l.debit),0) " \ "- COALESCE(SUM(l.credit), 0) as balance", 'debit': "COALESCE(SUM(l.debit), 0) as debit", - 'credit': "COALESCE(SUM(l.credit), 0) as credit" + 'credit': "COALESCE(SUM(l.credit), 0) as credit", + 'foreign_balance': "COALESCE(SUM(l.amount_currency), 0) as foreign_balance", } #get all the necessary accounts children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context) #compute for each account the balance/debit/credit from the move lines accounts = {} res = {} + null_result = dict((fn, 0.0) for fn in field_names) if children_and_consolidated: aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context) @@ -269,7 +271,7 @@ class account_account(osv.osv): # ON l.account_id = tmp.id # or make _get_children_and_consol return a query and join on that request = ("SELECT l.account_id as id, " +\ - ', '.join(map(mapping.__getitem__, field_names)) + + ', '.join(map(mapping.__getitem__, mapping.keys())) + " FROM account_move_line l" \ " WHERE l.account_id IN %s " \ + filters + @@ -288,7 +290,7 @@ class account_account(osv.osv): sums = {} currency_obj = self.pool.get('res.currency') while brs: - current = brs[0] + current = brs.pop(0) # can_compute = True # for child in current.child_id: # if child.id not in sums: @@ -298,7 +300,6 @@ class account_account(osv.osv): # except ValueError: # brs.insert(0, child) # if can_compute: - brs.pop(0) for fn in field_names: sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0) for child in current.child_id: @@ -306,12 +307,21 @@ class account_account(osv.osv): sums[current.id][fn] += sums[child.id][fn] else: sums[current.id][fn] += currency_obj.compute(cr, uid, child.company_id.currency_id.id, current.company_id.currency_id.id, sums[child.id][fn], context=context) - null_result = dict((fn, 0.0) for fn in field_names) + + # as we have to relay on values computed before this is calculated separately than previous fields + if current.currency_id and current.exchange_rate and \ + ('adjusted_balance' in field_names or 'unrealized_gain_loss' in field_names): + # Computing Adjusted Balance and Unrealized Gains and losses + # Adjusted Balance = Foreign Balance / Exchange Rate + # Unrealized Gains and losses = Adjusted Balance - Balance + adj_bal = sums[current.id].get('foreign_balance', 0.0) / current.exchange_rate + sums[current.id].update({'adjusted_balance': adj_bal, 'unrealized_gain_loss': adj_bal - sums[current.id].get('balance', 0.0)}) + for id in ids: res[id] = sums.get(id, null_result) else: for id in ids: - res[id] = 0.0 + res[id] = null_result return res def _get_company_currency(self, cr, uid, ids, field_name, arg, context=None): @@ -340,14 +350,61 @@ class account_account(osv.osv): accounts = self.browse(cr, uid, ids, context=context) for account in accounts: level = 0 - if account.parent_id: - obj = self.browse(cr, uid, account.parent_id.id) - level = obj.level + 1 + parent = account.parent_id + while parent: + level += 1 + parent = parent.parent_id res[account.id] = level return res + def _set_credit_debit(self, cr, uid, account_id, name, value, arg, context=None): + if context.get('config_invisible', True): + return True + + account = self.browse(cr, uid, account_id, context=context) + diff = value - getattr(account,name) + if not diff: + return True + + journal_obj = self.pool.get('account.journal') + jids = journal_obj.search(cr, uid, [('type','=','situation'),('centralisation','=',1),('company_id','=',account.company_id.id)], context=context) + if not jids: + raise osv.except_osv(_('Error!'),_("You need an Opening journal with centralisation checked to set the initial balance!")) + + period_obj = self.pool.get('account.period') + pids = period_obj.search(cr, uid, [('special','=',True),('company_id','=',account.company_id.id)], context=context) + if not pids: + raise osv.except_osv(_('Error!'),_("No opening/closing period defined, please create one to set the initial balance!")) + + move_obj = self.pool.get('account.move.line') + move_id = move_obj.search(cr, uid, [ + ('journal_id','=',jids[0]), + ('period_id','=',pids[0]), + ('account_id','=', account_id), + (name,'>', 0.0), + ('name','=', _('Opening Balance')) + ], context=context) + if move_id: + move = move_obj.browse(cr, uid, move_id[0], context=context) + move_obj.write(cr, uid, move_id[0], { + name: diff+getattr(move,name) + }, context=context) + else: + if diff<0.0: + raise osv.except_osv(_('Error!'),_("Unable to adapt the initial balance (negative value)!")) + nameinv = (name=='credit' and 'debit') or 'credit' + move_id = move_obj.create(cr, uid, { + 'name': _('Opening Balance'), + 'account_id': account_id, + 'journal_id': jids[0], + 'period_id': pids[0], + name: diff, + nameinv: 0.0 + }, context=context) + return True + _columns = { - 'name': fields.char('Name', size=128, required=True, select=True), + 'name': fields.char('Name', size=256, required=True, select=True), 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."), 'code': fields.char('Code', size=64, required=True, select=1), 'type': fields.selection([ @@ -358,21 +415,28 @@ class account_account(osv.osv): ('liquidity','Liquidity'), ('consolidation', 'Consolidation'), ('closed', 'Closed'), - ], 'Internal Type', required=True, help="This type is used to differentiate types with "\ - "special effects in OpenERP: view can not have entries, consolidation are accounts that "\ + ], 'Internal Type', required=True, help="The 'Internal Type' is used for features available on "\ + "different types of accounts: view can not have journal items, consolidation are accounts that "\ "can have children accounts for multi-company consolidations, payable/receivable are for "\ "partners accounts (for debit/credit computations), closed for depreciated accounts."), 'user_type': fields.many2one('account.account.type', 'Account Type', required=True, - help="These types are defined according to your country. The type contains more information "\ - "about the account and its specificities."), + help="Account Type is used for information purpose, to generate " + "country-specific legal reports, and set the rules to close a fiscal year and generate opening entries."), 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]), 'child_parent_ids': fields.one2many('account.account','parent_id','Children'), 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'), 'child_id': fields.function(_get_child_ids, type='many2many', relation="account.account", string="Child Accounts"), 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Balance', multi='balance'), - 'credit': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Credit', multi='balance'), - 'debit': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'), - 'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."), + 'credit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Credit', multi='balance'), + 'debit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'), + 'foreign_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Foreign Balance', multi='balance', + help="Total amount (in Secondary currency) for transactions held in secondary currency for this account."), + 'adjusted_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Adjusted Balance', multi='balance', + help="Total amount (in Company currency) for transactions held in secondary currency for this account."), + 'unrealized_gain_loss': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Unrealized Gain or Loss', multi='balance', + help="Value of Loss or Gain due to changes in exchange rate when doing multi-currency transactions."), + 'reconcile': fields.boolean('Allow Reconciliation', help="Check this box if this account allows reconciliation of journal items."), + 'exchange_rate': fields.related('currency_id', 'rate', type='float', string='Exchange Rate', digits=(12,6)), 'shortcut': fields.char('Shortcut', size=12), 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel', 'account_id', 'tax_id', 'Default Taxes'), @@ -390,11 +454,14 @@ class account_account(osv.osv): 'manage this. So if you import from another software system you may have to use the rate at date. ' \ 'Incoming transactions always use the rate at date.', \ required=True), - 'level': fields.function(_get_level, string='Level', store=True, type='integer'), + 'level': fields.function(_get_level, string='Level', method=True, type='integer', + store={ + 'account.account': (_get_children_and_consol, ['level', 'parent_id'], 10), + }), } _defaults = { - 'type': 'view', + 'type': 'other', 'reconcile': False, 'active': True, 'currency_mode': 'current', @@ -431,9 +498,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 +583,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 +603,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: @@ -604,11 +683,11 @@ class account_journal(osv.osv): 'name': fields.char('Journal Name', size=64, required=True), 'code': fields.char('Code', size=5, required=True, help="The code will be displayed on reports."), 'type': fields.selection([('sale', 'Sale'),('sale_refund','Sale Refund'), ('purchase', 'Purchase'), ('purchase_refund','Purchase Refund'), ('cash', 'Cash'), ('bank', 'Bank and Cheques'), ('general', 'General'), ('situation', 'Opening/Closing Situation')], 'Type', size=32, required=True, - help="Select 'Sale' for Sale journal to be used at the time of making invoice."\ - " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\ - " Select 'Cash' to be used at the time of making payment."\ - " Select 'General' for miscellaneous operations."\ - " Select 'Opening/Closing Situation' to be used at the time of new fiscal year creation or end of year entries generation."), + help="Select 'Sale' for customer invoices journals."\ + " Select 'Purchase' for supplier invoices journals."\ + " Select 'Cash' or 'Bank' for journals that are used in customer or supplier payments."\ + " Select 'General' for miscellaneous operations journals."\ + " Select 'Opening/Closing Situation' for entries generated for new fiscal years."), 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]), 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]), 'view_id': fields.many2one('account.journal.view', 'Display Mode', required=True, help="Gives the view used when writing or browsing entries in this journal. The view tells OpenERP which fields should be visible, required or readonly and in which order. You can create your own view for a faster encoding in each journal."), @@ -623,7 +702,7 @@ class account_journal(osv.osv): 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'), 'entry_posted': fields.boolean('Skip \'Draft\' State for Manual Entries', help='Check this box if you don\'t want new journal entries to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation. \nNote that journal entries that are automatically created by the system are always skipping that state.'), 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"), - 'allow_date':fields.boolean('Check Date not in the Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'), + 'allow_date':fields.boolean('Check Date in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'), } _defaults = { @@ -637,6 +716,19 @@ class account_journal(osv.osv): _order = 'code' + def _check_currency(self, cr, uid, ids, context=None): + for journal in self.browse(cr, uid, ids, context=context): + if journal.currency: + if journal.default_credit_account_id and not journal.default_credit_account_id.currency_id.id == journal.currency.id: + return False + if journal.default_debit_account_id and not journal.default_debit_account_id.currency_id.id == journal.currency.id: + return False + return True + + _constraints = [ + (_check_currency, 'Configuration error! The currency chosen should be shared by the default accounts too.', ['currency','default_debit_account_id','default_credit_account_id']), + ] + def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False): journal = self.browse(cr, uid, id, context=context) if not default: @@ -650,44 +742,33 @@ 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): + """ Create new no_gap entry sequence for every new Joural """ - Create new entry sequence for every new Joural - @param cr: cursor to database - @param user: id of current user - @param ids: list of record ids to be process - @param context: context arguments, like lang, time zone - @return: return a result - """ - seq_pool = self.pool.get('ir.sequence') - seq_typ_pool = self.pool.get('ir.sequence.type') - - name = vals['name'] - code = vals['code'].lower() - - types = { - 'name': name, - 'code': code - } - seq_typ_pool.create(cr, uid, types) + # in account.journal code is actually the prefix of the sequence + # whereas ir.sequence code is a key to lookup global sequences. + prefix = vals['code'].upper() seq = { - 'name': name, - 'code': code, + 'name': vals['name'], 'company_id': vals['company_id'], - 'active': True, - 'prefix': code + "/%(year)s/", + 'implementation':'no_gap', + 'prefix': prefix + "/%(year)s/", 'padding': 4, 'number_increment': 1 } - return seq_pool.create(cr, uid, seq) + if 'company_id' in vals: + seq['company_id'] = vals['company_id'] + return self.pool.get('ir.sequence').create(cr, uid, seq) def create(self, cr, uid, vals, context=None): if not 'sequence_id' in vals or not vals['sequence_id']: @@ -779,21 +860,8 @@ class account_fiscalyear(osv.osv): 'state': 'draft', 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, } - _order = "date_start" + _order = "date_start, id" - def _check_fiscal_year(self, cr, uid, ids, context=None): - current_fiscal_yr = self.browse(cr, uid, ids, context=context)[0] - obj_fiscal_ids = self.search(cr, uid, [('company_id', '=', current_fiscal_yr.company_id.id)], context=context) - obj_fiscal_ids.remove(ids[0]) - data_fiscal_yr = self.browse(cr, uid, obj_fiscal_ids, context=context) - - for old_fy in data_fiscal_yr: - if old_fy.company_id.id == current_fiscal_yr['company_id'].id: - # Condition to check if the current fiscal year falls in between any previously defined fiscal year - if old_fy.date_start <= current_fiscal_yr['date_start'] <= old_fy.date_stop or \ - old_fy.date_start <= current_fiscal_yr['date_stop'] <= old_fy.date_stop: - return False - return True def _check_duration(self, cr, uid, ids, context=None): obj_fy = self.browse(cr, uid, ids[0], context=context) @@ -802,8 +870,7 @@ 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']) ] def create_period3(self, cr, uid, ids, context=None): @@ -838,15 +905,26 @@ class account_fiscalyear(osv.osv): return True def find(self, cr, uid, dt=None, exception=True, context=None): + res = self.finds(cr, uid, dt, exception, context=context) + return res and res[0] or False + + def finds(self, cr, uid, dt=None, exception=True, context=None): + if context is None: context = {} if not dt: dt = time.strftime('%Y-%m-%d') - ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)]) + args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)] + if context.get('company_id', False): + args.append(('company_id', '=', context['company_id'])) + else: + company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id + args.append(('company_id', '=', company_id)) + ids = self.search(cr, uid, args, context=context) if not ids: if exception: raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.')) else: - return False - return ids[0] + return [] + return ids def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80): if args is None: @@ -881,6 +959,9 @@ 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) @@ -917,10 +998,17 @@ class account_period(osv.osv): return False def find(self, cr, uid, dt=None, context=None): + if context is None: context = {} if not dt: dt = time.strftime('%Y-%m-%d') #CHECKME: shouldn't we check the state of the period? - ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)]) + args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)] + if context.get('company_id', False): + args.append(('company_id', '=', context['company_id'])) + else: + company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id + args.append(('company_id', '=', company_id)) + ids = self.search(cr, uid, args, context=context) if not ids: raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create one.')%dt) return ids @@ -947,7 +1035,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): @@ -1164,22 +1252,10 @@ class account_move(osv.osv): return False return True - def _check_period_journal(self, cursor, user, ids, context=None): - for move in self.browse(cursor, user, ids, context=context): - for line in move.line_id: - if line.period_id.id != move.period_id.id: - return False - if line.journal_id.id != move.journal_id.id: - return False - return True - _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', - ['line_id']), ] def post(self, cr, uid, ids, context=None): @@ -1189,7 +1265,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 =='/': @@ -1201,7 +1277,7 @@ class account_move(osv.osv): else: if journal.sequence_id: c = {'fiscalyear_id': move.period_id.fiscalyear_id.id} - new_name = obj_sequence.get_id(cr, uid, journal.sequence_id.id, context=c) + new_name = obj_sequence.next_by_id(cr, uid, journal.sequence_id.id, c) else: raise osv.except_osv(_('Error'), _('No sequence defined on the journal !')) @@ -1225,7 +1301,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): @@ -1243,7 +1319,7 @@ class account_move(osv.osv): context = {} c = context.copy() c['novalidate'] = True - result = super(osv.osv, self).write(cr, uid, ids, vals, c) + result = super(account_move, self).write(cr, uid, ids, vals, c) self.validate(cr, uid, ids, context=context) return result @@ -1312,7 +1388,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 @@ -1332,7 +1408,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 = {} @@ -1462,8 +1538,6 @@ class account_move(osv.osv): # Update the move lines (set them as valid) obj_move_line.write(cr, uid, line_draft_ids, { - 'journal_id': move.journal_id.id, - 'period_id': move.period_id.id, 'state': 'valid' }, context, check=False) @@ -1504,8 +1578,6 @@ class account_move(osv.osv): # We can't validate it (it's unbalanced) # Setting the lines as draft obj_move_line.write(cr, uid, line_ids, { - 'journal_id': move.journal_id.id, - 'period_id': move.period_id.id, 'state': 'draft' }, context, check=False) # Create analytic lines for the valid moves @@ -1530,11 +1602,15 @@ class account_move_reconcile(osv.osv): _defaults = { 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/', } + def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None): total = 0.0 for rec in self.browse(cr, uid, ids, context=context): for line in rec.line_partial_ids: - total += (line.debit or 0.0) - (line.credit or 0.0) + if line.account_id.currency_id: + total += line.amount_currency + else: + total += (line.debit or 0.0) - (line.credit or 0.0) if not total: self.pool.get('account.move.line').write(cr, uid, map(lambda x: x.id, rec.line_partial_ids), @@ -1594,7 +1670,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) @@ -1611,13 +1687,15 @@ class account_tax_code(osv.osv): if context.get('state', 'all') == 'all': move_state = ('draft', 'posted', ) if context.get('fiscalyear_id', False): - fiscalyear_id = context['fiscalyear_id'] + fiscalyear_id = [context['fiscalyear_id']] else: - fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False) + fiscalyear_id = self.pool.get('account.fiscalyear').finds(cr, uid, exception=False) where = '' where_params = () if fiscalyear_id: - pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids) + pids = [] + for fy in fiscalyear_id: + pids += map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fy).period_ids) if pids: where = ' AND line.period_id IN %s AND move.state IN %s ' where_params = (tuple(pids), move_state) @@ -1763,6 +1841,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): """ @@ -1926,8 +2007,11 @@ class account_tax(osv.osv): cur_price_unit+=amount2 return res - def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None): + def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, 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 + tax. It's used in encoding by line where you don't matter if you encoded a tax with that boolean to True or + False RETURN: { 'total': 0.0, # Total without taxes 'total_included: 0.0, # Total with taxes @@ -1939,10 +2023,10 @@ class account_tax(osv.osv): tin = [] tex = [] for tax in taxes: - if tax.price_include: - tin.append(tax) - else: + if not tax.price_include or force_excluded: tex.append(tax) + else: + tin.append(tax) tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner) for r in tin: totalex -= r.get('amount', 0.0) @@ -2108,6 +2192,7 @@ class account_model(osv.osv): account_move_obj = self.pool.get('account.move') account_move_line_obj = self.pool.get('account.move.line') pt_obj = self.pool.get('account.payment.term') + period_obj = self.pool.get('account.period') if context is None: context = {} @@ -2115,13 +2200,18 @@ class account_model(osv.osv): if datas.get('date', False): context.update({'date': datas['date']}) - period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False)) - if not period_id: - raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !')) - period_id = period_id[0] - + move_date = context.get('date', time.strftime('%Y-%m-%d')) + move_date = datetime.strptime(move_date,"%Y-%m-%d") 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')} + ctx = context.copy() + ctx.update({'company_id': model.company_id.id}) + period_ids = period_obj.find(cr, uid, dt=context.get('date', False), context=ctx) + period_id = period_ids and period_ids[0] or False + ctx.update({'journal_id': model.journal_id.id,'period_id': period_id}) + try: + entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.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, @@ -2142,7 +2232,7 @@ class account_model(osv.osv): 'analytic_account_id': analytic_account_id } - date_maturity = time.strftime('%Y-%m-%d') + date_maturity = context.get('date',time.strftime('%Y-%m-%d')) if line.date_maturity == 'partner': if not line.partner_id: raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \ @@ -2166,9 +2256,7 @@ class account_model(osv.osv): 'date': context.get('date',time.strftime('%Y-%m-%d')), 'date_maturity': date_maturity }) - c = context.copy() - c.update({'journal_id': model.journal_id.id,'period_id': period_id}) - account_move_line_obj.create(cr, uid, val, context=c) + account_move_line_obj.create(cr, uid, val, context=ctx) return move_ids @@ -2193,8 +2281,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() @@ -2315,7 +2403,7 @@ class account_account_template(osv.osv): _description ='Templates for Accounts' _columns = { - 'name': fields.char('Name', size=128, required=True, select=True), + 'name': fields.char('Name', size=256, required=True, select=True), 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."), 'code': fields.char('Code', size=64, select=1), 'type': fields.selection([ @@ -2361,7 +2449,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']), ] @@ -2445,7 +2533,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 @@ -2700,7 +2788,8 @@ class account_fiscal_position_template(osv.osv): 'name': fields.char('Fiscal Position Template', size=64, required=True), 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True), 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'), - 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping') + 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping'), + 'note': fields.text('Notes', translate=True), } def generate_fiscal_position(self, cr, uid, chart_temp_id, tax_template_ref, acc_template_ref, company_id, context=None): @@ -2762,7 +2851,88 @@ class account_fiscal_position_account_template(osv.osv): account_fiscal_position_account_template() - # Multi charts of Accounts wizard +# --------------------------------------------------------- +# Account Financial Report +# --------------------------------------------------------- + +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, translate=True), + 'parent_id': fields.many2one('account.financial.report', 'Parent'), + 'children_ids': fields.one2many('account.financial.report', 'parent_id', 'Account Report'), + 'sequence': fields.integer('Sequence'), + 'note': fields.text('Notes'), + 'balance': fields.function(_get_balance, 'Balance'), + 'level': fields.function(_get_level, string='Level', store=True, type='integer'), + 'type': fields.selection([ + ('sum','View'), + ('accounts','Accounts'), + ('account_type','Account Type'), + ('account_report','Report Value'), + ],'Type'), + 'account_ids': fields.many2many('account.account', 'account_account_financial_report', 'report_line_id', 'account_id', 'Accounts'), + 'display_detail': fields.boolean('Display details', help='Display every account with its balance instead of the sum.'), + 'account_report_id': fields.many2one('account.financial.report', 'Report Value'), + 'account_type_ids': fields.many2many('account.account.type', 'account_account_financial_report_type', 'report_id', 'account_type_id', 'Account Types'), + } + + _defaults = { + 'type': 'sum', + } + +account_financial_report() + +# --------------------------------------------------------- +# Account generation from template wizards +# --------------------------------------------------------- class wizard_multi_charts_accounts(osv.osv_memory): """ @@ -2783,7 +2953,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): _columns = { 'company_id':fields.many2one('res.company', 'Company', required=True), 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True), - 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True), + 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True), 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"), 'seq_journal':fields.boolean('Separated Journal Sequences', help="Check this box if you want to use a different sequence for each created journal. Otherwise, all will use the same sequence."), "sale_tax": fields.many2one("account.tax.template", "Default Sales Tax"), @@ -2852,7 +3022,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 company_obj.browse(cr, uid, unconfigured_cmp)] @@ -3183,7 +3353,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): for line in journal_data: #create the account_account for this bank journal if not ref_acc_bank.code: - raise osv.except_osv(_('Configuration Error !'), _('The bank account defined on the selected chart of account hasn\'t a code.')) + raise osv.except_osv(_('Configuration Error !'), _('The bank account defined on the selected chart of accounts hasn\'t a code.')) while True: new_code = str(ref_acc_bank.code.ljust(code_digits-len(str(current_num)), '0')) + str(current_num) ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)]) diff --git a/addons/account/account_analytic_line.py b/addons/account/account_analytic_line.py index 08123c8f460..ea1829db310 100644 --- a/addons/account/account_analytic_line.py +++ b/addons/account/account_analytic_line.py @@ -123,7 +123,7 @@ class account_analytic_line(osv.osv): ctx['uom'] = unit amount_unit = prod.price_get(pricetype.field, context=ctx)[prod.id] prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account') - amount = amount_unit * quantity or 1.0 + amount = amount_unit * quantity or 0.0 result = round(amount, prec) if not flag: result *= -1 diff --git a/addons/account/account_bank.py b/addons/account/account_bank.py index a3d66ccdf7c..01eab2fbb01 100644 --- a/addons/account/account_bank.py +++ b/addons/account/account_bank.py @@ -37,15 +37,23 @@ class bank(osv.osv): self.post_write(cr, uid, ids, context=context) return result + def _prepare_name(self, bank): + "Return the name to use when creating a bank journal" + return (bank.bank_name or '') + ' ' + bank.acc_number + def post_write(self, cr, uid, ids, context={}): + if isinstance(ids, (int, long)): + ids = [ids] + obj_acc = self.pool.get('account.account') obj_data = self.pool.get('ir.model.data') + for bank in self.browse(cr, uid, ids, context): if bank.company_id and not bank.journal_id: # Find the code and parent of the bank account to create dig = 6 current_num = 1 - ids = obj_acc.search(cr, uid, [('type','=','liquidity')], context=context) + ids = obj_acc.search(cr, uid, [('type','=','liquidity')], context=context) # No liquidity account exists, no template available if not ids: continue @@ -57,9 +65,9 @@ class bank(osv.osv): if not ids: break current_num += 1 - + name = self._prepare_name(bank) acc = { - 'name': (bank.bank_name or '')+' '+bank.acc_number, + 'name': name, 'currency_id': bank.company_id.currency_id.id, 'code': new_code, 'type': 'liquidity', @@ -74,7 +82,7 @@ class bank(osv.osv): data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')]) data = obj_data.browse(cr, uid, data_id[0], context=context) view_id_cash = data.res_id - + jour_obj = self.pool.get('account.journal') new_code = 1 while True: @@ -86,7 +94,7 @@ class bank(osv.osv): #create the bank journal vals_journal = { - 'name': (bank.bank_name or '')+' '+bank.acc_number, + 'name': name, 'code': code, 'type': 'bank', 'company_id': bank.company_id.id, @@ -101,3 +109,4 @@ class bank(osv.osv): self.write(cr, uid, [bank.id], {'journal_id': journal_id}, context=context) return True +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index f712cc9ab0c..57472e228ef 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: @@ -123,11 +117,17 @@ class account_bank_statement(osv.osv): res[statement_id] = (currency_id, currency_names[currency_id]) return res + def _get_statement(self, cr, uid, ids, context=None): + result = {} + for line in self.pool.get('account.bank.statement.line').browse(cr, uid, ids, context=context): + result[line.statement_id.id] = True + return result.keys() + _order = "date desc, id desc" _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 +136,25 @@ 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 = { + 'account.bank.statement': (lambda self, cr, uid, ids, c={}: ids, ['line_ids','move_line_ids'], 10), + 'account.bank.statement.line': (_get_statement, ['amount'], 10), + }, + string="Computed 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', 'New'), + ('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 +164,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), @@ -214,6 +219,7 @@ class account_bank_statement(osv.osv): 'period_id': st.period_id.id, 'date': st_line.date, 'name': st_line_number, + 'ref': st_line.ref, }, context=context) account_bank_statement_line_obj.write(cr, uid, [st_line.id], { 'move_ids': [(4, move_id, False)] @@ -311,7 +317,7 @@ class account_bank_statement(osv.osv): return self.write(cr, uid, ids, {'state':'confirm'}, context=context) def check_status_condition(self, cr, uid, state, journal_type='bank'): - return state=='draft' + return state in ('draft','open') def button_confirm_bank(self, cr, uid, ids, context=None): obj_seq = self.pool.get('ir.sequence') @@ -335,9 +341,9 @@ class account_bank_statement(osv.osv): else: if st.journal_id.sequence_id: c = {'fiscalyear_id': st.period_id.fiscalyear_id.id} - st_number = obj_seq.get_id(cr, uid, st.journal_id.sequence_id.id, context=c) + st_number = obj_seq.next_by_id(cr, uid, st.journal_id.sequence_id.id, context=c) else: - st_number = obj_seq.get(cr, uid, 'account.bank.statement') + st_number = obj_seq.next_by_code(cr, uid, 'account.bank.statement') for line in st.move_line_ids: if line.state <> 'valid': @@ -386,7 +392,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 @@ -470,7 +476,7 @@ class account_bank_statement_line(osv.osv): } _defaults = { 'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement.line'), - 'date': lambda *a: time.strftime('%Y-%m-%d'), + 'date': lambda self,cr,uid,context={}: context.get('date', time.strftime('%Y-%m-%d')), 'type': 'general', } diff --git a/addons/account/account_bank_view.xml b/addons/account/account_bank_view.xml index d91739817ac..1a0e26e7c1f 100644 --- a/addons/account/account_bank_view.xml +++ b/addons/account/account_bank_view.xml @@ -13,7 +13,7 @@ - + @@ -23,16 +23,16 @@ - Bank Accounts + Setup your Bank Accounts res.partner.bank form tree,form - Configure your company's bank account and select those that must appear on the report footer. You can drag & drop bank in the list view to reorder bank accounts. If you use the accounting application of OpenERP, journals and accounts will be created automatically based on these data. + Configure your company's bank account and select those that must appear on the report footer. You can reorder banks in the list view. If you use the accounting application of OpenERP, journals and accounts will be created automatically based on these data. diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index 982383ea2a7..ebc97fe8a84 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') @@ -217,38 +184,33 @@ class account_cash_statement(osv.osv): res['end'] = end_l return res + def _get_statement(self, cr, uid, ids, context=None): + result = {} + for line in self.pool.get('account.bank.statement.line').browse(cr, uid, ids, context=context): + result[line.statement_id.id] = True + return result.keys() + _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"), + 'total_entry_encoding': fields.function(_get_sum_entry_encoding, string="Cash Transaction", help="Total cash transactions", + store = { + 'account.bank.statement': (lambda self, cr, uid, ids, c={}: ids, ['line_ids','move_line_ids'], 10), + 'account.bank.statement.line': (_get_statement, ['amount'], 10), + }), '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"), + 'balance_end_cash': fields.function(_balance_end_cash, store=True, string='Closing 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 = { 'state': 'draft', - 'date': lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"), + 'date': lambda self,cr,uid,context={}: context.get('date', time.strftime("%Y-%m-%d %H:%M:%S")), 'user_id': lambda self, cr, uid, context=None: uid, 'starting_details_ids': _get_cash_open_box_lines, 'ending_details_ids': _get_default_cash_close_box_lines } def create(self, cr, uid, vals, context=None): - sql = [ - ('journal_id', '=', vals.get('journal_id', False)), - ('state', '=', 'open') - ] - 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')) - 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) if vals.get('starting_details_ids', False): @@ -332,15 +294,14 @@ class account_cash_statement(osv.osv): if statement.name and statement.name == '/': if statement.journal_id.sequence_id: c = {'fiscalyear_id': statement.period_id.fiscalyear_id.id} - st_number = obj_seq.get_id(cr, uid, statement.journal_id.sequence_id.id, context=c) + st_number = obj_seq.next_by_id(cr, uid, statement.journal_id.sequence_id.id, context=c) else: - st_number = obj_seq.get(cr, uid, 'account.cash.statement') + st_number = obj_seq.next_by_code(cr, uid, 'account.cash.statement') vals.update({ 'name': st_number }) vals.update({ - 'date': time.strftime("%Y-%m-%d %H:%M:%S"), 'state': 'open', }) self.write(cr, uid, [statement.id], vals, context=context) @@ -350,7 +311,7 @@ class account_cash_statement(osv.osv): if journal_type == 'bank': return super(account_cash_statement, self).balance_check(cr, uid, cash_id, journal_type, context) if not self._equal_balance(cr, uid, cash_id, context): - raise osv.except_osv(_('Error !'), _('CashBox Balance is not matching with Calculated Balance !')) + raise osv.except_osv(_('Error !'), _('The closing balance should be the same than the computed balance !')) return True def statement_close(self, cr, uid, ids, journal_type='bank', context=None): diff --git a/addons/account/account_installer.xml b/addons/account/account_installer.xml index a0de49a168a..cbdb665e157 100644 --- a/addons/account/account_installer.xml +++ b/addons/account/account_installer.xml @@ -11,7 +11,7 @@ Accounting Application Configuration - Configure Your Accounting Chart + Configure Your Chart of Accounts The default Chart of Accounts is matching your country selection. If no certified Chart of Accounts exists for your specified country, a generic one can be installed and will be selected by default. @@ -23,12 +23,13 @@ 23 - + - + - + + @@ -39,28 +40,8 @@ - - account.installer.modules.form - base.setup.installer - form - - - - - - - - - - - - - - - - - Accounting Chart Configuration + Install your Chart of Accounts ir.actions.act_window account.installer @@ -72,7 +53,7 @@ Accounting 5 - + @@ -81,5 +62,48 @@ automatic + + Review your Financial Accounts + ir.actions.act_window + account.account + form + tree,form + {'config_invisible': False} + + + + + + + + + + Review your Financial Journals + ir.actions.act_window + account.journal + form + tree,form + Setup your accounting journals. For bank accounts, it's better to use the 'Setup Your Bank Accounts' tool that will automatically create the accounts and journals for you. + + + + + + + + + Review your Payment Terms + ir.actions.act_window + account.payment.term + form + tree,form + Payment terms define the conditions to pay a customer or supplier invoice in one or several payments. Customers periodic reminders will use the payment terms for each letter. Each customer or supplier can be assigned to one of these payment terms. + + + + + + + diff --git a/addons/account/invoice.py b/addons/account/account_invoice.py similarity index 97% rename from addons/account/invoice.py rename to addons/account/account_invoice.py index 09e8304340c..27881bf1280 100644 --- a/addons/account/invoice.py +++ b/addons/account/account_invoice.py @@ -209,7 +209,7 @@ class account_invoice(osv.osv): \n* The \'Open\' state is used when user create invoice,a invoice number is generated.Its in open state till user does not pay invoice. \ \n* The \'Paid\' state is set automatically when invoice is paid.\ \n* The \'Cancelled\' state is used when user cancel invoice.'), - 'date_invoice': fields.date('Invoice Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]}, select=True, help="Keep empty to use the current date"), + 'date_invoice': fields.date('Invoice Date', readonly=True, states={'draft':[('readonly',False)]}, select=True, help="Keep empty to use the current date"), 'date_due': fields.date('Due Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]}, select=True, help="If you use payment terms, the due date will be computed automatically at the generation "\ "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. The payment term may compute several due dates, for example 50% now, 50% in one month."), @@ -251,7 +251,7 @@ class account_invoice(osv.osv): 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}), 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}), - 'check_total': fields.float('Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}), + 'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}), 'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean', store={ 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ? @@ -286,6 +286,9 @@ class account_invoice(osv.osv): 'internal_number': False, 'user_id': lambda s, cr, u, c: u, } + _sql_constraints = [ + ('number_uniq', 'unique(number, company_id)', 'Invoice Number must be unique per Company!'), + ] def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False): journal_obj = self.pool.get('account.journal') @@ -306,9 +309,9 @@ class account_invoice(osv.osv): view_id = view_id[0] res = super(account_invoice,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu) - type = context.get('journal_type', 'sale') + type = context.get('journal_type', False) for field in res['fields']: - if field == 'journal_id': + if field == 'journal_id' and type: journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', type)], context=context, limit=None, name_get_uid=1) res['fields'][field]['selection'] = journal_select @@ -375,7 +378,7 @@ class account_invoice(osv.osv): if t['state'] in ('draft', 'cancel') and t['internal_number']== False: unlink_ids.append(t['id']) else: - raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened(or been in opened state ever) or paid!')) + raise osv.except_osv(_('Invalid action !'), _('You can not delete an invoice which is open or paid. We suggest you to refund it instead.')) osv.osv.unlink(self, cr, uid, unlink_ids, context=context) return True @@ -795,6 +798,7 @@ class account_invoice(osv.osv): """Creates invoice related analytics and financial move lines""" ait_obj = self.pool.get('account.invoice.tax') cur_obj = self.pool.get('res.currency') + period_obj = self.pool.get('account.period') context = {} for inv in self.browse(cr, uid, ids): if not inv.journal_id.sequence_id: @@ -828,7 +832,7 @@ class account_invoice(osv.osv): total_percent += line.value_amount total_fixed = (total_fixed * 100) / (inv.amount_total or 1.0) if (total_fixed + total_percent) > 100: - raise osv.except_osv(_('Error !'), _("Cannot create the invoice !\nThe payment term defined gives a computed amount greater than the total invoiced amount.")) + raise osv.except_osv(_('Error !'), _("Can not create the invoice !\nThe related payment term is probably misconfigured as it gives a computed amount greater than the total invoiced amount.")) # one move line per tax line iml += ait_obj.move_line_get(cr, uid, inv.id) @@ -860,10 +864,10 @@ class account_invoice(osv.osv): if totlines: res_amount_currency = total_currency i = 0 + ctx.update({'date': inv.date_invoice}) for t in totlines: if inv.currency_id.id != company_currency: - amount_currency = cur_obj.compute(cr, uid, - company_currency, inv.currency_id.id, t[1]) + amount_currency = cur_obj.compute(cr, uid, company_currency, inv.currency_id.id, t[1], context=ctx) else: amount_currency = False @@ -880,7 +884,7 @@ class account_invoice(osv.osv): 'account_id': acc_id, 'date_maturity': t[0], 'amount_currency': diff_currency_p \ - and amount_currency or False, + and amount_currency or False, 'currency_id': diff_currency_p \ and inv.currency_id.id or False, 'ref': ref, @@ -910,7 +914,7 @@ class account_invoice(osv.osv): journal = self.pool.get('account.journal').browse(cr, uid, journal_id) if journal.centralisation: raise osv.except_osv(_('UserError'), - _('Cannot create invoice move on centralised journal')) + _('You cannot create an invoice on a centralised journal. Uncheck the centralised counterpart box in the related journal from the configuration menu.')) line = self.finalize_invoice_move_lines(cr, uid, inv, line) @@ -923,10 +927,10 @@ class account_invoice(osv.osv): 'narration':inv.comment } period_id = inv.period_id and inv.period_id.id or False + ctx.update({'company_id': inv.company_id.id}) if not period_id: - period_ids = self.pool.get('account.period').search(cr, uid, [('date_start','<=',inv.date_invoice or time.strftime('%Y-%m-%d')),('date_stop','>=',inv.date_invoice or time.strftime('%Y-%m-%d')), ('company_id', '=', inv.company_id.id)]) - if period_ids: - period_id = period_ids[0] + period_ids = period_obj.find(cr, uid, inv.date_invoice, context=ctx) + period_id = period_ids and period_ids[0] or False if period_id: move['period_id'] = period_id for i in line: @@ -1019,7 +1023,7 @@ class account_invoice(osv.osv): pay_ids = account_move_line_obj.browse(cr, uid, i['payment_ids']) for move_line in pay_ids: if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids: - raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!')) + raise osv.except_osv(_('Error !'), _('You can not cancel an invoice which is partially paid! You need to unreconcile related payment entries first!')) # First, set the invoices as cancelled and detach the move ids self.write(cr, uid, ids, {'state':'cancel', 'move_id':False}) @@ -1241,6 +1245,7 @@ class account_invoice(osv.osv): account_invoice() class account_invoice_line(osv.osv): + def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict): res = {} tax_obj = self.pool.get('account.tax') @@ -1322,9 +1327,9 @@ class account_invoice_line(osv.osv): raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") ) if not product: if type in ('in_invoice', 'in_refund'): - return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}} + return {'value': {}, 'domain':{'product_uom':[]}} else: - return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}} + return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}} part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) fpos_obj = self.pool.get('account.fiscal.position') fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False @@ -1352,6 +1357,17 @@ class account_invoice_line(osv.osv): taxes = res.supplier_taxes_id and res.supplier_taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False) tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes) + if type in ('in_invoice','in_refund') and tax_id and price_unit: + tax_pool = self.pool.get('account.tax') + tax_browse = tax_pool.browse(cr, uid, tax_id) + if not isinstance(tax_browse, list): + tax_browse = [tax_browse] + taxes = tax_pool.compute_inv(cr, uid, tax_browse, price_unit, 1) + tax_amount = reduce(lambda total, tax_dict: total + tax_dict.get('amount', 0.0), taxes, 0.0) + price_unit = price_unit - tax_amount + if qty != 0: + price_unit = price_unit / float(qty) + if type in ('in_invoice', 'in_refund'): result.update( {'price_unit': price_unit or res.standard_price,'invoice_line_tax_id': tax_id} ) else: @@ -1366,7 +1382,6 @@ class account_invoice_line(osv.osv): if res2: domain = {'uos_id':[('category_id','=',res2 )]} - result['categ_id'] = res.categ_id.id res_final = {'value':result, 'domain':domain} if not company_id or not currency_id: @@ -1558,6 +1573,7 @@ class account_invoice_tax(osv.osv): for line in inv.invoice_line: for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, (line.price_unit* (1-(line.discount or 0.0)/100.0)), line.quantity, inv.address_invoice_id.id, line.product_id, inv.partner_id)['taxes']: + tax['price_unit'] = cur_obj.round(cr, uid, cur, tax['price_unit']) val={} val['invoice_id'] = inv.id val['name'] = tax['name'] diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index b21d3e9604f..4cc1a63db61 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -51,26 +51,22 @@ form
- - - - - - - - - - - - - - - - - - - + + +