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. -->
-
-
+
+
+
+
+
+
diff --git a/view/line_list.xml b/view/line_list.xml
index dc4adab..e38ebb3 100644
--- a/view/line_list.xml
+++ b/view/line_list.xml
@@ -5,6 +5,7 @@ full copyright notices and license terms. -->
+