add booking with category + test, todo: booking of asset

This commit is contained in:
Frederik Jaeckel 2024-03-03 21:54:16 +01:00
parent e51abf589b
commit b86e421298
6 changed files with 372 additions and 19 deletions

View file

@ -102,6 +102,10 @@ msgctxt "view:cashbook.planner:"
msgid "Booking" msgid "Booking"
msgstr "Buchung" msgstr "Buchung"
msgctxt "view:cashbook.planner:"
msgid "Booking text"
msgstr "Buchungstext"
msgctxt "field:cashbook.planner,company:" msgctxt "field:cashbook.planner,company:"
msgid "Company" msgid "Company"
msgstr "Unternehmen" msgstr "Unternehmen"
@ -238,18 +242,10 @@ msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Revenue" msgid "Revenue"
msgstr "Einnahme" msgstr "Einnahme"
msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Revenue Splitbooking"
msgstr "Einnahme Splitbuchung"
msgctxt "selection:cashbook.planner,bookingtype:" msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Expense" msgid "Expense"
msgstr "Ausgabe" msgstr "Ausgabe"
msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Expense Splitbooking"
msgstr "Ausgabe Splitbuchung"
msgctxt "selection:cashbook.planner,bookingtype:" msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Transfer from" msgid "Transfer from"
msgstr "Umbuchung von" msgstr "Umbuchung von"
@ -258,6 +254,50 @@ msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Transfer to" msgid "Transfer to"
msgstr "Umbuchung nach" msgstr "Umbuchung nach"
msgctxt "field:cashbook.planner,currency_cashbook:"
msgid "Currency"
msgstr "Währung"
msgctxt "help:cashbook.planner,currency_cashbook:"
msgid "Currency of Cashbook"
msgstr "Währung des Kassenbuchs"
msgctxt "field:cashbook.planner,amount:"
msgid "Amount"
msgstr "Betrag"
msgctxt "field:cashbook.planner,category:"
msgid "Category"
msgstr "Kategorie"
msgctxt "help:cashbook.planner,category:"
msgid "Category for the planned booking"
msgstr "Kategorie für die geplante Buchung"
msgctxt "field:cashbook.planner,booktransf:"
msgid "Source/Dest"
msgstr "Quelle/Ziel"
msgctxt "field:cashbook.planner,owner_cashbook:"
msgid "Owner"
msgstr "Eigentümer"
msgctxt "field:cashbook.planner,state_cashbook:"
msgid "State of Cashbook"
msgstr "Status des Kassenbuchs"
msgctxt "field:cashbook.planner,subject:"
msgid "Booking text"
msgstr "Buchungstext"
msgctxt "field:cashbook.planner,wfcheck:"
msgid "Set to 'Checked'"
msgstr "auf 'Geprüft' setzen"
msgctxt "help:cashbook.planner,wfcheck:"
msgid "Switches the booking to the 'Verified' state."
msgstr "Schaltet die Buchung in den Zustand 'Geprüft'"
############################ ############################
# cashbook.planner.nextrun # # cashbook.planner.nextrun #

View file

@ -78,6 +78,10 @@ msgctxt "view:cashbook.planner:"
msgid "Booking" msgid "Booking"
msgstr "Booking" msgstr "Booking"
msgctxt "view:cashbook.planner:"
msgid "Booking text"
msgstr "Booking text"
msgctxt "field:cashbook.planner,company:" msgctxt "field:cashbook.planner,company:"
msgid "Company" msgid "Company"
msgstr "Company" msgstr "Company"
@ -214,18 +218,10 @@ msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Revenue" msgid "Revenue"
msgstr "Revenue" msgstr "Revenue"
msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Revenue Splitbooking"
msgstr "Revenue Splitbooking"
msgctxt "selection:cashbook.planner,bookingtype:" msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Expense" msgid "Expense"
msgstr "Expense" msgstr "Expense"
msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Expense Splitbooking"
msgstr "Expense Splitbooking"
msgctxt "selection:cashbook.planner,bookingtype:" msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Transfer from" msgid "Transfer from"
msgstr "Transfer from" msgstr "Transfer from"
@ -234,6 +230,50 @@ msgctxt "selection:cashbook.planner,bookingtype:"
msgid "Transfer to" msgid "Transfer to"
msgstr "Transfer to" msgstr "Transfer to"
msgctxt "field:cashbook.planner,currency_cashbook:"
msgid "Currency"
msgstr "Currency"
msgctxt "help:cashbook.planner,currency_cashbook:"
msgid "Currency of Cashbook"
msgstr "Currency of Cashbook"
msgctxt "field:cashbook.planner,amount:"
msgid "Amount"
msgstr "Amount"
msgctxt "field:cashbook.planner,category:"
msgid "Category"
msgstr "Category"
msgctxt "help:cashbook.planner,category:"
msgid "Category for the planned booking"
msgstr "Category for the planned booking"
msgctxt "field:cashbook.planner,booktransf:"
msgid "Source/Dest"
msgstr "Source/Dest"
msgctxt "field:cashbook.planner,owner_cashbook:"
msgid "Owner"
msgstr "Owner"
msgctxt "field:cashbook.planner,state_cashbook:"
msgid "State of Cashbook"
msgstr "State of Cashbook"
msgctxt "field:cashbook.planner,subject:"
msgid "Booking text"
msgstr "Booking text"
msgctxt "field:cashbook.planner,wfcheck:"
msgid "Set to 'Checked'"
msgstr "Set to 'Checked'"
msgctxt "help:cashbook.planner,wfcheck:"
msgid "Switches the booking to the 'Verified' state."
msgstr "Switches the booking to the 'Verified' state."
msgctxt "model:cashbook.planner.nextrun,name:" msgctxt "model:cashbook.planner.nextrun,name:"
msgid "Next Execution Date" msgid "Next Execution Date"
msgstr "Next Execution Date" msgstr "Next Execution Date"

View file

@ -3,6 +3,7 @@
# The COPYRIGHT file at the top level of this repository contains the # The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms. # full copyright notices and license terms.
from decimal import Decimal
from datetime import date, timedelta from datetime import date, timedelta
from dateutil.rrule import ( from dateutil.rrule import (
rrule, YEARLY, MONTHLY, WEEKLY, DAILY, MO, TU, WE, TH, FR, SA, SU) rrule, YEARLY, MONTHLY, WEEKLY, DAILY, MO, TU, WE, TH, FR, SA, SU)
@ -11,9 +12,15 @@ from trytond.transaction import Transaction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.report import Report from trytond.report import Report
from trytond.pyson import Eval, Bool, If, And from trytond.pyson import Eval, Bool, If, And
from trytond.modules.cashbook.line import sel_bookingtype from trytond.modules.currency.fields import Monetary
from trytond.modules.cashbook.book import sel_state_book
from trytond.modules.cashbook.line import sel_bookingtype as sel_bookingtype_cb
sel_bookingtype = [
x for x in sel_bookingtype_cb if x[0]
in ['in', 'out', 'mvin', 'mvout']]
DEF_NONE = None DEF_NONE = None
SEL_FREQU = [ SEL_FREQU = [
('year', 'Yearly'), ('year', 'Yearly'),
@ -97,6 +104,48 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
bookingtype = fields.Selection( bookingtype = fields.Selection(
string='Type', selection=sel_bookingtype, required=True, string='Type', selection=sel_bookingtype, required=True,
help='Type of Booking') help='Type of Booking')
currency_cashbook = fields.Function(fields.Many2One(
string='Currency', help='Currency of Cashbook',
model_name='currency.currency'), 'on_change_with_currency_cashbook')
amount = Monetary(
string='Amount', currency='currency_cashbook',
digits='currency_cashbook', required=True)
category = fields.Many2One(
string='Category', model_name='cashbook.category',
help='Category for the planned booking', depends=['bookingtype'],
states={
'required': Eval('bookingtype', '').in_(['in', 'out']),
'invisible': ~Eval('bookingtype', '').in_(['in', 'out'])})
party = fields.Many2One(
string='Party', model_name='party.party', depends=['bookingtype'],
states={
'required': Eval('bookingtype', '').in_(['in', 'out']),
'invisible': ~Eval('bookingtype', '').in_(['in', 'out'])})
booktransf = fields.Many2One(
string='Source/Dest',
ondelete='RESTRICT', model_name='cashbook.book',
domain=[
('owner.id', '=', Eval('owner_cashbook', -1)),
('id', '!=', Eval('cashbook', -1)),
('btype', '!=', None)],
states={
'readonly': Eval('state_cashbook', '') != 'open',
'invisible': ~Eval('bookingtype', '').in_(['mvin', 'mvout']),
'required': Eval('bookingtype', '').in_(['mvin', 'mvout'])},
depends=[
'state_cashbook', 'bookingtype', 'owner_cashbook', 'cashbook'])
owner_cashbook = fields.Function(fields.Many2One(
string='Owner', readonly=True,
states={'invisible': True}, model_name='res.user'),
'on_change_with_owner_cashbook')
state_cashbook = fields.Function(fields.Selection(
string='State of Cashbook',
readonly=True, states={'invisible': True}, selection=sel_state_book),
'on_change_with_state_cashbook')
subject = fields.Text(string='Booking text', required=True)
wfcheck = fields.Boolean(
string="Set to 'Checked'",
help="Switches the booking to the 'Verified' state.")
@classmethod @classmethod
def __setup__(cls): def __setup__(cls):
@ -177,6 +226,19 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
break break
return result return result
@fields.depends('cashbook', '_parent_cashbook.currency')
def on_change_with_currency_cashbook(self, name=None):
""" get currency of selected cashbook
Args:
name (str, optional): name of field. Defaults to None.
Returns:
int: id of cashbook currency
"""
if self.cashbook:
return self.cashbook.currency.id
@fields.depends('nextrun') @fields.depends('nextrun')
def on_change_with_nextrun_link(self, name=None): def on_change_with_nextrun_link(self, name=None):
""" get nextrun-record if exist """ get nextrun-record if exist
@ -229,6 +291,30 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
'setpos': self.setpos} 'setpos': self.setpos}
)]) )])
@fields.depends('cashbook', '_parent_cashbook.owner')
def on_change_with_owner_cashbook(self, name=None):
""" get current owner
"""
if self.cashbook:
return self.cashbook.owner.id
@fields.depends('cashbook', '_parent_cashbook.state')
def on_change_with_state_cashbook(self, name=None):
""" get state of cashbook
"""
if self.cashbook:
return self.cashbook.state
@fields.depends('bookingtype', 'category', 'booktransf')
def on_change_bookingtype(self):
""" reset category/booktransf on change of bookingtype
"""
if self.bookingtype:
if self.bookingtype in ['in', 'out']:
self.booktransf = None
elif self.bookingtype in ['mvin', 'mvout']:
self.category = None
@fields.depends('frequ', 'setpos', 'weekday', 'monthday') @fields.depends('frequ', 'setpos', 'weekday', 'monthday')
def on_change_frequ(self): def on_change_frequ(self):
""" update fields """ update fields
@ -254,6 +340,24 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
""" """
self.on_change_frequ() self.on_change_frequ()
@classmethod
def default_wfcheck(cls):
""" False as default for wf-state 'checked'
Returns:
bool: False
"""
return False
@classmethod
def default_amount(cls):
""" default for amount
Returns:
Decimal: 0.00
"""
return Decimal('0.0')
@classmethod @classmethod
def default_interval(cls): def default_interval(cls):
""" get default for interval """ get default for interval
@ -387,8 +491,40 @@ class ScheduledBooking(DeactivableMixin, ModelSQL, ModelView):
def run_booking(cls, records): def run_booking(cls, records):
""" do prepared booking """ do prepared booking
""" """
pool = Pool()
IrDate = pool.get('ir.date')
Line = pool.get('cashbook.line')
to_create = []
to_create_check = []
for record in records: for record in records:
print('-- booking:', record.rec_name) line = {
'cashbook': record.cashbook.id,
'bookingtype': record.bookingtype,
'date': IrDate.today(),
'amount': record.amount,
'description': record.subject}
if record.bookingtype in ['in', 'out']:
if record.category:
line['category'] = record.category.id
if record.party:
line['party'] = record.party.id
elif record.bookingtype in ['mvin', 'mvout']:
if record.booktransf:
line['booktransf'] = record.booktransf.id
if record.wfcheck:
to_create_check.append(line)
else:
to_create.append(line)
if to_create_check:
lines = Line.create(to_create_check)
Line.wfcheck(lines)
if to_create:
Line.create(to_create)
@classmethod @classmethod
def cronjob(cls): def cronjob(cls):

View file

@ -3,6 +3,7 @@
# The COPYRIGHT file at the top level of this repository contains the # The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms. # full copyright notices and license terms.
from decimal import Decimal
from unittest.mock import MagicMock from unittest.mock import MagicMock
from trytond.tests.test_tryton import with_transaction from trytond.tests.test_tryton import with_transaction
from trytond.pool import Pool from trytond.pool import Pool
@ -19,6 +20,7 @@ class PlannerTestCase(object):
pool = Pool() pool = Pool()
Book = pool.get('cashbook.book') Book = pool.get('cashbook.book')
Planner = pool.get('cashbook.planner') Planner = pool.get('cashbook.planner')
Party = pool.get('party.party')
types = self.prep_type() types = self.prep_type()
company = self.prep_company() company = self.prep_company()
@ -36,11 +38,18 @@ class PlannerTestCase(object):
}]) }])
self.assertEqual(book.rec_name, 'Book 1 | 0.00 usd | Open') self.assertEqual(book.rec_name, 'Book 1 | 0.00 usd | Open')
category = self.prep_category()
party, = Party.create([{
'name': 'Party',
'addresses': [('create', [{}])]}])
job, = Planner.create([{ job, = Planner.create([{
'cashbook': book.id, 'cashbook': book.id,
'name': name, 'name': name,
'start_date': date(2022, 5, 1), 'start_date': date(2022, 5, 1),
'bookingtype': 'out'}]) 'bookingtype': 'out',
'category': category.id,
'party': party.id,
'subject': 'Booking text'}])
# check applied defaults # check applied defaults
self.assertEqual(job.rec_name, 'Job 1') self.assertEqual(job.rec_name, 'Job 1')
self.assertEqual(job.start_date, date(2022, 5, 1)) self.assertEqual(job.start_date, date(2022, 5, 1))
@ -286,4 +295,111 @@ class PlannerTestCase(object):
IrDate.today = MagicMock(return_value=date.today()) IrDate.today = MagicMock(return_value=date.today())
@with_transaction()
def test_planner_cronjobs_booking_with_category(self):
""" create job, configure booking with category, run job
"""
pool = Pool()
Planner = pool.get('cashbook.planner')
IrDate = pool.get('ir.date')
Category = pool.get('cashbook.category')
Cashbook = pool.get('cashbook.book')
job = self.prep_create_job()
self.assertEqual(
job._compute_dates_by_rrule(
count=1, query_date=date(2022, 5, 1)), [
date(2022, 5, 1)])
IrDate.today = MagicMock(return_value=date(2022, 5, 24))
category, = Category.search([('name', '=', 'Cat1')])
Planner.write(*[
[job],
{
'name': 'Booking to category',
'amount': Decimal('10.0'),
'bookingtype': 'out',
'category': category.id,
'subject': 'booking ${month}/${year}, ${date}',
'wfcheck': True,
}])
self.assertEqual(job.rec_name, 'Booking to category')
self.assertEqual(job.cashbook.rec_name, 'Book 1 | 0.00 usd | Open')
self.assertEqual(len(job.cashbook.lines), 0)
job, = Planner.search([])
self.assertEqual(job.nextrun[0].date, date(2022, 6, 1))
IrDate.today = MagicMock(return_value=date(2022, 6, 1))
Planner.cronjob()
self.assertEqual(job.nextrun[0].date, date(2022, 7, 1))
# check cashbook
self.assertEqual(len(job.cashbook.lines), 1)
self.assertEqual(
job.cashbook.lines[0].rec_name,
"06/01/2022|Exp|-10.00 usd|booking " +
"${month}/${year}, ${date} [Cat1]")
self.assertEqual(job.cashbook.lines[0].state, 'check')
with Transaction().set_context({'date': date(2022, 6, 1)}):
cashbook, = Cashbook.browse([job.cashbook])
self.assertEqual(cashbook.rec_name, 'Book 1 | -10.00 usd | Open')
IrDate.today = MagicMock(return_value=date.today())
@with_transaction()
def test_planner_cronjobs_booking_transfer_nonasset_eur(self):
""" create job, configure transfer-booking to non-asset-cashbook,
run job
"""
pool = Pool()
Planner = pool.get('cashbook.planner')
IrDate = pool.get('ir.date')
Category = pool.get('cashbook.category')
Cashbook = pool.get('cashbook.book')
job = self.prep_create_job()
self.assertEqual(
job._compute_dates_by_rrule(
count=1, query_date=date(2022, 5, 1)), [
date(2022, 5, 1)])
IrDate.today = MagicMock(return_value=date(2022, 5, 24))
category, = Category.search([('name', '=', 'Cat1')])
Planner.write(*[
[job],
{
'name': 'Booking to category',
'amount': Decimal('10.0'),
'bookingtype': 'out',
'category': category.id,
'subject': 'booking ${month}/${year}, ${date}',
'wfcheck': True,
}])
self.assertEqual(job.rec_name, 'Booking to category')
self.assertEqual(job.cashbook.rec_name, 'Book 1 | 0.00 usd | Open')
self.assertEqual(len(job.cashbook.lines), 0)
job, = Planner.search([])
self.assertEqual(job.nextrun[0].date, date(2022, 6, 1))
IrDate.today = MagicMock(return_value=date(2022, 6, 1))
Planner.cronjob()
self.assertEqual(job.nextrun[0].date, date(2022, 7, 1))
# check cashbook
self.assertEqual(len(job.cashbook.lines), 1)
self.assertEqual(
job.cashbook.lines[0].rec_name,
"06/01/2022|Exp|-10.00 usd|booking " +
"${month}/${year}, ${date} [Cat1]")
self.assertEqual(job.cashbook.lines[0].state, 'check')
with Transaction().set_context({'date': date(2022, 6, 1)}):
cashbook, = Cashbook.browse([job.cashbook])
self.assertEqual(cashbook.rec_name, 'Book 1 | -10.00 usd | Open')
IrDate.today = MagicMock(return_value=date.today())
# end PlannerTestCase # end PlannerTestCase

View file

@ -41,6 +41,23 @@ full copyright notices and license terms. -->
<page id="booking" col="4" string="Booking"> <page id="booking" col="4" string="Booking">
<label name="bookingtype"/> <label name="bookingtype"/>
<field name="bookingtype"/> <field name="bookingtype"/>
<label name="amount"/>
<field name="amount"/>
<label name="category"/>
<field name="category"/>
<label name="party"/>
<field name="party"/>
<label name="booktransf"/>
<field name="booktransf" colspan="3"/>
<label name="wfcheck"/>
<field name="wfcheck"/>
<newline/>
<separator name="subject" colspan="4" string="Booking text"/>
<field name="subject" colspan="4"/>
</page> </page>
<page name="description" col="1" string="Description"> <page name="description" col="1" string="Description">
<field name="description"/> <field name="description"/>

View file

@ -8,4 +8,8 @@ full copyright notices and license terms. -->
<field name="cashbook"/> <field name="cashbook"/>
<field name="nextrun_link"/> <field name="nextrun_link"/>
<field name="bookingtype"/> <field name="bookingtype"/>
<field name="amount"/>
<field name="category"/>
<field name="party"/>
<field name="booktransf"/>
</tree> </tree>