diff --git a/line.py b/line.py index 13ef07f..9ee5281 100644 --- a/line.py +++ b/line.py @@ -10,6 +10,8 @@ from trytond.transaction import Transaction from trytond.report import Report from trytond.exceptions import UserError from trytond.i18n import gettext +from sql import Cast, Literal +from sql.functions import DatePart from .book import sel_state_book @@ -121,7 +123,12 @@ class Line(Workflow, ModelSQL, ModelView): model_name='cashbook.book', ondelete='CASCADE', readonly=True) date = fields.Date(string='Date', required=True, select=True, states=STATES, depends=DEPENDS) - description = fields.Char(string='Description', + month = fields.Function(fields.Integer(string='Month', readonly=True), + 'on_change_with_month', searcher='search_month') + description = fields.Text(string='Description', + states=STATES, depends=DEPENDS) + category = fields.Many2One(string='Category', required=True, + model_name='cashbook.category', ondelete='RESTRICT', states=STATES, depends=DEPENDS) state = fields.Selection(string='State', required=True, readonly=True, select=True, selection=sel_linetype) @@ -220,6 +227,35 @@ class Line(Workflow, ModelSQL, ModelView): """ return [('description',) + tuple(clause[1:])] + @fields.depends('date') + def on_change_with_month(self, name=None): + """ get difference of month to current date + """ + IrDate = Pool().get('ir.date') + if self.date is not None: + dt1 = IrDate.today() + return (12 * dt1.year + dt1.month) - \ + (12 * self.date.year + self.date.month) + + @classmethod + def search_month(cls, names, clause): + """ search in month + """ + pool = Pool() + Line = pool.get('cashbook.line') + IrDate = pool.get('ir.date') + tab_line = Line.__table__() + Operator = fields.SQL_OPERATORS[clause[1]] + + dt1 = IrDate.today() + query = tab_line.select(tab_line.id, + where=Operator( + Literal(12 * dt1.year + dt1.month) - \ + (Literal(12) * DatePart('year', tab_line.date) + DatePart('month', tab_line.date)), + clause[2]), + ) + return [('id', 'in', query)] + @fields.depends('cashbook', '_parent_cashbook.state') def on_change_with_state_cashbook(self, name=None): """ get state of cashbook @@ -233,6 +269,21 @@ class Line(Workflow, ModelSQL, ModelView): """ return [('cashbook.state',) + tuple(clause[1:])] + @classmethod + def write(cls, *args): + """ deny update if cashbook.line!='open' + """ + actions = iter(args) + for lines, values in zip(actions, actions): + for line in lines: + if line.cashbook.state != 'open': + raise UserError(gettext( + 'cashbook.msg_book_deny_write', + bookname = line.cashbook.rec_name, + state_txt = line.cashbook.state_string, + )) + super(Line, cls).write(*args) + @classmethod def delete(cls, lines): """ deny delete if book is not 'open' or wf is not 'edit' diff --git a/line.xml b/line.xml index 212b091..d60291a 100644 --- a/line.xml +++ b/line.xml @@ -54,6 +54,28 @@ full copyright notices and license terms. --> + + + Current Month + + + + + + + Last Month + + + + + + + All + + + + + diff --git a/locale/de.po b/locale/de.po index e0682d7..5f84aea 100644 --- a/locale/de.po +++ b/locale/de.po @@ -242,6 +242,10 @@ msgctxt "view:cashbook.line:" msgid "Cashbook Line" msgstr "Kassenbuchzeile" +msgctxt "view:cashbook.line:" +msgid "Description" +msgstr "Beschreibung" + msgctxt "view:cashbook.line:" msgid "State" msgstr "Status" @@ -274,6 +278,26 @@ msgctxt "selection:cashbook.line,state:" msgid "Done" msgstr "Fertig" +msgctxt "field:cashbook.line,month:" +msgid "Month" +msgstr "Monat" + +msgctxt "field:cashbook.line,category:" +msgid "Category" +msgstr "Kategorie" + +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" + ################# # cashbook.type # diff --git a/locale/en.po b/locale/en.po index c2d2075..610765c 100644 --- a/locale/en.po +++ b/locale/en.po @@ -38,6 +38,10 @@ msgctxt "model:ir.message,text:msg_setting_already_exists" msgid "Settings for this user alredy exists." msgstr "Settings for this user alredy exists." +msgctxt "model:ir.message,text:msg_category_name_unique" +msgid "The category name already exists at this level." +msgstr "The category name already exists at this level." + msgctxt "model:res.group,name:group_cashbook" msgid "Cashbook" msgstr "Cashbook" @@ -90,6 +94,14 @@ msgctxt "model:ir.ui.menu,name:menu_open_lines" msgid "Open Cashbook" msgstr "Open Cashbook" +msgctxt "model:ir.ui.menu,name:menu_category" +msgid "Category" +msgstr "Category" + +msgctxt "model:ir.ui.menu,name:menu_category_list" +msgid "Category" +msgstr "Category" + msgctxt "model:ir.action,name:act_book_view" msgid "Account" msgstr "Account" @@ -106,6 +118,10 @@ msgctxt "model:ir.action,name:act_open_lines" msgid "Open Cashbook" msgstr "Open Cashbook" +msgctxt "model:ir.ui.menu,name:act_category_view" +msgid "Category" +msgstr "Category" + msgctxt "model:ir.model.button,string:line_wfedit_button" msgid "Edit" msgstr "Edit" @@ -182,6 +198,10 @@ msgctxt "help:cashbook.book,observer:" msgid "Group of users who have read-only access to the cashbook." msgstr "Group of users who have read-only access to the cashbook." +msgctxt "field:cashbook.book,account:" +msgid "Account" +msgstr "Account" + msgctxt "model:cashbook.line,name:" msgid "Cashbook Line" msgstr "Cashbook Line" @@ -222,6 +242,22 @@ msgctxt "selection:cashbook.line,state:" msgid "Done" msgstr "Done" +msgctxt "field:cashbook.line,month:" +msgid "Month" +msgstr "Month" + +msgctxt "model:ir.action.act_window.domain,name:act_line_domain_current" +msgid "Current Month" +msgstr "Current Month" + +msgctxt "model:ir.action.act_window.domain,name:act_line_domain_last" +msgid "Last Month" +msgstr "Last Month" + +msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all" +msgid "All" +msgstr "All" + msgctxt "model:cashbook.type,name:" msgid "Cashbook Type" msgstr "Cashbook Type" @@ -246,6 +282,54 @@ msgctxt "model:cashbook.type,name:atype_fixtermdep" msgid "Fixed-term deposit" msgstr "Fixed-term deposit" +msgctxt "model:cashbook.category,name:" +msgid "Category" +msgstr "Category" + +msgctxt "view:cashbook.category:" +msgid "General Information" +msgstr "General Information" + +msgctxt "view:cashbook.category:" +msgid "Children" +msgstr "Children" + +msgctxt "field:cashbook.category,name:" +msgid "Name" +msgstr "Name" + +msgctxt "field:cashbook.category,description:" +msgid "Description" +msgstr "Description" + +msgctxt "field:cashbook.category,account:" +msgid "Account" +msgstr "Account" + +msgctxt "field:cashbook.category,account_code:" +msgid "Account" +msgstr "Account" + +msgctxt "field:cashbook.category,company:" +msgid "Company" +msgstr "Company" + +msgctxt "field:cashbook.category,parent:" +msgid "Parent" +msgstr "Parent" + +msgctxt "field:cashbook.category,childs:" +msgid "Children" +msgstr "Children" + +msgctxt "field:cashbook.category,left:" +msgid "Left" +msgstr "Left" + +msgctxt "field:cashbook.category,right:" +msgid "Right" +msgstr "Right" + msgctxt "model:cashbook.open_lines.start,name:" msgid "Open Cashbook" msgstr "Open Cashbook" diff --git a/tests/test_book.py b/tests/test_book.py index 11259f7..8c29cbb 100644 --- a/tests/test_book.py +++ b/tests/test_book.py @@ -42,6 +42,7 @@ class BookTestCase(ModuleTestCase): Types = pool.get('cashbook.type') types, = Types.search([('short', '=', 'CAS')]) + category = self.prep_category() book, = Book.create([{ 'name': 'Book 1', @@ -49,6 +50,7 @@ class BookTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'test 1', + 'category': category.id, }])], }]) self.assertEqual(book.name, 'Book 1') @@ -68,6 +70,7 @@ class BookTestCase(ModuleTestCase): Types = pool.get('cashbook.type') types, = Types.search([('short', '=', 'CAS')]) + category = self.prep_category() book, = Book.create([{ 'name': 'Book 1', @@ -75,6 +78,7 @@ class BookTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'test 1', + 'category': category.id, }])], }]) self.assertEqual(book.name, 'Book 1') @@ -96,6 +100,7 @@ class BookTestCase(ModuleTestCase): Types = pool.get('cashbook.type') types, = Types.search([('short', '=', 'CAS')]) + category = self.prep_category() book, = Book.create([{ 'name': 'Book 1', @@ -103,6 +108,7 @@ class BookTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'test 1', + 'category': category.id, }])], }]) self.assertEqual(book.name, 'Book 1') diff --git a/tests/test_category.py b/tests/test_category.py index 03a4616..9ee834c 100644 --- a/tests/test_category.py +++ b/tests/test_category.py @@ -14,6 +14,25 @@ class CategoryTestCase(ModuleTestCase): 'Test cashbook categoy module' module = 'cashbook' + def prep_category(self, name='Cat1'): + """ create category + """ + pool = Pool() + 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') + + category, = Category.create([{ + 'company': company.id, + 'name': name, + }]) + return category + @with_transaction() def test_category_create_nodupl_at_root(self): """ create category, duplicates are allowed at root-level diff --git a/tests/test_line.py b/tests/test_line.py index 7cbaf6a..1c3579f 100644 --- a/tests/test_line.py +++ b/tests/test_line.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 unittest.mock import MagicMock class LineTestCase(ModuleTestCase): @@ -24,6 +25,7 @@ class LineTestCase(ModuleTestCase): Lines = pool.get('cashbook.line') types, = Types.search([('short', '=','CAS')]) + category = self.prep_category() book, = Book.create([{ 'name': 'Book 1', @@ -31,9 +33,11 @@ class LineTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', + 'category': category.id, }, { 'date': date(2022, 5, 2), 'description': 'Text 2', + 'category': category.id, }])], }]) self.assertEqual(book.name, 'Book 1') @@ -55,6 +59,106 @@ class LineTestCase(ModuleTestCase): self.assertEqual(Lines.search_count([('cashbook.state', '=', 'open')]), 2) self.assertEqual(Lines.search_count([('cashbook.state', '=', 'closed')]), 0) + @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')]) + category = self.prep_category() + + book, = Book.create([{ + 'name': 'Book 1', + 'btype': types.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Text 1', + 'category': category.id, + }, { + 'date': date(2022, 6, 1), + 'description': 'Text 2', + 'category': category.id, + }])], + }]) + self.assertEqual(book.name, 'Book 1') + self.assertEqual(book.state, 'open') + self.assertEqual(len(book.lines), 2) + + Book.wfclosed([book]) + self.assertEqual(book.state, 'closed') + + self.assertRaisesRegex(UserError, + "The cash book 'Book 1' is 'Closed' and cannot be changed.", + Line.write, + *[ + [book.lines[0]], + { + 'description': 'should be denied', + }, + ]) + + @with_transaction() + def test_line_create_check_month(self): + """ create cashbook + line, check 'month' + search + """ + 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')]) + category = self.prep_category() + + IrDate.today = MagicMock(return_value=date(2022, 6, 1)) + + book, = Book.create([{ + 'name': 'Book 1', + 'btype': types.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Text 1', + 'category': category.id, + }, { + 'date': date(2022, 6, 1), + 'description': 'Text 2', + 'category': category.id, + }])], + }]) + self.assertEqual(book.name, 'Book 1') + self.assertEqual(book.state, 'open') + self.assertEqual(len(book.lines), 2) + self.assertEqual(book.lines[0].date, date(2022, 5, 1)) + self.assertEqual(book.lines[0].month, 1) + self.assertEqual(book.lines[1].date, date(2022, 6, 1)) + self.assertEqual(book.lines[1].month, 0) + + l1, = Line.search([('month', '=', 0)]) + self.assertEqual(l1.date, date(2022, 6, 1)) + l1, = Line.search([('month', '=', 1)]) + self.assertEqual(l1.date, date(2022, 5, 1)) + + IrDate.today = MagicMock(return_value=date(2022, 6, 30)) + + l1, = Line.search([('month', '=', 0)]) + self.assertEqual(l1.date, date(2022, 6, 1)) + l1, = Line.search([('month', '=', 1)]) + self.assertEqual(l1.date, date(2022, 5, 1)) + + self.assertEqual(Line.search_count([('month', '=', 2)]), 0) + + IrDate.today = MagicMock(return_value=date(2022, 7, 1)) + + self.assertEqual(Line.search_count([('month', '=', 0)]), 0) + l1, = Line.search([('month', '=', 1)]) + self.assertEqual(l1.date, date(2022, 6, 1)) + + IrDate.today = MagicMock(return_value=date.today()) + @with_transaction() def test_line_delete_with_book_in_open_state(self): """ create cashbook + line, book in state=open, delete a line @@ -65,6 +169,7 @@ class LineTestCase(ModuleTestCase): Lines = pool.get('cashbook.line') types, = Types.search([('short', '=','CAS')]) + category = self.prep_category() book, = Book.create([{ 'name': 'Book 1', @@ -72,9 +177,11 @@ class LineTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', + 'category': category.id, }, { 'date': date(2022, 5, 2), 'description': 'Text 2', + 'category': category.id, }])], }]) self.assertEqual(book.name, 'Book 1') @@ -93,6 +200,7 @@ class LineTestCase(ModuleTestCase): Lines = pool.get('cashbook.line') types, = Types.search([('short', '=','CAS')]) + category = self.prep_category() book, = Book.create([{ 'name': 'Book 1', @@ -100,9 +208,11 @@ class LineTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', + 'category': category.id, }, { 'date': date(2022, 5, 2), 'description': 'Text 2', + 'category': category.id, }])], }]) self.assertEqual(book.name, 'Book 1') @@ -126,6 +236,7 @@ class LineTestCase(ModuleTestCase): Lines = pool.get('cashbook.line') types, = Types.search([('short', '=','CAS')]) + category = self.prep_category() book, = Book.create([{ 'name': 'Book 1', @@ -133,9 +244,11 @@ class LineTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Text 1', + 'category': category.id, }, { 'date': date(2022, 5, 2), 'description': 'Text 2', + 'category': category.id, }])], }]) self.assertEqual(book.name, 'Book 1') @@ -163,6 +276,7 @@ class LineTestCase(ModuleTestCase): Types = pool.get('cashbook.type') types, = Types.search([('short', '=', 'CAS')]) + category = self.prep_category() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) usr_lst = ResUser.create([{ 'login': 'frida', @@ -184,6 +298,7 @@ class LineTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Test 1', + 'category': category.id, }])], }]) self.assertEqual(book.rec_name, 'Fridas book'), @@ -223,6 +338,7 @@ class LineTestCase(ModuleTestCase): Types = pool.get('cashbook.type') types, = Types.search([('short', '=', 'CAS')]) + category = self.prep_category() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) grp_reviewer, = ResGroup.create([{ 'name': 'Cashbook Reviewer', @@ -251,6 +367,7 @@ class LineTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Test 1', + 'category': category.id, }])], }]) self.assertEqual(book.rec_name, 'Fridas book'), @@ -297,6 +414,7 @@ class LineTestCase(ModuleTestCase): Types = pool.get('cashbook.type') types, = Types.search([('short', '=', 'CAS')]) + category = self.prep_category() grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) grp_observer, = ResGroup.create([{ 'name': 'Cashbook Observer', @@ -325,6 +443,7 @@ class LineTestCase(ModuleTestCase): 'lines': [('create', [{ 'date': date(2022, 5, 1), 'description': 'Test 1', + 'category': category.id, }])], }]) self.assertEqual(book.rec_name, 'Fridas book'), diff --git a/view/line_form.xml b/view/line_form.xml index 028e17a..b37c587 100644 --- a/view/line_form.xml +++ b/view/line_form.xml @@ -16,7 +16,11 @@ full copyright notices and license terms. -->