formatting, line: test for delete of party

This commit is contained in:
Frederik Jaeckel 2023-05-18 12:15:53 +02:00
parent 78f160bf0b
commit 619a17bcd6
16 changed files with 701 additions and 516 deletions

View file

@ -18,6 +18,7 @@ from .cbreport import ReconciliationReport
from .currency import CurrencyRate from .currency import CurrencyRate
from .model import MemCache from .model import MemCache
def register(): def register():
Pool.register( Pool.register(
MemCache, MemCache,

191
book.py
View file

@ -20,16 +20,18 @@ from .model import order_name_hierarchical, sub_ids_hierarchical, \
# enable/disable caching of cachekey for 'currency.rate' # enable/disable caching of cachekey for 'currency.rate'
if config.get('cashbook', 'cache_currency', default='yes').lower() in ['yes', '1', 'true']: if config.get(
'cashbook', 'cache_currency', default='yes'
).lower() in ['yes', '1', 'true']:
ENA_CURRKEY = True ENA_CURRKEY = True
else : else:
ENA_CURRKEY = False ENA_CURRKEY = False
STATES = { STATES = {
'readonly': Eval('state', '') != 'open', 'readonly': Eval('state', '') != 'open',
} }
DEPENDS=['state'] DEPENDS = ['state']
# states in case of 'btype'!=None # states in case of 'btype'!=None
STATES2 = { STATES2 = {
@ -55,14 +57,17 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
'Cashbook' 'Cashbook'
__name__ = 'cashbook.book' __name__ = 'cashbook.book'
company = fields.Many2One(string='Company', model_name='company.company', company = fields.Many2One(
string='Company', model_name='company.company',
required=True, select=True, ondelete="RESTRICT") required=True, select=True, ondelete="RESTRICT")
name = fields.Char(string='Name', required=True, name = fields.Char(
states=STATES, depends=DEPENDS) string='Name', required=True, states=STATES, depends=DEPENDS)
description = fields.Text(string='Description', description = fields.Text(
states=STATES, depends=DEPENDS) string='Description', states=STATES, depends=DEPENDS)
btype = fields.Many2One(string='Type', select=True, btype = fields.Many2One(
help='A cash book with type can contain postings. Without type is a view.', string='Type', select=True,
help='A cash book with type can contain postings. ' +
'Without type is a view.',
model_name='cashbook.type', ondelete='RESTRICT', model_name='cashbook.type', ondelete='RESTRICT',
states={ states={
'readonly': Or( 'readonly': Or(
@ -70,40 +75,50 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
Len(Eval('lines')) > 0, Len(Eval('lines')) > 0,
), ),
}, depends=DEPENDS+['lines']) }, depends=DEPENDS+['lines'])
feature = fields.Function(fields.Char(string='Feature', readonly=True, feature = fields.Function(fields.Char(
string='Feature', readonly=True,
states={'invisible': True}), 'on_change_with_feature') states={'invisible': True}), 'on_change_with_feature')
owner = fields.Many2One(string='Owner', required=True, select=True, owner = fields.Many2One(
string='Owner', required=True, select=True,
model_name='res.user', ondelete='SET NULL', model_name='res.user', ondelete='SET NULL',
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
reviewer = fields.Many2One(string='Reviewer', select=True, reviewer = fields.Many2One(
string='Reviewer', select=True,
help='Group of users who have write access to the cashbook.', help='Group of users who have write access to the cashbook.',
model_name='res.group', ondelete='SET NULL', model_name='res.group', ondelete='SET NULL',
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
observer = fields.Many2One(string='Observer', select=True, observer = fields.Many2One(
string='Observer', select=True,
help='Group of users who have read-only access to the cashbook.', help='Group of users who have read-only access to the cashbook.',
model_name='res.group', ondelete='SET NULL', model_name='res.group', ondelete='SET NULL',
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
lines = fields.One2Many(string='Lines', field='cashbook', lines = fields.One2Many(
string='Lines', field='cashbook',
model_name='cashbook.line', model_name='cashbook.line',
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
reconciliations = fields.One2Many(string='Reconciliations', reconciliations = fields.One2Many(
string='Reconciliations',
field='cashbook', model_name='cashbook.recon', field='cashbook', model_name='cashbook.recon',
states=STATES2, depends=DEPENDS2) states=STATES2, depends=DEPENDS2)
number_sequ = fields.Many2One(string='Line numbering', number_sequ = fields.Many2One(
string='Line numbering',
help='Number sequence for numbering of the cash book lines.', help='Number sequence for numbering of the cash book lines.',
model_name='ir.sequence', model_name='ir.sequence',
domain=[ domain=[
('sequence_type', '=', Id('cashbook', 'sequence_type_cashbook_line')), ('sequence_type', '=',
Id('cashbook', 'sequence_type_cashbook_line')),
['OR', ['OR',
('company', '=', None), ('company', '=', None),
('company', '=', Eval('company', -1)), ('company', '=', Eval('company', -1))],
],
], ],
states=STATES3, depends=DEPENDS2+['company']) states=STATES3, depends=DEPENDS2+['company'])
number_atcheck = fields.Boolean(string="number when 'Checking'", number_atcheck = fields.Boolean(
help="The numbering of the lines is done in the step Check. If the check mark is inactive, this happens with Done.", string="number when 'Checking'",
help="The numbering of the lines is done in the step Check. " +
"If the check mark is inactive, this happens with Done.",
states=STATES2, depends=DEPENDS2) states=STATES2, depends=DEPENDS2)
start_date = fields.Date(string='Initial Date', start_date = fields.Date(
string='Initial Date',
states={ states={
'readonly': Or( 'readonly': Or(
STATES2['readonly'], STATES2['readonly'],
@ -112,25 +127,29 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
'invisible': STATES2['invisible'], 'invisible': STATES2['invisible'],
'required': ~STATES2['invisible'], 'required': ~STATES2['invisible'],
}, depends=DEPENDS2+['lines']) }, depends=DEPENDS2+['lines'])
balance = fields.Function(fields.Numeric(string='Balance', balance = fields.Function(fields.Numeric(
string='Balance',
readonly=True, depends=['currency_digits'], readonly=True, depends=['currency_digits'],
help='Balance of bookings to date', help='Balance of bookings to date',
digits=(16, Eval('currency_digits', 2))), digits=(16, Eval('currency_digits', 2))),
'get_balance_cashbook', searcher='search_balance') 'get_balance_cashbook', searcher='search_balance')
balance_all = fields.Function(fields.Numeric(string='Total balance', balance_all = fields.Function(fields.Numeric(
string='Total balance',
readonly=True, depends=['currency_digits'], readonly=True, depends=['currency_digits'],
help='Balance of all bookings', help='Balance of all bookings',
digits=(16, Eval('currency_digits', 2))), digits=(16, Eval('currency_digits', 2))),
'get_balance_cashbook', searcher='search_balance') 'get_balance_cashbook', searcher='search_balance')
balance_ref = fields.Function(fields.Numeric(string='Balance (Ref.)', balance_ref = fields.Function(fields.Numeric(
string='Balance (Ref.)',
help='Balance in company currency', help='Balance in company currency',
readonly=True, digits=(16, Eval('company_currency_digits', 2)), readonly=True, digits=(16, Eval('company_currency_digits', 2)),
states={ states={
'invisible': ~Bool(Eval('company_currency')), 'invisible': ~Bool(Eval('company_currency')),
}, depends=['company_currency_digits', 'company_currency']), }, depends=['company_currency_digits', 'company_currency']),
'get_balance_cashbook') 'get_balance_cashbook')
company_currency = fields.Function(fields.Many2One(readonly=True, company_currency = fields.Function(fields.Many2One(
readonly=True,
string='Company Currency', states={'invisible': True}, string='Company Currency', states={'invisible': True},
model_name='currency.currency'), model_name='currency.currency'),
'on_change_with_company_currency') 'on_change_with_company_currency')
@ -138,7 +157,8 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
string='Currency Digits (Ref.)', readonly=True), string='Currency Digits (Ref.)', readonly=True),
'on_change_with_currency_digits') 'on_change_with_currency_digits')
currency = fields.Many2One(string='Currency', select=True, currency = fields.Many2One(
string='Currency', select=True,
model_name='currency.currency', model_name='currency.currency',
states={ states={
'readonly': Or( 'readonly': Or(
@ -146,15 +166,19 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
Len(Eval('lines', [])) > 0, Len(Eval('lines', [])) > 0,
), ),
}, depends=DEPENDS2+['lines']) }, depends=DEPENDS2+['lines'])
currency_digits = fields.Function(fields.Integer(string='Currency Digits', currency_digits = fields.Function(fields.Integer(
string='Currency Digits',
readonly=True), 'on_change_with_currency_digits') readonly=True), 'on_change_with_currency_digits')
state = fields.Selection(string='State', required=True, state = fields.Selection(
string='State', required=True,
readonly=True, selection=sel_state_book) readonly=True, selection=sel_state_book)
state_string = state.translated('state') state_string = state.translated('state')
parent = fields.Many2One(string="Parent", parent = fields.Many2One(
string="Parent",
model_name='cashbook.book', ondelete='RESTRICT') model_name='cashbook.book', ondelete='RESTRICT')
childs = fields.One2Many(string='Children', field='parent', childs = fields.One2Many(
string='Children', field='parent',
model_name='cashbook.book') model_name='cashbook.book')
@classmethod @classmethod
@ -245,9 +269,8 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
query = tab_book.select( query = tab_book.select(
Case( Case(
(tab_book.state == 'open', 0), (tab_book.state == 'open', 0),
else_ = 1), else_=1),
where=tab_book.id==table.id where=tab_book.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -286,19 +309,21 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
# deny invalid date in context # deny invalid date in context
if isinstance(query_date, str): if isinstance(query_date, str):
try : try:
dt1 = date.fromisoformat(query_date) date.fromisoformat(query_date)
except : except Exception:
query_date = IrDate.today() query_date = IrDate.today()
query = tab_book.join(tab_line, query = tab_book.join(
condition=tab_book.id==tab_line.cashbook, tab_line,
condition=tab_book.id == tab_line.cashbook,
).select( ).select(
tab_line.cashbook, tab_line.cashbook,
tab_book.currency, tab_book.currency,
Sum(Case( Sum(Case(
(tab_line.date <= query_date, tab_line.credit - tab_line.debit), (tab_line.date <= query_date,
else_ = Decimal('0.0'), tab_line.credit - tab_line.debit),
else_=Decimal('0.0'),
)).as_('balance'), )).as_('balance'),
Sum(tab_line.credit - tab_line.debit).as_('balance_all'), Sum(tab_line.credit - tab_line.debit).as_('balance_all'),
group_by=[tab_line.cashbook, tab_book.currency], group_by=[tab_line.cashbook, tab_book.currency],
@ -313,9 +338,9 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
(tab_book, tab2) = Book2.get_balance_of_cashbook_sql() (tab_book, tab2) = Book2.get_balance_of_cashbook_sql()
table, _ = tables[None] table, _ = tables[None]
query = tab_book.select(tab_book.balance, query = tab_book.select(
where=tab_book.cashbook==table.id, tab_book.balance,
) where=tab_book.cashbook == table.id)
return [query] return [query]
@staticmethod @staticmethod
@ -326,9 +351,9 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
(tab_book, tab2) = Book2.get_balance_of_cashbook_sql() (tab_book, tab2) = Book2.get_balance_of_cashbook_sql()
table, _ = tables[None] table, _ = tables[None]
query = tab_book.select(tab_book.balance_all, query = tab_book.select(
where=tab_book.cashbook==table.id, tab_book.balance_all,
) where=tab_book.cashbook == table.id)
return [query] return [query]
@classmethod @classmethod
@ -361,32 +386,32 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
context = Transaction().context context = Transaction().context
result = { result = {
x:{y.id: Decimal('0.0') for y in cashbooks} x: {y.id: Decimal('0.0') for y in cashbooks}
for x in ['balance', 'balance_all', 'balance_ref']} for x in ['balance', 'balance_all', 'balance_ref']}
# deny invalid date in context # deny invalid date in context
query_date = context.get('date', IrDate.today()) query_date = context.get('date', IrDate.today())
if isinstance(query_date, str): if isinstance(query_date, str):
try : try:
dt1 = date.fromisoformat(query_date) date.fromisoformat(query_date)
except : except Exception:
query_date = IrDate.today() query_date = IrDate.today()
cache_keys = { cache_keys = {
x.id: MemCache.get_key_by_record( x.id: MemCache.get_key_by_record(
name = 'get_balance_cashbook', name='get_balance_cashbook',
record = x, record=x,
query = [{ query=[{
'model': 'cashbook.line', 'model': 'cashbook.line',
'query': [('cashbook.parent', 'child_of', [x.id])], 'query': [('cashbook.parent', 'child_of', [x.id])],
}, { }, {
'model': 'currency.currency.rate', 'model': 'currency.currency.rate',
'query': [('currency.id', '=', x.currency.id)], 'query': [('currency.id', '=', x.currency.id)],
'cachekey' if ENA_CURRKEY else 'disabled': CACHEKEY_CURRENCY % x.currency.id, 'cachekey' if ENA_CURRKEY
else 'disabled': CACHEKEY_CURRENCY % x.currency.id,
}, ], }, ],
addkeys = [query_date.isoformat()]) addkeys=[query_date.isoformat()])
for x in cashbooks for x in cashbooks}
}
# read from cache # read from cache
(todo_cashbook, result) = MemCache.read_from_cache( (todo_cashbook, result) = MemCache.read_from_cache(
@ -396,16 +421,19 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
# query balances of cashbooks and sub-cashbooks # query balances of cashbooks and sub-cashbooks
with Transaction().set_context({ with Transaction().set_context({
'date': query_date, 'date': query_date}):
}):
(tab_line, tab2) = cls.get_balance_of_cashbook_sql() (tab_line, tab2) = cls.get_balance_of_cashbook_sql()
tab_subids = sub_ids_hierarchical('cashbook.book') tab_subids = sub_ids_hierarchical('cashbook.book')
query = tab_book.join(tab_subids, query = tab_book.join(
condition=tab_book.id==tab_subids.parent, tab_subids,
).join(tab_comp, condition=tab_book.id == tab_subids.parent,
condition=tab_book.company==tab_comp.id, ).join(
).join(tab_line, tab_comp,
condition=tab_line.cashbook==AnyInArray(tab_subids.subids), condition=tab_book.company == tab_comp.id,
).join(
tab_line,
condition=tab_line.cashbook == AnyInArray(
tab_subids.subids),
).select( ).select(
tab_book.id, tab_book.id,
tab_book.currency.as_('to_currency'), tab_book.currency.as_('to_currency'),
@ -413,7 +441,8 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
tab_comp.currency.as_('company_currency'), tab_comp.currency.as_('company_currency'),
Sum(tab_line.balance).as_('balance'), Sum(tab_line.balance).as_('balance'),
Sum(tab_line.balance_all).as_('balance_all'), Sum(tab_line.balance_all).as_('balance_all'),
group_by=[tab_book.id, tab_line.currency, tab_comp.currency], group_by=[
tab_book.id, tab_line.currency, tab_comp.currency],
where=tab_book.id.in_([x.id for x in todo_cashbook]), where=tab_book.id.in_([x.id for x in todo_cashbook]),
) )
cursor.execute(*query) cursor.execute(*query)
@ -496,30 +525,29 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
if (values['btype'] is None) and (len(book.lines) > 0): if (values['btype'] is None) and (len(book.lines) > 0):
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_book_btype_with_lines', 'cashbook.msg_book_btype_with_lines',
cbname = book.rec_name, cbname=book.rec_name,
numlines = len(book.lines), numlines=len(book.lines)))
))
if book.state != 'open': if book.state != 'open':
# allow state-update, if its the only action # allow state-update, if its the only action
if not (('state' in values.keys()) and (len(values.keys()) == 1)): if not (('state' in values.keys()) and
(len(values.keys()) == 1)):
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_book_deny_write', 'cashbook.msg_book_deny_write',
bookname = book.rec_name, bookname=book.rec_name,
state_txt = book.state_string, state_txt=book.state_string))
))
# if owner changes, remove book from user-config # if owner changes, remove book from user-config
if 'owner' in values.keys(): if 'owner' in values.keys():
if book.owner.id != values['owner']: if book.owner.id != values['owner']:
for x in ['defbook', 'book1', 'book2', 'book3', for x in [
'defbook', 'book1', 'book2', 'book3',
'book4', 'book5']: 'book4', 'book5']:
cfg1 = ConfigUser.search([ cfg1 = ConfigUser.search([
('iduser.id', '=', book.owner.id), ('iduser.id', '=', book.owner.id),
('%s.id' % x, '=', book.id), ('%s.id' % x, '=', book.id)])
])
if len(cfg1) > 0: if len(cfg1) > 0:
to_write_config.extend([ cfg1, {x: None} ]) to_write_config.extend([cfg1, {x: None}])
super(Book, cls).write(*args) super(Book, cls).write(*args)
if len(to_write_config) > 0: if len(to_write_config) > 0:
@ -533,9 +561,8 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
if (len(book.lines) > 0) and (book.state != 'archive'): if (len(book.lines) > 0) and (book.state != 'archive'):
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_book_deny_delete', 'cashbook.msg_book_deny_delete',
bookname = book.rec_name, bookname=book.rec_name,
booklines = len(book.lines), booklines=len(book.lines)))
))
super(Book, cls).delete(books) super(Book, cls).delete(books)
# end Book # end Book

View file

@ -25,22 +25,27 @@ class Category(tree(separator='/'), ModelSQL, ModelView):
name = fields.Char(string='Name', required=True, translate=True) name = fields.Char(string='Name', required=True, translate=True)
description = fields.Char(string='Description', translate=True) description = fields.Char(string='Description', translate=True)
cattype = fields.Selection(string='Type', required=True, cattype = fields.Selection(
string='Type', required=True,
help='Type of Category', selection=sel_categorytype, help='Type of Category', selection=sel_categorytype,
states={'readonly': Bool(Eval('parent_cattype'))}, states={'readonly': Bool(Eval('parent_cattype'))},
domain=[If(Bool(Eval('parent_cattype')), domain=[If(Bool(Eval('parent_cattype')),
('cattype', '=', Eval('parent_cattype', '')), ('cattype', '=', Eval('parent_cattype', '')),
())], ())],
depends=['parent_cattype']) depends=['parent_cattype'])
parent_cattype = fields.Function(fields.Char(string='Parent Category Type', parent_cattype = fields.Function(fields.Char(
string='Parent Category Type',
readonly=True, states={'invisible': True}), readonly=True, states={'invisible': True}),
'on_change_with_parent_cattype') 'on_change_with_parent_cattype')
company = fields.Many2One(string='Company', model_name='company.company', company = fields.Many2One(
string='Company', model_name='company.company',
required=True, ondelete="RESTRICT") required=True, ondelete="RESTRICT")
parent = fields.Many2One(string="Parent", parent = fields.Many2One(
string="Parent",
model_name='cashbook.category', ondelete='RESTRICT') model_name='cashbook.category', ondelete='RESTRICT')
childs = fields.One2Many(string='Children', field='parent', childs = fields.One2Many(
string='Children', field='parent',
model_name='cashbook.category') model_name='cashbook.category')
@classmethod @classmethod
@ -61,7 +66,8 @@ class Category(tree(separator='/'), ModelSQL, ModelView):
Unique(t, t.name, t.company, t.parent), Unique(t, t.name, t.company, t.parent),
'cashbook.msg_category_name_unique'), 'cashbook.msg_category_name_unique'),
('name2_uniq', ('name2_uniq',
Exclude(t, Exclude(
t,
(t.name, Equal), (t.name, Equal),
(t.cattype, Equal), (t.cattype, Equal),
where=(t.parent == None)), where=(t.parent == None)),
@ -106,9 +112,8 @@ class Category(tree(separator='/'), ModelSQL, ModelView):
if category.parent.cattype != category.cattype: if category.parent.cattype != category.cattype:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_category_type_not_like_parent', 'cashbook.msg_category_type_not_like_parent',
parentname = category.parent.rec_name, parentname=category.parent.rec_name,
catname = category.rec_name, catname=category.rec_name,))
))
@classmethod @classmethod
def create(cls, vlist): def create(cls, vlist):

View file

@ -20,7 +20,8 @@ class ReconciliationReport(Report):
Company = Pool().get('company.company') Company = Pool().get('company.company')
context2 = Transaction().context context2 = Transaction().context
context = super(ReconciliationReport, cls).get_context(records, header, data) context = super(
ReconciliationReport, cls).get_context(records, header, data)
context['company'] = Company(context2['company']) context['company'] = Company(context2['company'])
return context return context
@ -41,14 +42,14 @@ class ReconciliationReport(Report):
recon_obj = pool.get(data['model'])(data['id']) recon_obj = pool.get(data['model'])(data['id'])
rep_name = gettext( rep_name = gettext(
'cashbook.msg_rep_reconciliation_fname', 'cashbook.msg_rep_reconciliation_fname',
recname = recon_obj.cashbook.rec_name[:50], recname=recon_obj.cashbook.rec_name[:50],
date_from = recon_obj.date_from.isoformat(), date_from=recon_obj.date_from.isoformat(),
date_to = recon_obj.date_to.isoformat(), date_to=recon_obj.date_to.isoformat())
) else:
else :
raise ValueError('invalid model') raise ValueError('invalid model')
(ext1, cont1, dirprint, title) = super(ReconciliationReport, cls).execute(ids, data) (ext1, cont1, dirprint, title) = super(
ReconciliationReport, cls).execute(ids, data)
return ( return (
ext1, ext1,
@ -63,5 +64,3 @@ class ReconciliationReport(Report):
) )
# end ReconciliationReport # end ReconciliationReport

View file

@ -9,11 +9,14 @@ from trytond.pyson import Eval, If
from trytond.pool import Pool from trytond.pool import Pool
field_checked = fields.Boolean(string='Checked', field_checked = fields.Boolean(
string='Checked',
help='Show cashbook lines in Checked-state.') help='Show cashbook lines in Checked-state.')
field_done = fields.Boolean(string='Done', field_done = fields.Boolean(
string='Done',
help='Show cashbook lines in Done-state.') help='Show cashbook lines in Done-state.')
field_catnamelong = fields.Boolean(string='Category: Show long name', 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.') help='Shows the long name of the category in the Category field of a cash book line.')
@ -21,13 +24,15 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
'Configuration' 'Configuration'
__name__ = 'cashbook.configuration' __name__ = 'cashbook.configuration'
date_from = fields.MultiValue(fields.Date(string='Start Date', depends=['date_to'], date_from = fields.MultiValue(fields.Date(
string='Start Date', depends=['date_to'],
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')), ('date_from', '<=', Eval('date_to')),
()), ()),
])) ]))
date_to = fields.MultiValue(fields.Date(string='End Date', depends=['date_from'], date_to = fields.MultiValue(fields.Date(
string='End Date', depends=['date_from'],
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')), ('date_from', '<=', Eval('date_to')),
@ -36,37 +41,43 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
checked = fields.MultiValue(field_checked) checked = fields.MultiValue(field_checked)
done = fields.MultiValue(field_done) done = fields.MultiValue(field_done)
catnamelong = fields.MultiValue(field_catnamelong) catnamelong = fields.MultiValue(field_catnamelong)
defbook = fields.MultiValue(fields.Many2One(string='Default Cashbook', defbook = fields.MultiValue(fields.Many2One(
string='Default Cashbook',
help='The default cashbook is selected when you open the booking wizard.', help='The default cashbook is selected when you open the booking wizard.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
('btype', '!=', None), ('state', '=', 'open'), ('btype', '!=', None), ('state', '=', 'open'),
])) ]))
book1 = fields.MultiValue(fields.Many2One(string='Cashbook 1', book1 = fields.MultiValue(fields.Many2One(
string='Cashbook 1',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
('btype', '!=', None), ('state', '=', 'open'), ('btype', '!=', None), ('state', '=', 'open'),
])) ]))
book2 = fields.MultiValue(fields.Many2One(string='Cashbook 2', book2 = fields.MultiValue(fields.Many2One(
string='Cashbook 2',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
('btype', '!=', None), ('state', '=', 'open'), ('btype', '!=', None), ('state', '=', 'open'),
])) ]))
book3 = fields.MultiValue(fields.Many2One(string='Cashbook 3', book3 = fields.MultiValue(fields.Many2One(
string='Cashbook 3',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
('btype', '!=', None), ('state', '=', 'open'), ('btype', '!=', None), ('state', '=', 'open'),
])) ]))
book4 = fields.MultiValue(fields.Many2One(string='Cashbook 4', book4 = fields.MultiValue(fields.Many2One(
string='Cashbook 4',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
('btype', '!=', None), ('state', '=', 'open'), ('btype', '!=', None), ('state', '=', 'open'),
])) ]))
book5 = fields.MultiValue(fields.Many2One(string='Cashbook 5', book5 = fields.MultiValue(fields.Many2One(
string='Cashbook 5',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
@ -79,7 +90,8 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
""" """
pool = Pool() pool = Pool()
if field in ['date_from', 'date_to', 'checked', 'done', if field in [
'date_from', 'date_to', 'checked', 'done',
'catnamelong', 'defbook', 'book1', 'book2', 'catnamelong', 'defbook', 'book1', 'book2',
'book3', 'book4', 'book5']: 'book3', 'book4', 'book5']:
return pool.get('cashbook.configuration_user') return pool.get('cashbook.configuration_user')
@ -104,13 +116,15 @@ class UserConfiguration(ModelSQL, UserValueMixin):
'User Configuration' 'User Configuration'
__name__ = 'cashbook.configuration_user' __name__ = 'cashbook.configuration_user'
date_from = fields.Date(string='Start Date', depends=['date_to'], date_from = fields.Date(
string='Start Date', depends=['date_to'],
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')), ('date_from', '<=', Eval('date_to')),
()), ()),
]) ])
date_to = fields.Date(string='End Date', depends=['date_from'], date_to = fields.Date(
string='End Date', depends=['date_from'],
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')), ('date_from', '<=', Eval('date_to')),
@ -119,7 +133,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
checked = field_checked checked = field_checked
done = field_done done = field_done
catnamelong = field_catnamelong catnamelong = field_catnamelong
defbook = fields.Many2One(string='Default Cashbook', defbook = fields.Many2One(
string='Default Cashbook',
help='The default cashbook is selected when you open the booking wizard.', help='The default cashbook is selected when you open the booking wizard.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
@ -127,7 +142,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'), ('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1)) ('owner.id', '=', Eval('iduser', -1))
], depends=['iduser']) ], depends=['iduser'])
book1 = fields.Many2One(string='Cashbook 1', book1 = fields.Many2One(
string='Cashbook 1',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
@ -135,7 +151,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'), ('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1)) ('owner.id', '=', Eval('iduser', -1))
], depends=['iduser']) ], depends=['iduser'])
book2 = fields.Many2One(string='Cashbook 2', book2 = fields.Many2One(
string='Cashbook 2',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
@ -143,7 +160,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'), ('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1)) ('owner.id', '=', Eval('iduser', -1))
], depends=['iduser']) ], depends=['iduser'])
book3 = fields.Many2One(string='Cashbook 3', book3 = fields.Many2One(
string='Cashbook 3',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
@ -151,7 +169,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'), ('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1)) ('owner.id', '=', Eval('iduser', -1))
], depends=['iduser']) ], depends=['iduser'])
book4 = fields.Many2One(string='Cashbook 4', book4 = fields.Many2One(
string='Cashbook 4',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[
@ -159,7 +178,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'), ('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1)) ('owner.id', '=', Eval('iduser', -1))
], depends=['iduser']) ], depends=['iduser'])
book5 = fields.Many2One(string='Cashbook 5', book5 = fields.Many2One(
string='Cashbook 5',
help='Cash book available in selection dialog.', help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL', model_name='cashbook.book', ondelete='SET NULL',
domain=[ domain=[

View file

@ -32,7 +32,8 @@ class CurrencyRate(metaclass=PoolMeta):
actions = iter(args) actions = iter(args)
for rates, values in zip(actions, actions): for rates, values in zip(actions, actions):
for rate in rates: for rate in rates:
MemCache.record_update(CACHEKEY_CURRENCY % rate.currency.id, rate) MemCache.record_update(
CACHEKEY_CURRENCY % rate.currency.id, rate)
@classmethod @classmethod
def delete(cls, records): def delete(cls, records):

376
line.py
View file

@ -45,26 +45,32 @@ STATES = {
Eval('state_cashbook', '') != 'open', Eval('state_cashbook', '') != 'open',
), ),
} }
DEPENDS=['state', 'state_cashbook'] DEPENDS = ['state', 'state_cashbook']
class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView): class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
'Cashbook Line' 'Cashbook Line'
__name__ = 'cashbook.line' __name__ = 'cashbook.line'
cashbook = fields.Many2One(string='Cashbook', required=True, select=True, cashbook = fields.Many2One(
string='Cashbook', required=True, select=True,
model_name='cashbook.book', ondelete='CASCADE', readonly=True, model_name='cashbook.book', ondelete='CASCADE', readonly=True,
domain=[('btype', '!=', None)]) domain=[('btype', '!=', None)])
date = fields.Date(string='Date', required=True, select=True, date = fields.Date(
string='Date', required=True, select=True,
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
month = fields.Function(fields.Integer(string='Month', readonly=True), month = fields.Function(fields.Integer(
string='Month', readonly=True),
'on_change_with_month', searcher='search_month') 'on_change_with_month', searcher='search_month')
number = fields.Char(string='Number', readonly=True) number = fields.Char(string='Number', readonly=True)
description = fields.Text(string='Description', select=True, description = fields.Text(
string='Description', select=True,
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
descr_short = fields.Function(fields.Char(string='Description', readonly=True), descr_short = fields.Function(fields.Char(
string='Description', readonly=True),
'on_change_with_descr_short', searcher='search_descr_short') 'on_change_with_descr_short', searcher='search_descr_short')
category = fields.Many2One(string='Category', select=True, category = fields.Many2One(
string='Category', select=True,
model_name='cashbook.category', ondelete='RESTRICT', model_name='cashbook.category', ondelete='RESTRICT',
states={ states={
'readonly': Or( 'readonly': Or(
@ -80,18 +86,23 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
('cattype', '=', 'in'), ('cattype', '=', 'in'),
('cattype', '=', 'out'), ('cattype', '=', 'out'),
)]) )])
category_view = fields.Function(fields.Char(string='Category', readonly=True), category_view = fields.Function(fields.Char(
string='Category', readonly=True),
'on_change_with_category_view', searcher='search_category_view') 'on_change_with_category_view', searcher='search_category_view')
feature = fields.Function(fields.Char(string='Feature', readonly=True, feature = fields.Function(fields.Char(
string='Feature', readonly=True,
states={'invisible': True}), 'on_change_with_feature') states={'invisible': True}), 'on_change_with_feature')
booktransf_feature = fields.Function(fields.Char(string='Feature', readonly=True, booktransf_feature = fields.Function(fields.Char(
string='Feature', readonly=True,
states={'invisible': True}), 'on_change_with_booktransf_feature') states={'invisible': True}), 'on_change_with_booktransf_feature')
bookingtype = fields.Selection(string='Type', required=True, bookingtype = fields.Selection(
string='Type', required=True,
help='Type of Booking', selection=sel_bookingtype, select=True, help='Type of Booking', selection=sel_bookingtype, select=True,
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
bookingtype_string = bookingtype.translated('bookingtype') bookingtype_string = bookingtype.translated('bookingtype')
amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)), amount = fields.Numeric(
string='Amount', digits=(16, Eval('currency_digits', 2)),
required=True, required=True,
states={ states={
'readonly': Or( 'readonly': Or(
@ -99,13 +110,16 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
Eval('bookingtype', '').in_(['spin', 'spout']), Eval('bookingtype', '').in_(['spin', 'spout']),
), ),
}, depends=DEPENDS+['currency_digits', 'bookingtype']) }, depends=DEPENDS+['currency_digits', 'bookingtype'])
debit = fields.Numeric(string='Debit', digits=(16, Eval('currency_digits', 2)), debit = fields.Numeric(
string='Debit', digits=(16, Eval('currency_digits', 2)),
required=True, readonly=True, depends=['currency_digits']) required=True, readonly=True, depends=['currency_digits'])
credit = fields.Numeric(string='Credit', digits=(16, Eval('currency_digits', 2)), credit = fields.Numeric(
string='Credit', digits=(16, Eval('currency_digits', 2)),
required=True, readonly=True, depends=['currency_digits']) required=True, readonly=True, depends=['currency_digits'])
# party or cashbook as counterpart # party or cashbook as counterpart
booktransf = fields.Many2One(string='Source/Dest', booktransf = fields.Many2One(
string='Source/Dest',
ondelete='RESTRICT', model_name='cashbook.book', ondelete='RESTRICT', model_name='cashbook.book',
domain=[ domain=[
('owner.id', '=', Eval('owner_cashbook', -1)), ('owner.id', '=', Eval('owner_cashbook', -1)),
@ -117,28 +131,35 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
'invisible': ~Eval('bookingtype', '').in_(['mvin', 'mvout']), 'invisible': ~Eval('bookingtype', '').in_(['mvin', 'mvout']),
'required': Eval('bookingtype', '').in_(['mvin', 'mvout']), 'required': Eval('bookingtype', '').in_(['mvin', 'mvout']),
}, depends=DEPENDS+['bookingtype', 'owner_cashbook', 'cashbook']) }, depends=DEPENDS+['bookingtype', 'owner_cashbook', 'cashbook'])
party = fields.Many2One(string='Party', model_name='party.party', party = fields.Many2One(
string='Party', model_name='party.party',
ondelete='RESTRICT', ondelete='RESTRICT',
states={ states={
'readonly': STATES['readonly'], 'readonly': STATES['readonly'],
'invisible': ~Eval('bookingtype', '').in_(['in', 'out', 'spin', 'spout']), 'invisible': ~Eval('bookingtype', '').in_(
['in', 'out', 'spin', 'spout']),
}, depends=DEPENDS+['bookingtype']) }, depends=DEPENDS+['bookingtype'])
payee = fields.Function(fields.Reference(string='Payee', readonly=True, payee = fields.Function(fields.Reference(
string='Payee', readonly=True,
selection=sel_payee), 'on_change_with_payee', searcher='search_payee') selection=sel_payee), 'on_change_with_payee', searcher='search_payee')
# link to lines created by this record # link to lines created by this record
reference = fields.Many2One(string='Reference', readonly=True, select=True, reference = fields.Many2One(
string='Reference', readonly=True, select=True,
states={ states={
'invisible': ~Bool(Eval('reference')), 'invisible': ~Bool(Eval('reference')),
}, model_name='cashbook.line', ondelete='CASCADE', }, model_name='cashbook.line', ondelete='CASCADE',
help='The current row was created by and is controlled by the reference row.') help='The current row was created by and is controlled ' +
references = fields.One2Many(string='References', 'by the reference row.')
references = fields.One2Many(
string='References',
model_name='cashbook.line', model_name='cashbook.line',
help='The rows are created and managed by the current record.', help='The rows are created and managed by the current record.',
states={ states={
'invisible': ~Bool(Eval('references')), 'invisible': ~Bool(Eval('references')),
}, field='reference', readonly=True) }, field='reference', readonly=True)
splitlines = fields.One2Many(string='Split booking lines', splitlines = fields.One2Many(
string='Split booking lines',
model_name='cashbook.split', model_name='cashbook.split',
help='Rows with different categories form the total sum of the booking', help='Rows with different categories form the total sum of the booking',
states={ states={
@ -150,7 +171,8 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
'required': Eval('bookingtype' '').in_(['spin', 'spout']), 'required': Eval('bookingtype' '').in_(['spin', 'spout']),
}, field='line', depends=DEPENDS+['bookingtype']) }, field='line', depends=DEPENDS+['bookingtype'])
reconciliation = fields.Many2One(string='Reconciliation', readonly=True, 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'))], domain=[('cashbook.id', '=', Eval('cashbook'))],
depends=['cashbook'], depends=['cashbook'],
@ -158,24 +180,31 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
'invisible': ~Bool(Eval('reconciliation')), 'invisible': ~Bool(Eval('reconciliation')),
}) })
balance = fields.Function(fields.Numeric(string='Balance', balance = fields.Function(fields.Numeric(
string='Balance',
digits=(16, Eval('currency_digits', 2)), digits=(16, Eval('currency_digits', 2)),
help='Balance of the cash book up to the current line, if the default sorting applies.', help='Balance of the cash book up to the current line, ' +
'if the default sorting applies.',
readonly=True, depends=['currency_digits']), readonly=True, depends=['currency_digits']),
'on_change_with_balance') 'on_change_with_balance')
currency = fields.Function(fields.Many2One(model_name='currency.currency', currency = fields.Function(fields.Many2One(
model_name='currency.currency',
string="Currency", readonly=True), 'on_change_with_currency') string="Currency", readonly=True), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits', currency_digits = fields.Function(fields.Integer(
string='Currency Digits',
readonly=True), 'on_change_with_currency_digits') readonly=True), 'on_change_with_currency_digits')
state = fields.Selection(string='State', required=True, readonly=True, state = fields.Selection(
string='State', required=True, readonly=True,
select=True, selection=sel_linetype) select=True, selection=sel_linetype)
state_string = state.translated('state') state_string = state.translated('state')
state_cashbook = fields.Function(fields.Selection(string='State of Cashbook', state_cashbook = fields.Function(fields.Selection(
string='State of Cashbook',
readonly=True, states={'invisible': True}, selection=sel_state_book), readonly=True, states={'invisible': True}, selection=sel_state_book),
'on_change_with_state_cashbook', searcher='search_state_cashbook') 'on_change_with_state_cashbook', searcher='search_state_cashbook')
owner_cashbook = fields.Function(fields.Many2One(string='Owner', readonly=True, owner_cashbook = fields.Function(fields.Many2One(
string='Owner', readonly=True,
states={'invisible': True}, model_name='res.user'), states={'invisible': True}, model_name='res.user'),
'on_change_with_owner_cashbook') 'on_change_with_owner_cashbook')
@ -230,8 +259,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
return super().view_attributes() + [ return super().view_attributes() + [
('/tree', 'visual', ('/tree', 'visual',
If(Eval('balance', 0) < 0, 'warning', If(Eval('balance', 0) < 0, 'warning',
If(Eval('date', Date()) > Date(), 'muted', '') If(Eval('date', Date()) > Date(), 'muted', ''))),
)),
] ]
@classmethod @classmethod
@ -247,13 +275,16 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
tab_book2 = Book2.__table__() # transfer-target tab_book2 = Book2.__table__() # transfer-target
cursor = Transaction().connection.cursor() cursor = Transaction().connection.cursor()
query = tab_line.join(tab_book, query = tab_line.join(
tab_book,
condition=tab_line.cashbook == tab_book.id, condition=tab_line.cashbook == tab_book.id,
).join(tab_book2, ).join(
condition=tab_line.booktransf== tab_book2.id, tab_book2,
).select(tab_line.id, condition=tab_line.booktransf == tab_book2.id,
where=tab_line.bookingtype.in_(['mvin', 'mvout']) & \ ).select(
(tab_line.amount_2nd_currency == None) & \ tab_line.id,
where=tab_line.bookingtype.in_(['mvin', 'mvout']) &
(tab_line.amount_2nd_currency == None) &
(tab_book.currency != tab_book2.currency) (tab_book.currency != tab_book2.currency)
) )
lines = Line2.search([('id', 'in', query)]) lines = Line2.search([('id', 'in', query)])
@ -270,9 +301,9 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
for line in to_write: for line in to_write:
qu1 = tab_line.update( qu1 = tab_line.update(
columns = [tab_line.amount_2nd_currency], columns=[tab_line.amount_2nd_currency],
values = [line['amount_2nd_currency']], values=[line['amount_2nd_currency']],
where = tab_line.id == line['id'], where=tab_line.id == line['id'],
) )
cursor.execute(*qu1) cursor.execute(*qu1)
@ -288,19 +319,18 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
to_delete_line = [] to_delete_line = []
for line in lines: for line in lines:
if line.reference: if line.reference:
if Transaction().context.get('line.allow.wfedit', False) == False: if Transaction().context.get(
'line.allow.wfedit', False) == False:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_denywf_by_reference', 'cashbook.msg_line_denywf_by_reference',
recname = line.reference.rec_name, recname=line.reference.rec_name,
cbook = line.reference.cashbook.rec_name, cbook=line.reference.cashbook.rec_name))
))
# delete references # delete references
to_delete_line.extend(list(line.references)) to_delete_line.extend(list(line.references))
if len(to_delete_line) > 0: if len(to_delete_line) > 0:
with Transaction().set_context({ with Transaction().set_context({
'line.allow.wfedit': True, 'line.allow.wfedit': True}):
}):
Line2.wfedit(to_delete_line) Line2.wfedit(to_delete_line)
Line2.delete(to_delete_line) Line2.delete(to_delete_line)
@ -323,11 +353,10 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
('state', 'in', ['check', 'done']), ('state', 'in', ['check', 'done']),
('cashbook.id', '=', line.cashbook.id), ('cashbook.id', '=', line.cashbook.id),
('date_from', '<', line.date), ('date_from', '<', line.date),
('date_to', '>', line.date), ('date_to', '>', line.date)]) > 0:
]) > 0:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_err_write_to_reconciled', 'cashbook.msg_line_err_write_to_reconciled',
datetxt = Report.format_date(line.date), datetxt=Report.format_date(line.date),
)) ))
# deny if date is at reconciliation limits and two # deny if date is at reconciliation limits and two
# reconciliations exist # reconciliations exist
@ -336,12 +365,10 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
('cashbook.id', '=', line.cashbook.id), ('cashbook.id', '=', line.cashbook.id),
['OR', ['OR',
('date_from', '=', line.date), ('date_from', '=', line.date),
('date_to', '=', line.date), ('date_to', '=', line.date)]]) > 1:
]
]) > 1:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_err_write_to_reconciled', 'cashbook.msg_line_err_write_to_reconciled',
datetxt = Report.format_date(line.date), datetxt=Report.format_date(line.date),
)) ))
if line.reference is None: if line.reference is None:
@ -356,25 +383,28 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
if sp_line.splittype != 'tr': if sp_line.splittype != 'tr':
continue continue
values = cls.get_counterpart_values(line, values = cls.get_counterpart_values(
splitline = sp_line, line, splitline=sp_line,
values = { values={
'cashbook': sp_line.booktransf.id, 'cashbook': sp_line.booktransf.id,
'description': sp_line.description, 'description': sp_line.description,
'amount': sp_line.amount \ 'amount': sp_line.amount
if sp_line.currency.id == sp_line.booktransf.currency.id \ if sp_line.currency.id == sp_line.
booktransf.currency.id
else sp_line.amount_2nd_currency, else sp_line.amount_2nd_currency,
'amount_2nd_currency': sp_line.amount \ 'amount_2nd_currency': sp_line.amount
if sp_line.currency.id != sp_line.booktransf.currency.id \ if sp_line.currency.id != sp_line.
booktransf.currency.id
else None, else None,
'bookingtype': 'mvin' \ 'bookingtype': 'mvin'
if line.bookingtype.endswith('out') else 'mvout', if line.bookingtype.endswith('out')
else 'mvout',
}) })
values.update(cls.get_debit_credit(values)) values.update(cls.get_debit_credit(values))
to_create_line.append(values) to_create_line.append(values)
# add number to line # add number to line
if line.cashbook.number_atcheck == True: if line.cashbook.number_atcheck is True:
if len(line.number or '') == 0: if len(line.number or '') == 0:
to_write_line.extend([ to_write_line.extend([
[line], [line],
@ -459,11 +489,12 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
'desc': (self.description or '-')[:40], 'desc': (self.description or '-')[:40],
'amount': Report.format_number(credit - debit, None), 'amount': Report.format_number(credit - debit, None),
'symbol': getattr(self.currency, 'symbol', '-'), 'symbol': getattr(self.currency, 'symbol', '-'),
'category': self.category_view \ 'category': self.category_view
if self.bookingtype in ['in', 'out'] \ if self.bookingtype in ['in', 'out']
else getattr(self.booktransf, 'rec_name', '-'), else getattr(self.booktransf, 'rec_name', '-'),
'type': gettext('cashbook.msg_line_bookingtype_%s' % self.bookingtype), 'type': gettext(
} 'cashbook.msg_line_bookingtype_%s' %
self.bookingtype)}
@staticmethod @staticmethod
def order_state(tables): def order_state(tables):
@ -477,8 +508,8 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
Case( Case(
(tab_line.state == 'edit', 1), (tab_line.state == 'edit', 1),
(tab_line.state.in_(['check', 'recon', 'done']), 0), (tab_line.state.in_(['check', 'recon', 'done']), 0),
else_ = 2), else_=2),
where=tab_line.id==table.id where=tab_line.id == table.id
) )
return [query] return [query]
@ -490,10 +521,9 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
Category = Pool().get('cashbook.category') Category = Pool().get('cashbook.category')
tab_cat = Category.__table__() tab_cat = Category.__table__()
tab2 = tab_cat.select(tab_cat.name, tab2 = tab_cat.select(
where=tab_cat.id==table.category tab_cat.name,
) where=tab_cat.id == table.category)
return [tab2] return [tab2]
@staticmethod @staticmethod
@ -509,8 +539,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
""" """
return ['OR', return ['OR',
('party.rec_name',) + tuple(clause[1:]), ('party.rec_name',) + tuple(clause[1:]),
('booktransf.rec_name',) + tuple(clause[1:]), ('booktransf.rec_name',) + tuple(clause[1:])]
]
@classmethod @classmethod
def search_category_view(cls, name, clause): def search_category_view(cls, name, clause):
@ -529,10 +558,12 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
Operator = fields.SQL_OPERATORS[clause[1]] Operator = fields.SQL_OPERATORS[clause[1]]
dt1 = IrDate.today() dt1 = IrDate.today()
query = tab_line.select(tab_line.id, query = tab_line.select(
tab_line.id,
where=Operator( where=Operator(
Literal(12 * dt1.year + dt1.month) - \ Literal(12 * dt1.year + dt1.month) -
(Literal(12) * DatePart('year', tab_line.date) + DatePart('month', tab_line.date)), (Literal(12) * DatePart('year', tab_line.date) +
DatePart('month', tab_line.date)),
clause[2]), clause[2]),
) )
return [('id', 'in', query)] return [('id', 'in', query)]
@ -553,9 +584,11 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
def on_change_splitlines(self): def on_change_splitlines(self):
""" update amount if splitlines change """ update amount if splitlines change
""" """
self.amount = sum([x.amount for x in self.splitlines if x.amount is not None]) self.amount = sum([
x.amount for x in self.splitlines if x.amount is not None])
@fields.depends('bookingtype', 'category', 'splitlines', 'booktransf',\ @fields.depends(
'bookingtype', 'category', 'splitlines', 'booktransf',
'currency2nd') 'currency2nd')
def on_change_bookingtype(self): def on_change_bookingtype(self):
""" clear category if not valid type """ clear category if not valid type
@ -567,19 +600,20 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
if self.bookingtype: if self.bookingtype:
if self.category: if self.category:
if not self.bookingtype in types.get(self.category.cattype, ''): if self.bookingtype not in types.get(self.category.cattype, ''):
self.category = None self.category = None
if self.bookingtype.startswith('sp'): # split booking if self.bookingtype.startswith('sp'): # split booking
self.category = None self.category = None
self.booktransf = None self.booktransf = None
for spline in self.splitlines: for spline in self.splitlines:
if not self.bookingtype in types.get(getattr(spline.category, 'cattype', '-'), ''): if self.bookingtype not in types.get(
getattr(spline.category, 'cattype', '-'), ''):
spline.category = None spline.category = None
elif self.bookingtype.startswith('mv'): # transfer elif self.bookingtype.startswith('mv'): # transfer
self.splitlines = [] self.splitlines = []
self.category = None self.category = None
else : # category else: # category
self.splitlines = [] self.splitlines = []
self.booktransf = None self.booktransf = None
self.currency2nd = self.on_change_with_currency2nd() self.currency2nd = self.on_change_with_currency2nd()
@ -627,9 +661,9 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
if self.category: if self.category:
cfg1 = Configuration.get_singleton() cfg1 = Configuration.get_singleton()
if getattr(cfg1, 'catnamelong', True) == True: if getattr(cfg1, 'catnamelong', True) is True:
return self.category.rec_name return self.category.rec_name
else : else:
return self.category.name return self.category.name
@fields.depends('date') @fields.depends('date')
@ -673,7 +707,9 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
return 2 return 2
@classmethod @classmethod
def get_balance_of_line(cls, line, field_name='amount', credit_name='credit', debit_name='debit'): def get_balance_of_line(
cls, line, field_name='amount', credit_name='credit',
debit_name='debit'):
""" get balance of current line, """ get balance of current line,
try to speed up by usage of last reconcilitaion try to speed up by usage of last reconcilitaion
""" """
@ -699,8 +735,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
('date', '<=', line2.date), ('date', '<=', line2.date),
['OR', ['OR',
('reconciliation', '=', None), ('reconciliation', '=', None),
('reconciliation.id', '!=', recons[0]), ('reconciliation.id', '!=', recons[0])],
],
]) ])
end_value = getattr(recons[0], 'end_%s' % field_name) end_value = getattr(recons[0], 'end_%s' % field_name)
return (query2, end_value) return (query2, end_value)
@ -719,13 +754,14 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
query.append( query.append(
('reconciliation.id', '=', line.reconciliation.id), ('reconciliation.id', '=', line.reconciliation.id),
) )
balance = getattr(line.reconciliation, 'start_%s' % field_name) balance = getattr(
else : line.reconciliation, 'start_%s' % field_name)
else:
(query2, balance2) = get_from_last_recon(line) (query2, balance2) = get_from_last_recon(line)
query.extend(query2) query.extend(query2)
if balance2 is not None: if balance2 is not None:
balance = balance2 balance = balance2
else : else:
(query2, balance2) = get_from_last_recon(line) (query2, balance2) = get_from_last_recon(line)
query.extend(query2) query.extend(query2)
if balance2 is not None: if balance2 is not None:
@ -743,18 +779,16 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
break break
return balance return balance
@fields.depends('id', 'date', 'cashbook', \ @fields.depends(
'_parent_cashbook.id', 'reconciliation', \ 'id', 'date', 'cashbook', '_parent_cashbook.id', 'reconciliation',
'_parent_reconciliation.start_amount',\ '_parent_reconciliation.start_amount', '_parent_reconciliation.state')
'_parent_reconciliation.state')
def on_change_with_balance(self, name=None): def on_change_with_balance(self, name=None):
""" compute balance until current line, with current sort order, """ compute balance until current line, with current sort order,
try to use a reconciliation as start to speed up calculation try to use a reconciliation as start to speed up calculation
""" """
Line2 = Pool().get('cashbook.line') Line2 = Pool().get('cashbook.line')
return Line2.get_balance_of_line(self, return Line2.get_balance_of_line(
field_name='amount', self, field_name='amount', credit_name='credit',
credit_name='credit',
debit_name='debit') debit_name='debit')
@classmethod @classmethod
@ -764,12 +798,14 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
values2 = {} values2 = {}
values2.update(values) values2.update(values)
bookingtype = values2.get('bookingtype', getattr(line, 'bookingtype', None)) bookingtype = values2.get(
'bookingtype', getattr(line, 'bookingtype', None))
if (bookingtype in ['in', 'out', 'mvin', 'mvout']) and \ if (bookingtype in ['in', 'out', 'mvin', 'mvout']) and \
('splitlines' not in values2.keys()): ('splitlines' not in values2.keys()):
if line: if line:
if len(line.splitlines) > 0: if len(line.splitlines) > 0:
values2['splitlines'] = [('delete', [x.id for x in line.splitlines])] values2['splitlines'] = [
('delete', [x.id for x in line.splitlines])]
if bookingtype in ['in', 'out']: if bookingtype in ['in', 'out']:
values2['booktransf'] = None values2['booktransf'] = None
@ -788,7 +824,8 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
transfer booking transfer booking
""" """
line_currency = getattr(line.currency, 'id', None) line_currency = getattr(line.currency, 'id', None)
booktransf_currency = getattr(getattr(line.booktransf, 'currency', {}), 'id', None) booktransf_currency = getattr(getattr(
line.booktransf, 'currency', {}), 'id', None)
result = { result = {
'cashbook': getattr(line.booktransf, 'id', None), 'cashbook': getattr(line.booktransf, 'id', None),
@ -797,13 +834,11 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
'description': line.description, 'description': line.description,
'booktransf': line.cashbook.id, 'booktransf': line.cashbook.id,
'reference': line.id, 'reference': line.id,
'amount': line.amount \ 'amount': line.amount
if line_currency == booktransf_currency \ if line_currency == booktransf_currency
else line.amount_2nd_currency, else line.amount_2nd_currency,
'amount_2nd_currency': line.amount \ 'amount_2nd_currency': line.amount
if line_currency != booktransf_currency \ if line_currency != booktransf_currency else None}
else None,
}
# update values from 'values' # update values from 'values'
result.update(values) result.update(values)
return result return result
@ -813,10 +848,12 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
""" compute debit/credit from amount """ compute debit/credit from amount
""" """
if isinstance(values, dict): if isinstance(values, dict):
type_ = values.get('bookingtype', getattr(line, 'bookingtype', None)) type_ = values.get(
'bookingtype', getattr(line, 'bookingtype', None))
amount = values.get('amount', None) amount = values.get('amount', None)
else : else:
type_ = getattr(values, 'bookingtype', getattr(line, 'bookingtype', None)) type_ = getattr(
values, 'bookingtype', getattr(line, 'bookingtype', None))
amount = getattr(values, 'amount', None) amount = getattr(values, 'amount', None)
result = {} result = {}
@ -824,15 +861,11 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
if amount is not None: if amount is not None:
if type_ in ['in', 'mvin', 'spin']: if type_ in ['in', 'mvin', 'spin']:
result.update({ result.update({
'debit': Decimal('0.0'), 'debit': Decimal('0.0'), 'credit': amount})
'credit': amount,
})
elif type_ in ['out', 'mvout', 'spout']: elif type_ in ['out', 'mvout', 'spout']:
result.update({ result.update({
'debit': amount, 'debit': amount, 'credit': Decimal('0.0')})
'credit': Decimal('0.0'), else:
})
else :
raise ValueError('invalid "bookingtype"') raise ValueError('invalid "bookingtype"')
return result return result
@ -851,30 +884,27 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
if line.date < line.cashbook.start_date: if line.date < line.cashbook.start_date:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_date_before_book', 'cashbook.msg_line_date_before_book',
datebook = Report.format_date(line.cashbook.start_date), datebook=Report.format_date(line.cashbook.start_date),
recname = line.rec_name, recname=line.rec_name))
))
# line: category <--> bookingtype? # line: category <--> bookingtype?
if line.category: if line.category:
if not line.bookingtype in types[line.category.cattype]: if line.bookingtype not in types[line.category.cattype]:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_invalid_category', 'cashbook.msg_line_invalid_category',
recname = line.rec_name, recname=line.rec_name,
booktype = line.bookingtype_string, booktype=line.bookingtype_string))
))
# splitline: category <--> bookingtype? # splitline: category <--> bookingtype?
for spline in line.splitlines: for spline in line.splitlines:
if spline.splittype != 'cat': if spline.splittype != 'cat':
continue continue
if not line.bookingtype in types[spline.category.cattype]: if line.bookingtype not in types[spline.category.cattype]:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_split_invalid_category', 'cashbook.msg_line_split_invalid_category',
recname = line.rec_name, recname=line.rec_name,
splitrecname = spline.rec_name, splitrecname=spline.rec_name,
booktype = line.bookingtype_string, booktype=line.bookingtype_string))
))
@classmethod @classmethod
def check_permission_write(cls, lines, values={}): def check_permission_write(cls, lines, values={}):
@ -885,29 +915,28 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
if line.cashbook.state != 'open': if line.cashbook.state != 'open':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_book_deny_write', 'cashbook.msg_book_deny_write',
bookname = line.cashbook.rec_name, bookname=line.cashbook.rec_name,
state_txt = line.cashbook.state_string, state_txt=line.cashbook.state_string))
))
# deny write if reconciliation is 'check' or 'done' # deny write if reconciliation is 'check' or 'done'
if line.reconciliation: if line.reconciliation:
if line.reconciliation.state == 'done': if line.reconciliation.state == 'done':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_write_by_reconciliation', 'cashbook.msg_line_deny_write_by_reconciliation',
recname = line.rec_name, recname=line.rec_name,
reconame = line.reconciliation.rec_name, reconame=line.reconciliation.rec_name))
))
# deny write if line is not 'Edit' # deny write if line is not 'Edit'
if line.state != 'edit': if line.state != 'edit':
# allow state-update, if its the only action # allow state-update, if its the only action
if not ((len(set({'state', 'reconciliation', 'number'}).intersection(values.keys())) > 0) \ if not ((len(set({
'state', 'reconciliation', 'number'
}).intersection(values.keys())) > 0)
and (len(values.keys()) == 1)): and (len(values.keys()) == 1)):
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_write', 'cashbook.msg_line_deny_write',
recname = line.rec_name, recname=line.rec_name,
state_txt = line.state_string, state_txt=line.state_string))
))
@classmethod @classmethod
def check_permission_delete(cls, lines): def check_permission_delete(cls, lines):
@ -917,16 +946,14 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
if line.cashbook.state == 'closed': if line.cashbook.state == 'closed':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_delete1', 'cashbook.msg_line_deny_delete1',
linetxt = line.rec_name, linetxt=line.rec_name,
bookname = line.cashbook.rec_name, bookname=line.cashbook.rec_name,
bookstate = line.cashbook.state_string, bookstate=line.cashbook.state_string))
))
if line.state != 'edit': if line.state != 'edit':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_delete2', 'cashbook.msg_line_deny_delete2',
linetxt = line.rec_name, linetxt=line.rec_name,
linestate = line.state_string, linestate=line.state_string))
))
@classmethod @classmethod
def update_values_by_splitlines(cls, lines): def update_values_by_splitlines(cls, lines):
@ -936,7 +963,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
for line in lines: for line in lines:
amount = sum([x.amount for x in line.splitlines]) amount = sum([x.amount for x in line.splitlines])
if amount != line.amount: if amount != line.amount:
to_write.extend([ [line], {'amount': amount,} ]) to_write.extend([[line], {'amount': amount}])
return to_write return to_write
@classmethod @classmethod
@ -946,7 +973,8 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
if ('splitlines' in values.keys()) and ('amount' not in values.keys()): if ('splitlines' in values.keys()) and ('amount' not in values.keys()):
for action in values['splitlines']: for action in values['splitlines']:
if action[0] == 'create': if action[0] == 'create':
values['amount'] = sum([x.get('amount', None) for x in action[1]]) values['amount'] = sum([
x.get('amount', None) for x in action[1]])
return values return values
@classmethod @classmethod
@ -956,7 +984,8 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
Cashbook = Pool().get('cashbook.book') Cashbook = Pool().get('cashbook.book')
cashbook = values.get('cashbook', None) cashbook = values.get('cashbook', None)
if cashbook: if cashbook:
values.update(cls.add_2nd_currency(values, Cashbook(cashbook).currency)) values.update(cls.add_2nd_currency(
values, Cashbook(cashbook).currency))
return values return values
@classmethod @classmethod
@ -975,7 +1004,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
default = default.copy() default = default.copy()
default.setdefault('number', None) default.setdefault('number', None)
default.setdefault('state', cls.default_state()) default.setdefault('state', cls.default_state())
return super(Line, cls).copy(moves, default=default) return super(Line, cls).copy(lines, default=default)
@classmethod @classmethod
def create(cls, vlist): def create(cls, vlist):
@ -988,7 +1017,8 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
values.update(cls.clear_by_bookingtype(values)) values.update(cls.clear_by_bookingtype(values))
values.update(cls.add_2nd_unit_values(values)) values.update(cls.add_2nd_unit_values(values))
# deny add to reconciliation if state is not 'check', 'recon' or 'done' # deny add to reconciliation if state is
# not 'check', 'recon' or 'done'
if values.get('reconciliation', None): if values.get('reconciliation', None):
if not values.get('state', '-') in ['check', 'done', 'recon']: if not values.get('state', '-') in ['check', 'done', 'recon']:
date_txt = '-' date_txt = '-'
@ -996,11 +1026,9 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
date_txt = Report.format_date(values.get('date', None)) date_txt = Report.format_date(values.get('date', None))
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_recon_by_state', 'cashbook.msg_line_deny_recon_by_state',
recname = '%(date)s|%(descr)s' % { recname='%(date)s|%(descr)s' % {
'date': date_txt, 'date': date_txt,
'descr': values.get('description', '-'), 'descr': values.get('description', '-')}))
},
))
return super(Line, cls).create(vlist) return super(Line, cls).create(vlist)
@classmethod @classmethod
@ -1015,21 +1043,21 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
for line in lines: for line in lines:
if line.reconciliation: if line.reconciliation:
# deny state-change to 'edit' if line is linked to reconciliation # deny state-change to 'edit' if line is
# linked to reconciliation
if values.get('state', '-') == 'edit': if values.get('state', '-') == 'edit':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_stateedit_with_recon', 'cashbook.msg_line_deny_stateedit_with_recon',
recname = line.rec_name, recname=line.rec_name))
))
# deny add to reconciliation if state is not 'check', 'recon' or 'done' # deny add to reconciliation if state is
# not 'check', 'recon' or 'done'
if values.get('reconciliation', None): if values.get('reconciliation', None):
for line in lines: for line in lines:
if not line.state in ['check', 'done', 'recon']: if line.state not in ['check', 'done', 'recon']:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_recon_by_state', 'cashbook.msg_line_deny_recon_by_state',
recname = line.rec_name recname=line.rec_name))
))
# update debit / credit # update debit / credit
fields_update = cls.get_fields_write_update() fields_update = cls.get_fields_write_update()
@ -1043,13 +1071,15 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
updt_fields = [] updt_fields = []
updt_fields.extend(values.keys()) updt_fields.extend(values.keys())
if 'bookingtype' in values.keys(): if 'bookingtype' in values.keys():
updt_fields.extend([x for x in fields_update if x not in values.keys()]) updt_fields.extend([
x for x in fields_update
if x not in values.keys()])
values2.update(cls.get_debit_credit({ values2.update(cls.get_debit_credit({
x:values.get(x, getattr(line, x)) for x in updt_fields x: values.get(x, getattr(line, x))
}, line=line)) for x in updt_fields}, line=line))
to_write.extend([lines, values2]) to_write.extend([lines, values2])
else : else:
to_write.extend([lines, values]) to_write.extend([lines, values])
super(Line, cls).write(*to_write) super(Line, cls).write(*to_write)
@ -1068,23 +1098,27 @@ class LineContext(ModelView):
'Line Context' 'Line Context'
__name__ = 'cashbook.line.context' __name__ = 'cashbook.line.context'
date_from = fields.Date(string='Start Date', depends=['date_to'], date_from = fields.Date(
string='Start Date', depends=['date_to'],
help='Limits the date range for the displayed entries.', help='Limits the date range for the displayed entries.',
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')), ('date_from', '<=', Eval('date_to')),
()), ()),
]) ])
date_to = fields.Date(string='End Date', depends=['date_from'], date_to = fields.Date(
string='End Date', depends=['date_from'],
help='Limits the date range for the displayed entries.', help='Limits the date range for the displayed entries.',
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')), ('date_from', '<=', Eval('date_to')),
()), ()),
]) ])
checked = fields.Boolean(string='Checked', checked = fields.Boolean(
string='Checked',
help='Show account lines in Checked-state.') help='Show account lines in Checked-state.')
done = fields.Boolean(string='Done', done = fields.Boolean(
string='Done',
help='Show account lines in Done-state.') help='Show account lines in Done-state.')
@classmethod @classmethod

View file

@ -17,13 +17,14 @@ STATES = {
Eval('state_cashbook', '') != 'open', Eval('state_cashbook', '') != 'open',
), ),
} }
DEPENDS=['state', 'state_cashbook'] DEPENDS = ['state', 'state_cashbook']
class SecondCurrencyMixin: class SecondCurrencyMixin:
""" two fields for 2nd currency: amount + rate """ two fields for 2nd currency: amount + rate
""" """
amount_2nd_currency = fields.Numeric(string='Amount Second Currency', amount_2nd_currency = fields.Numeric(
string='Amount Second Currency',
digits=(16, Eval('currency2nd_digits', 2)), digits=(16, Eval('currency2nd_digits', 2)),
states={ states={
'readonly': Or( 'readonly': Or(
@ -33,7 +34,8 @@ class SecondCurrencyMixin:
'required': Bool(Eval('currency2nd')), 'required': Bool(Eval('currency2nd')),
'invisible': ~Bool(Eval('currency2nd')), 'invisible': ~Bool(Eval('currency2nd')),
}, depends=DEPENDS+['currency2nd_digits', 'currency2nd']) }, depends=DEPENDS+['currency2nd_digits', 'currency2nd'])
rate_2nd_currency = fields.Function(fields.Numeric(string='Rate', rate_2nd_currency = fields.Function(fields.Numeric(
string='Rate',
help='Exchange rate between the currencies of the participating cashbooks.', help='Exchange rate between the currencies of the participating cashbooks.',
digits=(rate_decimal * 2, rate_decimal), digits=(rate_decimal * 2, rate_decimal),
states={ states={
@ -46,9 +48,11 @@ class SecondCurrencyMixin:
}, depends=DEPENDS+['currency2nd_digits', 'currency2nd']), }, depends=DEPENDS+['currency2nd_digits', 'currency2nd']),
'on_change_with_rate_2nd_currency', setter='set_rate_2nd_currency') 'on_change_with_rate_2nd_currency', setter='set_rate_2nd_currency')
currency2nd = fields.Function(fields.Many2One(model_name='currency.currency', currency2nd = fields.Function(fields.Many2One(
model_name='currency.currency',
string="2nd Currency", readonly=True), 'on_change_with_currency2nd') string="2nd Currency", readonly=True), 'on_change_with_currency2nd')
currency2nd_digits = fields.Function(fields.Integer(string='2nd Currency Digits', currency2nd_digits = fields.Function(fields.Integer(
string='2nd Currency Digits',
readonly=True), 'on_change_with_currency2nd_digits') readonly=True), 'on_change_with_currency2nd_digits')
@classmethod @classmethod
@ -69,8 +73,7 @@ class SecondCurrencyMixin:
booktransf = Cashbook(booktransf) booktransf = Cashbook(booktransf)
if from_currency.id != booktransf.currency.id: if from_currency.id != booktransf.currency.id:
with Transaction().set_context({ with Transaction().set_context({
'date': values.get('date', IrDate.today()), 'date': values.get('date', IrDate.today())}):
}):
values['amount_2nd_currency'] = Currency.compute( values['amount_2nd_currency'] = Currency.compute(
from_currency, from_currency,
amount, amount,
@ -78,22 +81,28 @@ class SecondCurrencyMixin:
) )
return values return values
@fields.depends('booktransf', '_parent_booktransf.currency', \ @fields.depends(
'currency', 'amount', 'date', 'amount_2nd_currency', 'rate_2nd_currency') 'booktransf', '_parent_booktransf.currency',
'currency', 'amount', 'date', 'amount_2nd_currency',
'rate_2nd_currency')
def on_change_booktransf(self): def on_change_booktransf(self):
""" update amount_2nd_currency """ update amount_2nd_currency
""" """
self.on_change_rate_2nd_currency() self.on_change_rate_2nd_currency()
@fields.depends('booktransf', '_parent_booktransf.currency', \ @fields.depends(
'currency', 'amount', 'date', 'amount_2nd_currency', 'rate_2nd_currency') 'booktransf', '_parent_booktransf.currency',
'currency', 'amount', 'date', 'amount_2nd_currency',
'rate_2nd_currency')
def on_change_amount(self): def on_change_amount(self):
""" update amount_2nd_currency """ update amount_2nd_currency
""" """
self.on_change_rate_2nd_currency() self.on_change_rate_2nd_currency()
@fields.depends('booktransf', '_parent_booktransf.currency', \ @fields.depends(
'currency', 'amount', 'date', 'amount_2nd_currency', 'rate_2nd_currency') 'booktransf', '_parent_booktransf.currency',
'currency', 'amount', 'date', 'amount_2nd_currency',
'rate_2nd_currency')
def on_change_rate_2nd_currency(self): def on_change_rate_2nd_currency(self):
""" update amount_2nd_currency + rate_2nd_currency """ update amount_2nd_currency + rate_2nd_currency
""" """
@ -109,16 +118,16 @@ class SecondCurrencyMixin:
if self.rate_2nd_currency is None: if self.rate_2nd_currency is None:
# no rate set, use current rate of target-currency # no rate set, use current rate of target-currency
with Transaction().set_context({ with Transaction().set_context({
'date': self.date or IrDate.today(), 'date': self.date or IrDate.today()}):
}):
self.amount_2nd_currency = Currency.compute( self.amount_2nd_currency = Currency.compute(
self.currency, self.currency,
self.amount, self.amount,
self.booktransf.currency self.booktransf.currency
) )
if self.amount != Decimal('0.0'): if self.amount != Decimal('0.0'):
self.rate_2nd_currency = self.amount_2nd_currency / self.amount self.rate_2nd_currency = \
else : self.amount_2nd_currency / self.amount
else:
self.amount_2nd_currency = self.booktransf.currency.round( self.amount_2nd_currency = self.booktransf.currency.round(
self.amount * self.rate_2nd_currency self.amount * self.rate_2nd_currency
) )
@ -201,5 +210,3 @@ class MemCacheIndexMx:
cls.create_date.select = True cls.create_date.select = True
# end MemCacheIndexMx # end MemCacheIndexMx

View file

@ -8,19 +8,21 @@ from trytond.transaction import Transaction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.cache import MemoryCache from trytond.cache import MemoryCache
from trytond.config import config from trytond.config import config
from datetime import timedelta, datetime from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from sql import With, Literal from sql import With
from sql.functions import Function from sql.functions import Function
from sql.conditionals import Coalesce from sql.conditionals import Coalesce
import copy import copy
if config.get('cashbook', 'memcache', default='yes').lower() in ['yes', '1', 'true']: if config.get('cashbook', 'memcache', default='yes').lower() \
in ['yes', '1', 'true']:
ENABLE_CACHE = True ENABLE_CACHE = True
else: else:
ENABLE_CACHE = False ENABLE_CACHE = False
if config.get('cashbook', 'sync', default='yes').lower() in ['yes', '1', 'true']: if config.get('cashbook', 'sync', default='yes').lower() \
in ['yes', '1', 'true']:
ENABLE_CACHESYNC = True ENABLE_CACHESYNC = True
else: else:
ENABLE_CACHESYNC = False ENABLE_CACHESYNC = False
@ -36,6 +38,7 @@ class ArrayAgg(Function):
# end ArrayAgg # end ArrayAgg
class ArrayAppend(Function): class ArrayAppend(Function):
""" sql: array_append """ sql: array_append
""" """
@ -87,14 +90,14 @@ class MemCache(Model):
_cashbook_value_cache = MemoryCache( _cashbook_value_cache = MemoryCache(
'cashbook.book.valuecache', 'cashbook.book.valuecache',
context = False, context=False,
duration = timedelta(seconds=60*60*4)) duration=timedelta(seconds=60*60*4))
@classmethod @classmethod
def read_value(cls, cache_key): def read_value(cls, cache_key):
""" read values from cache """ read values from cache
""" """
if ENABLE_CACHE == False: if ENABLE_CACHE is False:
return None return None
return copy.deepcopy(cls._cashbook_value_cache.get(cache_key)) return copy.deepcopy(cls._cashbook_value_cache.get(cache_key))
@ -102,23 +105,24 @@ class MemCache(Model):
def store_result(cls, records, cache_keys, values, skip_records=[]): def store_result(cls, records, cache_keys, values, skip_records=[]):
""" store result to cache """ store result to cache
""" """
if ENABLE_CACHE == False: if ENABLE_CACHE is False:
return return
for record in records: for record in records:
if record not in skip_records: if record not in skip_records:
continue continue
data = {x:values[x][record.id] data = {
for x in values.keys() x: values[x][record.id]
if record.id in values[x].keys()} for x in values.keys() if record.id in values[x].keys()}
cls._cashbook_value_cache.set(cache_keys[record.id], copy.deepcopy(data)) cls._cashbook_value_cache.set(
if ENABLE_CACHESYNC == True: cache_keys[record.id], copy.deepcopy(data))
if ENABLE_CACHESYNC is True:
cls._cashbook_value_cache.sync(Transaction()) cls._cashbook_value_cache.sync(Transaction())
@classmethod @classmethod
def store_value(cls, cache_key, values): def store_value(cls, cache_key, values):
""" store values to cache """ store values to cache
""" """
if ENABLE_CACHE == False: if ENABLE_CACHE is False:
return return
cls._cashbook_value_cache.set(cache_key, copy.deepcopy(values)) cls._cashbook_value_cache.set(cache_key, copy.deepcopy(values))
@ -126,7 +130,7 @@ class MemCache(Model):
def read_from_cache(cls, records, cache_keys, names, result): def read_from_cache(cls, records, cache_keys, names, result):
""" get stored values from memcache """ get stored values from memcache
""" """
if ENABLE_CACHE == False: if ENABLE_CACHE is False:
return (records, result) return (records, result)
todo_records = [] todo_records = []
@ -141,7 +145,7 @@ class MemCache(Model):
if result[name][record.id] is None: if result[name][record.id] is None:
result[name][record.id] = Decimal('0.0') result[name][record.id] = Decimal('0.0')
result[name][record.id] += values[name] result[name][record.id] += values[name]
else : else:
todo_records.append(record) todo_records.append(record)
return (todo_records, result) return (todo_records, result)
@ -152,7 +156,7 @@ class MemCache(Model):
pool = Pool() pool = Pool()
cursor = Transaction().connection.cursor() cursor = Transaction().connection.cursor()
if ENABLE_CACHE == False: if ENABLE_CACHE is False:
return '-' return '-'
fname = [name, str(record.id)] fname = [name, str(record.id)]
@ -173,15 +177,17 @@ class MemCache(Model):
tab_model = Model.__table__() tab_model = Model.__table__()
tab_query = Model.search(line['query'], query=True) tab_query = Model.search(line['query'], query=True)
qu1 = tab_model.join(tab_query, qu1 = tab_model.join(
condition=tab_query.id==tab_model.id, tab_query,
condition=tab_query.id == tab_model.id,
).select( ).select(
tab_model.id, tab_model.id,
tab_model.write_date, tab_model.write_date,
tab_model.create_date, tab_model.create_date,
limit = 1, limit=1,
order_by = [ order_by=[
Coalesce(tab_model.write_date, tab_model.create_date).desc, Coalesce(
tab_model.write_date, tab_model.create_date).desc,
tab_model.id.desc, tab_model.id.desc,
], ],
) )
@ -193,7 +199,7 @@ class MemCache(Model):
records[0][1], records[0][1],
records[0][2], records[0][2],
)) ))
else : else:
fname.append('0') fname.append('0')
if 'cachekey' in line.keys(): if 'cachekey' in line.keys():
@ -216,9 +222,10 @@ class MemCache(Model):
def record_update(cls, cache_key, record): def record_update(cls, cache_key, record):
""" update cache-value """ update cache-value
""" """
if ENABLE_CACHE == False: if ENABLE_CACHE is False:
return return
cls.store_value(cache_key, cls.store_value(
cache_key,
cls.genkey(record.id, record.write_date, record.create_date) cls.genkey(record.id, record.write_date, record.create_date)
if record is not None else None) if record is not None else None)
@ -235,18 +242,17 @@ def sub_ids_hierarchical(model_name):
lines = With('parent', 'id', recursive=True) lines = With('parent', 'id', recursive=True)
lines.query = tab_mod.select( lines.query = tab_mod.select(
tab_mod.id, tab_mod.id, tab_mod.id, tab_mod.id,
) | tab_mod2.join(lines, ) | tab_mod2.join(
condition=lines.id==tab_mod2.parent, lines,
).select( condition=lines.id == tab_mod2.parent,
lines.parent, tab_mod2.id, ).select(lines.parent, tab_mod2.id)
)
lines.query.all_ = True lines.query.all_ = True
query = lines.select( query = lines.select(
lines.parent, lines.parent,
ArrayAgg(lines.id).as_('subids'), ArrayAgg(lines.id).as_('subids'),
group_by=[lines.parent], group_by=[lines.parent],
with_ = [lines]) with_=[lines])
return query return query
@ -262,24 +268,28 @@ def order_name_hierarchical(model_name, tables):
lines = With('id', 'name', 'name_path', recursive=True) lines = With('id', 'name', 'name_path', recursive=True)
lines.query = tab_mod.select( lines.query = tab_mod.select(
tab_mod.id, tab_mod.name, Array(tab_mod.name), tab_mod.id, tab_mod.name, Array(tab_mod.name),
where = tab_mod.parent==None, where=tab_mod.parent == None,
) )
lines.query |= tab_mod2.join(lines, lines.query |= tab_mod2.join(
condition=lines.id==tab_mod2.parent, lines,
condition=lines.id == tab_mod2.parent,
).select( ).select(
tab_mod2.id, tab_mod2.name, ArrayAppend(lines.name_path, tab_mod2.name), tab_mod2.id,
tab_mod2.name,
ArrayAppend(lines.name_path, tab_mod2.name),
) )
lines.query.all_ = True lines.query.all_ = True
query = lines.select( query = lines.select(
ArrayToString(lines.name_path, '/').as_('rec_name'), ArrayToString(lines.name_path, '/').as_('rec_name'),
where = table.id==lines.id, where=table.id == lines.id,
with_ = [lines]) with_=[lines])
return [query] return [query]
class UserValueMixin(ValueMixin): class UserValueMixin(ValueMixin):
iduser = fields.Many2One(model_name='res.user', string="User", iduser = fields.Many2One(
model_name='res.user', string="User",
select=True, ondelete='CASCADE', required=True) select=True, ondelete='CASCADE', required=True)
@classmethod @classmethod
@ -313,6 +323,7 @@ class UserMultiValueMixin(MultiValueMixin):
Value = self.multivalue_model(name) Value = self.multivalue_model(name)
if issubclass(Value, UserValueMixin): if issubclass(Value, UserValueMixin):
pattern = self.updt_multivalue_pattern(pattern) pattern = self.updt_multivalue_pattern(pattern)
return super(UserMultiValueMixin, self).set_multivalue(name, value, **pattern) return super(
UserMultiValueMixin, self).set_multivalue(name, value, **pattern)
# end UserMultiValueMixin # end UserMultiValueMixin

View file

@ -4,15 +4,12 @@
# full copyright notices and license terms. # full copyright notices and license terms.
from trytond.model import Workflow, ModelView, ModelSQL, fields from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.transaction import Transaction from trytond.pyson import Eval, If, Or
from trytond.pyson import Eval, If, Or, Bool
from trytond.pool import Pool from trytond.pool import Pool
from trytond.report import Report from trytond.report import Report
from trytond.exceptions import UserError from trytond.exceptions import UserError
from trytond.i18n import gettext from trytond.i18n import gettext
from decimal import Decimal from decimal import Decimal
from sql.operators import Equal, Between
from sql import Literal, Null
from datetime import timedelta from datetime import timedelta
from .book import sel_state_book from .book import sel_state_book
@ -29,21 +26,25 @@ STATES = {
Eval('state_cashbook', '') != 'open', Eval('state_cashbook', '') != 'open',
), ),
} }
DEPENDS=['state', 'state_cashbook'] DEPENDS = ['state', 'state_cashbook']
class Reconciliation(Workflow, ModelSQL, ModelView): class Reconciliation(Workflow, ModelSQL, ModelView):
'Cashbook Reconciliation' 'Cashbook Reconciliation'
__name__ = 'cashbook.recon' __name__ = 'cashbook.recon'
cashbook = fields.Many2One(string='Cashbook', required=True, select=True, cashbook = fields.Many2One(
string='Cashbook', required=True, select=True,
model_name='cashbook.book', ondelete='CASCADE', readonly=True) model_name='cashbook.book', ondelete='CASCADE', readonly=True)
date = fields.Date(string='Date', required=True, select=True, date = fields.Date(
string='Date', required=True, select=True,
states=STATES, depends=DEPENDS) states=STATES, depends=DEPENDS)
feature = fields.Function(fields.Char(string='Feature', readonly=True, feature = fields.Function(fields.Char(
string='Feature', readonly=True,
states={'invisible': True}), 'on_change_with_feature') states={'invisible': True}), 'on_change_with_feature')
date_from = fields.Date(string='Start Date', date_from = fields.Date(
string='Start Date',
required=True, required=True,
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
@ -51,7 +52,8 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
()), ()),
], ],
states=STATES, depends=DEPENDS+['date_to']) states=STATES, depends=DEPENDS+['date_to'])
date_to = fields.Date(string='End Date', date_to = fields.Date(
string='End Date',
required=True, select=True, required=True, select=True,
domain=[ domain=[
If(Eval('date_to') & Eval('date_from'), If(Eval('date_to') & Eval('date_from'),
@ -59,14 +61,17 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
()), ()),
], ],
states=STATES, depends=DEPENDS+['date_from']) states=STATES, depends=DEPENDS+['date_from'])
start_amount = fields.Numeric(string='Start Amount', required=True, start_amount = fields.Numeric(
string='Start Amount', required=True,
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']) depends=['currency_digits'])
end_amount = fields.Numeric(string='End Amount', required=True, end_amount = fields.Numeric(
string='End Amount', required=True,
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']) depends=['currency_digits'])
lines = fields.One2Many(string='Lines', field='reconciliation', lines = fields.One2Many(
string='Lines', field='reconciliation',
model_name='cashbook.line', states=STATES, model_name='cashbook.line', states=STATES,
depends=DEPENDS+['date_from', 'date_to', 'cashbook'], depends=DEPENDS+['date_from', 'date_to', 'cashbook'],
add_remove=[ add_remove=[
@ -80,18 +85,23 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
('date', '<=', Eval('date_to')), ('date', '<=', Eval('date_to')),
]) ])
currency = fields.Function(fields.Many2One(model_name='currency.currency', currency = fields.Function(fields.Many2One(
model_name='currency.currency',
string="Currency"), 'on_change_with_currency') string="Currency"), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits'), currency_digits = fields.Function(fields.Integer(
string='Currency Digits'),
'on_change_with_currency_digits') 'on_change_with_currency_digits')
predecessor = fields.Function(fields.Many2One(string='Predecessor', readonly=True, predecessor = fields.Function(fields.Many2One(
string='Predecessor', readonly=True,
model_name='cashbook.recon'), model_name='cashbook.recon'),
'on_change_with_predecessor') 'on_change_with_predecessor')
state = fields.Selection(string='State', required=True, readonly=True, state = fields.Selection(
string='State', required=True, readonly=True,
select=True, selection=sel_reconstate) select=True, selection=sel_reconstate)
state_string = state.translated('state') state_string = state.translated('state')
state_cashbook = fields.Function(fields.Selection(string='State of Cashbook', state_cashbook = fields.Function(fields.Selection(
string='State of Cashbook',
readonly=True, states={'invisible': True}, selection=sel_state_book), readonly=True, states={'invisible': True}, selection=sel_state_book),
'on_change_with_state_cashbook') 'on_change_with_state_cashbook')
@ -140,9 +150,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
[ # enclose other record [ # enclose other record
('date_from', '>=', self.date_from), ('date_from', '>=', self.date_from),
('date_to', '<=', self.date_to), ('date_to', '<=', self.date_to),
], ]]]
],
]
if Recon.search_count(query) > 0: if Recon.search_count(query) > 0:
raise UserError(gettext('cashbook.msg_recon_err_overlap')) raise UserError(gettext('cashbook.msg_recon_err_overlap'))
@ -162,11 +170,10 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
]) > 0: ]) > 0:
raise UserError(gettext( raise UserError(gettext(
'cashbook.mds_recon_deny_line_not_check', 'cashbook.mds_recon_deny_line_not_check',
bookname = reconciliation.cashbook.rec_name, bookname=reconciliation.cashbook.rec_name,
reconame = reconciliation.rec_name, reconame=reconciliation.rec_name,
datefrom = Report.format_date(reconciliation.date_from), datefrom=Report.format_date(reconciliation.date_from),
dateto = Report.format_date(reconciliation.date_to), dateto=Report.format_date(reconciliation.date_to)))
))
@classmethod @classmethod
def get_values_wfedit(cls, reconciliation): def get_values_wfedit(cls, reconciliation):
@ -191,7 +198,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
values = {} values = {}
if reconciliation.predecessor: if reconciliation.predecessor:
values['start_amount'] = reconciliation.predecessor.end_amount values['start_amount'] = reconciliation.predecessor.end_amount
else : else:
values['start_amount'] = Decimal('0.0') values['start_amount'] = Decimal('0.0')
values['end_amount'] = values['start_amount'] values['end_amount'] = values['start_amount']
@ -209,7 +216,8 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
# add amounts of new lines # add amounts of new lines
values['end_amount'] += sum([x.credit - x.debit for x in lines]) values['end_amount'] += sum([x.credit - x.debit for x in lines])
# add amounts of already linked lines # add amounts of already linked lines
values['end_amount'] += sum([x.credit - x.debit for x in reconciliation.lines]) values['end_amount'] += sum([
x.credit - x.debit for x in reconciliation.lines])
return values return values
@classmethod @classmethod
@ -248,18 +256,18 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
if reconciliation.predecessor.state != 'done': if reconciliation.predecessor.state != 'done':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_recon_predecessor_not_done', 'cashbook.msg_recon_predecessor_not_done',
recname_p = reconciliation.predecessor.rec_name, recname_p=reconciliation.predecessor.rec_name,
recname_c = reconciliation.rec_name, recname_c=reconciliation.rec_name))
))
# check if current.date_from == predecessor.date_to # check if current.date_from == predecessor.date_to
if reconciliation.predecessor.date_to != reconciliation.date_from: if reconciliation.predecessor.date_to != \
reconciliation.date_from:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_recon_date_from_to_mismatch', 'cashbook.msg_recon_date_from_to_mismatch',
datefrom = Report.format_date(reconciliation.date_from), datefrom=Report.format_date(reconciliation.date_from),
dateto = Report.format_date(reconciliation.predecessor.date_to), dateto=Report.format_date(
recname = reconciliation.rec_name, reconciliation.predecessor.date_to),
)) recname=reconciliation.rec_name))
to_write.extend([ to_write.extend([
[reconciliation], [reconciliation],
@ -281,13 +289,11 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
to_wfrecon_line = [] to_wfrecon_line = []
for reconciliation in reconciliations: for reconciliation in reconciliations:
to_wfrecon_line.extend([ to_wfrecon_line.extend([
x for x in reconciliation.lines \ x for x in reconciliation.lines
if x.state == 'check' if x.state == 'check'])
])
to_wfdone_line.extend([ to_wfdone_line.extend([
x for x in reconciliation.lines \ x for x in reconciliation.lines
if x.state == 'recon' if x.state == 'recon'])
])
# deny if there are lines not linked to reconciliation # deny if there are lines not linked to reconciliation
if Line.search_count([ if Line.search_count([
@ -299,14 +305,12 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
('date', '<', reconciliation.date_to), ('date', '<', reconciliation.date_to),
], ],
# lines at from-date must relate to a reconciliation # lines at from-date must relate to a reconciliation
('date', '=', reconciliation.date_from), ('date', '=', reconciliation.date_from)],
],
]) > 0: ]) > 0:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_recon_lines_no_linked', 'cashbook.msg_recon_lines_no_linked',
date_from = Report.format_date(reconciliation.date_from), date_from=Report.format_date(reconciliation.date_from),
date_to = Report.format_date(reconciliation.date_to), date_to=Report.format_date(reconciliation.date_to),))
))
if len(to_wfrecon_line) > 0: if len(to_wfrecon_line) > 0:
Line.wfrecon(to_wfrecon_line) Line.wfrecon(to_wfrecon_line)
@ -318,9 +322,12 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
""" short + name """ short + name
""" """
return '%(from)s - %(to)s | %(start_amount)s %(symbol)s - %(end_amount)s %(symbol)s [%(num)s]' % { return '%(from)s - %(to)s | %(start_amount)s %(symbol)s - %(end_amount)s %(symbol)s [%(num)s]' % {
'from': Report.format_date(self.date_from, None) if self.date_from is not None else '-', 'from': Report.format_date(self.date_from, None)
'to': Report.format_date(self.date_to, None) if self.date_to is not None else '-', if self.date_from is not None else '-',
'start_amount': Report.format_number(self.start_amount or 0.0, None), 'to': Report.format_date(self.date_to, None)
if self.date_to is not None else '-',
'start_amount': Report.format_number(
self.start_amount or 0.0, None),
'end_amount': Report.format_number(self.end_amount or 0.0, None), 'end_amount': Report.format_number(self.end_amount or 0.0, None),
'symbol': getattr(self.currency, 'symbol', '-'), 'symbol': getattr(self.currency, 'symbol', '-'),
'num': len(self.lines), 'num': len(self.lines),
@ -459,9 +466,8 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
if reconciliation.cashbook.state != 'open': if reconciliation.cashbook.state != 'open':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_book_deny_write', 'cashbook.msg_book_deny_write',
bookname = reconciliation.cashbook.rec_name, bookname=reconciliation.cashbook.rec_name,
state_txt = reconciliation.cashbook.state_string, state_txt=reconciliation.cashbook.state_string))
))
super(Reconciliation, cls).write(*args) super(Reconciliation, cls).write(*args)
@classmethod @classmethod
@ -472,16 +478,14 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
if reconciliation.cashbook.state == 'closed': if reconciliation.cashbook.state == 'closed':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_line_deny_delete1', 'cashbook.msg_line_deny_delete1',
linetxt = reconciliation.rec_name, linetxt=reconciliation.rec_name,
bookname = reconciliation.cashbook.rec_name, bookname=reconciliation.cashbook.rec_name,
bookstate = reconciliation.cashbook.state_string, bookstate=reconciliation.cashbook.state_string))
))
if reconciliation.state != 'edit': if reconciliation.state != 'edit':
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_recon_deny_delete2', 'cashbook.msg_recon_deny_delete2',
recontxt = reconciliation.rec_name, recontxt=reconciliation.rec_name,
reconstate = reconciliation.state_string, reconstate=reconciliation.state_string))
))
super(Reconciliation, cls).delete(reconciliations) super(Reconciliation, cls).delete(reconciliations)

View file

@ -4,13 +4,12 @@
# full copyright notices and license terms. # full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, Workflow, fields, Check from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool from trytond.pool import Pool
from trytond.pyson import Eval, If from trytond.pyson import Eval, If
from trytond.report import Report from trytond.report import Report
from trytond.i18n import gettext from trytond.i18n import gettext
from trytond.transaction import Transaction from .line import sel_bookingtype, STATES, DEPENDS
from .line import sel_linetype, sel_bookingtype, STATES, DEPENDS
from .book import sel_state_book from .book import sel_state_book
from .mixin import SecondCurrencyMixin, MemCacheIndexMx from .mixin import SecondCurrencyMixin, MemCacheIndexMx
@ -30,15 +29,18 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
'Split booking line' 'Split booking line'
__name__ = 'cashbook.split' __name__ = 'cashbook.split'
line = fields.Many2One(string='Line', required=True, line = fields.Many2One(
string='Line', required=True,
select=True, ondelete='CASCADE', model_name='cashbook.line', select=True, ondelete='CASCADE', model_name='cashbook.line',
readonly=True) readonly=True)
description = fields.Text(string='Description', description = fields.Text(
states=STATES, depends=DEPENDS) string='Description', states=STATES, depends=DEPENDS)
splittype = fields.Selection(string='Type', required=True, splittype = fields.Selection(
string='Type', required=True,
help='Type of split booking line', selection=sel_linetype, help='Type of split booking line', selection=sel_linetype,
states=STATES, depends=DEPENDS, select=True) states=STATES, depends=DEPENDS, select=True)
category = fields.Many2One(string='Category', select=True, category = fields.Many2One(
string='Category', select=True,
model_name='cashbook.category', ondelete='RESTRICT', model_name='cashbook.category', ondelete='RESTRICT',
states={ states={
'readonly': STATES['readonly'], 'readonly': STATES['readonly'],
@ -51,9 +53,11 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
('cattype', '=', 'in'), ('cattype', '=', 'in'),
('cattype', '=', 'out'), ('cattype', '=', 'out'),
)]) )])
category_view = fields.Function(fields.Char(string='Category', readonly=True), category_view = fields.Function(fields.Char(
string='Category', readonly=True),
'on_change_with_category_view') 'on_change_with_category_view')
booktransf = fields.Many2One(string='Source/Dest', booktransf = fields.Many2One(
string='Source/Dest',
ondelete='RESTRICT', model_name='cashbook.book', ondelete='RESTRICT', model_name='cashbook.book',
domain=[ domain=[
('owner.id', '=', Eval('owner_cashbook', -1)), ('owner.id', '=', Eval('owner_cashbook', -1)),
@ -66,32 +70,43 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
'required': Eval('splittype', '') == 'tr', 'required': Eval('splittype', '') == 'tr',
}, depends=DEPENDS+['bookingtype', 'owner_cashbook', 'cashbook']) }, depends=DEPENDS+['bookingtype', 'owner_cashbook', 'cashbook'])
amount = fields.Numeric(string='Amount', digits=(16, Eval('currency_digits', 2)), amount = fields.Numeric(
string='Amount', digits=(16, Eval('currency_digits', 2)),
required=True, states=STATES, depends=DEPENDS+['currency_digits']) required=True, states=STATES, depends=DEPENDS+['currency_digits'])
date = fields.Function(fields.Date(string='Date', readonly=True), date = fields.Function(fields.Date(
'on_change_with_date') string='Date', readonly=True), 'on_change_with_date')
target = fields.Function(fields.Reference(string='Target', readonly=True, target = fields.Function(fields.Reference(
string='Target', readonly=True,
selection=sel_target), 'on_change_with_target') selection=sel_target), 'on_change_with_target')
currency = fields.Function(fields.Many2One(model_name='currency.currency', currency = fields.Function(fields.Many2One(
model_name='currency.currency',
string="Currency", readonly=True), 'on_change_with_currency') string="Currency", readonly=True), 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits', currency_digits = fields.Function(fields.Integer(
string='Currency Digits',
readonly=True), 'on_change_with_currency_digits') readonly=True), 'on_change_with_currency_digits')
bookingtype = fields.Function(fields.Selection(string='Type', readonly=True, bookingtype = fields.Function(fields.Selection(
string='Type', readonly=True,
selection=sel_bookingtype), 'on_change_with_bookingtype') selection=sel_bookingtype), 'on_change_with_bookingtype')
state = fields.Function(fields.Selection(string='State', readonly=True, state = fields.Function(fields.Selection(
string='State', readonly=True,
selection=sel_linetype), 'on_change_with_state') selection=sel_linetype), 'on_change_with_state')
cashbook = fields.Function(fields.Many2One(string='Cashbook', cashbook = fields.Function(fields.Many2One(
string='Cashbook',
readonly=True, states={'invisible': True}, model_name='cashbook.book'), readonly=True, states={'invisible': True}, model_name='cashbook.book'),
'on_change_with_cashbook') 'on_change_with_cashbook')
feature = fields.Function(fields.Char(string='Feature', readonly=True, feature = fields.Function(fields.Char(
string='Feature', readonly=True,
states={'invisible': True}), 'on_change_with_feature') states={'invisible': True}), 'on_change_with_feature')
booktransf_feature = fields.Function(fields.Char(string='Feature', readonly=True, booktransf_feature = fields.Function(fields.Char(
string='Feature', readonly=True,
states={'invisible': True}), 'on_change_with_booktransf_feature') states={'invisible': True}), 'on_change_with_booktransf_feature')
state_cashbook = fields.Function(fields.Selection(string='State of Cashbook', state_cashbook = fields.Function(fields.Selection(
string='State of Cashbook',
readonly=True, states={'invisible': True}, selection=sel_state_book), readonly=True, states={'invisible': True}, selection=sel_state_book),
'on_change_with_state_cashbook') 'on_change_with_state_cashbook')
owner_cashbook = fields.Function(fields.Many2One(string='Owner', readonly=True, owner_cashbook = fields.Function(fields.Many2One(
string='Owner', readonly=True,
states={'invisible': True}, model_name='res.user'), states={'invisible': True}, model_name='res.user'),
'on_change_with_owner_cashbook') 'on_change_with_owner_cashbook')
@ -108,8 +123,10 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
'desc': (self.description or '-')[:40], 'desc': (self.description or '-')[:40],
'amount': Report.format_number(self.amount, None), 'amount': Report.format_number(self.amount, None),
'symbol': getattr(self.currency, 'symbol', '-'), 'symbol': getattr(self.currency, 'symbol', '-'),
'target': self.category_view if self.splittype == 'cat' else self.booktransf.rec_name, 'target': self.category_view
'type': gettext('cashbook.msg_line_bookingtype_%s' % self.line.bookingtype), if self.splittype == 'cat' else self.booktransf.rec_name,
'type': gettext(
'cashbook.msg_line_bookingtype_%s' % self.line.bookingtype),
} }
@fields.depends('splittype', 'category', 'booktransf') @fields.depends('splittype', 'category', 'booktransf')
@ -151,9 +168,9 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
if self.category: if self.category:
cfg1 = Configuration.get_singleton() cfg1 = Configuration.get_singleton()
if getattr(cfg1, 'catnamelong', True) == True: if getattr(cfg1, 'catnamelong', True) is True:
return self.category.rec_name return self.category.rec_name
else : else:
return self.category.name return self.category.name
@fields.depends('line', '_parent_line.state') @fields.depends('line', '_parent_line.state')
@ -246,7 +263,7 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
to_update_line = [] to_update_line = []
for record in records: for record in records:
if not record.line in to_update_line: if record.line not in to_update_line:
to_update_line.append(record.line) to_update_line.append(record.line)
to_write = Line2.update_values_by_splitlines(to_update_line) to_write = Line2.update_values_by_splitlines(to_update_line)
@ -268,7 +285,7 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
if 'amount' in values.keys(): if 'amount' in values.keys():
for record in records: for record in records:
if not record.line in to_update_line: if record.line not in to_update_line:
to_update_line.append(record.line) to_update_line.append(record.line)
super(SplitLine, cls).write(*args) super(SplitLine, cls).write(*args)

View file

@ -811,6 +811,49 @@ class LineTestCase(ModuleTestCase):
'amount': Decimal('0.0'), 'amount': Decimal('0.0'),
}]) }])
@with_transaction()
def test_line_check_deny_delete_of_party(self):
""" create cashbook + line, delete party should fail
"""
pool = Pool()
Book = pool.get('cashbook.book')
Line = pool.get('cashbook.line')
Party = pool.get('party.party')
types = self.prep_type()
category = self.prep_category(cattype='in')
company = self.prep_company()
party = self.prep_party()
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),
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Text 1',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('1.0'),
'party': party.id,
}])],
}])
self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.state, 'open')
self.assertEqual(len(book.lines), 1)
self.assertEqual(book.lines[0].party.rec_name, 'Party')
self.assertEqual(book.lines[0].party.id, party.id)
self.assertEqual(Party.search_count([('name', '=', 'Party')]), 1)
self.assertRaisesRegex(
UserError,
'The records could not be deleted because they are used by field "Party" of "Cashbook Line".',
Party.delete,
[party])
@with_transaction() @with_transaction()
def test_line_create_check_deny_write(self): def test_line_create_check_deny_write(self):
""" create cashbook + line, 'close' book, write to line """ create cashbook + line, 'close' book, write to line

View file

@ -14,9 +14,11 @@ class Type(ModelSQL, ModelView):
name = fields.Char(string='Name', required=True, translate=True) name = fields.Char(string='Name', required=True, translate=True)
short = fields.Char(string='Abbreviation', required=True, size=3) short = fields.Char(string='Abbreviation', required=True, size=3)
company = fields.Many2One(string='Company', model_name='company.company', company = fields.Many2One(
string='Company', model_name='company.company',
required=True, ondelete="RESTRICT") required=True, ondelete="RESTRICT")
feature = fields.Selection(string='Feature', required=True, feature = fields.Selection(
string='Feature', required=True,
selection='get_sel_feature', select=True, selection='get_sel_feature', select=True,
help='Select feature set of the Cashbook.') help='Select feature set of the Cashbook.')
@ -53,7 +55,8 @@ class Type(ModelSQL, ModelView):
def search_rec_name(cls, name, clause): def search_rec_name(cls, name, clause):
""" search in name + short """ search in name + short
""" """
return ['OR', return [
'OR',
('name',) + tuple(clause[1:]), ('name',) + tuple(clause[1:]),
('short',) + tuple(clause[1:]), ('short',) + tuple(clause[1:]),
] ]

View file

@ -7,7 +7,7 @@ from trytond.model import ModelView, fields
from trytond.wizard import Wizard, StateView, StateTransition, Button from trytond.wizard import Wizard, StateView, StateTransition, Button
from trytond.pool import Pool from trytond.pool import Pool
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.pyson import Eval, Bool, If, And from trytond.pyson import Eval, Bool, If
from decimal import Decimal from decimal import Decimal
from .line import sel_bookingtype from .line import sel_bookingtype
@ -18,29 +18,36 @@ class EnterBookingStart(ModelView):
'Enter Booking' 'Enter Booking'
__name__ = 'cashbook.enterbooking.start' __name__ = 'cashbook.enterbooking.start'
cashbook = fields.Many2One(string='Cashbook', model_name='cashbook.book', cashbook = fields.Many2One(
string='Cashbook', model_name='cashbook.book',
domain=[('id', 'in', Eval('cashbooks', [])), ('btype', '!=', None)], domain=[('id', 'in', Eval('cashbooks', [])), ('btype', '!=', None)],
depends=['cashbooks'], required=True) depends=['cashbooks'], required=True)
cashbooks = fields.One2Many(string='Cashbooks', field=None, cashbooks = fields.One2Many(
string='Cashbooks', field=None,
model_name='cashbook.book', readonly=True, model_name='cashbook.book', readonly=True,
states={'invisible': True}) states={'invisible': True})
owner_cashbook = fields.Function(fields.Many2One(string='Owner', readonly=True, owner_cashbook = fields.Function(fields.Many2One(
string='Owner', readonly=True,
states={'invisible': True}, model_name='res.user'), states={'invisible': True}, model_name='res.user'),
'on_change_with_owner_cashbook') 'on_change_with_owner_cashbook')
currency = fields.Function(fields.Many2One(string='Currency', currency = fields.Function(fields.Many2One(
string='Currency',
model_name='currency.currency', states={'invisible': True}), model_name='currency.currency', states={'invisible': True}),
'on_change_with_currency') 'on_change_with_currency')
currency_digits = fields.Function(fields.Integer(string='Currency Digits', currency_digits = fields.Function(fields.Integer(
string='Currency Digits',
readonly=True, states={'invisible': True}), readonly=True, states={'invisible': True}),
'on_change_with_currency_digits') 'on_change_with_currency_digits')
bookingtype = fields.Selection(string='Type', required=True, bookingtype = fields.Selection(
selection=sel_booktypewiz) string='Type', required=True, selection=sel_booktypewiz)
amount = fields.Numeric(string='Amount', amount = fields.Numeric(
string='Amount',
depends=['currency_digits', 'bookingtype'], depends=['currency_digits', 'bookingtype'],
digits=(16, Eval('currency_digits', 2)), required=True, digits=(16, Eval('currency_digits', 2)), required=True,
domain=[('amount', '>=', Decimal('0.0'))]) domain=[('amount', '>=', Decimal('0.0'))])
description = fields.Text(string='Description') description = fields.Text(string='Description')
category = fields.Many2One(string='Category', category = fields.Many2One(
string='Category',
model_name='cashbook.category', depends=['bookingtype'], model_name='cashbook.category', depends=['bookingtype'],
states={ states={
'readonly': Bool(Eval('bookingtype')) == False, 'readonly': Bool(Eval('bookingtype')) == False,
@ -55,7 +62,8 @@ class EnterBookingStart(ModelView):
)]) )])
# party or cashbook as counterpart # party or cashbook as counterpart
booktransf = fields.Many2One(string='Source/Dest', booktransf = fields.Many2One(
string='Source/Dest',
model_name='cashbook.book', model_name='cashbook.book',
domain=[ domain=[
('owner.id', '=', Eval('owner_cashbook', -1)), ('owner.id', '=', Eval('owner_cashbook', -1)),
@ -65,7 +73,8 @@ class EnterBookingStart(ModelView):
'invisible': ~Eval('bookingtype', '').in_(['mvin', 'mvout']), 'invisible': ~Eval('bookingtype', '').in_(['mvin', 'mvout']),
'required': Eval('bookingtype', '').in_(['mvin', 'mvout']), 'required': Eval('bookingtype', '').in_(['mvin', 'mvout']),
}, depends=['bookingtype', 'owner_cashbook', 'cashbook']) }, depends=['bookingtype', 'owner_cashbook', 'cashbook'])
party = fields.Many2One(string='Party', model_name='party.party', party = fields.Many2One(
string='Party', model_name='party.party',
states={ states={
'invisible': ~Eval('bookingtype', '').in_(['in', 'out']), 'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
}, depends=['bookingtype']) }, depends=['bookingtype'])
@ -81,7 +90,7 @@ class EnterBookingStart(ModelView):
if self.bookingtype: if self.bookingtype:
if self.category: if self.category:
if not self.bookingtype in types.get(self.category.cattype, ''): if self.bookingtype not in types.get(self.category.cattype, ''):
self.category = None self.category = None
@fields.depends('cashbook', '_parent_cashbook.owner') @fields.depends('cashbook', '_parent_cashbook.owner')
@ -104,7 +113,7 @@ class EnterBookingStart(ModelView):
""" """
if self.cashbook: if self.cashbook:
return self.cashbook.currency.digits return self.cashbook.currency.digits
else : else:
return 2 return 2
# end EnterBookingStart # end EnterBookingStart
@ -115,7 +124,8 @@ class EnterBookingWizard(Wizard):
__name__ = 'cashbook.enterbooking' __name__ = 'cashbook.enterbooking'
start_state = 'start' start_state = 'start'
start = StateView('cashbook.enterbooking.start', start = StateView(
'cashbook.enterbooking.start',
'cashbook.enterbooking_start_form', [ 'cashbook.enterbooking_start_form', [
Button('Cancel', 'end', 'tryton-cancel'), Button('Cancel', 'end', 'tryton-cancel'),
Button('Save', 'save_', 'tryton-save', default=True), Button('Save', 'save_', 'tryton-save', default=True),

View file

@ -5,7 +5,8 @@
from trytond.model import ModelView, fields from trytond.model import ModelView, fields
from trytond.pyson import PYSONEncoder from trytond.pyson import PYSONEncoder
from trytond.wizard import Wizard, StateView, StateTransition, StateAction, Button from trytond.wizard import Wizard, StateView, StateTransition, \
StateAction, Button
from trytond.i18n import gettext from trytond.i18n import gettext
from trytond.pool import Pool from trytond.pool import Pool
from trytond.exceptions import UserError from trytond.exceptions import UserError
@ -28,7 +29,7 @@ class OLineMixin:
'checked': getattr(cfg1, 'checked', None), 'checked': getattr(cfg1, 'checked', None),
'done': getattr(cfg1, 'done', None), 'done': getattr(cfg1, 'done', None),
}), }),
'name' : '%(name)s: %(cashbook)s' % { 'name': '%(name)s: %(cashbook)s' % {
'name': gettext('cashbook.msg_name_cashbook'), 'name': gettext('cashbook.msg_name_cashbook'),
'cashbook': getattr(book, 'rec_name', '-/-'), 'cashbook': getattr(book, 'rec_name', '-/-'),
}, },
@ -42,10 +43,13 @@ class OpenCashBookStart(ModelView):
'Open Cashbook' 'Open Cashbook'
__name__ = 'cashbook.open_lines.start' __name__ = 'cashbook.open_lines.start'
cashbook = fields.Many2One(string='Cashbook', model_name='cashbook.book', cashbook = fields.Many2One(
string='Cashbook', model_name='cashbook.book',
required=True, domain=[('btype', '!=', None)]) required=True, domain=[('btype', '!=', None)])
checked = fields.Boolean(string='Checked', help="Show cashbook lines in Checked-state.") checked = fields.Boolean(
done = fields.Boolean(string='Done', help="Show cashbook lines in Done-state") string='Checked', help="Show cashbook lines in Checked-state.")
done = fields.Boolean(
string='Done', help="Show cashbook lines in Done-state")
date_from = fields.Date(string='Start Date') date_from = fields.Date(string='Start Date')
date_to = fields.Date(string='End Date') date_to = fields.Date(string='End Date')
@ -66,7 +70,8 @@ class OpenCashBook(OLineMixin, Wizard):
start_state = 'check' start_state = 'check'
check = StateTransition() check = StateTransition()
askuser = StateView('cashbook.open_lines.start', askuser = StateView(
'cashbook.open_lines.start',
'cashbook.open_lines_view_form', [ 'cashbook.open_lines_view_form', [
Button('Cancel', 'end', 'tryton-cancel'), Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'open_', 'tryton-ok', default=True), Button('Open', 'open_', 'tryton-ok', default=True),
@ -79,8 +84,7 @@ class OpenCashBook(OLineMixin, Wizard):
Book = Pool().get('cashbook.book') Book = Pool().get('cashbook.book')
with Transaction().set_context({ with Transaction().set_context({
'_check_access': True, '_check_access': True}):
}):
books = Book.search([('btype', '!=', None)]) books = Book.search([('btype', '!=', None)])
if len(books) == 1: if len(books) == 1:
return 'open_' return 'open_'
@ -115,8 +119,7 @@ class OpenCashBook(OLineMixin, Wizard):
book = getattr(self.askuser, 'cashbook', None) book = getattr(self.askuser, 'cashbook', None)
if book is None: if book is None:
with Transaction().set_context({ with Transaction().set_context({
'_check_access': True, '_check_access': True}):
}):
books = Book.search([('btype', '!=', None)]) books = Book.search([('btype', '!=', None)])
if len(books) > 0: if len(books) > 0:
book = books[0] book = books[0]
@ -158,17 +161,15 @@ class OpenCashBookTree(OLineMixin, Wizard):
book = self.record book = self.record
if book is None: if book is None:
with Transaction().set_context({ with Transaction().set_context({
'_check_access': True, '_check_access': True}):
}):
books = Book.search([('btype', '!=', None)]) books = Book.search([('btype', '!=', None)])
if len(books) > 0: if len(books) > 0:
book = books[0] book = books[0]
else : else:
if book.btype is None: if book.btype is None:
raise UserError(gettext( raise UserError(gettext(
'cashbook.msg_book_no_type_noopen', 'cashbook.msg_book_no_type_noopen',
bookname = book.rec_name, bookname=book.rec_name))
))
action.update(self.add_action_data(book)) action.update(self.add_action_data(book))
return action, {} return action, {}

View file

@ -4,7 +4,6 @@
# full copyright notices and license terms. # full copyright notices and license terms.
from trytond.model import ModelView, fields from trytond.model import ModelView, fields
from trytond.pyson import PYSONEncoder
from trytond.wizard import Wizard, StateView, StateReport, Button from trytond.wizard import Wizard, StateView, StateReport, Button
from trytond.pool import Pool from trytond.pool import Pool
from trytond.pyson import Eval, Bool from trytond.pyson import Eval, Bool
@ -15,17 +14,21 @@ class RunCbReportStart(ModelView):
'Cashbook Report' 'Cashbook Report'
__name__ = 'cashbook.runrepbook.start' __name__ = 'cashbook.runrepbook.start'
cashbook = fields.Many2One(string='Cashbook', required=True, cashbook = fields.Many2One(
string='Cashbook', required=True,
model_name='cashbook.book', depends=['cashbooks'], model_name='cashbook.book', depends=['cashbooks'],
domain=[('id', 'in', Eval('cashbooks', []))]) domain=[('id', 'in', Eval('cashbooks', []))])
cashbooks = fields.One2Many(string='Cashbooks', model_name='cashbook.book', cashbooks = fields.One2Many(
string='Cashbooks', model_name='cashbook.book',
field=None, readonly=True, states={'invisible': True}) field=None, readonly=True, states={'invisible': True})
reconciliation = fields.Many2One(string='Reconciliation', required=True, reconciliation = fields.Many2One(
string='Reconciliation', required=True,
model_name='cashbook.recon', depends=['reconciliations'], model_name='cashbook.recon', depends=['reconciliations'],
states={ states={
'readonly': ~Bool(Eval('reconciliations')), 'readonly': ~Bool(Eval('reconciliations')),
}, domain=[('id', 'in', Eval('reconciliations', []))]) }, domain=[('id', 'in', Eval('reconciliations', []))])
reconciliations = fields.Function(fields.One2Many(string='Reconciliations', reconciliations = fields.Function(fields.One2Many(
string='Reconciliations',
model_name='cashbook.recon', field=None, readonly=True, model_name='cashbook.recon', field=None, readonly=True,
states={'invisible': True}), states={'invisible': True}),
'on_change_with_reconciliations') 'on_change_with_reconciliations')
@ -38,9 +41,9 @@ class RunCbReportStart(ModelView):
self.reconciliations = self.on_change_with_reconciliations() self.reconciliations = self.on_change_with_reconciliations()
if len(self.reconciliations or []) > 0: if len(self.reconciliations or []) > 0:
self.reconciliation = self.reconciliations[0] self.reconciliation = self.reconciliations[0]
else : else:
self.reconciliation = None self.reconciliation = None
else : else:
self.reconciliations = [] self.reconciliations = []
self.reconciliation = None self.reconciliation = None
@ -64,14 +67,14 @@ class RunCbReport(Wizard):
__name__ = 'cashbook.runrepbook' __name__ = 'cashbook.runrepbook'
start_state = 'selrecon' start_state = 'selrecon'
selrecon = StateView('cashbook.runrepbook.start', selrecon = StateView(
'cashbook.runrepbook.start',
'cashbook.runrepbook_view_form', [ 'cashbook.runrepbook_view_form', [
Button(string='Cancel', state='end', icon='tryton-cancel'), Button(string='Cancel', state='end', icon='tryton-cancel'),
Button(string='Report', state='report_', icon='tryton-ok', default=True, Button(
states={ string='Report', state='report_', icon='tryton-ok',
'readonly': ~Bool(Eval('reconciliation')), default=True,
}), states={'readonly': ~Bool(Eval('reconciliation'))})])
])
report_ = StateReport('cashbook.reprecon') report_ = StateReport('cashbook.reprecon')
def default_selrecon(self, fields): def default_selrecon(self, fields):
@ -87,12 +90,11 @@ class RunCbReport(Wizard):
result['cashbook'] = context.get('active_id', None) result['cashbook'] = context.get('active_id', None)
elif context.get('active_model', '') == 'cashbook.line': elif context.get('active_model', '') == 'cashbook.line':
result['cashbook'] = context.get('cashbook', None) result['cashbook'] = context.get('cashbook', None)
else : else:
raise ValueError('invalid model') raise ValueError('invalid model')
with Transaction().set_context({ with Transaction().set_context({
'_check_access': True, '_check_access': True}):
}):
books = Book.search([]) books = Book.search([])
result['cashbooks'] = [x.id for x in books] result['cashbooks'] = [x.id for x in books]
@ -118,7 +120,7 @@ class RunCbReport(Wizard):
'id': self.selrecon.reconciliation.id, 'id': self.selrecon.reconciliation.id,
'ids': [self.selrecon.reconciliation.id], 'ids': [self.selrecon.reconciliation.id],
} }
else : else:
r1 = {'model': '', 'id': None, 'ids': []} r1 = {'model': '', 'id': None, 'ids': []}
return action, r1 return action, r1