diff --git a/CHANGELOG b/CHANGELOG index 0d47c34b0a..4f77e63a23 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add company on statement + Version 1.8.0 - 2010-11-01 * Bug fixes (see mercurial logs for details) diff --git a/statement.py b/statement.py index 668c67896e..9498e7304f 100644 --- a/statement.py +++ b/statement.py @@ -2,8 +2,9 @@ #this repository contains the full copyright notices and license terms. from decimal import Decimal from trytond.model import ModelWorkflow, ModelView, ModelSQL, fields -from trytond.pyson import Not, Equal, Eval, Or, Bool, Get, If +from trytond.pyson import Not, Equal, Eval, Or, Bool, Get, If, In from trytond.transaction import Transaction +from trytond.backend import TableHandler _STATES = {'readonly': Not(Equal(Eval('state'), 'draft'))} @@ -13,13 +14,21 @@ class Statement(ModelWorkflow, ModelSQL, ModelView): _name = 'account.statement' _description = __doc__ + company = fields.Many2One('company.company', 'Company', required=True, + select=1, states=_STATES, domain=[ + ('id', If(In('company', Eval('context', {})), '=', '!='), + Get(Eval('context', {}), 'company', 0)), + ]) journal = fields.Many2One('account.statement.journal', 'Journal', required=True, - states={ - 'readonly': Or(Not(Equal(Eval('state'), 'draft')), - Bool(Eval('lines'))), - }, on_change=['journal'], select=1) + domain=[ + ('company', '=', Eval('company')), + ], + states={ + 'readonly': Or(Not(Equal(Eval('state'), 'draft')), + Bool(Eval('lines'))), + }, on_change=['journal'], select=1) currency_digits = fields.Function(fields.Integer('Currency Digits', - on_change_with=['journal']), 'get_currency_digits') + on_change_with=['company']), 'get_currency_digits') date = fields.Date('Date', required=True, states=_STATES, select=1) start_balance = fields.Numeric('Start Balance', digits=(16, Eval('currency_digits', 2)), @@ -30,8 +39,8 @@ class Statement(ModelWorkflow, ModelSQL, ModelView): lines = fields.One2Many('account.statement.line', 'statement', 'Transactions', states={ 'readonly': Or(Not(Equal(Eval('state'), 'draft')), - Not(Bool(Eval('journal')))), - }, on_change=['lines', 'journal']) + Not(Bool(Eval('company')))), + }, on_change=['lines', 'company']) state = fields.Selection([ ('draft', 'Draft'), ('validated', 'Validated'), @@ -51,6 +60,33 @@ class Statement(ModelWorkflow, ModelSQL, ModelView): 'wrong_end_balance': 'End Balance must be %s!', }) + def init(self, module_name): + cursor = Transaction().cursor + + # Migration from 1.8: new field company + table = TableHandler(cursor, self, module_name) + company_exist = table.column_exist('company') + + super(Statement, self).init(module_name) + + # Migration from 1.8: fill new field company + if not company_exist: + offset = 0 + limit = cursor.IN_MAX + statement_ids = True + while statement_ids: + statement_ids = self.search([], offset=offset, limit=limit) + offset += limit + for statement in self.browse(statement_ids): + self.write(statement.id, { + 'company': statement.journal.company.id, + }) + table = TableHandler(cursor, self, module_name) + table.not_null_action('company', action='add') + + def default_company(self): + return Transaction().context.get('company') or False + def default_state(self): return 'draft' @@ -83,16 +119,16 @@ class Statement(ModelWorkflow, ModelSQL, ModelView): return res def on_change_with_currency_digits(self, vals): - journal_obj = self.pool.get('account.statement.journal') - if vals.get('journal'): - journal = journal_obj.browse(vals['journal']) - return journal.currency.digits + company_obj = self.pool.get('company.company') + if vals.get('company'): + company = company_obj.browse(vals['company']) + return company.currency.digits return 2 def get_currency_digits(self, ids, name): res = {} for statement in self.browse(ids): - res[statement.id] = statement.journal.currency.digits + res[statement.id] = statement.company.currency.digits return res def get_rec_name(self, ids, name): @@ -113,10 +149,10 @@ class Statement(ModelWorkflow, ModelSQL, ModelView): for statement in self.browse(ids): res[statement.id] = statement.journal.name + ' ' + \ lang.currency(lang, statement.start_balance, - statement.journal.currency, symbol=False, + statement.company.currency, symbol=False, grouping=True) + \ lang.currency(lang, statement.end_balance, - statement.journal.currency, symbol=False, + statement.company.currency, symbol=False, grouping=True) return res @@ -154,13 +190,13 @@ class Statement(ModelWorkflow, ModelSQL, ModelView): def on_change_lines(self, values): invoice_obj = self.pool.get('account.invoice') - journal_obj = self.pool.get('account.statement.journal') + company_obj = self.pool.get('company.company') currency_obj = self.pool.get('currency.currency') res = { 'lines': {}, } - if values.get('journal') and values.get('lines'): - journal = journal_obj.browse(values['journal']) + if values.get('company') and values.get('lines'): + company = company_obj.browse(values['company']) invoice_ids = set() for line in values['lines']: if line['invoice']: @@ -169,14 +205,14 @@ class Statement(ModelWorkflow, ModelSQL, ModelView): for invoice in invoice_obj.browse(invoice_ids): invoice_id2amount_to_pay[invoice.id] = currency_obj.compute( invoice.currency, invoice.amount_to_pay, - journal.currency) + company.currency) for line in values['lines']: if line['invoice'] and line['id']: amount_to_pay = invoice_id2amount_to_pay[line['invoice']] if abs(line['amount']) > amount_to_pay: res['lines'].setdefault('update', []) - if currency_obj.is_zero(journal.currency, + if currency_obj.is_zero(company.currency, amount_to_pay): res['lines']['update'].append({ 'id': line['id'], @@ -218,7 +254,7 @@ class Statement(ModelWorkflow, ModelSQL, ModelView): lang = lang_obj.browse(lang_ids[0]) amount = lang_obj.format(lang, - '%.' + str(statement.journal.currency.digits) + 'f', + '%.' + str(statement.company.currency.digits) + 'f', computed_end_balance, True) self.raise_user_error('wrong_end_balance', error_args=(amount,)) for line in statement.lines: @@ -267,11 +303,14 @@ class Line(ModelSQL, ModelView): digits=(16, Get(Eval('_parent_statement', {}), 'currency_digits', 2)), on_change=['amount', 'party', 'account', 'invoice', - '_parent_statement.journal']) + '_parent_statement.company']) party = fields.Many2One('party.party', 'Party', on_change=['amount', 'party', 'invoice']) account = fields.Many2One('account.account', 'Account', required=True, - on_change=['account', 'invoice'], domain=[('kind', '!=', 'view')]) + on_change=['account', 'invoice'], domain=[ + ('kind', '!=', 'view'), + ('company', '=', Get(Eval('_parent_statement', {}), 'company')), + ]) description = fields.Char('Description') move = fields.Many2One('account.move', 'Account Move', readonly=True) invoice = fields.Many2One('account.invoice', 'Invoice', @@ -323,7 +362,7 @@ class Line(ModelSQL, ModelView): def on_change_amount(self, value): party_obj = self.pool.get('party.party') invoice_obj = self.pool.get('account.invoice') - journal_obj = self.pool.get('account.statement.journal') + company_obj = self.pool.get('company.company') currency_obj = self.pool.get('currency.currency') res = {} @@ -341,11 +380,11 @@ class Line(ModelSQL, ModelView): res['account'] = account.id res['account.rec_name'] = account.rec_name if value.get('invoice'): - if value.get('amount') and value.get('_parent_statement.journal'): + if value.get('amount') and value.get('_parent_statement.company'): invoice = invoice_obj.browse(value['invoice']) - journal = journal_obj.browse(value['_parent_statement.journal']) + company = company_obj.browse(value['_parent_statement.company']) amount_to_pay = currency_obj.compute(invoice.currency, - invoice.amount_to_pay, journal.currency) + invoice.amount_to_pay, company.currency) if abs(value['amount']) > amount_to_pay: res['invoice'] = False else: @@ -379,7 +418,7 @@ class Line(ModelSQL, ModelView): move_line_obj = self.pool.get('account.move.line') lang_obj = self.pool.get('ir.lang') - period_id = period_obj.find(line.statement.journal.company.id, + period_id = period_obj.find(line.statement.company.id, date=line.date) move_lines = self._get_move_lines(line) @@ -398,7 +437,7 @@ class Line(ModelSQL, ModelView): if line.invoice: amount_to_pay = currency_obj.compute(line.invoice.currency, - line.invoice.amount_to_pay, line.statement.journal.currency) + line.invoice.amount_to_pay, line.statement.company.currency) if amount_to_pay < abs(line.amount): for code in [Transaction().language, 'en_US']: lang_ids = lang_obj.search([ @@ -409,13 +448,13 @@ class Line(ModelSQL, ModelView): lang = lang_obj.browse(lang_ids[0]) amount = lang_obj.format(lang, - '%.' + str(line.statement.journal.currency.digits) + 'f', + '%.' + str(line.statement.company.currency.digits) + 'f', line.amount, True) self.raise_user_error('amount_greater_invoice_amount_to_pay', error_args=(amount,)) - amount = currency_obj.compute(line.statement.journal.currency, - line.amount, line.statement.journal.company.currency) + amount = currency_obj.compute(line.statement.company.currency, + line.amount, line.statement.company.currency) reconcile_lines = invoice_obj.get_reconcile_lines_for_amount( line.invoice, abs(amount)) @@ -452,11 +491,11 @@ class Line(ModelSQL, ModelView): currency_obj = self.pool.get('currency.currency') zero = Decimal("0.0") amount = currency_obj.compute( - statement_line.statement.journal.currency, statement_line.amount, - statement_line.statement.journal.company.currency) - if statement_line.statement.journal.currency.id != \ - statement_line.statement.journal.company.currency.id: - second_currency = statement_line.statement.journal.currency.id + statement_line.statement.company.currency, statement_line.amount, + statement_line.statement.company.currency) + if statement_line.statement.company.currency.id != \ + statement_line.statement.company.currency.id: + second_currency = statement_line.statement.company.currency.id amount_second_currency = abs(statement_line.amount) else: amount_second_currency = False diff --git a/statement.xml b/statement.xml index 0ff4edc238..658510099a 100644 --- a/statement.xml +++ b/statement.xml @@ -50,8 +50,10 @@ this repository contains the full copyright notices and license terms. --> states="{'invisible': Not(Equal(Eval('state'), 'cancel'))}"/> - - + + @@ -163,6 +165,17 @@ this repository contains the full copyright notices and license terms. --> + + + + + + + = + User/Current Company + + + account.statement.line form diff --git a/tests/scenario_account_statement.rst b/tests/scenario_account_statement.rst new file mode 100644 index 0000000000..15001770fe --- /dev/null +++ b/tests/scenario_account_statement.rst @@ -0,0 +1,271 @@ +========================== +Account Statement Scenario +========================== + +============= +General Setup +============= + +Imports:: + + >>> import datetime + >>> from dateutil.relativedelta import relativedelta + >>> from decimal import Decimal + >>> from proteus import config, Model, Wizard + >>> today = datetime.date.today() + +Create database:: + + >>> config = config.set_trytond() + +Install account_statement and account_invoice:: + + >>> Module = Model.get('ir.module.module') + >>> modules = Module.find([ + ... ('name', 'in', ('account_statement', 'account_invoice')), + ... ]) + >>> Module.button_install([x.id for x in modules], config.context) + >>> Wizard('ir.module.module.install_upgrade').execute('start') + +Create company:: + + >>> Currency = Model.get('currency.currency') + >>> CurrencyRate = Model.get('currency.currency.rate') + >>> Company = Model.get('company.company') + >>> company_config = Wizard('company.company.config') + >>> company_config.execute('company') + >>> company = company_config.form + >>> company.name = 'B2CK' + >>> currencies = Currency.find([('code', '=', 'EUR')]) + >>> if not currencies: + ... currency = Currency(name='Euro', symbol=u'€', code='EUR', + ... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]', + ... mon_decimal_point=',') + ... currency.save() + ... CurrencyRate(date=today + relativedelta(month=1, day=1), + ... rate=Decimal('1.0'), currency=currency).save() + ... else: + ... currency, = currencies + >>> company.currency = currency + >>> company_config.execute('add') + >>> company, = Company.find() + +Reload the context:: + + >>> User = Model.get('res.user') + >>> config._context = User.get_preferences(True, config.context) + +Create fiscal year:: + + >>> FiscalYear = Model.get('account.fiscalyear') + >>> Sequence = Model.get('ir.sequence') + >>> SequenceStrict = Model.get('ir.sequence.strict') + >>> fiscalyear = FiscalYear(name='%s' % today.year) + >>> fiscalyear.start_date = today + relativedelta(month=1, day=1) + >>> fiscalyear.end_date = today + relativedelta(month=12, day=31) + >>> fiscalyear.company = company + >>> post_move_sequence = Sequence(name='%s' % today.year, + ... code='account.move', + ... company=company) + >>> post_move_sequence.save() + >>> fiscalyear.post_move_sequence = post_move_sequence + >>> invoice_sequence = SequenceStrict(name='%s' % today.year, + ... code='account.invoice', + ... company=company) + >>> invoice_sequence.save() + >>> fiscalyear.out_invoice_sequence = invoice_sequence + >>> fiscalyear.in_invoice_sequence = invoice_sequence + >>> fiscalyear.out_credit_note_sequence = invoice_sequence + >>> fiscalyear.in_credit_note_sequence = invoice_sequence + >>> fiscalyear.save() + >>> FiscalYear.create_period([fiscalyear.id], config.context) + True + +Create chart of accounts:: + + >>> AccountTemplate = Model.get('account.account.template') + >>> Account = Model.get('account.account') + >>> AccountJournal = Model.get('account.journal') + >>> account_template, = AccountTemplate.find([('parent', '=', False)]) + >>> create_chart_account = Wizard('account.account.create_chart_account') + >>> create_chart_account.execute('account') + >>> create_chart_account.form.account_template = account_template + >>> create_chart_account.form.company = company + >>> create_chart_account.execute('create_account') + >>> receivable, = Account.find([ + ... ('kind', '=', 'receivable'), + ... ('company', '=', company.id), + ... ]) + >>> payable, = Account.find([ + ... ('kind', '=', 'payable'), + ... ('company', '=', company.id), + ... ]) + >>> revenue, = Account.find([ + ... ('kind', '=', 'revenue'), + ... ('company', '=', company.id), + ... ]) + >>> expense, = Account.find([ + ... ('kind', '=', 'expense'), + ... ('company', '=', company.id), + ... ]) + >>> cash, = Account.find([ + ... ('name', '=', 'Main Cash'), + ... ('company', '=', company.id), + ... ]) + >>> create_chart_account.form.account_receivable = receivable + >>> create_chart_account.form.account_payable = payable + >>> create_chart_account.execute('create_properties') + +Create parties:: + + >>> Party = Model.get('party.party') + >>> supplier = Party(name='Supplier') + >>> supplier.save() + >>> customer = Party(name='Customer') + >>> customer.save() + +Create payment term:: + + >>> PaymentTerm = Model.get('account.invoice.payment_term') + >>> PaymentTermLine = Model.get('account.invoice.payment_term.line') + >>> payment_term = PaymentTerm(name='Direct') + >>> payment_term_line = PaymentTermLine(type='remainder') + >>> payment_term.lines.append(payment_term_line) + >>> payment_term.save() + +Create 2 customer invoices:: + + >>> Invoice = Model.get('account.invoice') + >>> InvoiceLine = Model.get('account.invoice.line') + >>> customer_invoice1 = Invoice(type='out_invoice') + >>> customer_invoice1.party = customer + >>> customer_invoice1.payment_term = payment_term + >>> invoice_line = InvoiceLine() + >>> customer_invoice1.lines.append(invoice_line) + >>> invoice_line.quantity = 1 + >>> invoice_line.unit_price = Decimal('100') + >>> invoice_line.account = revenue + >>> invoice_line.description = 'Test' + >>> customer_invoice1.save() + >>> Invoice.workflow_trigger_validate(customer_invoice1.id, 'open', + ... config.context) + >>> customer_invoice1.state + u'open' + + >>> customer_invoice2 = Invoice(type='out_invoice') + >>> customer_invoice2.party = customer + >>> customer_invoice2.payment_term = payment_term + >>> invoice_line = InvoiceLine() + >>> customer_invoice2.lines.append(invoice_line) + >>> invoice_line.quantity = 1 + >>> invoice_line.unit_price = Decimal('150') + >>> invoice_line.account = revenue + >>> invoice_line.description = 'Test' + >>> customer_invoice2.save() + >>> Invoice.workflow_trigger_validate(customer_invoice2.id, 'open', + ... config.context) + >>> customer_invoice2.state + u'open' + +Create 1 supplier invoices:: + + >>> supplier_invoice = Invoice(type='in_invoice') + >>> supplier_invoice.party = supplier + >>> supplier_invoice.payment_term = payment_term + >>> invoice_line = InvoiceLine() + >>> supplier_invoice.lines.append(invoice_line) + >>> invoice_line.quantity = 1 + >>> invoice_line.unit_price = Decimal('50') + >>> invoice_line.account = expense + >>> invoice_line.description = 'Test' + >>> supplier_invoice.save() + >>> Invoice.workflow_trigger_validate(supplier_invoice.id, 'open', + ... config.context) + >>> supplier_invoice.state + u'open' + +Create statement:: + + >>> StatementJournal = Model.get('account.statement.journal') + >>> Statement = Model.get('account.statement') + >>> StatementLine = Model.get('account.statement.line') + + >>> sequence = Sequence(name='Satement', + ... code='account.journal', + ... company=company, + ... ) + >>> sequence.save() + >>> account_journal = AccountJournal(name='Statement', + ... type='statement', + ... credit_account=cash, + ... debit_account=cash, + ... sequence=sequence, + ... ) + >>> account_journal.save() + + >>> statement_journal = StatementJournal(name='Test', + ... journal=account_journal, + ... ) + >>> statement_journal.save() + + >>> statement = Statement(journal=statement_journal, + ... start_balance=Decimal('0'), + ... end_balance=Decimal('130'), + ... ) + +Received 180 from customer:: + + >>> statement_line = StatementLine() + >>> statement.lines.append(statement_line) + >>> statement_line.date = today + >>> statement_line.amount = Decimal('180') + >>> statement_line.party = customer + >>> statement_line.account == receivable + True + >>> statement_line.invoice = customer_invoice1 + >>> statement_line.amount == Decimal('100') + True + >>> statement_line = statement.lines[-1] + >>> statement_line.amount == Decimal('80') + True + >>> statement_line.party == customer + True + >>> statement_line.account == receivable + True + >>> statement_line.invoice = customer_invoice2 + >>> statement_line.amount == Decimal('80') + True + +Paid 50 to supplier:: + + >>> statement_line = StatementLine() + >>> statement.lines.append(statement_line) + >>> statement_line.date = today + >>> statement_line.amount = Decimal('-50') + >>> statement_line.party = supplier + >>> statement_line.account == payable + True + >>> statement_line.invoice = supplier_invoice + + >>> statement.save() + +Validate statement:: + + >>> Statement.workflow_trigger_validate(statement.id, 'validated', + ... config.context) + >>> statement.state + u'validated' + +Test invoice state:: + + >>> customer_invoice1.reload() + >>> customer_invoice1.state + u'paid' + >>> customer_invoice2.reload() + >>> customer_invoice2.state + u'open' + >>> customer_invoice2.amount_to_pay == Decimal('70') + True + >>> supplier_invoice.reload() + >>> supplier_invoice.state + u'paid' diff --git a/tests/test_account_statement.py b/tests/test_account_statement.py index 1bcec2ac82..2cc478f37f 100644 --- a/tests/test_account_statement.py +++ b/tests/test_account_statement.py @@ -9,6 +9,7 @@ if os.path.isdir(DIR): sys.path.insert(0, os.path.dirname(DIR)) import unittest +import doctest import trytond.tests.test_tryton from trytond.tests.test_tryton import test_view @@ -27,10 +28,24 @@ class AccountStatementTestCase(unittest.TestCase): ''' test_view('account_statement') +def doctest_dropdb(test): + ''' + Remove sqlite memory database + ''' + database = SQLiteDatabase().connect() + cursor = database.cursor(autocommit=True) + try: + database.drop(cursor, ':memory:') + cursor.commit() + finally: + cursor.close() + def suite(): suite = trytond.tests.test_tryton.suite() suite.addTests(unittest.TestLoader().loadTestsFromTestCase( AccountStatementTestCase)) + suite.addTests(doctest.DocFileSuite('scenario_account_statement.rst', + setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8')) return suite if __name__ == '__main__':