diff --git a/line.py b/line.py
index b95e4ae..31e399f 100644
--- a/line.py
+++ b/line.py
@@ -78,7 +78,9 @@ class Line(Workflow, ModelSQL, ModelView):
credit = fields.Numeric(string='Credit', digits=(16, Eval('currency_digits', 2)),
required=True, readonly=True, depends=['currency_digits'])
reconciliation = fields.Many2One(string='Reconciliation', readonly=True,
- model_name='cashbook.recon', ondelete='SET NULL')
+ model_name='cashbook.recon', ondelete='SET NULL',
+ domain=[('cashbook.id', '=', Eval('cashbook'))],
+ depends=['cashbook'])
balance = fields.Function(fields.Numeric(string='Balance',
digits=(16, Eval('currency_digits', 2)),
@@ -184,9 +186,12 @@ class Line(Workflow, ModelSQL, ModelView):
def get_rec_name(self, name):
""" short + name
"""
- return '%(date)s %(desc)s' % {
+ return '%(date)s|%(amount)s %(symbol)s|%(desc)s [%(category)s]' % {
'date': Report.format_date(self.date),
'desc': (self.description or '-')[:40],
+ 'amount': Report.format_number(self.amount or 0.0, None),
+ 'symbol': getattr(self.currency, 'symbol', '-'),
+ 'category': self.category_view,
}
@staticmethod
@@ -360,8 +365,23 @@ class Line(Workflow, ModelSQL, ModelView):
""" add debit/credit
"""
vlist = [x.copy() for x in vlist]
- for vals in vlist:
- vals.update(cls.get_debit_credit(vals))
+ for values in vlist:
+ values.update(cls.get_debit_credit(values))
+
+ # deny add to reconciliation if state is not 'check' or 'done'
+ if values.get('reconciliation', None):
+ if not values.get('state', '-') in ['check', 'done']:
+ date_txt = '-'
+ if values.get('date', None):
+ date_txt = Report.format_date(values.get('date', None))
+ raise UserError(gettext(
+ 'cashbook.msg_line_deny_recon_by_state',
+ recname = '%(date)s|%(descr)s' % {
+ 'date': date_txt,
+ 'descr': values.get('description', '-'),
+ },
+ ))
+
return super(Line, cls).create(vlist)
@classmethod
@@ -373,14 +393,49 @@ class Line(Workflow, ModelSQL, ModelView):
to_write = []
for lines, values in zip(actions, actions):
for line in lines:
+ # deny write if chashbook is not open
if line.cashbook.state != 'open':
raise UserError(gettext(
'cashbook.msg_book_deny_write',
bookname = line.cashbook.rec_name,
state_txt = line.cashbook.state_string,
))
+ if line.reconciliation:
+ # deny state-change to 'edit' if line is linked to reconciliation
+ if values.get('state', '-') == 'edit':
+ raise UserError(gettext(
+ 'cashbook.msg_line_deny_stateedit_with_recon',
+ recname = line.rec_name,
+ ))
- # debit / credit
+ # deny write if reconciliation is 'check' or 'done'
+ if line.reconciliation.state == 'done':
+ raise UserError(gettext(
+ 'cashbook.msg_line_deny_write_by_reconciliation',
+ recname = line.rec_name,
+ reconame = line.reconciliation.rec_name,
+ ))
+ # 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) \
+ and (len(values.keys()) == 1)):
+ raise UserError(gettext(
+ 'cashbook.msg_line_deny_write',
+ recname = line.rec_name,
+ state_txt = line.state_string,
+ ))
+
+ # deny add to reconciliation if state is not 'check' or 'done'
+ if values.get('reconciliation', None):
+ for line in lines:
+ if not line.state in ['check', 'done']:
+ raise UserError(gettext(
+ 'cashbook.msg_line_deny_recon_by_state',
+ recname = line.rec_name
+ ))
+
+ # update debit / credit
if len(set(values.keys()).intersection(set({'amount', 'bookingtype'}))) > 0:
for line in lines:
values2 = {}
diff --git a/locale/de.po b/locale/de.po
index 621161a..d38bd9c 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -38,6 +38,22 @@ msgctxt "model:ir.message,text:msg_line_deny_delete2"
msgid "The cashbook line '%(linetxt)s' cannot be deleted, its in state '%(linestate)s'."
msgstr "Die Kassenbuchzeile '%(linetxt)s' kann nicht gelöscht werden, da sie im Status '%(linestate)s' ist."
+msgctxt "model:ir.message,text:msg_line_deny_stateedit_with_recon"
+msgid "The status cannot be changed to 'Edit' as long as the line '%(recname)s' is associated with a reconciliation."
+msgstr "Der Status kann nicht in 'Bearbeiten' geändert werden, solange die Zeile '%(recname)s' mit einer Abstimmung verbunden ist."
+
+msgctxt "model:ir.message,text:msg_line_deny_write"
+msgid "The cashbook line '%(recname)s' is '%(state_txt)s' and cannot be changed."
+msgstr "Die Kassenbuchzeile '%(recname)s' ist '%(state_txt)s' und kann nicht geändert werden."
+
+msgctxt "model:ir.message,text:msg_recon_deny_delete1"
+msgid "The reconciliation '%(recontxt)s' cannot be deleted because the Cashbook '%(bookname)s' is in state '%(bookstate)s'."
+msgstr "Die Abstimmung '%(recontxt)s' kann nicht gelöscht werden, weil das Kassenbuch '%(bookname)s' im Status '%(bookstate)s' ist."
+
+msgctxt "model:ir.message,text:msg_recon_deny_delete2"
+msgid "The reconciliation '%(recontxt)s' cannot be deleted, its in state '%(reconstate)s'."
+msgstr "Die Abstimmung '%(recontxt)s' kann nicht gelöscht werden, da sie im Status '%(reconstate)s' ist."
+
msgctxt "model:ir.message,text:msg_setting_already_exists"
msgid "Settings for this user alredy exists."
msgstr "Einstellungen für diesen Benutzer sind bereits vorhanden."
@@ -58,6 +74,22 @@ msgctxt "model:ir.message,text:msg_book_err_startamount_with_lines"
msgid "The initial amount of the cash book '%(bookname)s' cannot be changed because it already contains bookings."
msgstr "Der Anfangsbetrag des Kassenbuchs '%(bookname)s' kann nicht geändert werden, da es bereits Buchungen enthält."
+msgctxt "model:ir.message,text:msg_line_deny_recon_by_state"
+msgid "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'."
+msgstr "Für die Abstimmung muss die Zeile '%(recname)s' im Status 'Prüfen' oder 'Fertig' sein."
+
+msgctxt "model:ir.message,text:mds_recon_deny_line_not_check"
+msgid "For the reconciliation '%(reconame)s' of the cashbook '%(bookname)s', all lines in the date range from '%(datefrom)s' to '%(dateto)s' must be in the 'Check' state."
+msgstr "Für die Abstimmung '%(reconame)s' des Kassenbuchs '%(bookname)s' müssen alle Zeilen im Datumsbereich von '%(datefrom)s' bis '%(dateto)s' im Zustand 'Prüfen' sein."
+
+msgctxt "model:ir.message,text:msg_line_deny_write_by_reconciliation"
+msgid "The line '%(recname)s' cannot be changed because the reconciliation '%(reconame)s'is 'Done'."
+msgstr "Die Zeile '%(recname)s' kann nicht geändert werden, da die Abstimmung '%(reconame)s' ist."
+
+msgctxt "model:ir.message,text:msg_recon_err_overlap"
+msgid "The date range overlaps with another reconciliation."
+msgstr "Der Datumsbereich überschneidet sich mit einer anderen Abstimmung."
+
#############
# res.group #
@@ -801,3 +833,11 @@ msgstr "Geschlossen"
msgctxt "selection:cashbook.recon,state_cashbook:"
msgid "Archive"
msgstr "Archiv"
+
+msgctxt "field:cashbook.recon,start_amount:"
+msgid "Start Amount"
+msgstr "Anfangsbetrag"
+
+msgctxt "field:cashbook.recon,end_amount:"
+msgid "End Amount"
+msgstr "Endbetrag"
diff --git a/locale/en.po b/locale/en.po
index 610765c..c772074 100644
--- a/locale/en.po
+++ b/locale/en.po
@@ -23,8 +23,8 @@ msgid "The cashbook '%(bookname)s' cannot be deleted because it contains %(bookl
msgstr "The cashbook '%(bookname)s' cannot be deleted because it contains %(booklines)s lines and is not in the status 'Archive'."
msgctxt "model:ir.message,text:msg_book_deny_write"
-msgid "The cash book '%(bookname)s' is '%(state_txt)s' and cannot be changed."
-msgstr "The cash book '%(bookname)s' is '%(state_txt)s' and cannot be changed."
+msgid "The cashbook '%(bookname)s' is '%(state_txt)s' and cannot be changed."
+msgstr "The cashbook '%(bookname)s' is '%(state_txt)s' and cannot be changed."
msgctxt "model:ir.message,text:msg_line_deny_delete1"
msgid "The cashbook line '%(linetxt)s' cannot be deleted because the Cashbook '%(bookname)s' is in state '%(bookstate)s'."
@@ -34,6 +34,18 @@ msgctxt "model:ir.message,text:msg_line_deny_delete2"
msgid "The cashbook line '%(linetxt)s' cannot be deleted, its in state '%(linestate)s'."
msgstr "The cashbook line '%(linetxt)s' cannot be deleted, its in state '%(linestate)s'."
+msgctxt "model:ir.message,text:msg_line_deny_write"
+msgid "The cashbook line '%(recname)s' is '%(state_txt)s' and cannot be changed."
+msgstr "The cashbook line '%(recname)s' is '%(state_txt)s' and cannot be changed."
+
+msgctxt "model:ir.message,text:msg_recon_deny_delete1"
+msgid "The reconciliation '%(recontxt)s' cannot be deleted because the Cashbook '%(bookname)s' is in state '%(bookstate)s'."
+msgstr "The reconciliation '%(recontxt)s' cannot be deleted because the Cashbook '%(bookname)s' is in state '%(bookstate)s'."
+
+msgctxt "model:ir.message,text:msg_recon_deny_delete2"
+msgid "The reconciliation '%(recontxt)s' cannot be deleted, its in state '%(reconstate)s'."
+msgstr "The reconciliation '%(recontxt)s' cannot be deleted, its in state '%(reconstate)s'."
+
msgctxt "model:ir.message,text:msg_setting_already_exists"
msgid "Settings for this user alredy exists."
msgstr "Settings for this user alredy exists."
@@ -42,6 +54,30 @@ 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:ir.message,text:msg_category_account_unique"
+msgid "The account is already in use for a category."
+msgstr "The account is already in use for a category."
+
+msgctxt "model:ir.message,text:msg_category_type_not_like_parent"
+msgid "The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'."
+msgstr "The type of the current category '%(catname)s' must be equal to the type of the parent category '%(parentname)s'."
+
+msgctxt "model:ir.message,text:msg_book_err_startamount_with_lines"
+msgid "The initial amount of the cash book '%(bookname)s' cannot be changed because it already contains bookings."
+msgstr "The initial amount of the cash book '%(bookname)s' cannot be changed because it already contains bookings."
+
+msgctxt "model:ir.message,text:msg_line_deny_recon_by_state"
+msgid "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'."
+msgstr "For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'."
+
+msgctxt "model:ir.message,text:mds_recon_deny_line_not_check"
+msgid "For the reconciliation '%(reconame)s' of the cashbook '%(bookname)s', all lines in the date range from '%(datefrom)s' to '%(dateto)s' must be in the 'Check' state."
+msgstr "For the reconciliation '%(reconame)s' of the cashbook '%(bookname)s', all lines in the date range from '%(datefrom)s' to '%(dateto)s' must be in the 'Check' state."
+
+msgctxt "model:ir.message,text:msg_line_deny_write_by_reconciliation"
+msgid "The line '%(recname)s' cannot be changed because the reconciliation '%(reconame)s'is 'Done'."
+msgstr "The line '%(recname)s' cannot be changed because the reconciliation '%(reconame)s'is 'Done'."
+
msgctxt "model:res.group,name:group_cashbook"
msgid "Cashbook"
msgstr "Cashbook"
@@ -74,6 +110,30 @@ msgctxt "model:ir.rule.group,name:rg_line_read"
msgid "Observer: Cashbook line read"
msgstr "Observer: Cashbook line read"
+msgctxt "model:ir.rule.group,name:rg_line_read"
+msgid "User in companies"
+msgstr "User in companies"
+
+msgctxt "model:ir.rule.group,name:rg_type_companies"
+msgid "User in companies"
+msgstr "User in companies"
+
+msgctxt "model:ir.rule.group,name:rg_book_companies"
+msgid "User in companies"
+msgstr "User in companies"
+
+msgctxt "model:ir.rule.group,name:rg_recon_companies"
+msgid "User in companies"
+msgstr "User in companies"
+
+msgctxt "model:ir.rule.group,name:rg_recon_write_adm"
+msgid "Administrators: Reconciliation read/write"
+msgstr "Administrators: Reconciliation read/write"
+
+msgctxt "model:ir.rule.group,name:rg_recon_write"
+msgid "Owners and reviewers: Reconciliation write"
+msgstr "Owners and reviewers: Reconciliation write"
+
msgctxt "model:ir.ui.menu,name:menu_cashbook"
msgid "Cashbook"
msgstr "Cashbook"
@@ -86,7 +146,7 @@ msgctxt "model:ir.ui.menu,name:menu_typeconfig"
msgid "Cashbook Type"
msgstr "Cashbook Type"
-msgctxt "model:ir.ui.menu,name:menu_bookconfig"
+msgctxt "model:ir.ui.menu,name:menu_booklist"
msgid "Cashbook"
msgstr "Cashbook"
@@ -103,8 +163,8 @@ msgid "Category"
msgstr "Category"
msgctxt "model:ir.action,name:act_book_view"
-msgid "Account"
-msgstr "Account"
+msgid "Cashbook"
+msgstr "Cashbook"
msgctxt "model:ir.action,name:act_type_view"
msgid "Cashbook Type"
@@ -122,6 +182,18 @@ msgctxt "model:ir.ui.menu,name:act_category_view"
msgid "Category"
msgstr "Category"
+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:ir.model.button,string:line_wfedit_button"
msgid "Edit"
msgstr "Edit"
@@ -146,13 +218,29 @@ msgctxt "model:ir.model.button,string:book_wfarchive_button"
msgid "Archive"
msgstr "Archive"
+msgctxt "model:ir.model.button,string:recon_wfedit_button"
+msgid "Edit"
+msgstr "Edit"
+
+msgctxt "model:ir.model.button,string:recon_wfcheck_button"
+msgid "Check"
+msgstr "Check"
+
+msgctxt "model:ir.model.button,string:recon_wfdone_button"
+msgid "Done"
+msgstr "Done"
+
msgctxt "model:cashbook.book,name:"
msgid "Cashbook"
msgstr "Cashbook"
msgctxt "view:cashbook.book:"
-msgid "Owner & Authorizeds"
-msgstr "Owner & Authorizeds"
+msgid "Owner and Authorizeds"
+msgstr "Owner and Authorizeds"
+
+msgctxt "view:cashbook.book:"
+msgid "Reconciliations"
+msgstr "Reconciliations"
msgctxt "field:cashbook.book,name:"
msgid "Name"
@@ -202,14 +290,50 @@ msgctxt "field:cashbook.book,account:"
msgid "Account"
msgstr "Account"
+msgctxt "field:cashbook.book,company:"
+msgid "Company"
+msgstr "Company"
+
+msgctxt "field:cashbook.book,currency:"
+msgid "Currency"
+msgstr "Currency"
+
+msgctxt "field:cashbook.book,start_balance:"
+msgid "Initial Amount"
+msgstr "Initial Amount"
+
+msgctxt "field:cashbook.book,balance:"
+msgid "Balance"
+msgstr "Balance"
+
+msgctxt "field:cashbook.book,reconciliations:"
+msgid "Reconciliations"
+msgstr "Reconciliations"
+
+msgctxt "field:cashbook.book,lines:"
+msgid "Lines"
+msgstr "Lines"
+
msgctxt "model:cashbook.line,name:"
msgid "Cashbook Line"
msgstr "Cashbook Line"
+msgctxt "view:cashbook.line:"
+msgid "Credit"
+msgstr "Credit"
+
+msgctxt "view:cashbook.line:"
+msgid "Debit"
+msgstr "Debit"
+
msgctxt "view:cashbook.line:"
msgid "Cashbook Line"
msgstr "Cashbook Line"
+msgctxt "view:cashbook.line:"
+msgid "Description"
+msgstr "Description"
+
msgctxt "view:cashbook.line:"
msgid "State"
msgstr "State"
@@ -246,17 +370,73 @@ 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 "field:cashbook.line,category:"
+msgid "Category"
+msgstr "Category"
-msgctxt "model:ir.action.act_window.domain,name:act_line_domain_last"
-msgid "Last Month"
-msgstr "Last Month"
+msgctxt "field:cashbook.line,category_view:"
+msgid "Category"
+msgstr "Category"
-msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all"
-msgid "All"
-msgstr "All"
+msgctxt "field:cashbook.line,bookingtype:"
+msgid "Type"
+msgstr "Type"
+
+msgctxt "help:cashbook.line,bookingtype:"
+msgid "Type of Booking"
+msgstr "Type of Booking"
+
+msgctxt "selection:cashbook.line,bookingtype:"
+msgid "Revenue"
+msgstr "Revenue"
+
+msgctxt "selection:cashbook.line,bookingtype:"
+msgid "Expense"
+msgstr "Expense"
+
+msgctxt "selection:cashbook.line,bookingtype:"
+msgid "Transfer from"
+msgstr "Transfer from"
+
+msgctxt "selection:cashbook.line,bookingtype:"
+msgid "Transfer to"
+msgstr "Transfer to"
+
+msgctxt "field:cashbook.line,company:"
+msgid "Company"
+msgstr "Company"
+
+msgctxt "field:cashbook.line,amount:"
+msgid "Amount"
+msgstr "Amount"
+
+msgctxt "field:cashbook.line,debit:"
+msgid "Debit"
+msgstr "Debit"
+
+msgctxt "field:cashbook.line,credit:"
+msgid "Credit"
+msgstr "Credit"
+
+msgctxt "field:cashbook.line,currency:"
+msgid "Currency"
+msgstr "Currency"
+
+msgctxt "field:cashbook.line,currency_digits:"
+msgid "Currency Digits"
+msgstr "Currency Digits"
+
+msgctxt "field:cashbook.line,balance:"
+msgid "Balance"
+msgstr "Balance"
+
+msgctxt "help:cashbook.line,balance:"
+msgid "Balance of the cash book up to the current line, if the default sorting applies."
+msgstr "Balance of the cash book up to the current line, if the default sorting applies."
+
+msgctxt "field:cashbook.line,reconciliation:"
+msgid "Reconciliation"
+msgstr "Reconciliation"
msgctxt "model:cashbook.type,name:"
msgid "Cashbook Type"
@@ -270,17 +450,9 @@ msgctxt "field:cashbook.type,short:"
msgid "Abbreviation"
msgstr "Abbreviation"
-msgctxt "model:cashbook.type,name:atype_cash"
-msgid "Cash"
-msgstr "Cash"
-
-msgctxt "model:cashbook.type,name:atype_giro"
-msgid "Giro"
-msgstr "Giro"
-
-msgctxt "model:cashbook.type,name:atype_fixtermdep"
-msgid "Fixed-term deposit"
-msgstr "Fixed-term deposit"
+msgctxt "field:cashbook.type,company:"
+msgid "Company"
+msgstr "Company"
msgctxt "model:cashbook.category,name:"
msgid "Category"
@@ -330,6 +502,22 @@ msgctxt "field:cashbook.category,right:"
msgid "Right"
msgstr "Right"
+msgctxt "field:cashbook.category,cattype:"
+msgid "Type"
+msgstr "Type"
+
+msgctxt "help:cashbook.category,cattype:"
+msgid "Type of Category"
+msgstr "Type of Category"
+
+msgctxt "selection:cashbook.category,cattype:"
+msgid "Revenue"
+msgstr "Revenue"
+
+msgctxt "selection:cashbook.category,cattype:"
+msgid "Expense"
+msgstr "Expense"
+
msgctxt "model:cashbook.open_lines.start,name:"
msgid "Open Cashbook"
msgstr "Open Cashbook"
@@ -370,11 +558,11 @@ msgctxt "model:cashbook.open_lines,name:"
msgid "Open Cashbook"
msgstr "Open Cashbook"
-msgctxt "wizard_button:cashbook.open_lines,start,end:"
+msgctxt "wizard_button:cashbook.open_lines,askuser,end:"
msgid "Cancel"
msgstr "Cancel"
-msgctxt "wizard_button:cashbook.open_lines,start,open_:"
+msgctxt "wizard_button:cashbook.open_lines,askuser,open_:"
msgid "Open"
msgstr "Open"
@@ -418,6 +606,10 @@ msgctxt "view:cashbook.configuration:"
msgid "Open Cashbook Wizard"
msgstr "Open Cashbook Wizard"
+msgctxt "view:cashbook.configuration:"
+msgid "Cashbook"
+msgstr "Cashbook"
+
msgctxt "field:cashbook.configuration,date_from:"
msgid "Start Date"
msgstr "Start Date"
@@ -430,10 +622,34 @@ msgctxt "field:cashbook.configuration,checked:"
msgid "Checked"
msgstr "Checked"
+msgctxt "help:cashbook.configuration,checked:"
+msgid "Show cashbook lines in Checked-state."
+msgstr "Show cashbook lines in Checked-state."
+
msgctxt "field:cashbook.configuration,done:"
msgid "Done"
msgstr "Done"
+msgctxt "help:cashbook.configuration,done:"
+msgid "Show cashbook lines in Done-state."
+msgstr "Show cashbook lines in Done-state."
+
+msgctxt "field:cashbook.configuration,catnamelong:"
+msgid "Category: Show long name"
+msgstr "Category: Show long name"
+
+msgctxt "help:cashbook.configuration,catnamelong:"
+msgid "Shows the long name of the category in the Category field of a cash book line."
+msgstr "Shows the long name of the category in the Category field of a cash book line."
+
+msgctxt "field:cashbook.configuration,cataccno:"
+msgid "Category: Show account number"
+msgstr "Category: Show account number"
+
+msgctxt "help:cashbook.configuration,cataccno:"
+msgid "Shows the number of the linked account in the name of a category."
+msgstr "Shows the number of the linked account in the name of a category."
+
msgctxt "model:cashbook.configuration_user,name:"
msgid "User Configuration"
msgstr "User Configuration"
@@ -450,3 +666,91 @@ msgctxt "field:cashbook.configuration_user,checked:"
msgid "Checked"
msgstr "Checked"
+msgctxt "help:cashbook.configuration_user,checked:"
+msgid "Show cashbook lines in Checked-state."
+msgstr "Show cashbook lines in Checked-state."
+
+msgctxt "field:cashbook.configuration_user,done:"
+msgid "Done"
+msgstr "Done"
+
+msgctxt "help:cashbook.configuration_user,done:"
+msgid "Show cashbook lines in Done-state."
+msgstr "Show cashbook lines in Done-state."
+
+msgctxt "field:cashbook.configuration_user,catnamelong:"
+msgid "Category: Show long name"
+msgstr "Category: Show long name"
+
+msgctxt "help:cashbook.configuration_user,catnamelong:"
+msgid "Shows the long name of the category in the Category field of a cash book line."
+msgstr "Shows the long name of the category in the Category field of a cash book line."
+
+msgctxt "field:cashbook.configuration_user,cataccno:"
+msgid "Category: Show account number"
+msgstr "Category: Show account number"
+
+msgctxt "help:cashbook.configuration_user,cataccno:"
+msgid "Shows the number of the linked account in the name of a category."
+msgstr "Shows the number of the linked account in the name of a category."
+
+msgctxt "model:cashbook.recon,name:"
+msgid "Cashbook Reconciliation"
+msgstr "Cashbook Reconciliation"
+
+msgctxt "view:cashbook.recon:"
+msgid "Reconciliation period"
+msgstr "Reconciliation period"
+
+msgctxt "view:cashbook.recon:"
+msgid "State"
+msgstr "State"
+
+msgctxt "field:cashbook.recon,cashbook:"
+msgid "Cashbook"
+msgstr "Cashbook"
+
+msgctxt "field:cashbook.recon,date:"
+msgid "Date"
+msgstr "Date"
+
+msgctxt "field:cashbook.recon,date_from:"
+msgid "Start Date"
+msgstr "Start Date"
+
+msgctxt "field:cashbook.recon,date_to:"
+msgid "End Date"
+msgstr "End Date"
+
+msgctxt "field:cashbook.recon,state:"
+msgid "State"
+msgstr "State"
+
+msgctxt "selection:cashbook.recon,state:"
+msgid "Edit"
+msgstr "Edit"
+
+msgctxt "selection:cashbook.recon,state:"
+msgid "Check"
+msgstr "Check"
+
+msgctxt "selection:cashbook.recon,state:"
+msgid "Done"
+msgstr "Done"
+
+msgctxt "field:cashbook.recon,lines:"
+msgid "Lines"
+msgstr "Lines"
+
+msgctxt "field:cashbook.recon,state_cashbook:"
+msgid "State of Cashbook"
+msgstr "State of Cashbook"
+
+msgctxt "selection:cashbook.recon,state_cashbook:"
+msgid "Open"
+msgstr "Open"
+
+msgctxt "selection:cashbook.recon,state_cashbook:"
+msgid "Closed"
+msgstr "Closed"
+
diff --git a/message.xml b/message.xml
index 439317f..f4f8ba1 100644
--- a/message.xml
+++ b/message.xml
@@ -29,6 +29,18 @@ full copyright notices and license terms. -->
The cashbook line '%(linetxt)s' cannot be deleted, its in state '%(linestate)s'.
+
+ The cashbook line '%(recname)s' is '%(state_txt)s' and cannot be changed.
+
+
+ The status cannot be changed to 'Edit' as long as the line '%(recname)s' is associated with a reconciliation.
+
+
+ The reconciliation '%(recontxt)s' cannot be deleted because the Cashbook '%(bookname)s' is in state '%(bookstate)s'.
+
+
+ The reconciliation '%(recontxt)s' cannot be deleted, its in state '%(reconstate)s'.
+
Settings for this user alredy exists.
@@ -44,6 +56,18 @@ full copyright notices and license terms. -->
The initial amount of the cash book '%(bookname)s' cannot be changed because it already contains bookings.
+
+ For reconciliation, the line '%(recname)s' must be in the status 'Check' or 'Done'.
+
+
+ For the reconciliation '%(reconame)s' of the cashbook '%(bookname)s', all lines in the date range from '%(datefrom)s' to '%(dateto)s' must be in the 'Check' state.
+
+
+ The line '%(recname)s' cannot be changed because the reconciliation '%(reconame)s'is 'Done'.
+
+
+ The date range overlaps with another reconciliation.
+
diff --git a/reconciliation.py b/reconciliation.py
index 6105259..55fe50c 100644
--- a/reconciliation.py
+++ b/reconciliation.py
@@ -3,12 +3,16 @@
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
-from trytond.model import Workflow, ModelView, ModelSQL, fields, Unique
+from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.transaction import Transaction
from trytond.pyson import Eval, If, Or
from trytond.pool import Pool
from trytond.report import Report
+from trytond.exceptions import UserError
+from trytond.i18n import gettext
from decimal import Decimal
+from sql.operators import Equal, Between
+from sql import Literal, Null
from .book import sel_state_book
@@ -52,11 +56,19 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
()),
],
states=STATES, depends=DEPENDS+['date_from'])
+ start_amount = fields.Numeric(string='Start Amount', required=True,
+ readonly=True, digits=(16, Eval('currency_digits', 2)),
+ depends=['currency_digits'])
+ end_amount = fields.Numeric(string='End Amount', required=True,
+ readonly=True, digits=(16, Eval('currency_digits', 2)),
+ depends=['currency_digits'])
+
lines = fields.One2Many(string='Lines', field='reconciliation',
model_name='cashbook.line', states=STATES,
depends=DEPENDS+['date_from', 'date_to', 'cashbook'],
add_remove=[
('cashbook', '=', Eval('cashbook')),
+ ('state', 'in', ['check', 'done']),
('date', '>=', Eval('date_from')),
('date', '<=', Eval('date_to')),
],
@@ -65,6 +77,11 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
('date', '<=', Eval('date_to')),
])
+ currency = fields.Function(fields.Many2One(model_name='currency.currency',
+ string="Currency"), 'on_change_with_currency')
+ currency_digits = fields.Function(fields.Integer(string='Currency Digits'),
+ 'on_change_with_currency_digits')
+
state = fields.Selection(string='State', required=True, readonly=True,
select=True, selection=sel_reconstate)
state_string = state.translated('state')
@@ -96,40 +113,169 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
},
})
+ @classmethod
+ def check_overlap_dates(cls, date_from, date_to, id_cashbook, record=None):
+ """ deny overlap of date_from/date_to between records of same cashbook
+ allow: date_to=date_from
+ """
+ Recon = Pool().get('cashbook.recon')
+
+ query = [
+ ('cashbook.id', '=', id_cashbook),
+ ['OR',
+ [ # 'start' is inside of other record
+ ('date_from', '<=', date_from),
+ ('date_to', '>', date_from),
+ ],
+ [ # 'end' is inside of other record
+ ('date_from', '<', date_to),
+ ('date_to', '>=', date_to),
+ ],
+ [ # enclose other record
+ ('date_from', '>=', date_from),
+ ('date_to', '<=', date_to),
+ ],
+ ],
+ ]
+
+ # avoid finding ourselves
+ if record:
+ query.append(('id', '!=', record.id))
+
+ if Recon.search_count(query) > 0:
+ raise UserError(gettext('cashbook.msg_recon_err_overlap'))
+
+ @classmethod
+ def check_lines_not_checked(cls, reconciliations):
+ """ deny lines in date-range not 'checked', w/o records at date-limit
+ """
+ Line = Pool().get('cashbook.line')
+
+ for reconciliation in reconciliations:
+ if Line.search_count([
+ ('date', '>', reconciliation.date_from),
+ ('date', '<', reconciliation.date_to),
+ ('cashbook.id', '=', reconciliation.cashbook.id),
+ ('state', '!=', 'check'),
+ ]) > 0:
+ raise UserError(gettext(
+ 'cashbook.mds_recon_deny_line_not_check',
+ bookname = reconciliation.cashbook.rec_name,
+ reconame = reconciliation.rec_name,
+ datefrom = Report.format_date(reconciliation.date_from),
+ dateto = Report.format_date(reconciliation.date_to),
+ ))
+
@classmethod
@ModelView.button
@Workflow.transition('edit')
- def wfedit(cls, lines):
+ def wfedit(cls, reconciliations):
""" edit
"""
- pass
+ Recon = Pool().get('cashbook.recon')
+
+ to_write = []
+ for reconciliation in reconciliations:
+ values = {
+ 'start_amount': Decimal('0.0'),
+ 'end_amount': Decimal('0.0'),
+ }
+
+ # unlink lines from reconciliation
+ if len(reconciliation.lines) > 0:
+ values['lines'] = [('remove', [x.id for x in reconciliation.lines])]
+
+ to_write.extend([[reconciliation], values])
+
+ if len(to_write) > 0:
+ Recon.write(*to_write)
@classmethod
@ModelView.button
@Workflow.transition('check')
- def wfcheck(cls, lines):
- """ is checked
+ def wfcheck(cls, reconciliations):
+ """ checked: add lines of book in date-range to reconciliation,
+ state of lines must be 'checked'
"""
- pass
+ pool = Pool()
+ Line = pool.get('cashbook.line')
+ Recon = pool.get('cashbook.recon')
+
+ cls.check_lines_not_checked(reconciliations)
+
+ to_write = []
+ for reconciliation in reconciliations:
+ values = {}
+
+ # get start_amount: end_amount of predecessor
+ pre_recon = Recon.search([
+ ('cashbook.id', '=', reconciliation.cashbook.id),
+ ('date_to', '<=', reconciliation.date_from),
+ ('state', 'in', ['check', 'done']),
+ ], order=[('date_to', 'DESC')], limit=1)
+ if len(pre_recon) > 0:
+ values['start_amount'] = pre_recon[0].end_amount
+ else :
+ # not found, use 'start_balance' of cashbook
+ values['start_amount'] = reconciliation.cashbook.start_balance
+ values['end_amount'] = values['start_amount']
+
+ # add 'checked'-lines to reconciliation
+ lines = Line.search([
+ ('date', '>=', reconciliation.date_from),
+ ('date', '<=', reconciliation.date_to),
+ ('cashbook.id', '=', reconciliation.cashbook.id),
+ ('reconciliation', '=', None),
+ ('state', '=', 'check'),
+ ])
+ if len(lines) > 0:
+ values['lines'] = [('add', [x.id for x in lines])]
+
+ # add amounts of new lines
+ values['end_amount'] += sum([x.credit - x.debit for x in lines])
+ # add amounts of already linked lines
+ values['end_amount'] += sum([x.credit - x.debit for x in reconciliation.lines])
+
+ to_write.extend([[reconciliation], values])
+
+ if len(to_write) > 0:
+ Recon.write(*to_write)
@classmethod
@ModelView.button
@Workflow.transition('done')
- def wfdone(cls, lines):
+ def wfdone(cls, reconciliations):
""" is done
"""
- pass
+ Line = Pool().get('cashbook.line')
+
+ to_wfdone_line = []
+ for reconciliation in reconciliations:
+ to_wfdone_line.extend(list(reconciliation.lines))
+
+ if len(to_wfdone_line) > 0:
+ Line.wfdone(to_wfdone_line)
def get_rec_name(self, name):
""" short + name
"""
- return '%(from)s - %(to)s: %(amount)s %(symbol)s' % {
+ return '%(from)s - %(to)s | %(start_amount)s %(symbol)s - %(start_amount)s %(symbol)s [%(num)s]' % {
'from': Report.format_date(self.date_from, None) if self.date_from is not None else '-',
'to': Report.format_date(self.date_to, None) if self.date_to is not None else '-',
- 'amount': Decimal('0.0'),
- 'symbol': getattr(getattr(self.cashbook, 'currency', None), 'symbol', '-'),
+ 'start_amount': Report.format_number(self.start_amount or 0.0, None),
+ 'end_amount': Report.format_number(self.end_amount or 0.0, None),
+ 'symbol': getattr(self.currency, 'symbol', '-'),
+ 'num': len(self.lines),
}
+ @classmethod
+ def default_start_amount(cls):
+ return Decimal('0.0')
+
+ @classmethod
+ def default_end_amount(cls):
+ return Decimal('0.0')
+
@classmethod
def default_state(cls):
return 'edit'
@@ -141,6 +287,22 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
IrDate = Pool().get('ir.date')
return IrDate.today()
+ @fields.depends('cashbook', '_parent_cashbook.currency')
+ def on_change_with_currency(self, name=None):
+ """ currency of cashbook
+ """
+ if self.cashbook:
+ return self.cashbook.currency.id
+
+ @fields.depends('cashbook', '_parent_cashbook.currency')
+ def on_change_with_currency_digits(self, name=None):
+ """ currency of cashbook
+ """
+ if self.cashbook:
+ return self.cashbook.currency.digits
+ else:
+ return 2
+
@fields.depends('cashbook', '_parent_cashbook.state')
def on_change_with_state_cashbook(self, name=None):
""" get state of cashbook
@@ -148,4 +310,63 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
if self.cashbook:
return self.cashbook.state
+ @classmethod
+ def create(cls, vlist):
+ """ add debit/credit
+ """
+ for values in vlist:
+ cls.check_overlap_dates(
+ values.get('date_from', None),
+ values.get('date_to', None),
+ values.get('cashbook', None))
+ return super(Reconciliation, cls).create(vlist)
+
+ @classmethod
+ def write(cls, *args):
+ """ deny update if cashbook.line!='open',
+ add or update debit/credit
+ """
+ actions = iter(args)
+ for reconciliations, values in zip(actions, actions):
+ # deny write if chashbook is not open
+ for reconciliation in reconciliations:
+
+ # deny overlap
+ if len(set({'date_from', 'date_to'}).intersection(set(values.keys()))) > 0:
+ cls.check_overlap_dates(
+ values.get('date_from', reconciliation.date_from),
+ values.get('date_to', reconciliation.date_to),
+ reconciliation.cashbook.id,
+ reconciliation)
+
+ if reconciliation.cashbook.state != 'open':
+ raise UserError(gettext(
+ 'cashbook.msg_book_deny_write',
+ bookname = reconciliation.cashbook.rec_name,
+ state_txt = reconciliation.cashbook.state_string,
+ ))
+
+ super(Reconciliation, cls).write(*args)
+
+ @classmethod
+ def delete(cls, reconciliations):
+ """ deny delete if book is not 'open' or wf is not 'edit'
+ """
+ for reconciliation in reconciliations:
+ if reconciliation.cashbook.state == 'closed':
+ raise UserError(gettext(
+ 'cashbook.msg_line_deny_delete1',
+ linetxt = reconciliation.rec_name,
+ bookname = reconciliation.cashbook.rec_name,
+ bookstate = reconciliation.cashbook.state_string,
+ ))
+ if reconciliation.state != 'edit':
+ raise UserError(gettext(
+ 'cashbook.msg_recon_deny_delete2',
+ recontxt = reconciliation.rec_name,
+ reconstate = reconciliation.state_string,
+ ))
+
+ return super(Reconciliation, cls).delete(reconciliations)
+
# end Type
diff --git a/tests/__init__.py b/tests/__init__.py
index 25789bd..8ecdac3 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -9,11 +9,13 @@ from trytond.modules.cashbook.tests.test_book import BookTestCase
from trytond.modules.cashbook.tests.test_line import LineTestCase
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
__all__ = ['suite']
class CashbookTestCase(\
+ ReconTestCase,\
CategoryTestCase,\
ConfigTestCase,\
LineTestCase,
diff --git a/tests/test_line.py b/tests/test_line.py
index 3bcd986..0466dff 100644
--- a/tests/test_line.py
+++ b/tests/test_line.py
@@ -51,10 +51,10 @@ class LineTestCase(ModuleTestCase):
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].rec_name, '05/01/2022 Text 1')
+ self.assertEqual(book.lines[0].rec_name, '05/01/2022|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 Text 2')
+ self.assertEqual(book.lines[1].rec_name, '05/02/2022|1.00 usd|Text 2 [Cat1]')
self.assertEqual(Lines.search_count([('rec_name', '=', 'Text 1')]), 1)
self.assertEqual(Lines.search_count([('rec_name', '=', 'Text 1a')]), 0)
@@ -67,9 +67,9 @@ class LineTestCase(ModuleTestCase):
# sorting: date -> state -> id
self.assertEqual(len(book.lines), 2)
- self.assertEqual(book.lines[0].rec_name, '05/01/2022 Text 1')
+ self.assertEqual(book.lines[0].rec_name, '05/01/2022|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[0].state, 'edit')
- self.assertEqual(book.lines[1].rec_name, '05/02/2022 Text 2')
+ self.assertEqual(book.lines[1].rec_name, '05/02/2022|1.00 usd|Text 2 [Cat1]')
self.assertEqual(book.lines[1].state, 'edit')
# set to same date
@@ -80,17 +80,17 @@ class LineTestCase(ModuleTestCase):
}])
# check again
book, = Book.search([])
- self.assertEqual(book.lines[0].rec_name, '05/01/2022 Text 1')
+ self.assertEqual(book.lines[0].rec_name, '05/01/2022|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[0].state, 'edit')
- self.assertEqual(book.lines[1].rec_name, '05/01/2022 Text 2')
+ self.assertEqual(book.lines[1].rec_name, '05/01/2022|1.00 usd|Text 2 [Cat1]')
self.assertEqual(book.lines[1].state, 'edit')
# set to 'check', will sort first
Lines.wfcheck([book.lines[1]])
book, = Book.search([])
- self.assertEqual(book.lines[0].rec_name, '05/01/2022 Text 2')
+ self.assertEqual(book.lines[0].rec_name, '05/01/2022|1.00 usd|Text 2 [Cat1]')
self.assertEqual(book.lines[0].state, 'check')
- self.assertEqual(book.lines[1].rec_name, '05/01/2022 Text 1')
+ self.assertEqual(book.lines[1].rec_name, '05/01/2022|1.00 usd|Text 1 [Cat1]')
self.assertEqual(book.lines[1].state, 'edit')
@with_transaction()
@@ -127,6 +127,24 @@ class LineTestCase(ModuleTestCase):
self.assertEqual(book.state, 'open')
self.assertEqual(len(book.lines), 2)
+ Line.write(*[
+ [book.lines[0]],
+ {
+ 'description': 'works',
+ }])
+ Line.wfcheck([book.lines[0]])
+ self.assertEqual(book.lines[0].state, 'check')
+
+ self.assertRaisesRegex(UserError,
+ "The cashbook line '05/01/2022|1.00 usd|works [Cat1]' is 'Checked' and cannot be changed.",
+ Line.write,
+ *[
+ [book.lines[0]],
+ {
+ 'description': 'denied by line.state',
+ },
+ ])
+
Book.wfclosed([book])
self.assertEqual(book.state, 'closed')
@@ -521,7 +539,7 @@ class LineTestCase(ModuleTestCase):
self.assertEqual(book.lines[0].state, 'check')
self.assertRaisesRegex(UserError,
- "The cashbook line '05/01/2022 Text 1' cannot be deleted, its in state 'Checked'.",
+ "The cashbook line '05/01/2022|1.00 usd|Test 1 [Cat1]' cannot be deleted, its in state 'Checked'.",
Lines.delete,
[book.lines[0]])
@@ -586,14 +604,14 @@ class LineTestCase(ModuleTestCase):
lines = Line.search([])
self.assertEqual(len(lines), 1)
self.assertEqual(lines[0].cashbook.rec_name, 'Fridas book | 1.00 usd | Open')
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 1')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 1 [Cat1]')
Line.write(*[
lines,
{
'description': 'Test 2',
}])
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 2')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 2 [Cat1]')
@with_transaction()
def test_line_permission_reviewer(self):
@@ -659,25 +677,25 @@ class LineTestCase(ModuleTestCase):
self.assertEqual(len(lines), 1)
self.assertEqual(len(lines[0].cashbook.reviewer.users), 1)
self.assertEqual(lines[0].cashbook.reviewer.users[0].rec_name, 'Diego')
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 1')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 1 [Cat1]')
Line.write(*[
lines,
{
'description': 'Test 2',
}])
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 2')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 2 [Cat1]')
# change to user 'frida' read/write line
with Transaction().set_user(usr_lst[0].id):
lines = Line.search([])
self.assertEqual(len(lines), 1)
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 2')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 2 [Cat1]')
Line.write(*[
lines,
{
'description': 'Test 3',
}])
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 3')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 3 [Cat1]')
@with_transaction()
def test_line_permission_observer(self):
@@ -743,7 +761,7 @@ class LineTestCase(ModuleTestCase):
self.assertEqual(len(lines), 1)
self.assertEqual(len(lines[0].cashbook.observer.users), 1)
self.assertEqual(lines[0].cashbook.observer.users[0].rec_name, 'Diego')
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 1')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 1 [Cat1]')
self.assertRaisesRegex(UserError,
'You are not allowed to write to records "[0-9]{1,}" of "Cashbook Line" because of at least one of these rules:\nOwners and reviewers: Cashbook line write - ',
@@ -759,12 +777,12 @@ class LineTestCase(ModuleTestCase):
with Transaction().set_user(usr_lst[0].id):
lines = Line.search([])
self.assertEqual(len(lines), 1)
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 1')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 1 [Cat1]')
Line.write(*[
lines,
{
'description': 'Test 2',
}])
- self.assertEqual(lines[0].rec_name, '05/01/2022 Test 2')
+ self.assertEqual(lines[0].rec_name, '05/01/2022|1.00 usd|Test 2 [Cat1]')
# end LineTestCase
diff --git a/tests/test_reconciliation.py b/tests/test_reconciliation.py
new file mode 100644
index 0000000..6e12b81
--- /dev/null
+++ b/tests/test_reconciliation.py
@@ -0,0 +1,365 @@
+# -*- 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
+
+
+class ReconTestCase(ModuleTestCase):
+ 'Test cashbook reconciliation module'
+ module = 'cashbook'
+
+ @with_transaction()
+ def test_recon_check_overlap(self):
+ """ create, check deny of overlap date
+ """
+ pool = Pool()
+ Book = pool.get('cashbook.book')
+ Reconciliation = pool.get('cashbook.recon')
+
+ types = self.prep_type()
+ category = self.prep_category(cattype='in')
+ company = self.prep_company()
+ book, = Book.create([{
+ 'name': 'Book 1',
+ 'btype': types.id,
+ 'company': company.id,
+ 'currency': company.currency.id,
+ }])
+
+ recon1, = Reconciliation.create([{
+ 'date': date(2022, 5, 1),
+ 'date_from': date(2022, 5, 1),
+ 'date_to': date(2022, 5, 31),
+ 'cashbook': book.id,
+ }])
+ self.assertEqual(recon1.rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
+
+ recon2, = Reconciliation.create([{
+ 'date': date(2022, 6, 1),
+ 'date_from': date(2022, 6, 1),
+ 'date_to': date(2022, 6, 30),
+ 'cashbook': book.id,
+ }])
+ self.assertEqual(recon2.rec_name, '06/01/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]')
+
+ # same day for end of '0' and start of '1'
+ Reconciliation.write(*[
+ [recon2],
+ {
+ 'date_from': date(2022, 5, 31),
+ },
+ ])
+ self.assertEqual(recon1.rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
+ self.assertEqual(recon2.rec_name, '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0]')
+
+ # 'date_from' inside of other record
+ self.assertRaisesRegex(UserError,
+ 'The date range overlaps with another reconciliation.',
+ Reconciliation.write,
+ *[
+ [recon2],
+ {
+ 'date_from': date(2022, 5, 30),
+ },
+ ])
+
+ # 'date_from' inside of other record
+ self.assertRaisesRegex(UserError,
+ 'The date range overlaps with another reconciliation.',
+ Reconciliation.write,
+ *[
+ [recon2],
+ {
+ 'date_from': date(2022, 5, 2),
+ },
+ ])
+
+ # 'date_from' same date_from like other record
+ self.assertRaisesRegex(UserError,
+ 'The date range overlaps with another reconciliation.',
+ Reconciliation.write,
+ *[
+ [recon2],
+ {
+ 'date_from': date(2022, 5, 1),
+ },
+ ])
+
+ # enclose other record
+ self.assertRaisesRegex(UserError,
+ 'The date range overlaps with another reconciliation.',
+ Reconciliation.write,
+ *[
+ [recon2],
+ {
+ 'date_from': date(2022, 4, 30),
+ },
+ ])
+
+ # within other record
+ self.assertRaisesRegex(UserError,
+ 'The date range overlaps with another reconciliation.',
+ Reconciliation.write,
+ *[
+ [recon2],
+ {
+ 'date_from': date(2022, 5, 2),
+ 'date_to': date(2022, 5, 12),
+ },
+ ])
+
+ # from after to
+ self.assertRaisesRegex(UserError,
+ 'The value for field "Start Date" in "Cashbook Reconciliation" is not valid according to its domain.',
+ Reconciliation.write,
+ *[
+ [recon2],
+ {
+ 'date_from': date(2022, 4, 15),
+ 'date_to': date(2022, 4, 10),
+ },
+ ])
+
+ # 'date_to' at 'date_from' of other record - allowed
+ Reconciliation.write(*[
+ [recon2],
+ {
+ 'date_from': date(2022, 4, 15),
+ 'date_to': date(2022, 5, 1),
+ }])
+
+ # 'date_to' after other date_from
+ self.assertRaisesRegex(UserError,
+ 'The date range overlaps with another reconciliation.',
+ Reconciliation.write,
+ *[
+ [recon2],
+ {
+ 'date_to': date(2022, 5, 2),
+ },
+ ])
+
+ # overlap at create
+ self.assertRaisesRegex(UserError,
+ 'The date range overlaps with another reconciliation.',
+ Reconciliation.create,
+ [{
+ 'date': date(2022, 5, 1),
+ 'date_from': date(2022, 4, 1),
+ 'date_to': date(2022, 4, 16),
+ 'cashbook': book.id,
+ }])
+
+ recon3, = Reconciliation.create([{
+ 'date': date(2022, 4, 17),
+ 'date_from': date(2022, 4, 1),
+ 'date_to': date(2022, 4, 15),
+ 'cashbook': book.id,
+ }])
+ self.assertEqual(recon1.rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
+ self.assertEqual(recon2.rec_name, '04/15/2022 - 05/01/2022 | 0.00 usd - 0.00 usd [0]')
+ self.assertEqual(recon3.rec_name, '04/01/2022 - 04/15/2022 | 0.00 usd - 0.00 usd [0]')
+
+ @with_transaction()
+ def test_recon_create_check_line_add_to_recon(self):
+ """ create, booklines, add reconciliation
+ """
+ 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()
+ book, = Book.create([{
+ 'name': 'Book 1',
+ 'btype': types.id,
+ 'company': company.id,
+ 'currency': company.currency.id,
+ 'lines': [('create', [{
+ 'date': date(2022, 5, 1),
+ 'description': 'Text 1',
+ 'category': category.id,
+ 'bookingtype': 'in',
+ 'amount': Decimal('1.0'),
+ }, {
+ 'date': date(2022, 5, 5),
+ 'description': 'Text 2',
+ 'category': category.id,
+ 'bookingtype': 'in',
+ 'amount': Decimal('1.0'),
+ }])],
+ 'reconciliations': [('create', [{
+ 'date': date(2022, 5, 28),
+ 'date_from': date(2022, 5, 1),
+ 'date_to': date(2022, 5, 31),
+ }])],
+ }])
+ self.assertEqual(book.name, 'Book 1')
+ self.assertEqual(book.state, 'open')
+ self.assertEqual(len(book.lines), 2)
+ self.assertEqual(book.lines[0].rec_name, '05/01/2022|1.00 usd|Text 1 [Cat1]')
+ self.assertEqual(book.lines[1].rec_name, '05/05/2022|1.00 usd|Text 2 [Cat1]')
+ self.assertEqual(len(book.reconciliations), 1)
+ self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
+ self.assertEqual(len(book.reconciliations[0].lines), 0)
+
+ self.assertRaisesRegex(UserError,
+ "For reconciliation, the line '05/01/2022|1.00 usd|Text 1 [Cat1]' must be in the status 'Check' or 'Done'.",
+ Lines.write,
+ *[
+ [book.lines[0]],
+ {
+ 'reconciliation': book.reconciliations[0].id,
+ }
+ ])
+ Lines.wfcheck(book.lines)
+
+ Lines.write(*[
+ list(book.lines),
+ {
+ 'reconciliation': book.reconciliations[0].id,
+ }])
+ self.assertEqual(len(book.reconciliations[0].lines), 2)
+
+ self.assertRaisesRegex(UserError,
+ "The status cannot be changed to 'Edit' as long as the line '05/01/2022|1.00 usd|Text 1 [Cat1]' is associated with a reconciliation.",
+ Lines.wfedit,
+ [book.lines[0]])
+
+ @with_transaction()
+ def test_recon_check_deny_delete(self):
+ """ create, booklines, reconciliation, try delete
+ """
+ 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()
+ book, = Book.create([{
+ 'name': 'Book 1',
+ 'btype': types.id,
+ 'company': company.id,
+ 'currency': company.currency.id,
+ 'lines': [('create', [{
+ 'date': date(2022, 5, 1),
+ 'description': 'Text 1',
+ 'category': category.id,
+ 'bookingtype': 'in',
+ 'amount': Decimal('1.0'),
+ }])],
+ 'reconciliations': [('create', [{
+ 'date': date(2022, 5, 28),
+ 'date_from': date(2022, 5, 1),
+ 'date_to': date(2022, 5, 31),
+ }])],
+ }])
+ self.assertEqual(book.name, 'Book 1')
+ self.assertEqual(book.state, 'open')
+
+ Lines.wfcheck(list(book.lines))
+ Reconciliation.wfcheck(list(book.reconciliations))
+ self.assertEqual(len(book.reconciliations), 1)
+ self.assertEqual(len(book.reconciliations[0].lines), 1)
+
+ self.assertRaisesRegex(UserError,
+ "The reconciliation '05/01/2022 - 05/31/2022 | 0.00 - 0.00 usd [0]' cannot be deleted, its in state 'Check'.",
+ Reconciliation.delete,
+ list(book.reconciliations))
+
+ Book.wfclosed([book])
+
+ self.assertRaisesRegex(UserError,
+ "The cashbook line '05/01/2022 - 05/31/2022: 0.00 usd' cannot be deleted because the Cashbook 'Book 1 | 1.00 usd | Closed' is in state 'Closed'.",
+ Reconciliation.delete,
+ list(book.reconciliations))
+
+ self.assertRaisesRegex(UserError,
+ "The cash book 'Book 1 | 1.00 usd | Closed' is 'Closed' and cannot be changed.",
+ Reconciliation.write,
+ *[
+ list(book.reconciliations),
+ {
+ 'date': date(2022, 5, 29),
+ },
+ ])
+
+ @with_transaction()
+ def test_recon_check_wf_edit_to_check(self):
+ """ create, booklines, add reconciliation
+ """
+ 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()
+ book, = Book.create([{
+ 'name': 'Book 1',
+ 'btype': types.id,
+ 'company': company.id,
+ 'currency': company.currency.id,
+ 'lines': [('create', [{
+ 'date': date(2022, 5, 1),
+ 'description': 'Text 1',
+ 'category': category.id,
+ 'bookingtype': 'in',
+ 'amount': Decimal('1.0'),
+ }, {
+ 'date': date(2022, 5, 5),
+ 'description': 'Text 2',
+ 'category': category.id,
+ 'bookingtype': 'in',
+ 'amount': Decimal('1.0'),
+ }])],
+ 'reconciliations': [('create', [{
+ 'date': date(2022, 5, 28),
+ 'date_from': date(2022, 5, 1),
+ 'date_to': date(2022, 5, 31),
+ }])],
+ }])
+ self.assertEqual(book.name, 'Book 1')
+ self.assertEqual(book.state, 'open')
+ self.assertEqual(len(book.lines), 2)
+ self.assertEqual(book.lines[0].rec_name, '05/01/2022|1.00 usd|Text 1 [Cat1]')
+ self.assertEqual(book.lines[1].rec_name, '05/05/2022|1.00 usd|Text 2 [Cat1]')
+ self.assertEqual(book.lines[0].reconciliation, None)
+ self.assertEqual(book.lines[1].reconciliation, None)
+ self.assertEqual(len(book.reconciliations), 1)
+ self.assertEqual(book.reconciliations[0].rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]')
+ self.assertEqual(len(book.reconciliations[0].lines), 0)
+
+ # run wf, fail with lines not 'checked'
+ self.assertRaisesRegex(UserError,
+ "For the reconciliation '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0]' of the cashbook 'Book 1 | 2.00 usd | Open', all lines in the date range from '05/01/2022' to '05/31/2022' must be in the 'Check' state.",
+ Reconciliation.wfcheck,
+ list(book.reconciliations),
+ )
+
+ # edit --> check
+ Lines.wfcheck(book.lines)
+ Reconciliation.wfcheck(list(book.reconciliations))
+ self.assertEqual(len(book.reconciliations[0].lines), 2)
+ self.assertEqual(book.lines[0].reconciliation.rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [2]')
+ self.assertEqual(book.lines[1].reconciliation.rec_name, '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [2]')
+
+ # check --> edit
+ Reconciliation.wfedit(list(book.reconciliations))
+ self.assertEqual(len(book.reconciliations[0].lines), 0)
+ self.assertEqual(book.lines[0].reconciliation, None)
+ self.assertEqual(book.lines[1].reconciliation, None)
+
+# end ReconTestCase
diff --git a/view/recon_form.xml b/view/recon_form.xml
index 94ce19b..536fe66 100644
--- a/view/recon_form.xml
+++ b/view/recon_form.xml
@@ -22,7 +22,11 @@ full copyright notices and license terms. -->
-
+
+
+
+
+
diff --git a/view/recon_list.xml b/view/recon_list.xml
index 6d49b47..2b1248e 100644
--- a/view/recon_list.xml
+++ b/view/recon_list.xml
@@ -7,6 +7,8 @@ full copyright notices and license terms. -->
+
+