diff --git a/book.py b/book.py index 37e8320..3bdbfd6 100644 --- a/book.py +++ b/book.py @@ -4,10 +4,11 @@ # full copyright notices and license terms. from trytond.model import Workflow, ModelView, ModelSQL, fields, Check -from trytond.pyson import Eval +from trytond.pyson import Eval, Or, Bool from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.transaction import Transaction +from trytond.pool import Pool STATES = { @@ -26,6 +27,8 @@ class Book(Workflow, ModelSQL, ModelView): 'Cashbook' __name__ = 'cashbook.book' + company = fields.Many2One(string='Company', model_name='company.company', + required=True, ondelete="RESTRICT") name = fields.Char(string='Name', required=True, states=STATES, depends=DEPENDS) btype = fields.Many2One(string='Type', required=True, @@ -45,6 +48,14 @@ class Book(Workflow, ModelSQL, ModelView): account = fields.Many2One(string='Account', select=True, model_name='account.account', ondelete='RESTRICT', states=STATES, depends=DEPENDS) + currency = fields.Many2One(string='Currency', required=True, + model_name='currency.currency', + states={ + 'readonly': Or( + STATES['readonly'], + Bool(Eval('lines', [])), + ), + }, depends=DEPENDS+['lines']) state = fields.Selection(string='State', required=True, readonly=True, selection=sel_state_book) state_string = state.translated('state') @@ -79,6 +90,22 @@ class Book(Workflow, ModelSQL, ModelView): }, }) + @classmethod + def default_currency(cls): + """ currency of company + """ + Company = Pool().get('company.company') + + company = cls.default_company() + if company: + company = Company(company) + if company.currency: + return company.currency.id + + @staticmethod + def default_company(): + return Transaction().context.get('company') or None + @classmethod def default_state(cls): return 'open' diff --git a/book.xml b/book.xml index ea9d71f..1aecde0 100644 --- a/book.xml +++ b/book.xml @@ -107,6 +107,19 @@ full copyright notices and license terms. --> + + User in companies + + + + + + + + wfopen diff --git a/category.xml b/category.xml index 7258106..6df3c33 100644 --- a/category.xml +++ b/category.xml @@ -87,5 +87,18 @@ full copyright notices and license terms. --> + + User in companies + + + + + + + + diff --git a/line.py b/line.py index 56b8549..9e6ab0e 100644 --- a/line.py +++ b/line.py @@ -10,8 +10,10 @@ from trytond.transaction import Transaction from trytond.report import Report from trytond.exceptions import UserError from trytond.i18n import gettext +from decimal import Decimal from sql import Cast, Literal from sql.functions import DatePart +from sql.conditionals import Case from .book import sel_state_book @@ -21,6 +23,13 @@ sel_linetype = [ ('done', 'Done'), ] +sel_bookingtype = [ + ('in', 'Revenue'), + ('out', 'Expense'), + ('mvin', 'Transfer from'), + ('mvout', 'Transfer to'), + ] + STATES = { 'readonly': Or( Eval('state', '') != 'edit', @@ -47,6 +56,21 @@ class Line(Workflow, ModelSQL, ModelView): states=STATES, depends=DEPENDS) category_view = fields.Function(fields.Char(string='Category', readonly=True), 'on_change_with_category_view', searcher='search_category_view') + + bookingtype = fields.Selection(string='Type', required=True, + help='Type of Booking', selection=sel_bookingtype, + states=STATES, depends=DEPENDS) + amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)), + required=True, states=STATES, depends=DEPENDS+['currency_digits']) + debit = fields.Numeric(string='Debit', digits=(16, Eval('currency_digits', 2)), + required=True, readonly=True, depends=['currency_digits']) + credit = fields.Numeric(string='Credit', digits=(16, Eval('currency_digits', 2)), + required=True, readonly=True, depends=['currency_digits']) + currency = fields.Function(fields.Many2One(model_name='currency.currency', + string="Currency"), 'on_change_with_currency') + currency_digits = fields.Function(fields.Integer(string='Currency Digits'), + 'on_change_with_currency_digits') + state = fields.Selection(string='State', required=True, readonly=True, select=True, selection=sel_linetype) state_string = state.translated('state') @@ -59,6 +83,7 @@ class Line(Workflow, ModelSQL, ModelView): @classmethod def __setup__(cls): super(Line, cls).__setup__() + cls._order.insert(0, ('state', 'ASC')) cls._order.insert(0, ('date', 'ASC')) t = cls.__table__() cls._sql_constraints.extend([ @@ -138,6 +163,23 @@ class Line(Workflow, ModelSQL, ModelView): 'desc': (self.description or '-')[:40], } + @staticmethod + def order_state(tables): + """ edit = 0, check/done = 1 + """ + Line = Pool().get('cashbook.line') + tab_line = Line.__table__() + table, _ = tables[None] + + query = tab_line.select( + Case( + (tab_line.state == 'edit', 1), + (tab_line.state.in_(['check', 'done']), 0), + else_ = 2), + where=tab_line.id==table.id + ) + return [query] + @staticmethod def order_category_view(tables): """ order: name @@ -219,11 +261,65 @@ class Line(Workflow, ModelSQL, ModelView): """ return [('cashbook.state',) + tuple(clause[1:])] + @fields.depends('cashbook', '_parent_cashbook.currency') + def on_change_with_currency(self, name=None): + """ currency of cashbook + """ + if self.cashbook: + return self.cashbook.currency.id + + @fields.depends('cashbook', '_parent_cashbook.currency') + def on_change_with_currency_digits(self, name=None): + """ currency of cashbook + """ + if self.cashbook: + return self.cashbook.currency.digits + else: + return 2 + + @classmethod + def get_debit_credit(cls, values): + """ compute debit/credit from amount + """ + if isinstance(values, dict): + type_ = values.get('bookingtype', None) + amount = values.get('amount', None) + else : + type_ = getattr(values, 'bookingtype', None) + amount = getattr(values, 'amount', None) + + if type_: + if amount is not None: + if type_ in ['in', 'mvin']: + return { + 'debit': Decimal('0.0'), + 'credit': amount, + } + elif type_ in ['out', 'mvout']: + return { + 'debit': amount, + 'credit': Decimal('0.0'), + } + else : + raise ValueError('invalid "bookingtype"') + return {} + + @classmethod + def create(cls, vlist): + """ add debit/credit + """ + vlist = [x.copy() for x in vlist] + for vals in vlist: + vals.update(cls.get_debit_credit(vals)) + return super(Line, cls).create(vlist) + @classmethod def write(cls, *args): - """ deny update if cashbook.line!='open' + """ deny update if cashbook.line!='open', + add or update debit/credit """ actions = iter(args) + to_write = [] for lines, values in zip(actions, actions): for line in lines: if line.cashbook.state != 'open': @@ -232,7 +328,19 @@ class Line(Workflow, ModelSQL, ModelView): bookname = line.cashbook.rec_name, state_txt = line.cashbook.state_string, )) - super(Line, cls).write(*args) + + # debit / credit + if len(set(values.keys()).intersection(set({'amount', 'bookingtype'}))) > 0: + for line in lines: + values2 = {} + values2.update(values) + values2.update(cls.get_debit_credit({ + x:values.get(x, getattr(line, x)) for x in ['amount', 'bookingtype'] + })) + to_write.extend([lines, values2]) + else : + to_write.extend([lines, values]) + super(Line, cls).write(*to_write) @classmethod def delete(cls, lines): diff --git a/line.xml b/line.xml index d60291a..036f7b0 100644 --- a/line.xml +++ b/line.xml @@ -169,6 +169,19 @@ full copyright notices and license terms. --> + + User in companies + + + + + + + + diff --git a/locale/de.po b/locale/de.po index 5e632e7..5f65023 100644 --- a/locale/de.po +++ b/locale/de.po @@ -27,7 +27,7 @@ msgid "The cashbook '%(bookname)s' cannot be deleted because it contains %(bookl msgstr "Das Kassenbuch '%(bookname)s' kann nicht gelöscht werden, da es %(booklines)s Zeilen enthält und nicht im Status 'Archiv' ist." msgctxt "model:ir.message,text:msg_book_deny_write" -msgid "The cash book '%(bookname)s' is '%(state_txt)s' and cannot be changed." +msgid "The cashbook '%(bookname)s' is '%(state_txt)s' and cannot be changed." msgstr "Das Kassenbuch '%(bookname)s' ist '%(state_txt)s' und kann nicht geändert werden." msgctxt "model:ir.message,text:msg_line_deny_delete1" @@ -90,6 +90,18 @@ msgctxt "model:ir.rule.group,name:rg_line_read" msgid "Observer: Cashbook line read" msgstr "Beobachter: Kassenbuchzeile lesen" +msgctxt "model:ir.rule.group,name:rg_line_read" +msgid "User in companies" +msgstr "Benutzer im Unternehmen" + +msgctxt "model:ir.rule.group,name:rg_type_companies" +msgid "User in companies" +msgstr "Benutzer im Unternehmen" + +msgctxt "model:ir.rule.group,name:rg_book_companies" +msgid "User in companies" +msgstr "Benutzer im Unternehmen" + ############## # ir.ui.menu # @@ -127,8 +139,8 @@ msgstr "Kategorie" # ir.action # ############# msgctxt "model:ir.action,name:act_book_view" -msgid "Account" -msgstr "Konto" +msgid "Cashbook" +msgstr "Kassenbuch" msgctxt "model:ir.action,name:act_type_view" msgid "Cashbook Type" @@ -147,6 +159,22 @@ msgid "Category" msgstr "Kategorie" +############################### +# ir.action.act_window.domain # +############################### +msgctxt "model:ir.action.act_window.domain,name:act_line_domain_current" +msgid "Current Month" +msgstr "aktueller Monat" + +msgctxt "model:ir.action.act_window.domain,name:act_line_domain_last" +msgid "Last Month" +msgstr "letzter Monat" + +msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all" +msgid "All" +msgstr "Alle" + + ################### # ir.model.button # ################### @@ -183,8 +211,8 @@ msgid "Cashbook" msgstr "Kassenbuch" msgctxt "view:cashbook.book:" -msgid "Owner & Authorizeds" -msgstr "Eigentümer & Autorisierte" +msgid "Owner and Authorizeds" +msgstr "Eigentümer und Autorisierte" msgctxt "field:cashbook.book,name:" msgid "Name" @@ -234,6 +262,14 @@ msgctxt "field:cashbook.book,account:" msgid "Account" msgstr "Konto" +msgctxt "field:cashbook.book,company:" +msgid "Company" +msgstr "Unternehmen" + +msgctxt "field:cashbook.book,currency:" +msgid "Currency" +msgstr "Währung" + ################# # cashbook.line # @@ -242,6 +278,14 @@ msgctxt "model:cashbook.line,name:" msgid "Cashbook Line" msgstr "Kassenbuchzeile" +msgctxt "view:cashbook.line:" +msgid "Credit" +msgstr "Einnahme" + +msgctxt "view:cashbook.line:" +msgid "Debit" +msgstr "Ausgabe" + msgctxt "view:cashbook.line:" msgid "Cashbook Line" msgstr "Kassenbuchzeile" @@ -294,17 +338,53 @@ msgctxt "field:cashbook.line,category_view:" msgid "Category" msgstr "Kategorie" -msgctxt "model:ir.action.act_window.domain,name:act_line_domain_current" -msgid "Current Month" -msgstr "aktueller Monat" +msgctxt "field:cashbook.line,bookingtype:" +msgid "Type" +msgstr "Typ" -msgctxt "model:ir.action.act_window.domain,name:act_line_domain_last" -msgid "Last Month" -msgstr "letzter Monat" +msgctxt "help:cashbook.line,bookingtype:" +msgid "Type of Booking" +msgstr "Typ der Buchung" -msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all" -msgid "All" -msgstr "Alle" +msgctxt "selection:cashbook.line,bookingtype:" +msgid "Revenue" +msgstr "Einnahme" + +msgctxt "selection:cashbook.line,bookingtype:" +msgid "Expense" +msgstr "Ausgabe" + +msgctxt "selection:cashbook.line,bookingtype:" +msgid "Transfer from" +msgstr "Umbuchung von" + +msgctxt "selection:cashbook.line,bookingtype:" +msgid "Transfer to" +msgstr "Umbuchung nach" + +msgctxt "field:cashbook.line,company:" +msgid "Company" +msgstr "Unternehmen" + +msgctxt "field:cashbook.line,amount:" +msgid "Amount" +msgstr "Betrag" + +msgctxt "field:cashbook.line,debit:" +msgid "Debit" +msgstr "Ausgabe" + +msgctxt "field:cashbook.line,credit:" +msgid "Credit" +msgstr "Einnahme" + +msgctxt "field:cashbook.line,currency:" +msgid "Currency" +msgstr "Währung" + +msgctxt "field:cashbook.line,currency_digits:" +msgid "Currency Digits" +msgstr "Nachkommastellen Währung" ################# @@ -322,17 +402,9 @@ msgctxt "field:cashbook.type,short:" msgid "Abbreviation" msgstr "Kürzel" -msgctxt "model:cashbook.type,name:atype_cash" -msgid "Cash" -msgstr "Bar" - -msgctxt "model:cashbook.type,name:atype_giro" -msgid "Giro" -msgstr "Giro" - -msgctxt "model:cashbook.type,name:atype_fixtermdep" -msgid "Fixed-term deposit" -msgstr "Festgeld" +msgctxt "field:cashbook.type,company:" +msgid "Company" +msgstr "Unternehmen" ##################### @@ -434,11 +506,11 @@ msgctxt "model:cashbook.open_lines,name:" msgid "Open Cashbook" msgstr "Kassenbuch öffnen" -msgctxt "wizard_button:cashbook.open_lines,start,end:" +msgctxt "wizard_button:cashbook.open_lines,askuser,end:" msgid "Cancel" msgstr "Abbruch" -msgctxt "wizard_button:cashbook.open_lines,start,open_:" +msgctxt "wizard_button:cashbook.open_lines,askuser,open_:" msgid "Open" msgstr "Öffnen" diff --git a/message.xml b/message.xml index 9ced7e9..ffc9d3f 100644 --- a/message.xml +++ b/message.xml @@ -18,10 +18,10 @@ full copyright notices and license terms. --> Invalid value of the field 'Status', allowed: open, closed, archive. - The cash book '%(bookname)s' cannot be deleted because it contains %(booklines)s lines and is not in the status 'Archive'. + The cashbook '%(bookname)s' cannot be deleted because it contains %(booklines)s lines and is not in the status 'Archive'. - The cash book '%(bookname)s' is '%(state_txt)s' and cannot be changed. + The cashbook '%(bookname)s' is '%(state_txt)s' and cannot be changed. The cashbook line '%(linetxt)s' cannot be deleted because the Cashbook '%(bookname)s' is in state '%(bookstate)s'. diff --git a/tests/test_book.py b/tests/test_book.py index 8c29cbb..0db06ec 100644 --- a/tests/test_book.py +++ b/tests/test_book.py @@ -8,6 +8,7 @@ from trytond.pool import Pool from trytond.transaction import Transaction from trytond.exceptions import UserError from datetime import date +from decimal import Decimal class BookTestCase(ModuleTestCase): @@ -20,13 +21,14 @@ class BookTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') - - types, = Types.search([('short', '=','CAS')]) + types = self.prep_type() + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, }]) self.assertEqual(book.name, 'Book 1') self.assertEqual(book.btype.rec_name, 'CAS - Cash') @@ -39,18 +41,21 @@ class BookTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'test 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -67,18 +72,21 @@ class BookTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'test 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -97,18 +105,21 @@ class BookTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'test 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -125,13 +136,14 @@ class BookTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') - - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, }]) self.assertEqual(book.name, 'Book 1') self.assertEqual(book.state, 'open') @@ -188,18 +200,22 @@ class BookTestCase(ModuleTestCase): ResUser = pool.get('res.user') ResGroup = pool.get('res.group') Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() + company = self.prep_company() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) usr_lst = ResUser.create([{ 'login': 'frida', 'name': 'Frida', 'groups': [('add', [grp_cashbook.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }, { 'login': 'diego', 'name': 'Diego', 'groups': [('add', [grp_cashbook.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }]) self.assertEqual(len(usr_lst), 2) self.assertEqual(usr_lst[0].name, 'Frida') @@ -209,6 +225,8 @@ class BookTestCase(ModuleTestCase): 'name': 'Fridas book', 'owner': usr_lst[0].id, 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, }]) self.assertEqual(book.rec_name, 'Fridas book'), self.assertEqual(book.owner.rec_name, 'Frida'), @@ -245,9 +263,9 @@ class BookTestCase(ModuleTestCase): ResUser = pool.get('res.user') ResGroup = pool.get('res.group') Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() + company = self.prep_company() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) grp_reviewer, = ResGroup.create([{ 'name': 'Cashbook Reviewer', @@ -257,10 +275,14 @@ class BookTestCase(ModuleTestCase): 'login': 'frida', 'name': 'Frida', 'groups': [('add', [grp_cashbook.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }, { 'login': 'diego', 'name': 'Diego', 'groups': [('add', [grp_cashbook.id, grp_reviewer.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }]) self.assertEqual(len(usr_lst), 2) self.assertEqual(usr_lst[0].name, 'Frida') @@ -272,6 +294,8 @@ class BookTestCase(ModuleTestCase): 'name': 'Fridas book', 'owner': usr_lst[0].id, 'reviewer': grp_reviewer.id, + 'company': company.id, + 'currency': company.currency.id, 'btype': types.id, }]) self.assertEqual(book.rec_name, 'Fridas book'), @@ -301,9 +325,9 @@ class BookTestCase(ModuleTestCase): ResUser = pool.get('res.user') ResGroup = pool.get('res.group') Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() + company = self.prep_company() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) grp_observer, = ResGroup.create([{ 'name': 'Cashbook Observer', @@ -313,10 +337,14 @@ class BookTestCase(ModuleTestCase): 'login': 'frida', 'name': 'Frida', 'groups': [('add', [grp_cashbook.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }, { 'login': 'diego', 'name': 'Diego', 'groups': [('add', [grp_cashbook.id, grp_observer.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }]) self.assertEqual(len(usr_lst), 2) self.assertEqual(usr_lst[0].name, 'Frida') @@ -328,6 +356,8 @@ class BookTestCase(ModuleTestCase): 'name': 'Fridas book', 'owner': usr_lst[0].id, 'observer': grp_observer.id, + 'company': company.id, + 'currency': company.currency.id, 'btype': types.id, }]) self.assertEqual(book.rec_name, 'Fridas book'), diff --git a/tests/test_category.py b/tests/test_category.py index c3b5102..f4a7325 100644 --- a/tests/test_category.py +++ b/tests/test_category.py @@ -7,7 +7,6 @@ from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.pool import Pool from trytond.transaction import Transaction from trytond.exceptions import UserError -from trytond.modules.company.tests import create_company class CategoryTestCase(ModuleTestCase): @@ -21,12 +20,7 @@ class CategoryTestCase(ModuleTestCase): Company = pool.get('company.company') Category = pool.get('cashbook.category') - company = Company.search([]) - if len(company) > 0: - company = company[0] - else : - company = create_company(name='m-ds') - + company = self.prep_company() category, = Category.create([{ 'company': company.id, 'name': name, @@ -40,7 +34,7 @@ class CategoryTestCase(ModuleTestCase): pool = Pool() Category = pool.get('cashbook.category') - company = create_company(name='m-ds') + company = self.prep_company() with Transaction().set_context({ 'company': company.id, @@ -73,7 +67,7 @@ class CategoryTestCase(ModuleTestCase): pool = Pool() Category = pool.get('cashbook.category') - company = create_company(name='m-ds') + company = self.prep_company() with Transaction().set_context({ 'company': company.id, @@ -100,7 +94,7 @@ class CategoryTestCase(ModuleTestCase): pool = Pool() Category = pool.get('cashbook.category') - company = create_company(name='m-ds') + company = self.prep_company() with Transaction().set_context({ 'company': company.id, @@ -126,7 +120,7 @@ class CategoryTestCase(ModuleTestCase): Account = pool.get('account.account') Category = pool.get('cashbook.category') - company = create_company(name='m-ds') + company = self.prep_company() with Transaction().set_context({ 'company': company.id, diff --git a/tests/test_config.py b/tests/test_config.py index 6f95827..f00d171 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,6 +7,7 @@ from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.pool import Pool from trytond.transaction import Transaction from trytond.exceptions import UserError +from trytond.modules.company.tests import create_company from datetime import date @@ -14,6 +15,18 @@ class ConfigTestCase(ModuleTestCase): 'Test config type module' module = 'cashbook' + def prep_company(self): + """ get/create company + """ + Company = Pool().get('company.company') + + company = Company.search([]) + if len(company) > 0: + company = company[0] + else : + company = create_company(name='m-ds') + return company + @with_transaction() def test_config_create(self): """ create config diff --git a/tests/test_line.py b/tests/test_line.py index b6a0fa5..5f7d905 100644 --- a/tests/test_line.py +++ b/tests/test_line.py @@ -9,6 +9,7 @@ from trytond.transaction import Transaction from trytond.exceptions import UserError from datetime import date from unittest.mock import MagicMock +from decimal import Decimal class LineTestCase(ModuleTestCase): @@ -21,23 +22,28 @@ class LineTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') Lines = pool.get('cashbook.line') - types, = Types.search([('short', '=','CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }, { 'date': date(2022, 5, 2), 'description': 'Text 2', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -59,29 +65,62 @@ class LineTestCase(ModuleTestCase): self.assertEqual(Lines.search_count([('cashbook.state', '=', 'open')]), 2) self.assertEqual(Lines.search_count([('cashbook.state', '=', 'closed')]), 0) + # sorting: date -> state -> id + self.assertEqual(len(book.lines), 2) + self.assertEqual(book.lines[0].rec_name, '05/01/2022 Text 1') + self.assertEqual(book.lines[0].state, 'edit') + self.assertEqual(book.lines[1].rec_name, '05/02/2022 Text 2') + self.assertEqual(book.lines[1].state, 'edit') + + # set to same date + Lines.write(*[ + list(book.lines), + { + 'date': date(2022, 5, 1), + }]) + # check again + book, = Book.search([]) + self.assertEqual(book.lines[0].rec_name, '05/01/2022 Text 1') + self.assertEqual(book.lines[0].state, 'edit') + self.assertEqual(book.lines[1].rec_name, '05/01/2022 Text 2') + self.assertEqual(book.lines[1].state, 'edit') + + # set to 'check', will sort first + Lines.wfcheck([book.lines[1]]) + book, = Book.search([]) + self.assertEqual(book.lines[0].rec_name, '05/01/2022 Text 2') + self.assertEqual(book.lines[0].state, 'check') + self.assertEqual(book.lines[1].rec_name, '05/01/2022 Text 1') + self.assertEqual(book.lines[1].state, 'edit') + @with_transaction() def test_line_create_check_deny_write(self): """ create cashbook + line, 'close' book, write to line """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') Line = pool.get('cashbook.line') - types, = Types.search([('short', '=','CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }, { 'date': date(2022, 6, 1), 'description': 'Text 2', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -107,26 +146,31 @@ class LineTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') Line = pool.get('cashbook.line') IrDate = pool.get('ir.date') - types, = Types.search([('short', '=','CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() IrDate.today = MagicMock(return_value=date(2022, 6, 1)) book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }, { 'date': date(2022, 6, 1), 'description': 'Text 2', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -159,21 +203,125 @@ class LineTestCase(ModuleTestCase): IrDate.today = MagicMock(return_value=date.today()) + @with_transaction() + def test_line_create_check_debit_credit(self): + """ create cashbook + line, check calculation of debit/credit + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + Configuration = pool.get('cashbook.configuration') + Category = pool.get('cashbook.category') + + types = self.prep_type() + category = self.prep_category() + company = self.prep_company() + + category2, = Category.create([{ + 'company': company.id, + 'name': 'Category', + }]) + + book, = Book.create([{ + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Revenue', + 'category': category2.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), + }, { + 'date': date(2022, 6, 1), + 'description': 'Expense', + 'category': category2.id, + 'bookingtype': 'out', + 'amount': Decimal('1.0'), + }, { + 'date': date(2022, 6, 1), + 'description': 'Transfer from', + 'category': category2.id, + 'bookingtype': 'mvin', + 'amount': Decimal('1.0'), + }, { + 'date': date(2022, 6, 1), + 'description': 'Transfer to', + 'category': category2.id, + 'bookingtype': 'mvout', + 'amount': Decimal('1.0'), + }])], + }]) + self.assertEqual(book.name, 'Book 1') + self.assertEqual(book.state, 'open') + self.assertEqual(len(book.lines), 4) + + self.assertEqual(book.lines[0].amount, Decimal('1.0')) + self.assertEqual(book.lines[0].bookingtype, 'in') + self.assertEqual(book.lines[0].credit, Decimal('1.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + + self.assertEqual(book.lines[1].amount, Decimal('1.0')) + self.assertEqual(book.lines[1].bookingtype, 'out') + self.assertEqual(book.lines[1].credit, Decimal('0.0')) + self.assertEqual(book.lines[1].debit, Decimal('1.0')) + + self.assertEqual(book.lines[2].amount, Decimal('1.0')) + self.assertEqual(book.lines[2].bookingtype, 'mvin') + self.assertEqual(book.lines[2].credit, Decimal('1.0')) + self.assertEqual(book.lines[2].debit, Decimal('0.0')) + + self.assertEqual(book.lines[3].amount, Decimal('1.0')) + self.assertEqual(book.lines[3].bookingtype, 'mvout') + self.assertEqual(book.lines[3].credit, Decimal('0.0')) + self.assertEqual(book.lines[3].debit, Decimal('1.0')) + + Line.write(*[ + [book.lines[0]], + { + 'amount': Decimal('2.0'), + }]) + self.assertEqual(book.lines[0].amount, Decimal('2.0')) + self.assertEqual(book.lines[0].bookingtype, 'in') + self.assertEqual(book.lines[0].credit, Decimal('2.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + + Line.write(*[ + [book.lines[0]], + { + 'bookingtype': 'out', + }]) + self.assertEqual(book.lines[0].amount, Decimal('2.0')) + self.assertEqual(book.lines[0].bookingtype, 'out') + self.assertEqual(book.lines[0].credit, Decimal('0.0')) + self.assertEqual(book.lines[0].debit, Decimal('2.0')) + + Line.write(*[ + [book.lines[0]], + { + 'bookingtype': 'mvin', + 'amount': Decimal('3.0'), + }]) + self.assertEqual(book.lines[0].amount, Decimal('3.0')) + self.assertEqual(book.lines[0].bookingtype, 'mvin') + self.assertEqual(book.lines[0].credit, Decimal('3.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + @with_transaction() def test_line_create_check_category_view(self): """ create cashbook + line, check 'category_view' """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') Line = pool.get('cashbook.line') Configuration = pool.get('cashbook.configuration') Category = pool.get('cashbook.category') Account = pool.get('account.account') - types, = Types.search([('short', '=','CAS')]) + types = self.prep_type() category = self.prep_category() - company = category.company + company = self.prep_company() with Transaction().set_context({ 'company': company.id, @@ -208,14 +356,20 @@ class LineTestCase(ModuleTestCase): book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', 'category': category2.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }, { 'date': date(2022, 6, 1), 'description': 'Text 2', 'category': category2.childs[0].id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -257,23 +411,28 @@ class LineTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') Lines = pool.get('cashbook.line') - types, = Types.search([('short', '=','CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }, { 'date': date(2022, 5, 2), 'description': 'Text 2', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -288,23 +447,28 @@ class LineTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') Lines = pool.get('cashbook.line') - types, = Types.search([('short', '=','CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }, { 'date': date(2022, 5, 2), 'description': 'Text 2', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -324,23 +488,28 @@ class LineTestCase(ModuleTestCase): """ pool = Pool() Book = pool.get('cashbook.book') - Types = pool.get('cashbook.type') Lines = pool.get('cashbook.line') - types, = Types.search([('short', '=','CAS')]) + types = self.prep_type() category = self.prep_category() - + company = self.prep_company() book, = Book.create([{ 'name': 'Book 1', 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }, { 'date': date(2022, 5, 2), 'description': 'Text 2', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.name, 'Book 1') @@ -365,19 +534,23 @@ class LineTestCase(ModuleTestCase): ResGroup = pool.get('res.group') Book = pool.get('cashbook.book') Line = pool.get('cashbook.line') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() category = self.prep_category() + company = self.prep_company() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) usr_lst = ResUser.create([{ 'login': 'frida', 'name': 'Frida', 'groups': [('add', [grp_cashbook.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }, { 'login': 'diego', 'name': 'Diego', 'groups': [('add', [grp_cashbook.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }]) self.assertEqual(len(usr_lst), 2) self.assertEqual(usr_lst[0].name, 'Frida') @@ -387,10 +560,14 @@ class LineTestCase(ModuleTestCase): 'name': 'Fridas book', 'owner': usr_lst[0].id, 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Test 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.rec_name, 'Fridas book'), @@ -427,10 +604,10 @@ class LineTestCase(ModuleTestCase): ResGroup = pool.get('res.group') Book = pool.get('cashbook.book') Line = pool.get('cashbook.line') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() category = self.prep_category() + company = self.prep_company() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) grp_reviewer, = ResGroup.create([{ 'name': 'Cashbook Reviewer', @@ -440,10 +617,14 @@ class LineTestCase(ModuleTestCase): 'login': 'frida', 'name': 'Frida', 'groups': [('add', [grp_cashbook.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }, { 'login': 'diego', 'name': 'Diego', 'groups': [('add', [grp_cashbook.id, grp_reviewer.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }]) self.assertEqual(len(usr_lst), 2) self.assertEqual(usr_lst[0].name, 'Frida') @@ -456,10 +637,14 @@ class LineTestCase(ModuleTestCase): 'owner': usr_lst[0].id, 'reviewer': grp_reviewer.id, 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Test 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.rec_name, 'Fridas book'), @@ -503,10 +688,10 @@ class LineTestCase(ModuleTestCase): ResGroup = pool.get('res.group') Book = pool.get('cashbook.book') Line = pool.get('cashbook.line') - Types = pool.get('cashbook.type') - types, = Types.search([('short', '=', 'CAS')]) + types = self.prep_type() category = self.prep_category() + company = self.prep_company() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) grp_observer, = ResGroup.create([{ 'name': 'Cashbook Observer', @@ -516,10 +701,14 @@ class LineTestCase(ModuleTestCase): 'login': 'frida', 'name': 'Frida', 'groups': [('add', [grp_cashbook.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }, { 'login': 'diego', 'name': 'Diego', 'groups': [('add', [grp_cashbook.id, grp_observer.id])], + 'companies': [('add', [company.id])], + 'company': company.id, }]) self.assertEqual(len(usr_lst), 2) self.assertEqual(usr_lst[0].name, 'Frida') @@ -532,10 +721,14 @@ class LineTestCase(ModuleTestCase): 'owner': usr_lst[0].id, 'observer': grp_observer.id, 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Test 1', 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), }])], }]) self.assertEqual(book.rec_name, 'Fridas book'), diff --git a/tests/test_type.py b/tests/test_type.py index 9e8544a..ab20b94 100644 --- a/tests/test_type.py +++ b/tests/test_type.py @@ -13,17 +13,20 @@ class TypeTestCase(ModuleTestCase): 'Test cashbook type module' module = 'cashbook' - @with_transaction() - def test_type_read_existing(self): - """ read predefined types + def prep_type(self, name='Cash', short='CAS'): + """ create book-type """ AccType = Pool().get('cashbook.type') - t_lst = AccType.search([], order=[('name', 'ASC')]) - self.assertEqual(len(t_lst), 3) - self.assertEqual(t_lst[0].rec_name, 'CAS - Cash') - self.assertEqual(t_lst[1].rec_name, 'FTD - Fixed-term deposit') - self.assertEqual(t_lst[2].rec_name, 'GIR - Giro') + company = self.prep_company() + at, = AccType.create([{ + 'name': name, + 'short': short, + 'company': company.id, + }]) + self.assertEqual(at.name, name) + self.assertEqual(at.short, short) + return at @with_transaction() def test_type_create(self): @@ -31,9 +34,12 @@ class TypeTestCase(ModuleTestCase): """ AccType = Pool().get('cashbook.type') + company = self.prep_company() + at, = AccType.create([{ 'name': 'Test 1', 'short': 'T1', + 'company': company.id, }]) self.assertEqual(at.name, 'Test 1') self.assertEqual(at.short, 'T1') @@ -45,6 +51,7 @@ class TypeTestCase(ModuleTestCase): [{ 'name': 'Test 2', 'short': 'T1', + 'company': company.id, }]) # end TypeTestCase diff --git a/tryton.cfg b/tryton.cfg index 62d0ac8..065201a 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -3,6 +3,7 @@ version=6.0.0 depends: res account + currency xml: icon.xml group.xml diff --git a/types.py b/types.py index c8d76dd..7181cd2 100644 --- a/types.py +++ b/types.py @@ -4,6 +4,7 @@ # full copyright notices and license terms. from trytond.model import ModelView, ModelSQL, fields, Unique +from trytond.transaction import Transaction class Type(ModelSQL, ModelView): @@ -12,6 +13,8 @@ class Type(ModelSQL, ModelView): name = fields.Char(string='Name', required=True, translate=True) short = fields.Char(string='Abbreviation', required=True, size=3) + company = fields.Many2One(string='Company', model_name='company.company', + required=True, ondelete="RESTRICT") @classmethod def __setup__(cls): @@ -39,4 +42,8 @@ class Type(ModelSQL, ModelView): ('short',) + tuple(clause[1:]), ] + @staticmethod + def default_company(): + return Transaction().context.get('company') or None + # end Type diff --git a/types.xml b/types.xml index 21d31ef..d3a628a 100644 --- a/types.xml +++ b/types.xml @@ -63,18 +63,17 @@ full copyright notices and license terms. --> - - - CAS - Cash + + User in companies + + - - GIR - Giro - - - FTD - Fixed-term deposit + + + diff --git a/view/book_form.xml b/view/book_form.xml index 1a20cdf..ec543c1 100644 --- a/view/book_form.xml +++ b/view/book_form.xml @@ -8,9 +8,10 @@ full copyright notices and license terms. -->