diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py index cd19c730a3f..e1b9078ec3f 100644 --- a/addons/account/__openerp__.py +++ b/addons/account/__openerp__.py @@ -29,25 +29,23 @@ Accounting and Financial Management. Financial and accounting module that covers: -------------------------------------------- -General accountings -Cost / Analytic accounting -Third party accounting -Taxes management -Budgets -Customer and Supplier Invoices -Bank statements -Reconciliation process by partner + * General Accounting + * Cost/Analytic accounting + * Third party accounting + * Taxes management + * Budgets + * Customer and Supplier Invoices + * Bank statements + * Reconciliation process by partner Creates a dashboard for accountants that includes: -------------------------------------------------- -* List of Customer Invoice to Approve -* Company Analysis -* Graph of Aged Receivables -* Graph of Treasury + * List of Customer Invoice to Approve + * Company Analysis + * Graph of Treasury -The processes like maintaining of general ledger is done through the defined financial Journals (entry move line or -grouping is maintained through journal) for a particular financial year and for preparation of vouchers there is a -module named account_voucher. +The processes like maintaining of general ledger is done through the defined financial Journals (entry move line orgrouping is maintained through journal) for a particular +financial year and for preparation of vouchers there is a 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'], @@ -99,6 +97,7 @@ module named account_voucher. 'wizard/account_reconcile_partner_process_view.xml', 'wizard/account_automatic_reconcile_view.xml', 'wizard/account_financial_report_view.xml', + 'wizard/pos_box.xml', 'project/wizard/project_account_analytic_line_view.xml', 'account_end_fy.xml', 'account_invoice_view.xml', @@ -145,8 +144,8 @@ module named account_voucher. 'test/account_use_model.yml', 'test/account_validate_account_move.yml', 'test/account_fiscalyear_close.yml', - 'test/account_bank_statement.yml', - 'test/account_cash_statement.yml', + #'test/account_bank_statement.yml', + #'test/account_cash_statement.yml', 'test/test_edi_invoice.yml', 'test/account_report.yml', 'test/account_fiscalyear_close_state.yml', #last test, as it will definitively close the demo fiscalyear diff --git a/addons/account/account.py b/addons/account/account.py index 571ea40698d..cbba0237c78 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -29,6 +29,8 @@ import pooler from osv import fields, osv import decimal_precision as dp from tools.translate import _ +from tools.float_utils import float_round + _logger = logging.getLogger(__name__) def check_cycle(self, cr, uid, ids, context=None): @@ -101,7 +103,7 @@ class account_payment_term_line(osv.osv): 'value': fields.selection([('procent', 'Percent'), ('balance', 'Balance'), ('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."""), + 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 treated."""), '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." \ @@ -313,8 +315,8 @@ class account_account(osv.osv): cr.execute(request, params) _logger.debug('Status: %s',(cr.statusmessage)) - for res in cr.dictfetchall(): - accounts[res['id']] = res + for row in cr.dictfetchall(): + accounts[row['id']] = row # consolidate accounts with direct children children_and_consolidated.reverse() @@ -474,7 +476,7 @@ class account_account(osv.osv): 'shortcut': fields.char('Shortcut', size=12), 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel', 'account_id', 'tax_id', 'Default Taxes'), - 'note': fields.text('Note'), + 'note': fields.text('Internal Notes'), 'company_currency_id': fields.function(_get_company_currency, type='many2one', relation='res.currency', string='Company Currency'), 'company_id': fields.many2one('res.company', 'Company', required=True), 'active': fields.boolean('Active', select=2, help="If the active field is set to False, it will allow you to hide the account without removing it."), @@ -714,6 +716,7 @@ class account_journal(osv.osv): _name = "account.journal" _description = "Journal" _columns = { + 'with_last_closing_balance' : fields.boolean('Opening With Last Closing Balance'), '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, @@ -730,16 +733,21 @@ class account_journal(osv.osv): 'centralisation': fields.boolean('Centralised counterpart', help="Check this box to determine that each entry of this journal won't create a new counterpart but will share the same counterpart. This is used in fiscal year closing."), 'update_posted': fields.boolean('Allow Cancelling Entries', help="Check this box if you want to allow the cancellation the entries related to this journal or of the invoice related to this journal"), 'group_invoice_lines': fields.boolean('Group Invoice Lines', help="If this box is checked, the system will try to group the accounting lines when generating them from invoices."), - 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="This field contains the informatin related to the numbering of the journal entries of this journal.", required=True), + 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="This field contains the information related to the numbering of the journal entries of this journal.", required=True), 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"), 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'), '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 in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'), + + 'profit_account_id' : fields.many2one('account.account', 'Profit Account'), + 'loss_account_id' : fields.many2one('account.account', 'Loss Account'), + 'internal_account_id' : fields.many2one('account.account', 'Internal Transfers Account', select=1), } _defaults = { + 'with_last_closing_balance' : False, 'user_id': lambda self, cr, uid, context: uid, 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, } @@ -1263,7 +1271,7 @@ class account_move(osv.osv): 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}), 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'Status', required=True, readonly=True, - help='All manually created new journal entries are usually in the state \'Unposted\', but you can set the option to skip that state on the related journal. In that case, they will be behave as journal entries automatically created by the system on document validation (invoices, bank statements...) and will be created in \'Posted\' state.'), + help='All manually created new journal entries are usually in the state \'Unposted\', but you can set the option to skip that state on the related journal. In that case, they will behave as journal entries automatically created by the system on document validation (invoices, bank statements...) and will be created in \'Posted\' state.'), 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}), 'to_check': fields.boolean('To Review', help='Check this box if you are unsure of that journal entry and if you want to note it as \'to be reviewed\' by an accounting expert.'), 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True), @@ -1782,7 +1790,7 @@ class account_tax_code(osv.osv): 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'), 'company_id': fields.many2one('res.company', 'Company', required=True), 'sign': fields.float('Coefficent for parent', required=True, help='You can specify here the coefficient that will be used when consolidating the amount of this case into its parent. For example, set 1/-1 if you want to add/substract it.'), - 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"), + 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any tax related to this tax code to appear on invoices"), 'sequence': fields.integer('Sequence', help="Determine the display order in the report 'Accounting \ Reporting \ Generic Reporting \ Taxes \ Taxes Report'"), } @@ -1874,17 +1882,17 @@ class account_tax(osv.osv): 'python_applicable':fields.text('Python Code'), # - # Fields used for the VAT declaration + # Fields used for the Tax declaration # - 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."), - 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."), + 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the tax declaration."), + 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the tax declaration."), 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."), 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."), # Same fields for refund invoices - 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."), - 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."), + 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the tax declaration."), + 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the tax declaration."), 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."), 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."), 'include_base_amount': fields.boolean('Included in base amount', help="Indicates if the amount of tax must be included in the base amount for the computation of the next taxes"), @@ -2084,7 +2092,7 @@ class account_tax(osv.osv): tax_compute_precision = precision if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally': tax_compute_precision += 5 - totalin = totalex = round(price_unit * quantity, precision) + totalin = totalex = float_round(price_unit * quantity, precision) tin = [] tex = [] for tax in taxes: @@ -2211,7 +2219,7 @@ class account_tax(osv.osv): def compute_inv(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None): """ Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID. - Price Unit is a VAT included price + Price Unit is a Tax included price RETURN: [ tax ] @@ -2667,7 +2675,7 @@ class account_tax_code_template(osv.osv): 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True), 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'), 'sign': fields.float('Sign For Parent', required=True), - 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"), + 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any tax related to this tax Code to appear on invoices."), } _defaults = { @@ -2782,17 +2790,17 @@ class account_tax_template(osv.osv): 'python_applicable':fields.text('Python Code'), # - # Fields used for the VAT declaration + # Fields used for the Tax declaration # - 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."), - 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."), + 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the tax declaration."), + 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the tax declaration."), 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."), 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."), # Same fields for refund invoices - 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."), - 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."), + 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the tax declaration."), + 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the tax declaration."), 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."), 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."), 'include_base_amount': fields.boolean('Include in Base Amount', help="Set if the amount of tax must be included in the base amount before computing the next taxes."), @@ -3345,15 +3353,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): # Create Bank journals self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context) - action = { - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'board.board', - 'view_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'board_account_form')[1], - 'menu_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'menu_finance')[1] - } - return action + return {} def _prepare_bank_journal(self, cr, uid, line, current_num, default_account_id, company_id, context=None): ''' diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 1f47a6be9bd..11bcf565260 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -26,24 +26,18 @@ from tools.translate import _ import decimal_precision as dp class account_bank_statement(osv.osv): - def create(self, cr, uid, vals, context=None): - seq = 0 if 'line_ids' in vals: - new_line_ids = [] - for line in vals['line_ids']: - seq += 1 - line[2]['sequence'] = seq + for idx, line in enumerate(vals['line_ids']): + line[2]['sequence'] = idx + 1 return super(account_bank_statement, self).create(cr, uid, vals, context=context) def write(self, cr, uid, ids, vals, context=None): res = super(account_bank_statement, self).write(cr, uid, ids, vals, context=context) account_bank_statement_line_obj = self.pool.get('account.bank.statement.line') for statement in self.browse(cr, uid, ids, context): - seq = 0 - for line in statement.line_ids: - seq += 1 - account_bank_statement_line_obj.write(cr, uid, [line.id], {'sequence': seq}, context=context) + for idx, line in enumerate(statement.line_ids): + account_bank_statement_line_obj.write(cr, uid, [line.id], {'sequence': idx + 1}, context=context) return res def _default_journal_id(self, cr, uid, context=None): @@ -51,45 +45,19 @@ class account_bank_statement(osv.osv): context = {} journal_pool = self.pool.get('account.journal') journal_type = context.get('journal_type', False) - journal_id = False company_id = self.pool.get('res.company')._company_default_get(cr, uid, 'account.bank.statement',context=context) if journal_type: ids = journal_pool.search(cr, uid, [('type', '=', journal_type),('company_id','=',company_id)]) if ids: - journal_id = ids[0] - return journal_id + return ids[0] + return False 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: + for statement in self.browse(cursor, user, ids, context=context): 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) + for line in statement.line_ids: + res[statement.id] += line.amount return res def _get_period(self, cr, uid, context=None): @@ -129,7 +97,7 @@ class account_bank_statement(osv.osv): _description = "Bank Statement" _inherit = ['mail.thread'] _columns = { - '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 + 'name': fields.char('Reference', size=64, required=True, states={'draft': [('readonly', False)]}, readonly=True, help='if you give the Name other then /, its created Accounting Entries Move will be with same name as statement name. This allows the statement entries to have the same references than the statement itself'), # readonly for account_cash_statement 'date': fields.date('Date', required=True, states={'confirm': [('readonly', True)]}, select=True), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}), @@ -141,7 +109,7 @@ class account_bank_statement(osv.osv): 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': (lambda self, cr, uid, ids, c={}: ids, ['line_ids','move_line_ids','balance_start'], 10), 'account.bank.statement.line': (_get_statement, ['amount'], 10), }, string="Computed Balance", help='Balance as calculated based on Starting Balance and transaction lines'), @@ -205,7 +173,158 @@ class account_bank_statement(osv.osv): def button_dummy(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {}, context=context) + def _prepare_move(self, cr, uid, st_line, st_line_number, context=None): + """Prepare the dict of values to create the move from a + statement line. This method may be overridden to implement custom + move generation (making sure to call super() to establish + a clean extension chain). + + :param browse_record st_line: account.bank.statement.line record to + create the move from. + :param char st_line_number: will be used as the name of the generated account move + :return: dict of value to create() the account.move + """ + return { + 'journal_id': st_line.statement_id.journal_id.id, + 'period_id': st_line.statement_id.period_id.id, + 'date': st_line.date, + 'name': st_line_number, + 'ref': st_line.ref, + } + + def _prepare_bank_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, + context=None): + """Compute the args to build the dict of values to create the bank move line from a + statement line by calling the _prepare_move_line_vals. This method may be + overridden to implement custom move generation (making sure to call super() to + establish a clean extension chain). + + :param browse_record st_line: account.bank.statement.line record to + create the move from. + :param int/long move_id: ID of the account.move to link the move line + :param float amount: amount of the move line + :param int/long company_currency_id: ID of currency of the concerned company + :return: dict of value to create() the bank account.move.line + """ + anl_id = st_line.analytic_account_id and st_line.analytic_account_id.id or False + debit = ((amount<0) and -amount) or 0.0 + credit = ((amount>0) and amount) or 0.0 + cur_id = False + amt_cur = False + if st_line.statement_id.currency.id <> company_currency_id: + cur_id = st_line.statement_id.currency.id + if st_line.account_id and st_line.account_id.currency_id and st_line.account_id.currency_id.id <> company_currency_id: + cur_id = st_line.account_id.currency_id.id + if cur_id: + res_currency_obj = self.pool.get('res.currency') + amt_cur = -res_currency_obj.compute(cr, uid, company_currency_id, cur_id, amount, context=context) + + res = self._prepare_move_line_vals(cr, uid, st_line, move_id, debit, credit, + amount_currency=amt_cur, currency_id=cur_id, analytic_id=anl_id, context=context) + return res + + def _get_counter_part_account(sefl, cr, uid, st_line, context=None): + """Retrieve the account to use in the counterpart move. + This method may be overridden to implement custom move generation (making sure to + call super() to establish a clean extension chain). + + :param browse_record st_line: account.bank.statement.line record to + create the move from. + :return: int/long of the account.account to use as counterpart + """ + if st_line.amount >= 0: + return st_line.statement_id.journal_id.default_credit_account_id.id + return st_line.statement_id.journal_id.default_debit_account_id.id + + def _get_counter_part_partner(sefl, cr, uid, st_line, context=None): + """Retrieve the partner to use in the counterpart move. + This method may be overridden to implement custom move generation (making sure to + call super() to establish a clean extension chain). + + :param browse_record st_line: account.bank.statement.line record to + create the move from. + :return: int/long of the res.partner to use as counterpart + """ + return st_line.partner_id and st_line.partner_id.id or False + + def _prepare_counterpart_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, + context=None): + """Compute the args to build the dict of values to create the counter part move line from a + statement line by calling the _prepare_move_line_vals. This method may be + overridden to implement custom move generation (making sure to call super() to + establish a clean extension chain). + + :param browse_record st_line: account.bank.statement.line record to + create the move from. + :param int/long move_id: ID of the account.move to link the move line + :param float amount: amount of the move line + :param int/long account_id: ID of account to use as counter part + :param int/long company_currency_id: ID of currency of the concerned company + :return: dict of value to create() the bank account.move.line + """ + account_id = self._get_counter_part_account(cr, uid, st_line, context=context) + partner_id = self._get_counter_part_partner(cr, uid, st_line, context=context) + debit = ((amount > 0) and amount) or 0.0 + credit = ((amount < 0) and -amount) or 0.0 + cur_id = False + amt_cur = False + if st_line.statement_id.currency.id <> company_currency_id: + amt_cur = st_line.amount + cur_id = st_line.statement_id.currency.id + return self._prepare_move_line_vals(cr, uid, st_line, move_id, debit, credit, + amount_currency = amt_cur, currency_id = cur_id, account_id = account_id, + partner_id = partner_id, context=context) + + def _prepare_move_line_vals(self, cr, uid, st_line, move_id, debit, credit, currency_id = False, + amount_currency= False, account_id = False, analytic_id = False, + partner_id = False, context=None): + """Prepare the dict of values to create the move line from a + statement line. All non-mandatory args will replace the default computed one. + This method may be overridden to implement custom move generation (making sure to + call super() to establish a clean extension chain). + + :param browse_record st_line: account.bank.statement.line record to + create the move from. + :param int/long move_id: ID of the account.move to link the move line + :param float debit: debit amount of the move line + :param float credit: credit amount of the move line + :param int/long currency_id: ID of currency of the move line to create + :param float amount_currency: amount of the debit/credit expressed in the currency_id + :param int/long account_id: ID of the account to use in the move line if different + from the statement line account ID + :param int/long analytic_id: ID of analytic account to put on the move line + :param int/long partner_id: ID of the partner to put on the move line + :return: dict of value to create() the account.move.line + """ + acc_id = account_id or st_line.account_id.id + cur_id = currency_id or st_line.statement_id.currency.id + par_id = partner_id or (((st_line.partner_id) and st_line.partner_id.id) or False) + return { + 'name': st_line.name, + 'date': st_line.date, + 'ref': st_line.ref, + 'move_id': move_id, + 'partner_id': partner_id, + 'account_id': acc_id, + 'credit': credit, + 'debit': debit, + 'statement_id': st_line.statement_id.id, + 'journal_id': st_line.statement_id.journal_id.id, + 'period_id': st_line.statement_id.period_id.id, + 'currency_id': cur_id, + 'amount_currency': amount_currency, + 'analytic_account_id': analytic_id, + } + def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, st_line_number, context=None): + """Create the account move from the statement line. + + :param int/long st_line_id: ID of the account.bank.statement.line to create the move from. + :param int/long company_currency_id: ID of the res.currency of the company + :param char st_line_number: will be used as the name of the generated account move + :return: ID of the account.move created + """ + if context is None: context = {} res_currency_obj = self.pool.get('res.currency') @@ -217,82 +336,28 @@ class account_bank_statement(osv.osv): context.update({'date': st_line.date}) - move_id = account_move_obj.create(cr, uid, { - 'journal_id': st.journal_id.id, - 'period_id': st.period_id.id, - 'date': st_line.date, - 'name': st_line_number, - 'ref': st_line.ref, - }, context=context) + move_vals = self._prepare_move(cr, uid, st_line, st_line_number, context=context) + move_id = account_move_obj.create(cr, uid, move_vals, context=context) account_bank_statement_line_obj.write(cr, uid, [st_line.id], { 'move_ids': [(4, move_id, False)] }) - torec = [] - if st_line.amount >= 0: - account_id = st.journal_id.default_credit_account_id.id - else: - account_id = st.journal_id.default_debit_account_id.id - acc_cur = ((st_line.amount<=0) and st.journal_id.default_debit_account_id) or st_line.account_id + context.update({ 'res.currency.compute.account': acc_cur, }) amount = res_currency_obj.compute(cr, uid, st.currency.id, company_currency_id, st_line.amount, context=context) - val = { - 'name': st_line.name, - 'date': st_line.date, - 'ref': st_line.ref, - 'move_id': move_id, - 'partner_id': ((st_line.partner_id) and st_line.partner_id.id) or False, - 'account_id': (st_line.account_id) and st_line.account_id.id, - 'credit': ((amount>0) and amount) or 0.0, - 'debit': ((amount<0) and -amount) or 0.0, - 'statement_id': st.id, - 'journal_id': st.journal_id.id, - 'period_id': st.period_id.id, - 'currency_id': st.currency.id, - 'analytic_account_id': st_line.analytic_account_id and st_line.analytic_account_id.id or False - } - - if st.currency.id <> company_currency_id: - amount_cur = res_currency_obj.compute(cr, uid, company_currency_id, - st.currency.id, amount, context=context) - val['amount_currency'] = -amount_cur - - if st_line.account_id and st_line.account_id.currency_id and st_line.account_id.currency_id.id <> company_currency_id: - val['currency_id'] = st_line.account_id.currency_id.id - amount_cur = res_currency_obj.compute(cr, uid, company_currency_id, - st_line.account_id.currency_id.id, amount, context=context) - val['amount_currency'] = -amount_cur - - move_line_id = account_move_line_obj.create(cr, uid, val, context=context) + bank_move_vals = self._prepare_bank_move_line(cr, uid, st_line, move_id, amount, + company_currency_id, context=context) + move_line_id = account_move_line_obj.create(cr, uid, bank_move_vals, context=context) torec.append(move_line_id) - # Fill the secondary amount/currency - # if currency is not the same than the company - amount_currency = False - currency_id = False - if st.currency.id <> company_currency_id: - amount_currency = st_line.amount - currency_id = st.currency.id - account_move_line_obj.create(cr, uid, { - 'name': st_line.name, - 'date': st_line.date, - 'ref': st_line.ref, - 'move_id': move_id, - 'partner_id': ((st_line.partner_id) and st_line.partner_id.id) or False, - 'account_id': account_id, - 'credit': ((amount < 0) and -amount) or 0.0, - 'debit': ((amount > 0) and amount) or 0.0, - 'statement_id': st.id, - 'journal_id': st.journal_id.id, - 'period_id': st.period_id.id, - 'amount_currency': amount_currency, - 'currency_id': currency_id, - }, context=context) + counterpart_move_vals = self._prepare_counterpart_move_line(cr, uid, st_line, move_id, + amount, company_currency_id, context=context) + account_move_line_obj.create(cr, uid, counterpart_move_vals, context=context) for line in account_move_line_obj.browse(cr, uid, [x.id for x in account_move_obj.browse(cr, uid, move_id, @@ -311,7 +376,7 @@ class account_bank_statement(osv.osv): def balance_check(self, cr, uid, st_id, journal_type='bank', context=None): st = self.browse(cr, uid, st_id, context=context) - if not ((abs((st.balance_end or 0.0) - st.balance_end_real) < 0.0001) or (abs((st.balance_end or 0.0) - st.balance_end_cash) < 0.0001)): + if not ((abs((st.balance_end or 0.0) - st.balance_end_real) < 0.0001) or (abs((st.balance_end or 0.0) - st.balance_end_real) < 0.0001)): raise osv.except_osv(_('Error !'), _('The statement balance is incorrect !\nThe expected balance (%.2f) is different than the computed one. (%.2f)') % (st.balance_end_real, st.balance_end)) return True @@ -380,14 +445,18 @@ class account_bank_statement(osv.osv): account_move_obj.unlink(cr, uid, ids, context) done.append(st.id) return self.write(cr, uid, done, {'state':'draft'}, context=context) - - def onchange_journal_id(self, cr, uid, statement_id, journal_id, context=None): + + def _compute_balance_end_real(self, cr, uid, journal_id, context=None): cr.execute('SELECT balance_end_real \ FROM account_bank_statement \ WHERE journal_id = %s AND NOT state = %s \ ORDER BY date DESC,id DESC LIMIT 1', (journal_id, 'draft')) res = cr.fetchone() - balance_start = res and res[0] or 0.0 + return res and res[0] or 0.0 + + def onchange_journal_id(self, cr, uid, statement_id, journal_id, context=None): + balance_start = self._compute_balance_end_real(cr, uid, journal_id, context=context) + journal_data = self.pool.get('account.journal').read(cr, uid, journal_id, ['default_debit_account_id', 'company_id'], context=context) account_id = journal_data['default_debit_account_id'] company_id = journal_data['company_id'] diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index f19c1e1c639..927ce94db31 100644 --- a/addons/account/account_cash_statement.py +++ b/addons/account/account_cash_statement.py @@ -43,24 +43,27 @@ class account_cashbox_line(osv.osv): """ res = {} for obj in self.browse(cr, uid, ids, context=context): - res[obj.id] = obj.pieces * obj.number + res[obj.id] = { + 'subtotal_opening' : obj.pieces * obj.number_opening, + 'subtotal_closing' : obj.pieces * obj.number_closing, + } return res - def on_change_sub(self, cr, uid, ids, pieces, number, *a): + def on_change_sub_opening(self, cr, uid, ids, pieces, number, *a): + """ Compute the subtotal for the opening """ + return {'value' : {'subtotal_opening' : (pieces * number) or 0.0 }} - """ Calculates Sub total on change of number - @param pieces: Names of fields. - @param number: - """ - sub = pieces * number - return {'value': {'subtotal': sub or 0.0}} + def on_change_sub_closing(self, cr, uid, ids, pieces, number, *a): + """ Compute the subtotal for the closing """ + return {'value' : {'subtotal_closing' : (pieces * number) or 0.0 }} _columns = { - 'pieces': fields.float('Values', digits_compute=dp.get_precision('Account')), - 'number': fields.integer('Number'), - 'subtotal': fields.function(_sub_total, string='Sub Total', type='float', digits_compute=dp.get_precision('Account')), - 'starting_id': fields.many2one('account.bank.statement', ondelete='cascade'), - 'ending_id': fields.many2one('account.bank.statement', ondelete='cascade'), + 'pieces': fields.float('Unit of Currency', digits_compute=dp.get_precision('Account')), + 'number_opening' : fields.integer('Number of Units', help='Opening Unit Numbers'), + 'number_closing' : fields.integer('Number of Units', help='Closing Unit Numbers'), + 'subtotal_opening': fields.function(_sub_total, string='Opening Subtotal', type='float', digits_compute=dp.get_precision('Account'), multi='subtotal'), + 'subtotal_closing': fields.function(_sub_total, string='Closing Subtotal', type='float', digits_compute=dp.get_precision('Account'), multi='subtotal'), + 'bank_statement_id' : fields.many2one('account.bank.statement', ondelete='cascade'), } account_cashbox_line() @@ -69,39 +72,24 @@ class account_cash_statement(osv.osv): _inherit = 'account.bank.statement' - def _get_starting_balance(self, cr, uid, ids, context=None): - - """ Find starting balance - @param name: Names of fields. - @param arg: User defined arguments - @return: Dictionary of values. + def _update_balances(self, cr, uid, ids, context=None): + """ + Set starting and ending balances according to pieces count """ res = {} for statement in self.browse(cr, uid, ids, context=context): - amount_total = 0.0 - - if statement.journal_id.type not in('cash'): + if statement.journal_id.type not in ('cash',): continue - - for line in statement.starting_details_ids: - amount_total+= line.pieces * line.number - res[statement.id] = { - 'balance_start': amount_total + start = end = 0 + for line in statement.details_ids: + start += line.subtotal_opening + end += line.subtotal_closing + data = { + 'balance_start': start, + 'balance_end_real': end, } - return res - - def _balance_end_cash(self, cr, uid, ids, name, arg, context=None): - """ Find ending balance " - @param name: Names of fields. - @param arg: User defined arguments - @return: Dictionary of values. - """ - res = {} - for statement in self.browse(cr, uid, ids, context=context): - amount_total = 0.0 - for line in statement.ending_details_ids: - amount_total += line.pieces * line.number - res[statement.id] = amount_total + res[statement.id] = data + super(account_cash_statement, self).write(cr, uid, [statement.id], data, context=context) return res def _get_sum_entry_encoding(self, cr, uid, ids, name, arg, context=None): @@ -111,13 +99,10 @@ class account_cash_statement(osv.osv): @param arg: User defined arguments @return: Dictionary of values. """ - res2 = {} + res = {} for statement in self.browse(cr, uid, ids, context=context): - encoding_total=0.0 - for line in statement.line_ids: - encoding_total += line.amount - res2[statement.id] = encoding_total - return res2 + res[statement.id] = sum((line.amount for line in statement.line_ids), 0.0) + return res def _get_company(self, cr, uid, context=None): user_pool = self.pool.get('res.users') @@ -128,96 +113,98 @@ class account_cash_statement(osv.osv): company_id = company_pool.search(cr, uid, []) return company_id and company_id[0] or False - def _get_cash_open_box_lines(self, cr, uid, context=None): - res = [] - curr = [1, 2, 5, 10, 20, 50, 100, 500] - for rs in curr: - dct = { - 'pieces': rs, - 'number': 0 - } - res.append(dct) - journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'cash')], context=context) - if journal_ids: - results = self.search(cr, uid, [('journal_id', 'in', journal_ids),('state', '=', 'confirm')], context=context) - if results: - cash_st = self.browse(cr, uid, results, context=context)[0] - for cash_line in cash_st.ending_details_ids: - for r in res: - if cash_line.pieces == r['pieces']: - r['number'] = cash_line.number - return res - - def _get_default_cash_close_box_lines(self, cr, uid, context=None): - res = [] - curr = [1, 2, 5, 10, 20, 50, 100, 500] - for rs in curr: - dct = { - 'pieces': rs, - 'number': 0 - } - res.append(dct) - return res - - def _get_cash_close_box_lines(self, cr, uid, context=None): - res = [] - curr = [1, 2, 5, 10, 20, 50, 100, 500] - for rs in curr: - dct = { - 'pieces': rs, - 'number': 0 - } - res.append((0, 0, dct)) - return res - - def _get_cash_open_close_box_lines(self, cr, uid, context=None): - res = {} - start_l = [] - end_l = [] - starting_details = self._get_cash_open_box_lines(cr, uid, context=context) - ending_details = self._get_default_cash_close_box_lines(cr, uid, context) - for start in starting_details: - start_l.append((0, 0, start)) - for end in ending_details: - end_l.append((0, 0, end)) - res['start'] = start_l - res['end'] = end_l - return res - - def _get_statement(self, cr, uid, ids, context=None): + def _get_statement_from_line(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() + def _compute_difference(self, cr, uid, ids, fieldnames, args, context=None): + result = dict.fromkeys(ids, 0.0) + + for obj in self.browse(cr, uid, ids, context=context): + result[obj.id] = obj.balance_end_real - obj.balance_end + + return result + + def _compute_last_closing_balance(self, cr, uid, ids, fieldnames, args, context=None): + result = dict.fromkeys(ids, 0.0) + + for obj in self.browse(cr, uid, ids, context=context): + if obj.state == 'draft': + statement_ids = self.search(cr, uid, + [('journal_id', '=', obj.journal_id.id),('state', '=', 'confirm')], + order='create_date desc', + limit=1, + context=context + ) + + if not statement_ids: + continue + else: + st = self.browse(cr, uid, statement_ids[0], context=context) + result[obj.id] = st.balance_end_real + + return result + + def onchange_journal_id(self, cr, uid, ids, journal_id, context=None): + result = super(account_cash_statement, self).onchange_journal_id(cr, uid, ids, journal_id) + + if not journal_id: + return result + + statement_ids = self.search(cr, uid, + [('journal_id', '=', journal_id),('state', '=', 'confirm')], + order='create_date desc', + limit=1, + context=context + ) + + if not statement_ids: + return result + + st = self.browse(cr, uid, statement_ids[0], context=context) + result.setdefault('value', {}).update({'last_closing_balance' : st.balance_end_real}) + + return result + _columns = { - 'total_entry_encoding': fields.function(_get_sum_entry_encoding, string="Cash Transaction", help="Total cash transactions", + 'total_entry_encoding': fields.function(_get_sum_entry_encoding, string="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), + 'account.bank.statement': (lambda self, cr, uid, ids, context=None: ids, ['line_ids','move_line_ids'], 10), + 'account.bank.statement.line': (_get_statement_from_line, ['amount'], 10), }), 'closing_date': fields.datetime("Closed On"), - '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'), + 'details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='CashBox Lines'), + 'opening_details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='Opening Cashbox Lines'), + 'closing_details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='Closing Cashbox Lines'), 'user_id': fields.many2one('res.users', 'Responsible', required=False), + 'difference' : fields.function(_compute_difference, method=True, string="Difference", type="float"), + 'last_closing_balance' : fields.function(_compute_last_closing_balance, method=True, string='Last Closing Balance', type='float'), } _defaults = { 'state': 'draft', - 'date': lambda self,cr,uid,context={}: context.get('date', 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): - if self.pool.get('account.journal').browse(cr, uid, vals['journal_id'], context=context).type == 'cash': - amount_total = 0.0 - for line in vals.get('starting_details_ids',[]): - if line and len(line)==3 and line[2]: - amount_total+= line[2]['pieces'] * line[2]['number'] - vals.update(balance_start= amount_total) - return super(account_cash_statement, self).create(cr, uid, vals, context=context) + journal = False + if vals.get('journal_id'): + journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'], context=context) + if journal and (journal.type == 'cash') and not vals.get('details_ids'): + vals['details_ids'] = [] + for value in journal.cashbox_line_ids: + nested_values = { + 'number_closing' : 0, + 'number_opening' : 0, + 'pieces' : value.pieces + } + vals['details_ids'].append([0, False, nested_values]) + + res_id = super(account_cash_statement, self).create(cr, uid, vals, context=context) + self._update_balances(cr, uid, [res_id], context) + return res_id def write(self, cr, uid, ids, vals, context=None): """ @@ -233,34 +220,9 @@ class account_cash_statement(osv.osv): @return: True on success, False otherwise """ - super(account_cash_statement, self).write(cr, uid, ids, vals, context=context) - res = self._get_starting_balance(cr, uid, ids) - for rs in res: - super(account_cash_statement, self).write(cr, uid, [rs], res.get(rs)) - return True - - def onchange_journal_id(self, cr, uid, statement_id, journal_id, context=None): - """ Changes balance start and starting details if journal_id changes" - @param statement_id: Changed statement_id - @param journal_id: Changed journal_id - @return: Dictionary of changed values - """ - res = {} - balance_start = 0.0 - if not journal_id: - res.update({ - 'balance_start': balance_start - }) - return res - return super(account_cash_statement, self).onchange_journal_id(cr, uid, statement_id, journal_id, context=context) - - def _equal_balance(self, cr, uid, cash_id, context=None): - statement = self.browse(cr, uid, cash_id, context=context) - self.write(cr, uid, [cash_id], {'balance_end_real': statement.balance_end}) - statement.balance_end_real = statement.balance_end - if statement.balance_end != statement.balance_end_cash: - return False - return True + res = super(account_cash_statement, self).write(cr, uid, ids, vals, context=context) + self._update_balances(cr, uid, ids, context) + return res def _user_allow(self, cr, uid, statement_id, context=None): return True @@ -276,7 +238,7 @@ class account_cash_statement(osv.osv): for statement in statement_pool.browse(cr, uid, ids, context=context): vals = {} if not self._user_allow(cr, uid, statement.id, context=context): - raise osv.except_osv(_('Error !'), (_('User %s does not have rights to access %s journal !') % (statement.user_id.name, statement.journal_id.name))) + raise osv.except_osv(_('Error !'), (_('You do not have rights to open this %s journal !') % (statement.journal_id.name, ))) if statement.name and statement.name == '/': c = {'fiscalyear_id': statement.period_id.fiscalyear_id.id} @@ -294,13 +256,6 @@ class account_cash_statement(osv.osv): self.write(cr, uid, [statement.id], vals, context=context) return True - def balance_check(self, cr, uid, cash_id, journal_type='bank', context=None): - 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 !'), _('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): if journal_type == 'bank': return super(account_cash_statement, self).statement_close(cr, uid, ids, journal_type, context) @@ -317,16 +272,67 @@ class account_cash_statement(osv.osv): def button_confirm_cash(self, cr, uid, ids, context=None): super(account_cash_statement, self).button_confirm_bank(cr, uid, ids, context=context) - return self.write(cr, uid, ids, {'closing_date': time.strftime("%Y-%m-%d %H:%M:%S")}, context=context) + absl_proxy = self.pool.get('account.bank.statement.line') - def button_cancel(self, cr, uid, ids, context=None): - cash_box_line_pool = self.pool.get('account.cashbox.line') - super(account_cash_statement, self).button_cancel(cr, uid, ids, context=context) - for st in self.browse(cr, uid, ids, context): - for end in st.ending_details_ids: - cash_box_line_pool.write(cr, uid, [end.id], {'number': 0}) - return True + TABLES = (('Profit', 'profit_account_id'), ('Loss', 'loss_account_id'),) + + for obj in self.browse(cr, uid, ids, context=context): + if obj.difference == 0.0: + continue + + for item_label, item_account in TALBES: + if getattr(obj.journal_id, item_account): + raise osv.except_osv(_('Error !'), + _('There is no %s Account on the Journal %s') % (item_label, obj.journal_id.name,)) + + is_profit = obj.difference < 0.0 + + account = getattr(obj.journal_id, TABLES[is_profit][1]) + + values = { + 'statement_id' : obj.id, + 'journal_id' : obj.journal_id.id, + 'account_id' : account.id, + 'amount' : obj.difference, + 'name' : 'Exceptional %s' % TABLES[is_profit][0], + } + + absl_proxy.create(cr, uid, values, context=context) + + return self.write(cr, uid, ids, {'closing_date': time.strftime("%Y-%m-%d %H:%M:%S")}, context=context) account_cash_statement() +class account_journal(osv.osv): + _inherit = 'account.journal' + + def _default_cashbox_line_ids(self, cr, uid, context=None): + # Return a list of coins in Euros. + result = [ + dict(pieces=value) for value in [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500] + ] + return result + + _columns = { + 'cashbox_line_ids' : fields.one2many('account.journal.cashbox.line', 'journal_id', 'CashBox'), + } + + _defaults = { + 'cashbox_line_ids' : _default_cashbox_line_ids, + } + +account_journal() + +class account_journal_cashbox_line(osv.osv): + _name = 'account.journal.cashbox.line' + _rec_name = 'value' + _columns = { + 'pieces': fields.float('Values', digits_compute=dp.get_precision('Account')), + 'journal_id' : fields.many2one('account.journal', 'Journal', required=True, select=1), + } + + _order = 'pieces asc' + +account_journal_cashbox_line() + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 211a40f030f..393c468c952 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -177,11 +177,11 @@ domain="[('supplier', '=', True)]"/> -