diff --git a/README.rst b/README.rst index 97408f7..2b72d75 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,36 @@ Requires Changes ======= +*6.0.7 - 07.09.2022* + +- add: enter-booking-wizard + +*6.0.6 - 06.09.2022* + +- updt: optimized form - line, line-context +- updt: extended search in cashbook-lines + +*6.0.5 - 05.09.2022* + +- updt: view of book + line optimized + +*6.0.4 - 05.09.2022* + +- fix: write number at state-change 'check' -> 'done' +- updt: speedup transaction view + +*6.0.3 - 31.08.2022* + +- updt: checks, sorting + +*6.0.2 - 25.08.2022* + +- add: split-booking + +*6.0.1 - 23.08.2022* + +- works + *6.0.0 - 05.08.2022* - init diff --git a/__init__.py b/__init__.py index cb57d15..6115d74 100644 --- a/__init__.py +++ b/__init__.py @@ -10,6 +10,7 @@ from .line import Line, LineContext from .splitline import SplitLine from .wizard_openline import OpenCashBook, OpenCashBookStart from .wizard_runreport import RunCbReport, RunCbReportStart +from .wizard_booking import EnterBookingWizard, EnterBookingStart from .configuration import Configuration, UserConfiguration from .category import Category from .reconciliation import Reconciliation @@ -28,6 +29,7 @@ def register(): Reconciliation, OpenCashBookStart, RunCbReportStart, + EnterBookingStart, module='cashbook', type_='model') Pool.register( ReconciliationReport, @@ -35,4 +37,5 @@ def register(): Pool.register( OpenCashBook, RunCbReport, + EnterBookingWizard, module='cashbook', type_='wizard') diff --git a/book.py b/book.py index dd92f35..2adfb4f 100644 --- a/book.py +++ b/book.py @@ -76,14 +76,16 @@ class Book(Workflow, ModelSQL, ModelView): ), }, depends=DEPENDS+['lines']) start_balance = fields.Numeric(string='Initial Amount', required=True, + digits=(16, Eval('currency_digits', 2)), states={ 'readonly': Or( STATES['readonly'], Bool(Eval('lines')), ), - }, depends=DEPENDS+['lines']) - balance = fields.Function(fields.Numeric(string='Balance', readonly=True), - 'on_change_with_balance') + }, depends=DEPENDS+['lines', 'currency_digits']) + balance = fields.Function(fields.Numeric(string='Balance', readonly=True, + digits=(16, Eval('currency_digits', 2)), + depends=['currency_digits']), 'on_change_with_balance') currency = fields.Many2One(string='Currency', required=True, model_name='currency.currency', states={ @@ -92,6 +94,8 @@ class Book(Workflow, ModelSQL, ModelView): Bool(Eval('lines', [])), ), }, depends=DEPENDS+['lines']) + currency_digits = fields.Function(fields.Integer(string='Currency Digits', + readonly=True), 'on_change_with_currency_digits') state = fields.Selection(string='State', required=True, readonly=True, selection=sel_state_book) state_string = state.translated('state') @@ -196,6 +200,15 @@ class Book(Workflow, ModelSQL, ModelView): 'state': self.state_string, } + @fields.depends('currency') + def on_change_with_currency_digits(self, name=None): + """ currency of cashbook + """ + if self.currency: + return self.currency.digits + else: + return 2 + @fields.depends('id', 'start_balance') def on_change_with_balance(self, name=None): """ compute balance diff --git a/category.py b/category.py index 0d23eb8..477016c 100644 --- a/category.py +++ b/category.py @@ -3,12 +3,46 @@ # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. -from trytond.model import ModelView, ModelSQL, fields, Unique, tree, sequence_ordered +from trytond.model import ModelView, ModelSQL, fields, Unique, Exclude, tree from trytond.transaction import Transaction from trytond.pool import Pool from trytond.pyson import Eval, If, Bool from trytond.exceptions import UserError from trytond.i18n import gettext +from sql.operators import Equal +from sql.functions import Function +from sql import With, Literal + + +class ArrayApppend(Function): + """ sql: array_append + """ + __slots__ = () + _function = 'ARRAY_APPEND' + +# end ArrayApppend + + +class ArrayToString(Function): + """ sql: array_to_string + """ + __slots__ = () + _function = 'ARRAY_TO_STRING' + +# end ArrayToString + + +class Array(Function): + """ sql: array-type + """ + __slots__ = () + _function = 'ARRAY' + + def __str__(self): + return self._function + '[' + ', '.join( + map(self._format, self.args)) + ']' + +# end Array sel_categorytype = [ @@ -17,7 +51,7 @@ sel_categorytype = [ ] -class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView): +class Category(tree(separator='/'), ModelSQL, ModelView): 'Category' __name__ = 'cashbook.category' @@ -36,7 +70,6 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView): company = fields.Many2One(string='Company', model_name='company.company', required=True, ondelete="RESTRICT") - sequence = fields.Integer(string='Sequence', select=True) parent = fields.Many2One(string="Parent", model_name='cashbook.category', ondelete='RESTRICT', left='left', right='right') @@ -45,15 +78,35 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView): left = fields.Integer(string='Left', required=True, select=True) right = fields.Integer(string='Right', required=True, select=True) + @classmethod + def __register__(cls, module_name): + super(Category, cls).__register__(module_name) + cls.migrate_sequence(module_name) + @classmethod def __setup__(cls): super(Category, cls).__setup__() - cls._order.insert(0, ('name', 'ASC')) + cls._order.insert(0, ('rec_name', 'ASC')) t = cls.__table__() cls._sql_constraints.extend([ - ('name_uniq', Unique(t, t.name, t.company, t.parent), 'cashbook.msg_category_name_unique'), + ('name_uniq', + Unique(t, t.name, t.company, t.parent), + 'cashbook.msg_category_name_unique'), + ('name2_uniq', + Exclude(t, + (t.name, Equal), + (t.cattype, Equal), + where=(t.parent == None)), + 'cashbook.msg_category_name_unique'), ]) + @classmethod + def migrate_sequence(cls, module_name): + """ remove colum 'sequence' + """ + table = cls.__table_handler__(module_name) + table.drop_column('sequence') + @classmethod def default_cattype(cls): return 'out' @@ -70,6 +123,34 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView): def default_right(): return 0 + @staticmethod + def order_rec_name(tables): + """ order by pos + a recursive sorting + """ + Category2 = Pool().get('cashbook.category') + tab_cat = Category2.__table__() + tab_cat2 = Category2.__table__() + table, _ = tables[None] + + categories = With('id', 'name', 'name_path', recursive=True) + categories.query = tab_cat.select( + tab_cat.id, tab_cat.name, Array(tab_cat.name), + where = tab_cat.parent==None, + ) + categories.query |= tab_cat2.join(categories, + condition=categories.id==tab_cat2.parent, + ).select( + tab_cat2.id, tab_cat2.name, ArrayApppend(categories.name_path, tab_cat2.name), + ) + categories.query.all_ = True + + query = categories.select( + ArrayToString(categories.name_path, '/').as_('rec_name'), + where = table.id==categories.id, + with_ = [categories]) + return [query] + @fields.depends('parent', '_parent_parent.cattype') def on_change_with_parent_cattype(self, name=None): """ get type of parent category or None diff --git a/category.xml b/category.xml index a5dcc66..516e4c7 100644 --- a/category.xml +++ b/category.xml @@ -42,6 +42,20 @@ full copyright notices and license terms. --> + + + Revenue + + + + + + Expense + + + + + Category @@ -59,6 +73,21 @@ full copyright notices and license terms. --> + + + Revenue + + + + + + Expense + + + + + + diff --git a/configuration.py b/configuration.py index e0e460f..e8de3b6 100644 --- a/configuration.py +++ b/configuration.py @@ -15,6 +15,9 @@ field_done = fields.Boolean(string='Done', help='Show cashbook lines in Done-state.') field_catnamelong = fields.Boolean(string='Category: Show long name', help='Shows the long name of the category in the Category field of a cash book line.') +field_defbook = fields.Many2One(string='Default Cashbook', + help='The default cashbook is selected when you open the booking wizard.', + model_name='cashbook.book', ondelete='SET NULL') class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin): @@ -36,6 +39,7 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin): checked = fields.MultiValue(field_checked) done = fields.MultiValue(field_done) catnamelong = fields.MultiValue(field_catnamelong) + defbook = fields.MultiValue(field_defbook) @classmethod def multivalue_model(cls, field): @@ -44,7 +48,7 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin): pool = Pool() if field in ['date_from', 'date_to', 'checked', 'done', - 'catnamelong']: + 'catnamelong', 'defbook']: return pool.get('cashbook.configuration_user') return super(Configuration, cls).multivalue_model(field) @@ -82,6 +86,7 @@ class UserConfiguration(ModelSQL, UserValueMixin): checked = field_checked done = field_done catnamelong = field_catnamelong + defbook = field_defbook @classmethod def default_checked(cls): diff --git a/line.py b/line.py index a8ccda4..8e0f17f 100644 --- a/line.py +++ b/line.py @@ -57,8 +57,10 @@ class Line(Workflow, ModelSQL, ModelView): month = fields.Function(fields.Integer(string='Month', readonly=True), 'on_change_with_month', searcher='search_month') number = fields.Char(string='Number', readonly=True) - description = fields.Text(string='Description', + description = fields.Text(string='Description', select=True, states=STATES, depends=DEPENDS) + descr_short = fields.Function(fields.Char(string='Description', readonly=True), + 'on_change_with_descr_short', searcher='search_descr_short') category = fields.Many2One(string='Category', model_name='cashbook.category', ondelete='RESTRICT', states={ @@ -89,7 +91,8 @@ class Line(Workflow, ModelSQL, ModelView): STATES['readonly'], Eval('bookingtype', '').in_(['spin', 'spout']), ), - }, depends=DEPENDS+['currency_digits', 'bookingtype']) + }, depends=DEPENDS+['currency_digits', 'bookingtype'], + domain=[('amount', '>=', Decimal('0.0'))]) 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)), @@ -181,6 +184,9 @@ class Line(Workflow, ModelSQL, ModelView): ('state_val', Check(t, t.state.in_(['edit', 'check', 'done'])), 'cashbook.msg_line_wrong_state_value'), + ('amount_val', + Check(t, t.amount >= Decimal('0.0')), + 'cashbook.msg_line_must_positive'), ]) cls._transitions |= set(( ('edit', 'check'), @@ -345,7 +351,11 @@ class Line(Workflow, ModelSQL, ModelView): def search_rec_name(cls, name, clause): """ search in description +... """ - return [('description',) + tuple(clause[1:])] + l1 = cls.search_payee(name, clause) + [ + ('description',) + tuple(clause[1:]), + ('category.rec_name',) + tuple(clause[1:]), + ] + return l1 def get_rec_name(self, name): """ short + name @@ -414,6 +424,13 @@ class Line(Workflow, ModelSQL, ModelView): return [tab2] + @staticmethod + def order_descr_short(tables): + """ order by 'description' + """ + table, _ = tables[None] + return [table.description] + @classmethod def search_payee(cls, names, clause): """ search in payee for party or cashbook @@ -454,6 +471,12 @@ class Line(Workflow, ModelSQL, ModelView): """ return [('cashbook.state',) + tuple(clause[1:])] + @classmethod + def search_descr_short(cls, names, clause): + """ search in description + """ + return [('description',) + tuple(clause[1:])] + @fields.depends('amount', 'splitlines') def on_change_splitlines(self): """ update amount if splitlines change @@ -481,6 +504,13 @@ class Line(Workflow, ModelSQL, ModelView): else : self.splitlines = [] + @fields.depends('description') + def on_change_with_descr_short(self, name=None): + """ to speed up list-view + """ + if self.description: + return self.description[:50] + @fields.depends('party', 'booktransf', 'bookingtype') def on_change_with_payee(self, name=None): """ get party or cashbook @@ -549,7 +579,8 @@ class Line(Workflow, ModelSQL, ModelView): @fields.depends('id', 'date', 'cashbook', \ '_parent_cashbook.start_balance', '_parent_cashbook.id',\ - 'reconciliation', '_parent_reconciliation.start_amount') + 'reconciliation', '_parent_reconciliation.start_amount', + '_parent_reconciliation.state') def on_change_with_balance(self, name=None): """ compute balance until current line, with current sort order, try to use a reconciliation as start to speed up calculation @@ -558,6 +589,30 @@ class Line(Workflow, ModelSQL, ModelView): Reconciliation = pool.get('cashbook.recon') Line = pool.get('cashbook.line') + def get_from_last_recon(line2): + """ search last reconciliation in state 'done', + generate query + """ + query2 = [] + end_amount = None + + recons = Reconciliation.search([ + ('cashbook.id', '=', self.cashbook.id), + ('date_to', '<=', line2.date), + ('state', '=', 'done'), + ], order=[('date_from', 'DESC')], limit=1) + if len(recons) > 0: + query2.append([ + ('date', '>=', recons[0].date_to), + ('date', '<=', line2.date), + ['OR', + ('reconciliation', '=', None), + ('reconciliation.id', '!=', recons[0]), + ], + ]) + end_amount = recons[0].end_amount + return (query2, end_amount) + if self.cashbook: query = [ ('cashbook.id', '=', self.cashbook.id), @@ -567,22 +622,22 @@ class Line(Workflow, ModelSQL, ModelView): # get existing reconciliation, starting before current line # this will speed up calculation of by-line-balance if self.date is not None: - recons = Reconciliation.search([ - ('cashbook.id', '=', self.cashbook.id), - ('date_from', '<=', self.date), - ('state', '=', 'done'), - ], order=[('date_from', 'DESC')], limit=1) - if len(recons) > 0: - query.extend([ - ['OR', - ('date', '>', recons[0].date_from), - [ - ('date', '=', recons[0].date_from), - ('reconciliation.id', '=',recons[0].id), - ], - ] - ]) - balance = recons[0].start_amount + if self.reconciliation: + if self.reconciliation.state == 'done': + query.append( + ('reconciliation.id', '=', self.reconciliation.id), + ) + balance = self.reconciliation.start_amount + else : + (query2, balance2) = get_from_last_recon(self) + query.extend(query2) + if balance2 is not None: + balance = balance2 + else : + (query2, balance2) = get_from_last_recon(self) + query.extend(query2) + if balance2 is not None: + balance = balance2 lines = Line.search(query) for line in lines: @@ -722,7 +777,7 @@ class Line(Workflow, ModelSQL, ModelView): # deny write if line is not 'Edit' if line.state != 'edit': # allow state-update, if its the only action - if not ((len(set({'state', 'reconciliation'}).intersection(values.keys())) > 0) \ + if not ((len(set({'state', 'reconciliation', 'number'}).intersection(values.keys())) > 0) \ and (len(values.keys()) == 1)): raise UserError(gettext( 'cashbook.msg_line_deny_write', @@ -848,12 +903,14 @@ class LineContext(ModelView): 'readonly': Eval('num_cashbook', 0) < 2, }, depends=['num_cashbook']) date_from = fields.Date(string='Start Date', depends=['date_to'], + help='Limits the date range for the displayed entries.', domain=[ If(Eval('date_to') & Eval('date_from'), ('date_from', '<=', Eval('date_to')), ()), ]) date_to = fields.Date(string='End Date', depends=['date_from'], + help='Limits the date range for the displayed entries.', domain=[ If(Eval('date_to') & Eval('date_from'), ('date_from', '<=', Eval('date_to')), diff --git a/locale/de.po b/locale/de.po index 073d916..71403b9 100644 --- a/locale/de.po +++ b/locale/de.po @@ -150,6 +150,10 @@ msgctxt "model:ir.message,text:msg_line_invalid_category" msgid "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'." msgstr "Die Kategorie der Buchungszeile '%(recname)s' paßt nicht zum Buchungstyp '%(booktype)s'." +msgctxt "model:ir.message,text:msg_line_must_positive" +msgid "The amount must be positive." +msgstr "Der Betrag muß positiv sein." + ############# # res.group # @@ -274,6 +278,10 @@ msgctxt "model:ir.ui.menu,name:act_category_view" msgid "Category" msgstr "Kategorie" +msgctxt "model:ir.ui.menu,name:menu_enter_booking" +msgid "Enter Booking" +msgstr "Buchung eingeben" + ############# # ir.action # @@ -302,6 +310,10 @@ msgctxt "model:ir.action,name:act_wizard_report" msgid "Cashbook Report" msgstr "Kassenbuch Bericht" +msgctxt "model:ir.action,name:act_enterbooking_wiz" +msgid "Enter Booking" +msgstr "Buchung eingeben" + ############################### # ir.action.act_window.domain # @@ -318,6 +330,22 @@ msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all" msgid "All" msgstr "Alle" +msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_in" +msgid "Revenue" +msgstr "Einnahmen" + +msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_out" +msgid "Expense" +msgstr "Ausgaben" + +msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_in" +msgid "Revenue" +msgstr "Einnahmen" + +msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_out" +msgid "Expense" +msgstr "Ausgaben" + ################### # ir.model.button # @@ -390,6 +418,14 @@ msgctxt "view:cashbook.book:" msgid "Owner and Authorizeds" msgstr "Eigentümer und Autorisierte" +msgctxt "view:cashbook.book:" +msgid "General Information" +msgstr "Allgemein" + +msgctxt "view:cashbook.book:" +msgid "Balance" +msgstr "Saldo" + msgctxt "view:cashbook.book:" msgid "Reconciliations" msgstr "Abstimmungen" @@ -446,6 +482,10 @@ msgctxt "field:cashbook.book,currency:" msgid "Currency" msgstr "Währung" +msgctxt "field:cashbook.book,currency_digits:" +msgid "Currency Digits" +msgstr "Nachkommastellen Währung" + msgctxt "field:cashbook.book,start_balance:" msgid "Initial Amount" msgstr "Anfangsbetrag" @@ -618,6 +658,10 @@ msgctxt "field:cashbook.line,description:" msgid "Description" msgstr "Beschreibung" +msgctxt "field:cashbook.line,descr_short:" +msgid "Description" +msgstr "Beschreibung" + msgctxt "field:cashbook.line,state:" msgid "State" msgstr "Status" @@ -934,10 +978,18 @@ msgctxt "field:cashbook.line.context,date_from:" msgid "Start Date" msgstr "Beginndatum" +msgctxt "help:cashbook.line.context,date_from:" +msgid "Limits the date range for the displayed entries." +msgstr "Begrenzt den Datumsbereich für die angezeigten Einträge." + msgctxt "field:cashbook.line.context,date_to:" msgid "End Date" msgstr "Endedatum" +msgctxt "help:cashbook.line.context,date_to:" +msgid "Limits the date range for the displayed entries." +msgstr "Begrenzt den Datumsbereich für die angezeigten Einträge." + ########################## # cashbook.configuration # @@ -946,6 +998,10 @@ msgctxt "model:cashbook.configuration,name:" msgid "Configuration" msgstr "Konfiguration" +msgctxt "view:cashbook.configuration:" +msgid "Enter Booking Wizard" +msgstr "Dialog: Buchung eingeben" + msgctxt "view:cashbook.configuration:" msgid "Open Cashbook Wizard" msgstr "Dialog: Kassenbuch öffnen" @@ -954,6 +1010,14 @@ msgctxt "view:cashbook.configuration:" msgid "Cashbook" msgstr "Kassenbuch" +msgctxt "field:cashbook.configuration,defbook:" +msgid "Default Cashbook" +msgstr "Standardkassenbuch" + +msgctxt "help:cashbook.configuration,defbook:" +msgid "The default cashbook is selected when you open the booking wizard." +msgstr "Das Standardkassenbuch wird beim Öffnen des Buchungswizards ausgewählt." + msgctxt "field:cashbook.configuration,date_from:" msgid "Start Date" msgstr "Beginndatum" @@ -1026,6 +1090,14 @@ msgctxt "help:cashbook.configuration_user,catnamelong:" msgid "Shows the long name of the category in the Category field of a cash book line." msgstr "Zeigt im Feld 'Kategorie' einer Kassenbuchzeile den langen Namen der Kategorie." +msgctxt "field:cashbook.configuration_user,defbook:" +msgid "Default Cashbook" +msgstr "Standardkassenbuch" + +msgctxt "help:cashbook.configuration_user,defbook:" +msgid "The default cashbook is selected when you open the booking wizard." +msgstr "Das Standardkassenbuch wird beim Öffnen des Buchungswizards ausgewählt." + ################## # cashbook.recon # @@ -1209,3 +1281,103 @@ msgstr "Gesamt" msgctxt "report:cashbook.reprecon:" msgid "Payee" msgstr "Empfänger" + + +############################### +# cashbook.enterbooking.start # +############################### +msgctxt "model:cashbook.enterbooking.start,name:" +msgid "Enter Booking" +msgstr "Buchung eingeben" + +msgctxt "view:cashbook.enterbooking.start:" +msgid "Description" +msgstr "Beschreibung" + +msgctxt "field:cashbook.enterbooking.start,cashbook:" +msgid "Cashbook" +msgstr "Kassenbuch" + +msgctxt "field:cashbook.enterbooking.start,cashbooks:" +msgid "Cashbooks" +msgstr "Kassenbücher" + +msgctxt "field:cashbook.enterbooking.start,balance:" +msgid "Balance" +msgstr "Saldo" + +msgctxt "field:cashbook.enterbooking.start,currency_digits:" +msgid "Currency Digits" +msgstr "Nachkommastellen Währung" + +msgctxt "field:cashbook.enterbooking.start,currency:" +msgid "Currency" +msgstr "Währung" + +msgctxt "field:cashbook.enterbooking.start,bookingtype:" +msgid "Type" +msgstr "Typ" + +msgctxt "selection:cashbook.enterbooking.start,bookingtype:" +msgid "Revenue" +msgstr "Einnahme" + +msgctxt "selection:cashbook.enterbooking.start,bookingtype:" +msgid "Revenue Splitbooking" +msgstr "Einnahme Splitbuchung" + +msgctxt "selection:cashbook.enterbooking.start,bookingtype:" +msgid "Expense" +msgstr "Ausgabe" + +msgctxt "selection:cashbook.enterbooking.start,bookingtype:" +msgid "Expense Splitbooking" +msgstr "Ausgabe Splitbuchung" + +msgctxt "selection:cashbook.enterbooking.start,bookingtype:" +msgid "Transfer from" +msgstr "Umbuchung von" + +msgctxt "selection:cashbook.enterbooking.start,bookingtype:" +msgid "Transfer to" +msgstr "Umbuchung nach" + +msgctxt "field:cashbook.enterbooking.start,amount:" +msgid "Amount" +msgstr "Betrag" + +msgctxt "field:cashbook.enterbooking.start,owner_cashbook:" +msgid "Owner" +msgstr "Eigentümer" + +msgctxt "field:cashbook.enterbooking.start,category:" +msgid "Category" +msgstr "Kategorie" + +msgctxt "field:cashbook.enterbooking.start,booktransf:" +msgid "Source/Dest" +msgstr "Quelle/Ziel" + +msgctxt "field:cashbook.enterbooking.start,party:" +msgid "Party" +msgstr "Partei" + + +######################### +# cashbook.enterbooking # +######################### +msgctxt "model:cashbook.enterbooking,name:" +msgid "Enter Booking" +msgstr "Buchung eingeben" + +msgctxt "wizard_button:cashbook.enterbooking,start,end:" +msgid "Cancel" +msgstr "Abbruch" + +msgctxt "wizard_button:cashbook.enterbooking,start,save_:" +msgid "Save" +msgstr "Speichern" + +msgctxt "wizard_button:cashbook.enterbooking,start,savenext_:" +msgid "Save & Next" +msgstr "Speichern & Weiter" diff --git a/locale/en.po b/locale/en.po index c6f7965..49684f9 100644 --- a/locale/en.po +++ b/locale/en.po @@ -146,6 +146,10 @@ msgctxt "model:ir.message,text:msg_line_invalid_category" msgid "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'." msgstr "The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'." +msgctxt "model:ir.message,text:msg_line_must_positive" +msgid "The amount must be positive." +msgstr "The amount must be positive." + msgctxt "model:res.group,name:group_cashbook" msgid "Cashbook" msgstr "Cashbook" @@ -294,6 +298,22 @@ msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all" msgid "All" msgstr "All" +msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_in" +msgid "Revenue" +msgstr "Revenue" + +msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_out" +msgid "Expense" +msgstr "Expense" + +msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_in" +msgid "Revenue" +msgstr "Revenue" + +msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_out" +msgid "Expense" +msgstr "Expense" + msgctxt "model:ir.model.button,string:line_wfedit_button" msgid "Edit" msgstr "Edit" @@ -354,6 +374,14 @@ msgctxt "view:cashbook.book:" msgid "Owner and Authorizeds" msgstr "Owner and Authorizeds" +msgctxt "view:cashbook.book:" +msgid "General Information" +msgstr "General Information" + +msgctxt "view:cashbook.book:" +msgid "Balance" +msgstr "Balance" + msgctxt "view:cashbook.book:" msgid "Reconciliations" msgstr "Reconciliations" @@ -410,6 +438,10 @@ msgctxt "field:cashbook.book,currency:" msgid "Currency" msgstr "Currency" +msgctxt "field:cashbook.book,currency_digits:" +msgid "Currency Digits" +msgstr "Currency Digits" + msgctxt "field:cashbook.book,start_balance:" msgid "Initial Amount" msgstr "Initial Amount" @@ -574,6 +606,10 @@ msgctxt "field:cashbook.line,description:" msgid "Description" msgstr "Description" +msgctxt "field:cashbook.line,descr_short:" +msgid "Description" +msgstr "Description" + msgctxt "field:cashbook.line,state:" msgid "State" msgstr "State" @@ -870,10 +906,18 @@ msgctxt "field:cashbook.line.context,date_from:" msgid "Start Date" msgstr "Start Date" +msgctxt "help:cashbook.line.context,date_from:" +msgid "Limits the date range for the displayed entries." +msgstr "Limits the date range for the displayed entries." + msgctxt "field:cashbook.line.context,date_to:" msgid "End Date" msgstr "End Date" +msgctxt "help:cashbook.line.context,date_to:" +msgid "Limits the date range for the displayed entries." +msgstr "Limits the date range for the displayed entries." + msgctxt "model:cashbook.configuration,name:" msgid "Configuration" msgstr "Configuration" diff --git a/menu.xml b/menu.xml index c37eb13..d4c8a1b 100644 --- a/menu.xml +++ b/menu.xml @@ -56,10 +56,23 @@ full copyright notices and license terms. --> + + + + + + + + + + + + parent="menu_cashbook" sequence="30"/> @@ -72,7 +85,7 @@ full copyright notices and license terms. --> + parent="menu_cashbook" sequence="40"/> diff --git a/message.xml b/message.xml index 8fea230..18ce400 100644 --- a/message.xml +++ b/message.xml @@ -113,6 +113,9 @@ full copyright notices and license terms. --> The category of the booking line '%(recname)s' does not match the posting type '%(booktype)s'. + + The amount must be positive. + diff --git a/reconciliation.py b/reconciliation.py index 72040ee..5d19307 100644 --- a/reconciliation.py +++ b/reconciliation.py @@ -96,7 +96,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView): @classmethod def __setup__(cls): super(Reconciliation, cls).__setup__() - cls._order.insert(0, ('date_from', 'ASC')) + cls._order.insert(0, ('date_from', 'DESC')) cls._transitions |= set(( ('edit', 'check'), ('check', 'done'), diff --git a/tests/__init__.py b/tests/__init__.py index f557851..dea659d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -11,11 +11,14 @@ from trytond.modules.cashbook.tests.test_splitline import SplitLineTestCase from trytond.modules.cashbook.tests.test_config import ConfigTestCase from trytond.modules.cashbook.tests.test_category import CategoryTestCase from trytond.modules.cashbook.tests.test_reconciliation import ReconTestCase +from trytond.modules.cashbook.tests.test_bookingwiz import BookingWizardTestCase + __all__ = ['suite'] class CashbookTestCase(\ + BookingWizardTestCase,\ ReconTestCase,\ CategoryTestCase,\ ConfigTestCase,\ diff --git a/tests/test_bookingwiz.py b/tests/test_bookingwiz.py new file mode 100644 index 0000000..29f2665 --- /dev/null +++ b/tests/test_bookingwiz.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +# This file is part of the cashbook-module from m-ds for Tryton. +# The COPYRIGHT file at the top level of this repository contains the +# full copyright notices and license terms. + +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 datetime import date +from decimal import Decimal +from unittest.mock import MagicMock + + +class BookingWizardTestCase(ModuleTestCase): + 'Test cashbook booking wizard module' + module = 'cashbook' + + @with_transaction() + def test_bookwiz_expense(self): + """ run booking-wizard to store expense + """ + pool = Pool() + BookingWiz = pool.get('cashbook.enterbooking', type='wizard') + Book = pool.get('cashbook.book') + Category = pool.get('cashbook.category') + Party = pool.get('party.party') + IrDate = pool.get('ir.date') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id, + }): + types = self.prep_type() + book, = Book.create([{ + 'name': 'Cash Book', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 1, 1), + 'start_balance': Decimal('0.0'), + }]) + + party, = Party.create([{ + 'name': 'Foodshop Zehlendorf', + 'addresses':[('create', [{}])], + }]) + + categories = Category.create([{ + 'name':'Income', + 'cattype': 'in', + }, { + 'name': 'Food', + 'cattype': 'out', + }]) + + (sess_id, start_state, end_state) = BookingWiz.create() + w_obj = BookingWiz(sess_id) + self.assertEqual(start_state, 'start') + self.assertEqual(end_state, 'end') + + result = BookingWiz.execute(sess_id, {}, start_state) + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['bookingtype'], 'out') + self.assertEqual(result['view']['defaults']['cashbook'], None) + self.assertEqual(result['view']['defaults']['amount'], None) + self.assertEqual(result['view']['defaults']['party'], None) + self.assertEqual(result['view']['defaults']['booktransf'], None) + self.assertEqual(result['view']['defaults']['description'], None) + self.assertEqual(result['view']['defaults']['category'], None) + + self.assertEqual(len(book.lines), 0) + + r1 = { + 'amount': Decimal('10.0'), + 'cashbook': book.id, + 'party': party.id, + 'description': 'Test 1', + 'category': categories[1].id, + 'bookingtype': 'out', + } + for x in r1.keys(): + setattr(w_obj.start, x, r1[x]) + + IrDate.today = MagicMock(return_value=date(2022, 5, 1)) + result = BookingWiz.execute(sess_id, {'start': r1}, 'save_') + BookingWiz.delete(sess_id) + IrDate.today = MagicMock(return_value=date.today()) + + self.assertEqual(len(book.lines), 1) + self.assertEqual(book.lines[0].rec_name, '05/01/2022|Exp|-10.00 usd|Test 1 [Food]') + + @with_transaction() + def test_bookwiz_transfer(self): + """ run booking-wizard to store expense + """ + pool = Pool() + BookingWiz = pool.get('cashbook.enterbooking', type='wizard') + Book = pool.get('cashbook.book') + Category = pool.get('cashbook.category') + Party = pool.get('party.party') + IrDate = pool.get('ir.date') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id, + }): + types = self.prep_type() + books = Book.create([{ + 'name': 'Cash Book', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 1, 1), + 'start_balance': Decimal('0.0'), + }, { + 'name': 'Bank', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 1, 1), + 'start_balance': Decimal('0.0'), + }]) + + party, = Party.create([{ + 'name': 'Foodshop Zehlendorf', + 'addresses':[('create', [{}])], + }]) + + categories = Category.create([{ + 'name':'Income', + 'cattype': 'in', + }, { + 'name': 'Food', + 'cattype': 'out', + }]) + + (sess_id, start_state, end_state) = BookingWiz.create() + w_obj = BookingWiz(sess_id) + self.assertEqual(start_state, 'start') + self.assertEqual(end_state, 'end') + + result = BookingWiz.execute(sess_id, {}, start_state) + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['bookingtype'], 'out') + self.assertEqual(result['view']['defaults']['cashbook'], None) + self.assertEqual(result['view']['defaults']['amount'], None) + self.assertEqual(result['view']['defaults']['party'], None) + self.assertEqual(result['view']['defaults']['booktransf'], None) + self.assertEqual(result['view']['defaults']['description'], None) + self.assertEqual(result['view']['defaults']['category'], None) + + self.assertEqual(len(books[0].lines), 0) + self.assertEqual(len(books[1].lines), 0) + + r1 = { + 'amount': Decimal('10.0'), + 'cashbook': books[0].id, + 'description': 'Test 1', + 'booktransf': books[1].id, + 'bookingtype': 'mvout', + } + for x in r1.keys(): + setattr(w_obj.start, x, r1[x]) + + IrDate.today = MagicMock(return_value=date(2022, 5, 1)) + result = BookingWiz.execute(sess_id, {'start': r1}, 'save_') + BookingWiz.delete(sess_id) + IrDate.today = MagicMock(return_value=date.today()) + + self.assertEqual(len(books[0].lines), 1) + self.assertEqual(len(books[1].lines), 0) + self.assertEqual(books[0].lines[0].rec_name, + '05/01/2022|to|-10.00 usd|Test 1 [Bank | 0.00 usd | Open]') + +# end BookingWizardTestCase diff --git a/tests/test_category.py b/tests/test_category.py index bfea57e..898e699 100644 --- a/tests/test_category.py +++ b/tests/test_category.py @@ -28,6 +28,72 @@ class CategoryTestCase(ModuleTestCase): }]) return category + @with_transaction() + def test_category_check_rec_name(self): + """ create category, test rec_name, search, order + """ + pool = Pool() + Category = pool.get('cashbook.category') + company = self.prep_company() + + Category.create([{ + 'company': company.id, + 'name': 'Level 1', + 'cattype': 'in', + 'childs': [('create', [{ + 'company': company.id, + 'name': 'Level 2a', + 'cattype': 'in', + }, { + 'company': company.id, + 'name': 'Level 2b', + 'cattype': 'in', + }])], + }, { + 'company': company.id, + 'name': 'Level 1b', + 'cattype': 'in', + 'childs': [('create', [{ + 'company': company.id, + 'name': 'Level 1b.2a', + 'cattype': 'in', + }, { + 'company': company.id, + 'name': 'Level 1b.2b', + 'cattype': 'in', + }])], + }]) + + self.assertEqual(Category.search_count([ + ('rec_name', 'ilike', '%1b.2b%'), + ]), 1) + self.assertEqual(Category.search_count([ + ('rec_name', 'ilike', '%1b.2%'), + ]), 2) + self.assertEqual(Category.search_count([ + ('rec_name', '=', 'Level 1b/Level 1b.2b'), + ]), 1) + + # ordering #1 + categories = Category.search([], order=[('rec_name', 'ASC')]) + self.assertEqual(len(categories), 6) + self.assertEqual(categories[0].rec_name, 'Level 1') + self.assertEqual(categories[1].rec_name, 'Level 1b') + self.assertEqual(categories[2].rec_name, 'Level 1b/Level 1b.2a') + self.assertEqual(categories[3].rec_name, 'Level 1b/Level 1b.2b') + self.assertEqual(categories[4].rec_name, 'Level 1/Level 2a') + self.assertEqual(categories[5].rec_name, 'Level 1/Level 2b') + + # ordering #2 + categories = Category.search([], order=[('rec_name', 'DESC')]) + self.assertEqual(len(categories), 6) + self.assertEqual(categories[0].rec_name, 'Level 1/Level 2b') + self.assertEqual(categories[1].rec_name, 'Level 1/Level 2a') + self.assertEqual(categories[2].rec_name, 'Level 1b/Level 1b.2b') + self.assertEqual(categories[3].rec_name, 'Level 1b/Level 1b.2a') + self.assertEqual(categories[4].rec_name, 'Level 1b') + self.assertEqual(categories[5].rec_name, 'Level 1') + @with_transaction() def test_category_create_check_category_type(self): """ create category, update type of category @@ -90,6 +156,7 @@ class CategoryTestCase(ModuleTestCase): cat1, = Category.create([{ 'name': 'Test 1', 'description': 'Info', + 'cattype': 'in', }]) self.assertEqual(cat1.name, 'Test 1') self.assertEqual(cat1.rec_name, 'Test 1') @@ -97,10 +164,11 @@ class CategoryTestCase(ModuleTestCase): self.assertEqual(cat1.company.rec_name, 'm-ds') self.assertEqual(cat1.parent, None) - # duplicate, allowed + # duplicate of different type, allowed cat2, = Category.create([{ 'name': 'Test 1', 'description': 'Info', + 'cattype': 'out', }]) self.assertEqual(cat2.name, 'Test 1') self.assertEqual(cat2.rec_name, 'Test 1') @@ -108,6 +176,16 @@ class CategoryTestCase(ModuleTestCase): self.assertEqual(cat2.company.rec_name, 'm-ds') self.assertEqual(cat2.parent, None) + # deny duplicate of same type + self.assertRaisesRegex(UserError, + 'The category name already exists at this level.', + Category.create, + [{ + 'name': 'Test 1', + 'description': 'Info', + 'cattype': 'in', + }]) + @with_transaction() def test_category_create_nodupl_diff_level(self): """ create category diff --git a/tests/test_config.py b/tests/test_config.py index 7932229..368ad10 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -42,6 +42,7 @@ class ConfigTestCase(ModuleTestCase): self.assertEqual(cfg2.checked, True) self.assertEqual(cfg2.done, False) self.assertEqual(cfg2.catnamelong, True) + self.assertEqual(cfg2.defbook, None) return cfg2 def prep_party(self, name='Party'): @@ -105,6 +106,37 @@ class ConfigTestCase(ModuleTestCase): """ self.prep_config() + @with_transaction() + def test_config_defbook(self): + """ create config, add default-cashbook + """ + pool = Pool() + Configuration = pool.get('cashbook.configuration') + Book = pool.get('cashbook.book') + + self.prep_config() + types = self.prep_type() + company = self.prep_company() + book, = Book.create([{ + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + }]) + self.assertEqual(book.name, 'Book 1') + + cfg1 = Configuration.get_singleton() + + Configuration.write(*[ + [cfg1], + { + 'defbook': book.id, + }]) + + cfg2 = Configuration.get_singleton() + self.assertEqual(cfg2.defbook.rec_name, 'Book 1 | 0.00 usd | Open') + @with_transaction() def test_config_create_multi_user(self): """ create config, multi-user diff --git a/tests/test_line.py b/tests/test_line.py index e08d0b1..32e175b 100644 --- a/tests/test_line.py +++ b/tests/test_line.py @@ -366,6 +366,93 @@ class LineTestCase(ModuleTestCase): self.assertEqual(book.lines[3].reconciliation, None) self.assertEqual(book.lines[3].state, 'edit') + @with_transaction() + def test_line_set_number_with_done(self): + """ create cashbook + line, write number to line + at state-change check->done + """ + pool = Pool() + Book = pool.get('cashbook.book') + Lines = pool.get('cashbook.line') + Reconciliation = pool.get('cashbook.recon') + + types = self.prep_type() + category = self.prep_category(cattype='in') + company = self.prep_company() + party = self.prep_party() + with Transaction().set_context({ + 'company': company.id, + }): + book, = Book.create([{ + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'number_atcheck': False, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Text 1', + 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), + 'party': party.id, + }, { + 'date': date(2022, 5, 2), + 'description': 'Text 2', + 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), + 'party': party.id, + }])], + }]) + self.assertEqual(book.name, 'Book 1') + self.assertEqual(book.btype.rec_name, 'CAS - Cash') + self.assertEqual(book.state, 'open') + self.assertEqual(book.number_atcheck, False) + self.assertEqual(len(book.lines), 2) + self.assertEqual(book.lines[0].date, date(2022, 5, 1)) + self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') + self.assertEqual(book.lines[0].state_cashbook, 'open') + self.assertEqual(book.lines[1].date, date(2022, 5, 2)) + self.assertEqual(book.lines[1].rec_name, '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]') + + # add reconciliation + Book.write(*[ + [book], + { + 'reconciliations': [('create', [{ + 'date': date(2022, 5, 1), + 'date_from': date(2022, 5, 1), + 'date_to': date(2022, 5, 30), + }])], + }]) + self.assertEqual(len(book.reconciliations), 1) + self.assertEqual(len(book.reconciliations[0].lines), 0) + self.assertEqual(book.reconciliations[0].date_from, date(2022, 5, 1)) + self.assertEqual(book.reconciliations[0].date_to, date(2022, 5, 30)) + self.assertEqual(book.reconciliations[0].state, 'edit') + + Lines.wfcheck(book.lines) + self.assertEqual(book.lines[0].state, 'check') + self.assertEqual(book.lines[0].number, None) + self.assertEqual(book.lines[1].state, 'check') + self.assertEqual(book.lines[1].number, None) + + Reconciliation.wfcheck(book.reconciliations) + self.assertEqual(len(book.reconciliations[0].lines), 2) + self.assertEqual(book.reconciliations[0].lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') + self.assertEqual(book.reconciliations[0].lines[1].rec_name, '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]') + self.assertEqual(book.reconciliations[0].lines[0].number, None) + self.assertEqual(book.reconciliations[0].lines[1].number, None) + + Reconciliation.wfdone(book.reconciliations) + self.assertEqual(book.reconciliations[0].lines[0].number, '1') + self.assertEqual(book.reconciliations[0].lines[0].state, 'done') + self.assertEqual(book.reconciliations[0].lines[1].number, '2') + self.assertEqual(book.reconciliations[0].lines[1].state, 'done') + @with_transaction() def test_line_create_check_names_search(self): """ create cashbook + line diff --git a/tests/test_reconciliation.py b/tests/test_reconciliation.py index 10eacdb..f3c8c11 100644 --- a/tests/test_reconciliation.py +++ b/tests/test_reconciliation.py @@ -49,14 +49,14 @@ class ReconTestCase(ModuleTestCase): 'date_to': date(2022, 6, 30), }])], }]) - self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') - self.assertEqual(book.reconciliations[1].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertRaisesRegex(UserError, 'The date range overlaps with another reconciliation.', Reconciliation.write, *[ - [book.reconciliations[1]], + [book.reconciliations[0]], { 'date_from': date(2022, 4, 15), 'date_to': date(2022, 5, 2), @@ -97,14 +97,14 @@ class ReconTestCase(ModuleTestCase): 'date_to': date(2022, 6, 30), }])], }]) - self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') - self.assertEqual(book.reconciliations[1].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertRaisesRegex(UserError, 'The date range overlaps with another reconciliation.', Reconciliation.write, *[ - [book.reconciliations[1]], + [book.reconciliations[0]], { 'date_from': date(2022, 5, 30), }, @@ -144,14 +144,14 @@ class ReconTestCase(ModuleTestCase): 'date_to': date(2022, 6, 30), }])], }]) - self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') - self.assertEqual(book.reconciliations[1].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertRaisesRegex(UserError, 'The date range overlaps with another reconciliation.', Reconciliation.write, *[ - [book.reconciliations[1]], + [book.reconciliations[0]], { 'date_from': date(2022, 5, 5), 'date_to': date(2022, 5, 15), @@ -192,8 +192,8 @@ class ReconTestCase(ModuleTestCase): 'date_to': date(2022, 6, 30), }])], }]) - self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') - self.assertEqual(book.reconciliations[1].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]') self.assertRaisesRegex(UserError, 'The date range overlaps with another reconciliation.', @@ -536,9 +536,11 @@ class ReconTestCase(ModuleTestCase): self.assertEqual(len(book.reconciliations[0].lines), 1) self.assertEqual(book.reconciliations[0].lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev|1.00 usd|Text 1 [Cat1]') + self.assertEqual(book.lines[0].state, 'check') self.assertEqual(book.lines[1].rec_name, '06/01/2022|Rev|1.00 usd|Text 2 [Cat1]') + self.assertEqual(book.lines[1].state, 'edit') - # move 2nd line into date-range of checked-reconciliation, wf-check + # move 1st line into date-range of checked-reconciliation, wf-check Lines.write(*[ [book.lines[1]], { @@ -570,7 +572,9 @@ class ReconTestCase(ModuleTestCase): 'date_from': date(2022, 5, 31), 'date_to': date(2022, 6, 30), }]) - Reconciliation.wfdone([book.reconciliations[0]]) + self.assertEqual(book.reconciliations[0].rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]') + self.assertEqual(book.reconciliations[1].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 1.00 usd [1]') + Reconciliation.wfdone([book.reconciliations[1]]) Reconciliation.wfcheck([recon2]) Lines.write(*[ diff --git a/tryton.cfg b/tryton.cfg index dab4bf6..2d43fda 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.0.0 +version=6.0.7 depends: res currency @@ -19,4 +19,5 @@ xml: splitline.xml wizard_openline.xml wizard_runreport.xml + wizard_booking.xml menu.xml diff --git a/view/book_form.xml b/view/book_form.xml index 9689b01..53b925a 100644 --- a/view/book_form.xml +++ b/view/book_form.xml @@ -2,10 +2,10 @@ -
-