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 .model import MemCache
def register():
Pool.register(
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'
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
else :
else:
ENA_CURRKEY = False
STATES = {
'readonly': Eval('state', '') != 'open',
}
DEPENDS=['state']
DEPENDS = ['state']
# states in case of 'btype'!=None
STATES2 = {
@ -55,14 +57,17 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
'Cashbook'
__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")
name = fields.Char(string='Name', required=True,
states=STATES, depends=DEPENDS)
description = fields.Text(string='Description',
states=STATES, depends=DEPENDS)
btype = fields.Many2One(string='Type', select=True,
help='A cash book with type can contain postings. Without type is a view.',
name = fields.Char(
string='Name', required=True, states=STATES, depends=DEPENDS)
description = fields.Text(
string='Description', states=STATES, depends=DEPENDS)
btype = fields.Many2One(
string='Type', select=True,
help='A cash book with type can contain postings. ' +
'Without type is a view.',
model_name='cashbook.type', ondelete='RESTRICT',
states={
'readonly': Or(
@ -70,40 +75,50 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
Len(Eval('lines')) > 0,
),
}, 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')
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',
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.',
model_name='res.group', ondelete='SET NULL',
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.',
model_name='res.group', ondelete='SET NULL',
states=STATES, depends=DEPENDS)
lines = fields.One2Many(string='Lines', field='cashbook',
lines = fields.One2Many(
string='Lines', field='cashbook',
model_name='cashbook.line',
states=STATES, depends=DEPENDS)
reconciliations = fields.One2Many(string='Reconciliations',
reconciliations = fields.One2Many(
string='Reconciliations',
field='cashbook', model_name='cashbook.recon',
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.',
model_name='ir.sequence',
domain=[
('sequence_type', '=', Id('cashbook', 'sequence_type_cashbook_line')),
('sequence_type', '=',
Id('cashbook', 'sequence_type_cashbook_line')),
['OR',
('company', '=', None),
('company', '=', Eval('company', -1)),
],
('company', '=', Eval('company', -1))],
],
states=STATES3, depends=DEPENDS2+['company'])
number_atcheck = fields.Boolean(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.",
number_atcheck = fields.Boolean(
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)
start_date = fields.Date(string='Initial Date',
start_date = fields.Date(
string='Initial Date',
states={
'readonly': Or(
STATES2['readonly'],
@ -112,25 +127,29 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
'invisible': STATES2['invisible'],
'required': ~STATES2['invisible'],
}, depends=DEPENDS2+['lines'])
balance = fields.Function(fields.Numeric(string='Balance',
balance = fields.Function(fields.Numeric(
string='Balance',
readonly=True, depends=['currency_digits'],
help='Balance of bookings to date',
digits=(16, Eval('currency_digits', 2))),
'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'],
help='Balance of all bookings',
digits=(16, Eval('currency_digits', 2))),
'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',
readonly=True, digits=(16, Eval('company_currency_digits', 2)),
states={
'invisible': ~Bool(Eval('company_currency')),
}, depends=['company_currency_digits', 'company_currency']),
'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},
model_name='currency.currency'),
'on_change_with_company_currency')
@ -138,7 +157,8 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
string='Currency Digits (Ref.)', readonly=True),
'on_change_with_currency_digits')
currency = fields.Many2One(string='Currency', select=True,
currency = fields.Many2One(
string='Currency', select=True,
model_name='currency.currency',
states={
'readonly': Or(
@ -146,15 +166,19 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
Len(Eval('lines', [])) > 0,
),
}, 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')
state = fields.Selection(string='State', required=True,
state = fields.Selection(
string='State', required=True,
readonly=True, selection=sel_state_book)
state_string = state.translated('state')
parent = fields.Many2One(string="Parent",
parent = fields.Many2One(
string="Parent",
model_name='cashbook.book', ondelete='RESTRICT')
childs = fields.One2Many(string='Children', field='parent',
childs = fields.One2Many(
string='Children', field='parent',
model_name='cashbook.book')
@classmethod
@ -245,9 +269,8 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
query = tab_book.select(
Case(
(tab_book.state == 'open', 0),
else_ = 1),
where=tab_book.id==table.id
)
else_=1),
where=tab_book.id == table.id)
return [query]
@staticmethod
@ -286,19 +309,21 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
# deny invalid date in context
if isinstance(query_date, str):
try :
dt1 = date.fromisoformat(query_date)
except :
try:
date.fromisoformat(query_date)
except Exception:
query_date = IrDate.today()
query = tab_book.join(tab_line,
condition=tab_book.id==tab_line.cashbook,
query = tab_book.join(
tab_line,
condition=tab_book.id == tab_line.cashbook,
).select(
tab_line.cashbook,
tab_book.currency,
Sum(Case(
(tab_line.date <= query_date, tab_line.credit - tab_line.debit),
else_ = Decimal('0.0'),
(tab_line.date <= query_date,
tab_line.credit - tab_line.debit),
else_=Decimal('0.0'),
)).as_('balance'),
Sum(tab_line.credit - tab_line.debit).as_('balance_all'),
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()
table, _ = tables[None]
query = tab_book.select(tab_book.balance,
where=tab_book.cashbook==table.id,
)
query = tab_book.select(
tab_book.balance,
where=tab_book.cashbook == table.id)
return [query]
@staticmethod
@ -326,9 +351,9 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
(tab_book, tab2) = Book2.get_balance_of_cashbook_sql()
table, _ = tables[None]
query = tab_book.select(tab_book.balance_all,
where=tab_book.cashbook==table.id,
)
query = tab_book.select(
tab_book.balance_all,
where=tab_book.cashbook == table.id)
return [query]
@classmethod
@ -361,32 +386,32 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
context = Transaction().context
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']}
# deny invalid date in context
query_date = context.get('date', IrDate.today())
if isinstance(query_date, str):
try :
dt1 = date.fromisoformat(query_date)
except :
try:
date.fromisoformat(query_date)
except Exception:
query_date = IrDate.today()
cache_keys = {
x.id: MemCache.get_key_by_record(
name = 'get_balance_cashbook',
record = x,
query = [{
name='get_balance_cashbook',
record=x,
query=[{
'model': 'cashbook.line',
'query': [('cashbook.parent', 'child_of', [x.id])],
}, {
'model': 'currency.currency.rate',
'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()])
for x in cashbooks
}
addkeys=[query_date.isoformat()])
for x in cashbooks}
# 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
with Transaction().set_context({
'date': query_date,
}):
'date': query_date}):
(tab_line, tab2) = cls.get_balance_of_cashbook_sql()
tab_subids = sub_ids_hierarchical('cashbook.book')
query = tab_book.join(tab_subids,
condition=tab_book.id==tab_subids.parent,
).join(tab_comp,
condition=tab_book.company==tab_comp.id,
).join(tab_line,
condition=tab_line.cashbook==AnyInArray(tab_subids.subids),
query = tab_book.join(
tab_subids,
condition=tab_book.id == tab_subids.parent,
).join(
tab_comp,
condition=tab_book.company == tab_comp.id,
).join(
tab_line,
condition=tab_line.cashbook == AnyInArray(
tab_subids.subids),
).select(
tab_book.id,
tab_book.currency.as_('to_currency'),
@ -413,7 +441,8 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
tab_comp.currency.as_('company_currency'),
Sum(tab_line.balance).as_('balance'),
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]),
)
cursor.execute(*query)
@ -496,30 +525,29 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
if (values['btype'] is None) and (len(book.lines) > 0):
raise UserError(gettext(
'cashbook.msg_book_btype_with_lines',
cbname = book.rec_name,
numlines = len(book.lines),
))
cbname=book.rec_name,
numlines=len(book.lines)))
if book.state != 'open':
# 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(
'cashbook.msg_book_deny_write',
bookname = book.rec_name,
state_txt = book.state_string,
))
bookname=book.rec_name,
state_txt=book.state_string))
# if owner changes, remove book from user-config
if 'owner' in values.keys():
if book.owner.id != values['owner']:
for x in ['defbook', 'book1', 'book2', 'book3',
for x in [
'defbook', 'book1', 'book2', 'book3',
'book4', 'book5']:
cfg1 = ConfigUser.search([
('iduser.id', '=', book.owner.id),
('%s.id' % x, '=', book.id),
])
('%s.id' % x, '=', book.id)])
if len(cfg1) > 0:
to_write_config.extend([ cfg1, {x: None} ])
to_write_config.extend([cfg1, {x: None}])
super(Book, cls).write(*args)
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'):
raise UserError(gettext(
'cashbook.msg_book_deny_delete',
bookname = book.rec_name,
booklines = len(book.lines),
))
bookname=book.rec_name,
booklines=len(book.lines)))
super(Book, cls).delete(books)
# end Book

View file

@ -25,22 +25,27 @@ class Category(tree(separator='/'), ModelSQL, ModelView):
name = fields.Char(string='Name', required=True, 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,
states={'readonly': Bool(Eval('parent_cattype'))},
domain=[If(Bool(Eval('parent_cattype')),
('cattype', '=', Eval('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}),
'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")
parent = fields.Many2One(string="Parent",
parent = fields.Many2One(
string="Parent",
model_name='cashbook.category', ondelete='RESTRICT')
childs = fields.One2Many(string='Children', field='parent',
childs = fields.One2Many(
string='Children', field='parent',
model_name='cashbook.category')
@classmethod
@ -61,7 +66,8 @@ class Category(tree(separator='/'), ModelSQL, ModelView):
Unique(t, t.name, t.company, t.parent),
'cashbook.msg_category_name_unique'),
('name2_uniq',
Exclude(t,
Exclude(
t,
(t.name, Equal),
(t.cattype, Equal),
where=(t.parent == None)),
@ -106,9 +112,8 @@ class Category(tree(separator='/'), ModelSQL, ModelView):
if category.parent.cattype != category.cattype:
raise UserError(gettext(
'cashbook.msg_category_type_not_like_parent',
parentname = category.parent.rec_name,
catname = category.rec_name,
))
parentname=category.parent.rec_name,
catname=category.rec_name,))
@classmethod
def create(cls, vlist):

View file

@ -20,7 +20,8 @@ class ReconciliationReport(Report):
Company = Pool().get('company.company')
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'])
return context
@ -41,14 +42,14 @@ class ReconciliationReport(Report):
recon_obj = pool.get(data['model'])(data['id'])
rep_name = gettext(
'cashbook.msg_rep_reconciliation_fname',
recname = recon_obj.cashbook.rec_name[:50],
date_from = recon_obj.date_from.isoformat(),
date_to = recon_obj.date_to.isoformat(),
)
else :
recname=recon_obj.cashbook.rec_name[:50],
date_from=recon_obj.date_from.isoformat(),
date_to=recon_obj.date_to.isoformat())
else:
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 (
ext1,
@ -63,5 +64,3 @@ class ReconciliationReport(Report):
)
# end ReconciliationReport

View file

@ -9,11 +9,14 @@ from trytond.pyson import Eval, If
from trytond.pool import Pool
field_checked = fields.Boolean(string='Checked',
field_checked = fields.Boolean(
string='Checked',
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.')
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.')
@ -21,13 +24,15 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
'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=[
If(Eval('date_to') & Eval('date_from'),
('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=[
If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')),
@ -36,37 +41,43 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
checked = fields.MultiValue(field_checked)
done = fields.MultiValue(field_done)
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.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('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.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('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.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('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.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('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.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
('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.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
@ -79,7 +90,8 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
"""
pool = Pool()
if field in ['date_from', 'date_to', 'checked', 'done',
if field in [
'date_from', 'date_to', 'checked', 'done',
'catnamelong', 'defbook', 'book1', 'book2',
'book3', 'book4', 'book5']:
return pool.get('cashbook.configuration_user')
@ -104,13 +116,15 @@ class UserConfiguration(ModelSQL, UserValueMixin):
'User Configuration'
__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=[
If(Eval('date_to') & Eval('date_from'),
('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=[
If(Eval('date_to') & Eval('date_from'),
('date_from', '<=', Eval('date_to')),
@ -119,7 +133,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
checked = field_checked
done = field_done
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.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
@ -127,7 +142,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book1 = fields.Many2One(string='Cashbook 1',
book1 = fields.Many2One(
string='Cashbook 1',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
@ -135,7 +151,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book2 = fields.Many2One(string='Cashbook 2',
book2 = fields.Many2One(
string='Cashbook 2',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
@ -143,7 +160,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book3 = fields.Many2One(string='Cashbook 3',
book3 = fields.Many2One(
string='Cashbook 3',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
@ -151,7 +169,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book4 = fields.Many2One(string='Cashbook 4',
book4 = fields.Many2One(
string='Cashbook 4',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[
@ -159,7 +178,8 @@ class UserConfiguration(ModelSQL, UserValueMixin):
('state', '=', 'open'),
('owner.id', '=', Eval('iduser', -1))
], depends=['iduser'])
book5 = fields.Many2One(string='Cashbook 5',
book5 = fields.Many2One(
string='Cashbook 5',
help='Cash book available in selection dialog.',
model_name='cashbook.book', ondelete='SET NULL',
domain=[

View file

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

376
line.py
View file

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

View file

@ -17,13 +17,14 @@ STATES = {
Eval('state_cashbook', '') != 'open',
),
}
DEPENDS=['state', 'state_cashbook']
DEPENDS = ['state', 'state_cashbook']
class SecondCurrencyMixin:
""" 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)),
states={
'readonly': Or(
@ -33,7 +34,8 @@ class SecondCurrencyMixin:
'required': Bool(Eval('currency2nd')),
'invisible': ~Bool(Eval('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.',
digits=(rate_decimal * 2, rate_decimal),
states={
@ -46,9 +48,11 @@ class SecondCurrencyMixin:
}, depends=DEPENDS+['currency2nd_digits', 'currency2nd']),
'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')
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')
@classmethod
@ -69,8 +73,7 @@ class SecondCurrencyMixin:
booktransf = Cashbook(booktransf)
if from_currency.id != booktransf.currency.id:
with Transaction().set_context({
'date': values.get('date', IrDate.today()),
}):
'date': values.get('date', IrDate.today())}):
values['amount_2nd_currency'] = Currency.compute(
from_currency,
amount,
@ -78,22 +81,28 @@ class SecondCurrencyMixin:
)
return values
@fields.depends('booktransf', '_parent_booktransf.currency', \
'currency', 'amount', 'date', 'amount_2nd_currency', 'rate_2nd_currency')
@fields.depends(
'booktransf', '_parent_booktransf.currency',
'currency', 'amount', 'date', 'amount_2nd_currency',
'rate_2nd_currency')
def on_change_booktransf(self):
""" update amount_2nd_currency
"""
self.on_change_rate_2nd_currency()
@fields.depends('booktransf', '_parent_booktransf.currency', \
'currency', 'amount', 'date', 'amount_2nd_currency', 'rate_2nd_currency')
@fields.depends(
'booktransf', '_parent_booktransf.currency',
'currency', 'amount', 'date', 'amount_2nd_currency',
'rate_2nd_currency')
def on_change_amount(self):
""" update amount_2nd_currency
"""
self.on_change_rate_2nd_currency()
@fields.depends('booktransf', '_parent_booktransf.currency', \
'currency', 'amount', 'date', 'amount_2nd_currency', 'rate_2nd_currency')
@fields.depends(
'booktransf', '_parent_booktransf.currency',
'currency', 'amount', 'date', 'amount_2nd_currency',
'rate_2nd_currency')
def on_change_rate_2nd_currency(self):
""" update amount_2nd_currency + rate_2nd_currency
"""
@ -109,16 +118,16 @@ class SecondCurrencyMixin:
if self.rate_2nd_currency is None:
# no rate set, use current rate of target-currency
with Transaction().set_context({
'date': self.date or IrDate.today(),
}):
'date': self.date or IrDate.today()}):
self.amount_2nd_currency = Currency.compute(
self.currency,
self.amount,
self.booktransf.currency
)
if self.amount != Decimal('0.0'):
self.rate_2nd_currency = self.amount_2nd_currency / self.amount
else :
self.rate_2nd_currency = \
self.amount_2nd_currency / self.amount
else:
self.amount_2nd_currency = self.booktransf.currency.round(
self.amount * self.rate_2nd_currency
)
@ -201,5 +210,3 @@ class MemCacheIndexMx:
cls.create_date.select = True
# end MemCacheIndexMx

View file

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

View file

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

View file

@ -4,13 +4,12 @@
# 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.pyson import Eval, If
from trytond.report import Report
from trytond.i18n import gettext
from trytond.transaction import Transaction
from .line import sel_linetype, sel_bookingtype, STATES, DEPENDS
from .line import sel_bookingtype, STATES, DEPENDS
from .book import sel_state_book
from .mixin import SecondCurrencyMixin, MemCacheIndexMx
@ -30,15 +29,18 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
'Split booking line'
__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',
readonly=True)
description = fields.Text(string='Description',
states=STATES, depends=DEPENDS)
splittype = fields.Selection(string='Type', required=True,
description = fields.Text(
string='Description', states=STATES, depends=DEPENDS)
splittype = fields.Selection(
string='Type', required=True,
help='Type of split booking line', selection=sel_linetype,
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',
states={
'readonly': STATES['readonly'],
@ -51,9 +53,11 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
('cattype', '=', 'in'),
('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')
booktransf = fields.Many2One(string='Source/Dest',
booktransf = fields.Many2One(
string='Source/Dest',
ondelete='RESTRICT', model_name='cashbook.book',
domain=[
('owner.id', '=', Eval('owner_cashbook', -1)),
@ -66,32 +70,43 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
'required': Eval('splittype', '') == 'tr',
}, 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'])
date = fields.Function(fields.Date(string='Date', readonly=True),
'on_change_with_date')
target = fields.Function(fields.Reference(string='Target', readonly=True,
date = fields.Function(fields.Date(
string='Date', readonly=True), 'on_change_with_date')
target = fields.Function(fields.Reference(
string='Target', readonly=True,
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')
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')
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')
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')
cashbook = fields.Function(fields.Many2One(string='Cashbook',
cashbook = fields.Function(fields.Many2One(
string='Cashbook',
readonly=True, states={'invisible': True}, model_name='cashbook.book'),
'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')
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')
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),
'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'),
'on_change_with_owner_cashbook')
@ -108,8 +123,10 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
'desc': (self.description or '-')[:40],
'amount': Report.format_number(self.amount, None),
'symbol': getattr(self.currency, 'symbol', '-'),
'target': self.category_view if self.splittype == 'cat' else self.booktransf.rec_name,
'type': gettext('cashbook.msg_line_bookingtype_%s' % self.line.bookingtype),
'target': self.category_view
if self.splittype == 'cat' else self.booktransf.rec_name,
'type': gettext(
'cashbook.msg_line_bookingtype_%s' % self.line.bookingtype),
}
@fields.depends('splittype', 'category', 'booktransf')
@ -151,9 +168,9 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
if self.category:
cfg1 = Configuration.get_singleton()
if getattr(cfg1, 'catnamelong', True) == True:
if getattr(cfg1, 'catnamelong', True) is True:
return self.category.rec_name
else :
else:
return self.category.name
@fields.depends('line', '_parent_line.state')
@ -246,7 +263,7 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
to_update_line = []
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_write = Line2.update_values_by_splitlines(to_update_line)
@ -268,7 +285,7 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
if 'amount' in values.keys():
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)
super(SplitLine, cls).write(*args)

View file

@ -811,6 +811,49 @@ class LineTestCase(ModuleTestCase):
'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()
def test_line_create_check_deny_write(self):
""" 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)
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")
feature = fields.Selection(string='Feature', required=True,
feature = fields.Selection(
string='Feature', required=True,
selection='get_sel_feature', select=True,
help='Select feature set of the Cashbook.')
@ -53,7 +55,8 @@ class Type(ModelSQL, ModelView):
def search_rec_name(cls, name, clause):
""" search in name + short
"""
return ['OR',
return [
'OR',
('name',) + 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.pool import Pool
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 .line import sel_bookingtype
@ -18,29 +18,36 @@ class EnterBookingStart(ModelView):
'Enter Booking'
__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)],
depends=['cashbooks'], required=True)
cashbooks = fields.One2Many(string='Cashbooks', field=None,
cashbooks = fields.One2Many(
string='Cashbooks', field=None,
model_name='cashbook.book', readonly=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'),
'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}),
'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}),
'on_change_with_currency_digits')
bookingtype = fields.Selection(string='Type', required=True,
selection=sel_booktypewiz)
amount = fields.Numeric(string='Amount',
bookingtype = fields.Selection(
string='Type', required=True, selection=sel_booktypewiz)
amount = fields.Numeric(
string='Amount',
depends=['currency_digits', 'bookingtype'],
digits=(16, Eval('currency_digits', 2)), required=True,
domain=[('amount', '>=', Decimal('0.0'))])
description = fields.Text(string='Description')
category = fields.Many2One(string='Category',
category = fields.Many2One(
string='Category',
model_name='cashbook.category', depends=['bookingtype'],
states={
'readonly': Bool(Eval('bookingtype')) == False,
@ -55,7 +62,8 @@ class EnterBookingStart(ModelView):
)])
# party or cashbook as counterpart
booktransf = fields.Many2One(string='Source/Dest',
booktransf = fields.Many2One(
string='Source/Dest',
model_name='cashbook.book',
domain=[
('owner.id', '=', Eval('owner_cashbook', -1)),
@ -65,7 +73,8 @@ class EnterBookingStart(ModelView):
'invisible': ~Eval('bookingtype', '').in_(['mvin', 'mvout']),
'required': Eval('bookingtype', '').in_(['mvin', 'mvout']),
}, depends=['bookingtype', 'owner_cashbook', 'cashbook'])
party = fields.Many2One(string='Party', model_name='party.party',
party = fields.Many2One(
string='Party', model_name='party.party',
states={
'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
}, depends=['bookingtype'])
@ -81,7 +90,7 @@ class EnterBookingStart(ModelView):
if self.bookingtype:
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
@fields.depends('cashbook', '_parent_cashbook.owner')
@ -104,7 +113,7 @@ class EnterBookingStart(ModelView):
"""
if self.cashbook:
return self.cashbook.currency.digits
else :
else:
return 2
# end EnterBookingStart
@ -115,7 +124,8 @@ class EnterBookingWizard(Wizard):
__name__ = 'cashbook.enterbooking'
start_state = 'start'
start = StateView('cashbook.enterbooking.start',
start = StateView(
'cashbook.enterbooking.start',
'cashbook.enterbooking_start_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Save', 'save_', 'tryton-save', default=True),

View file

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

View file

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