Compare commits
17 commits
main
...
ver_6.8.32
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a895d2a0de | ||
![]() |
57dea9a65d | ||
![]() |
6008532475 | ||
![]() |
d2ceb5419e | ||
![]() |
04acb27b60 | ||
![]() |
aa7af9844b | ||
![]() |
018e265cff | ||
![]() |
9ee924f0d8 | ||
![]() |
510357a13c | ||
![]() |
b237981a9b | ||
![]() |
aac6930999 | ||
![]() |
7a4fd94981 | ||
![]() |
6550baa468 | ||
![]() |
e017f20df4 | ||
![]() |
3a0df50d52 | ||
![]() |
eaa502a626 | ||
![]() |
7769f5f14a |
27 changed files with 224 additions and 185 deletions
20
README.rst
20
README.rst
|
@ -9,7 +9,7 @@ pip install mds-cashbook
|
||||||
|
|
||||||
Requires
|
Requires
|
||||||
========
|
========
|
||||||
- Tryton 6.0
|
- Tryton 6.8
|
||||||
|
|
||||||
How to
|
How to
|
||||||
======
|
======
|
||||||
|
@ -153,6 +153,22 @@ currency are converted into the display currency of the parent cash book.
|
||||||
Changes
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
*6.0.0 - 05.08.2022*
|
*6.8.32 - 06.12.2023*
|
||||||
|
|
||||||
|
- columns optional
|
||||||
|
|
||||||
|
*6.8.31 - 30.11.2023*
|
||||||
|
|
||||||
|
- optimized ir.rule
|
||||||
|
|
||||||
|
*6.8.30 - 25.07.2023*
|
||||||
|
|
||||||
|
- updt: optimize code, add tests
|
||||||
|
|
||||||
|
*6.8.29 - 24.07.2023*
|
||||||
|
|
||||||
|
- fix: type of indexes
|
||||||
|
|
||||||
|
*6.8.28 - 05.06.2023*
|
||||||
|
|
||||||
- init
|
- init
|
||||||
|
|
17
book.py
17
book.py
|
@ -74,8 +74,7 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
||||||
states={
|
states={
|
||||||
'readonly': Or(
|
'readonly': Or(
|
||||||
STATES['readonly'],
|
STATES['readonly'],
|
||||||
Len(Eval('lines')) > 0,
|
Len(Eval('lines')) > 0),
|
||||||
),
|
|
||||||
}, depends=DEPENDS+['lines'])
|
}, depends=DEPENDS+['lines'])
|
||||||
feature = fields.Function(fields.Char(
|
feature = fields.Function(fields.Char(
|
||||||
string='Feature', readonly=True,
|
string='Feature', readonly=True,
|
||||||
|
@ -124,8 +123,7 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
||||||
states={
|
states={
|
||||||
'readonly': Or(
|
'readonly': Or(
|
||||||
STATES2['readonly'],
|
STATES2['readonly'],
|
||||||
Len(Eval('lines')) > 0,
|
Len(Eval('lines')) > 0),
|
||||||
),
|
|
||||||
'invisible': STATES2['invisible'],
|
'invisible': STATES2['invisible'],
|
||||||
'required': ~STATES2['invisible'],
|
'required': ~STATES2['invisible'],
|
||||||
}, depends=DEPENDS2+['lines'])
|
}, depends=DEPENDS2+['lines'])
|
||||||
|
@ -165,8 +163,7 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
||||||
states={
|
states={
|
||||||
'readonly': Or(
|
'readonly': Or(
|
||||||
STATES2['readonly'],
|
STATES2['readonly'],
|
||||||
Len(Eval('lines', [])) > 0,
|
Len(Eval('lines', [])) > 0),
|
||||||
),
|
|
||||||
}, depends=DEPENDS2+['lines'])
|
}, depends=DEPENDS2+['lines'])
|
||||||
currency_digits = fields.Function(fields.Integer(
|
currency_digits = fields.Function(fields.Integer(
|
||||||
string='Currency Digits',
|
string='Currency Digits',
|
||||||
|
@ -202,9 +199,6 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
||||||
Index(
|
Index(
|
||||||
t,
|
t,
|
||||||
(t.btype, Index.Equality())),
|
(t.btype, Index.Equality())),
|
||||||
Index(
|
|
||||||
t,
|
|
||||||
(t.parent, Index.Equality())),
|
|
||||||
Index(
|
Index(
|
||||||
t,
|
t,
|
||||||
(t.company, Index.Equality())),
|
(t.company, Index.Equality())),
|
||||||
|
@ -396,8 +390,7 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
||||||
query = tab_line.select(
|
query = tab_line.select(
|
||||||
tab_line.cashbook,
|
tab_line.cashbook,
|
||||||
where=Operator(
|
where=Operator(
|
||||||
getattr(tab_line, name), clause[2]),
|
getattr(tab_line, name), clause[2]))
|
||||||
)
|
|
||||||
return [('id', 'in', query)]
|
return [('id', 'in', query)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -574,7 +567,7 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
||||||
'defbook', 'book1', 'book2', 'book3',
|
'defbook', 'book1', 'book2', 'book3',
|
||||||
'book4', 'book5']:
|
'book4', 'book5']:
|
||||||
cfg1 = ConfigUser.search([
|
cfg1 = ConfigUser.search([
|
||||||
('iduser.id', '=', book.owner.id),
|
('iduser', '=', 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}])
|
||||||
|
|
4
book.xml
4
book.xml
|
@ -143,8 +143,8 @@ full copyright notices and license terms. -->
|
||||||
</record>
|
</record>
|
||||||
<record model="ir.rule" id="rg_book_read_nonowner-1">
|
<record model="ir.rule" id="rg_book_read_nonowner-1">
|
||||||
<field name="domain" eval="['OR',
|
<field name="domain" eval="['OR',
|
||||||
('observer.id', 'in', Eval('user', {}).get('groups', [])),
|
('observer.id', 'in', Eval('groups', [])),
|
||||||
('reviewer.id', 'in', Eval('user', {}).get('groups', [])),
|
('reviewer.id', 'in', Eval('groups', [])),
|
||||||
]" pyson="1"/>
|
]" pyson="1"/>
|
||||||
<field name="rule_group" ref="rg_book_read_nonowner"/>
|
<field name="rule_group" ref="rg_book_read_nonowner"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
@ -11,6 +11,7 @@ from trytond.exceptions import UserError
|
||||||
from trytond.i18n import gettext
|
from trytond.i18n import gettext
|
||||||
from sql.operators import Equal
|
from sql.operators import Equal
|
||||||
from .model import order_name_hierarchical
|
from .model import order_name_hierarchical
|
||||||
|
from .const import DEF_NONE
|
||||||
|
|
||||||
|
|
||||||
sel_categorytype = [
|
sel_categorytype = [
|
||||||
|
@ -70,7 +71,7 @@ class Category(tree(separator='/'), ModelSQL, ModelView):
|
||||||
t,
|
t,
|
||||||
(t.name, Equal),
|
(t.name, Equal),
|
||||||
(t.cattype, Equal),
|
(t.cattype, Equal),
|
||||||
where=(t.parent == None)),
|
where=(t.parent == DEF_NONE)),
|
||||||
'cashbook.msg_category_name_unique'),
|
'cashbook.msg_category_name_unique'),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@ field_done = fields.Boolean(
|
||||||
help='Show cashbook lines in Done-state.')
|
help='Show cashbook lines in Done-state.')
|
||||||
field_catnamelong = fields.Boolean(
|
field_catnamelong = fields.Boolean(
|
||||||
string='Category: Show long name',
|
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.')
|
||||||
|
|
||||||
|
|
||||||
class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
|
class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
|
||||||
|
@ -29,60 +30,47 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, UserMultiValueMixin):
|
||||||
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(
|
date_to = fields.MultiValue(fields.Date(
|
||||||
string='End Date', depends=['date_from'],
|
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')),
|
||||||
()),
|
())]))
|
||||||
]))
|
|
||||||
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(
|
defbook = fields.MultiValue(fields.Many2One(
|
||||||
string='Default Cashbook',
|
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(
|
book1 = fields.MultiValue(fields.Many2One(
|
||||||
string='Cashbook 1',
|
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(
|
book2 = fields.MultiValue(fields.Many2One(
|
||||||
string='Cashbook 2',
|
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(
|
book3 = fields.MultiValue(fields.Many2One(
|
||||||
string='Cashbook 3',
|
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(
|
book4 = fields.MultiValue(fields.Many2One(
|
||||||
string='Cashbook 4',
|
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(
|
book5 = fields.MultiValue(fields.Many2One(
|
||||||
string='Cashbook 5',
|
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=[('btype', '!=', None), ('state', '=', 'open')]))
|
||||||
('btype', '!=', None), ('state', '=', 'open'),
|
|
||||||
]))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def multivalue_model(cls, field):
|
def multivalue_model(cls, field):
|
||||||
|
@ -121,21 +109,20 @@ class UserConfiguration(ModelSQL, UserValueMixin):
|
||||||
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(
|
date_to = fields.Date(
|
||||||
string='End Date', depends=['date_from'],
|
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')),
|
||||||
()),
|
())])
|
||||||
])
|
|
||||||
checked = field_checked
|
checked = field_checked
|
||||||
done = field_done
|
done = field_done
|
||||||
catnamelong = field_catnamelong
|
catnamelong = field_catnamelong
|
||||||
defbook = fields.Many2One(
|
defbook = fields.Many2One(
|
||||||
string='Default Cashbook',
|
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),
|
('btype', '!=', None),
|
||||||
|
|
8
const.py
Normal file
8
const.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# This file is part of the cashbook-module from m-ds.de for Tryton.
|
||||||
|
# The COPYRIGHT file at the top level of this repository contains the
|
||||||
|
# full copyright notices and license terms.
|
||||||
|
|
||||||
|
|
||||||
|
DEF_NONE = None
|
||||||
|
|
|
@ -42,7 +42,8 @@ class CurrencyRate(metaclass=PoolMeta):
|
||||||
MemCache = Pool().get('cashbook.memcache')
|
MemCache = Pool().get('cashbook.memcache')
|
||||||
|
|
||||||
for record in records:
|
for record in records:
|
||||||
MemCache.record_update(CACHEKEY_CURRENCY % record.currency.id, None)
|
MemCache.record_update(
|
||||||
|
CACHEKEY_CURRENCY % record.currency.id, None)
|
||||||
super(CurrencyRate, cls).delete(records)
|
super(CurrencyRate, cls).delete(records)
|
||||||
|
|
||||||
# end
|
# end
|
||||||
|
|
69
line.py
69
line.py
|
@ -16,6 +16,7 @@ from sql.functions import DatePart
|
||||||
from sql.conditionals import Case
|
from sql.conditionals import Case
|
||||||
from .book import sel_state_book
|
from .book import sel_state_book
|
||||||
from .mixin import SecondCurrencyMixin, MemCacheIndexMx
|
from .mixin import SecondCurrencyMixin, MemCacheIndexMx
|
||||||
|
from .const import DEF_NONE
|
||||||
|
|
||||||
|
|
||||||
sel_payee = [
|
sel_payee = [
|
||||||
|
@ -23,7 +24,7 @@ sel_payee = [
|
||||||
('party.party', 'Party')
|
('party.party', 'Party')
|
||||||
]
|
]
|
||||||
|
|
||||||
sel_linetype = [
|
sel_linestate = [
|
||||||
('edit', 'Edit'),
|
('edit', 'Edit'),
|
||||||
('check', 'Checked'),
|
('check', 'Checked'),
|
||||||
('recon', 'Reconciled'),
|
('recon', 'Reconciled'),
|
||||||
|
@ -48,7 +49,9 @@ STATES = {
|
||||||
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'
|
||||||
|
|
||||||
|
@ -75,8 +78,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
states={
|
states={
|
||||||
'readonly': Or(
|
'readonly': Or(
|
||||||
STATES['readonly'],
|
STATES['readonly'],
|
||||||
Bool(Eval('bookingtype')) == False,
|
~Bool(Eval('bookingtype'))),
|
||||||
),
|
|
||||||
'required': Eval('bookingtype', '').in_(['in', 'out']),
|
'required': Eval('bookingtype', '').in_(['in', 'out']),
|
||||||
'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
|
'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
|
||||||
}, depends=DEPENDS+['bookingtype'],
|
}, depends=DEPENDS+['bookingtype'],
|
||||||
|
@ -161,7 +163,8 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
splitlines = fields.One2Many(
|
splitlines = fields.One2Many(
|
||||||
string='Split booking lines',
|
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={
|
||||||
'invisible': ~Eval('bookingtype' '').in_(['spin', 'spout']),
|
'invisible': ~Eval('bookingtype' '').in_(['spin', 'spout']),
|
||||||
'readonly': Or(
|
'readonly': Or(
|
||||||
|
@ -197,7 +200,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
|
|
||||||
state = fields.Selection(
|
state = fields.Selection(
|
||||||
string='State', required=True, readonly=True,
|
string='State', required=True, readonly=True,
|
||||||
selection=sel_linetype)
|
selection=sel_linestate)
|
||||||
state_string = state.translated('state')
|
state_string = state.translated('state')
|
||||||
state_cashbook = fields.Function(fields.Selection(
|
state_cashbook = fields.Function(fields.Selection(
|
||||||
string='State of Cashbook',
|
string='State of Cashbook',
|
||||||
|
@ -225,9 +228,6 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
t = cls.__table__()
|
t = cls.__table__()
|
||||||
|
|
||||||
cls._sql_indexes.update({
|
cls._sql_indexes.update({
|
||||||
Index(
|
|
||||||
t,
|
|
||||||
(t.cashbook, Index.Equality())),
|
|
||||||
Index(
|
Index(
|
||||||
t,
|
t,
|
||||||
(t.date, Index.Range(order='ASC'))),
|
(t.date, Index.Range(order='ASC'))),
|
||||||
|
@ -245,7 +245,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
(t.state, Index.Equality())),
|
(t.state, Index.Equality())),
|
||||||
Index(
|
Index(
|
||||||
t,
|
t,
|
||||||
(t.reference, Index.Range())),
|
(t.reference, Index.Equality())),
|
||||||
})
|
})
|
||||||
cls._sql_constraints.extend([
|
cls._sql_constraints.extend([
|
||||||
('state_val2',
|
('state_val2',
|
||||||
|
@ -308,7 +308,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
).select(
|
).select(
|
||||||
tab_line.id,
|
tab_line.id,
|
||||||
where=tab_line.bookingtype.in_(['mvin', 'mvout']) &
|
where=tab_line.bookingtype.in_(['mvin', 'mvout']) &
|
||||||
(tab_line.amount_2nd_currency == None) &
|
(tab_line.amount_2nd_currency == DEF_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)])
|
||||||
|
@ -344,7 +344,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.reference:
|
if line.reference:
|
||||||
if Transaction().context.get(
|
if Transaction().context.get(
|
||||||
'line.allow.wfedit', False) == False:
|
'line.allow.wfedit', False) is 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,
|
||||||
|
@ -375,7 +375,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
# allow cashbook-line at range-limits
|
# allow cashbook-line at range-limits
|
||||||
if Recon.search_count([
|
if Recon.search_count([
|
||||||
('state', 'in', ['check', 'done']),
|
('state', 'in', ['check', 'done']),
|
||||||
('cashbook.id', '=', line.cashbook.id),
|
('cashbook', '=', line.cashbook.id),
|
||||||
('date_from', '<', line.date),
|
('date_from', '<', line.date),
|
||||||
('date_to', '>', line.date)]) > 0:
|
('date_to', '>', line.date)]) > 0:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
|
@ -386,7 +386,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
# reconciliations exist
|
# reconciliations exist
|
||||||
if Recon.search_count([
|
if Recon.search_count([
|
||||||
('state', 'in', ['check', 'done']),
|
('state', 'in', ['check', 'done']),
|
||||||
('cashbook.id', '=', line.cashbook.id),
|
('cashbook', '=', line.cashbook.id),
|
||||||
['OR',
|
['OR',
|
||||||
('date_from', '=', line.date),
|
('date_from', '=', line.date),
|
||||||
('date_to', '=', line.date)]]) > 1:
|
('date_to', '=', line.date)]]) > 1:
|
||||||
|
@ -508,19 +508,20 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
"""
|
"""
|
||||||
credit = self.credit if self.credit is not None else Decimal('0.0')
|
credit = self.credit if self.credit is not None else Decimal('0.0')
|
||||||
debit = self.debit if self.debit is not None else Decimal('0.0')
|
debit = self.debit if self.debit is not None else Decimal('0.0')
|
||||||
return '%(date)s|%(type)s|%(amount)s %(symbol)s|%(desc)s [%(category)s]' % {
|
return '|'.join([
|
||||||
'date': Report.format_date(self.date),
|
Report.format_date(self.date),
|
||||||
'desc': (self.description or '-')[:40],
|
gettext('cashbook.msg_line_bookingtype_%s' % self.bookingtype),
|
||||||
'amount': Report.format_number(
|
'%(amount)s %(symbol)s' % {
|
||||||
credit - debit, None,
|
'amount': Report.format_number(
|
||||||
digits=getattr(self.currency, 'digits', 2)),
|
credit - debit, None,
|
||||||
'symbol': getattr(self.currency, 'symbol', '-'),
|
digits=getattr(self.currency, 'digits', 2)),
|
||||||
'category': self.category_view
|
'symbol': getattr(self.currency, 'symbol', '-')},
|
||||||
if self.bookingtype in ['in', 'out']
|
'%(desc)s [%(category)s]' % {
|
||||||
else getattr(self.booktransf, 'rec_name', '-'),
|
'desc': (self.description or '-')[:40],
|
||||||
'type': gettext(
|
'category': self.category_view
|
||||||
'cashbook.msg_line_bookingtype_%s' %
|
if self.bookingtype in ['in', 'out']
|
||||||
self.bookingtype)}
|
else getattr(self.booktransf, 'rec_name', '-')},
|
||||||
|
])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def order_state(tables):
|
def order_state(tables):
|
||||||
|
@ -535,8 +536,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
(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]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -626,7 +626,8 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
|
|
||||||
if self.bookingtype:
|
if self.bookingtype:
|
||||||
if self.category:
|
if self.category:
|
||||||
if self.bookingtype not 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
|
||||||
|
@ -751,7 +752,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
end_value = None
|
end_value = None
|
||||||
|
|
||||||
recons = Reconciliation.search([
|
recons = Reconciliation.search([
|
||||||
('cashbook.id', '=', line.cashbook.id),
|
('cashbook', '=', line.cashbook.id),
|
||||||
('date_to', '<=', line2.date),
|
('date_to', '<=', line2.date),
|
||||||
('state', '=', 'done'),
|
('state', '=', 'done'),
|
||||||
], order=[('date_from', 'DESC')], limit=1)
|
], order=[('date_from', 'DESC')], limit=1)
|
||||||
|
@ -761,14 +762,14 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
('date', '<=', line2.date),
|
('date', '<=', line2.date),
|
||||||
['OR',
|
['OR',
|
||||||
('reconciliation', '=', None),
|
('reconciliation', '=', None),
|
||||||
('reconciliation.id', '!=', recons[0])],
|
('reconciliation', '!=', 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)
|
||||||
|
|
||||||
if line.cashbook:
|
if line.cashbook:
|
||||||
query = [
|
query = [
|
||||||
('cashbook.id', '=', line.cashbook.id),
|
('cashbook', '=', line.cashbook.id),
|
||||||
]
|
]
|
||||||
balance = Decimal('0.0')
|
balance = Decimal('0.0')
|
||||||
|
|
||||||
|
@ -778,7 +779,7 @@ class Line(SecondCurrencyMixin, MemCacheIndexMx, Workflow, ModelSQL, ModelView):
|
||||||
if line.reconciliation:
|
if line.reconciliation:
|
||||||
if line.reconciliation.state == 'done':
|
if line.reconciliation.state == 'done':
|
||||||
query.append(
|
query.append(
|
||||||
('reconciliation.id', '=', line.reconciliation.id),
|
('reconciliation', '=', line.reconciliation.id),
|
||||||
)
|
)
|
||||||
balance = getattr(
|
balance = getattr(
|
||||||
line.reconciliation, 'start_%s' % field_name)
|
line.reconciliation, 'start_%s' % field_name)
|
||||||
|
|
4
line.xml
4
line.xml
|
@ -146,7 +146,7 @@ full copyright notices and license terms. -->
|
||||||
<record model="ir.rule" id="rg_line_write-1">
|
<record model="ir.rule" id="rg_line_write-1">
|
||||||
<field name="domain" eval="['OR',
|
<field name="domain" eval="['OR',
|
||||||
('cashbook.owner.id', '=', Eval('user', {}).get('id', -1)),
|
('cashbook.owner.id', '=', Eval('user', {}).get('id', -1)),
|
||||||
('cashbook.reviewer.id', 'in', Eval('user', {}).get('groups', [])),
|
('cashbook.reviewer.id', 'in', Eval('groups', [])),
|
||||||
]" pyson="1"/>
|
]" pyson="1"/>
|
||||||
<field name="rule_group" ref="rg_line_write"/>
|
<field name="rule_group" ref="rg_line_write"/>
|
||||||
</record>
|
</record>
|
||||||
|
@ -168,7 +168,7 @@ full copyright notices and license terms. -->
|
||||||
</record>
|
</record>
|
||||||
<record model="ir.rule" id="rg_line_read-1">
|
<record model="ir.rule" id="rg_line_read-1">
|
||||||
<field name="domain" eval="[
|
<field name="domain" eval="[
|
||||||
('cashbook.observer.id', 'in', Eval('user', {}).get('groups', [])),
|
('cashbook.observer.id', 'in', Eval('groups', [])),
|
||||||
]" pyson="1"/>
|
]" pyson="1"/>
|
||||||
<field name="rule_group" ref="rg_line_read"/>
|
<field name="rule_group" ref="rg_line_read"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
15
mixin.py
15
mixin.py
|
@ -31,20 +31,19 @@ class SecondCurrencyMixin:
|
||||||
states={
|
states={
|
||||||
'readonly': Or(
|
'readonly': Or(
|
||||||
STATES['readonly'],
|
STATES['readonly'],
|
||||||
~Bool(Eval('currency2nd'))
|
~Bool(Eval('currency2nd'))),
|
||||||
),
|
|
||||||
'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(
|
rate_2nd_currency = fields.Function(fields.Numeric(
|
||||||
string='Rate',
|
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={
|
||||||
'readonly': Or(
|
'readonly': Or(
|
||||||
STATES['readonly'],
|
STATES['readonly'],
|
||||||
~Bool(Eval('currency2nd'))
|
~Bool(Eval('currency2nd'))),
|
||||||
),
|
|
||||||
'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']),
|
||||||
|
@ -124,15 +123,13 @@ class SecondCurrencyMixin:
|
||||||
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.rate_2nd_currency = \
|
||||||
self.amount_2nd_currency / self.amount
|
self.amount_2nd_currency / self.amount
|
||||||
else:
|
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)
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_rate_2nd_currency(cls, lines, name, value):
|
def set_rate_2nd_currency(cls, lines, name, value):
|
||||||
|
|
4
model.py
4
model.py
|
@ -15,6 +15,8 @@ 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
|
||||||
|
from .const import DEF_NONE
|
||||||
|
|
||||||
|
|
||||||
if config.get('cashbook', 'memcache', default='yes').lower() \
|
if config.get('cashbook', 'memcache', default='yes').lower() \
|
||||||
in ['yes', '1', 'true']:
|
in ['yes', '1', 'true']:
|
||||||
|
@ -269,7 +271,7 @@ 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 == DEF_NONE,
|
||||||
)
|
)
|
||||||
lines.query |= tab_mod2.join(
|
lines.query |= tab_mod2.join(
|
||||||
lines,
|
lines,
|
||||||
|
|
|
@ -183,7 +183,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
|
||||||
if Line.search_count([
|
if Line.search_count([
|
||||||
('date', '>', reconciliation.date_from),
|
('date', '>', reconciliation.date_from),
|
||||||
('date', '<', reconciliation.date_to),
|
('date', '<', reconciliation.date_to),
|
||||||
('cashbook.id', '=', reconciliation.cashbook.id),
|
('cashbook', '=', reconciliation.cashbook.id),
|
||||||
('state', 'not in', ['check', 'recon']),
|
('state', 'not in', ['check', 'recon']),
|
||||||
]) > 0:
|
]) > 0:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
|
@ -204,7 +204,8 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
|
||||||
|
|
||||||
# unlink lines from reconciliation
|
# unlink lines from reconciliation
|
||||||
if len(reconciliation.lines) > 0:
|
if len(reconciliation.lines) > 0:
|
||||||
values['lines'] = [('remove', [x.id for x in reconciliation.lines])]
|
values['lines'] = [
|
||||||
|
('remove', [x.id for x in reconciliation.lines])]
|
||||||
return values
|
return values
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -224,7 +225,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
|
||||||
lines = Line.search([
|
lines = Line.search([
|
||||||
('date', '>=', reconciliation.date_from),
|
('date', '>=', reconciliation.date_from),
|
||||||
('date', '<=', reconciliation.date_to),
|
('date', '<=', reconciliation.date_to),
|
||||||
('cashbook.id', '=', reconciliation.cashbook.id),
|
('cashbook', '=', reconciliation.cashbook.id),
|
||||||
('reconciliation', '=', None),
|
('reconciliation', '=', None),
|
||||||
('state', 'in', ['check', 'recon']),
|
('state', 'in', ['check', 'recon']),
|
||||||
])
|
])
|
||||||
|
@ -315,7 +316,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
|
||||||
|
|
||||||
# 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([
|
||||||
('cashbook.id', '=', reconciliation.cashbook.id),
|
('cashbook', '=', reconciliation.cashbook.id),
|
||||||
('reconciliation', '=', None),
|
('reconciliation', '=', None),
|
||||||
['OR',
|
['OR',
|
||||||
[ # lines inside of date-range
|
[ # lines inside of date-range
|
||||||
|
@ -339,20 +340,24 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
|
||||||
def get_rec_name(self, name):
|
def get_rec_name(self, name):
|
||||||
""" short + name
|
""" short + name
|
||||||
"""
|
"""
|
||||||
return '%(from)s - %(to)s | %(start_amount)s %(symbol)s - %(end_amount)s %(symbol)s [%(num)s]' % {
|
return ' '.join([
|
||||||
'from': Report.format_date(self.date_from, None)
|
Report.format_date(self.date_from, None)
|
||||||
if self.date_from is not None else '-',
|
if self.date_from is not None else '-',
|
||||||
'to': Report.format_date(self.date_to, None)
|
'-',
|
||||||
if self.date_to is not None else '-',
|
Report.format_date(self.date_to, None)
|
||||||
'start_amount': Report.format_number(
|
if self.date_to is not None else '-',
|
||||||
|
'|',
|
||||||
|
Report.format_number(
|
||||||
self.start_amount or 0.0, None,
|
self.start_amount or 0.0, None,
|
||||||
digits=getattr(self.currency, 'digits', 2)),
|
digits=getattr(self.currency, 'digits', 2)),
|
||||||
'end_amount': Report.format_number(
|
getattr(self.currency, 'symbol', '-'),
|
||||||
|
'-',
|
||||||
|
Report.format_number(
|
||||||
self.end_amount or 0.0, None,
|
self.end_amount or 0.0, None,
|
||||||
digits=getattr(self.currency, 'digits', 2)),
|
digits=getattr(self.currency, 'digits', 2)),
|
||||||
'symbol': getattr(self.currency, 'symbol', '-'),
|
getattr(self.currency, 'symbol', '-'),
|
||||||
'num': len(self.lines),
|
'[%(num)s]' % {'num': len(self.lines)},
|
||||||
}
|
])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_date_from(cls):
|
def default_date_from(cls):
|
||||||
|
@ -405,7 +410,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
|
||||||
if self.cashbook:
|
if self.cashbook:
|
||||||
if self.date_from is not None:
|
if self.date_from is not None:
|
||||||
reconciliations = Recon.search([
|
reconciliations = Recon.search([
|
||||||
('cashbook.id', '=', self.cashbook.id),
|
('cashbook', '=', self.cashbook.id),
|
||||||
('date_from', '<', self.date_from),
|
('date_from', '<', self.date_from),
|
||||||
], order=[('date_from', 'DESC')], limit=1)
|
], order=[('date_from', 'DESC')], limit=1)
|
||||||
if len(reconciliations) > 0:
|
if len(reconciliations) > 0:
|
||||||
|
@ -457,7 +462,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
|
||||||
|
|
||||||
# set date_from to date_to of predecessor
|
# set date_from to date_to of predecessor
|
||||||
recons = Recon.search([
|
recons = Recon.search([
|
||||||
('cashbook.id', '=', id_cashbook),
|
('cashbook', '=', id_cashbook),
|
||||||
], order=[('date_to', 'DESC')], limit=1)
|
], order=[('date_to', 'DESC')], limit=1)
|
||||||
if len(recons) > 0:
|
if len(recons) > 0:
|
||||||
values['date_from'] = recons[0].date_to
|
values['date_from'] = recons[0].date_to
|
||||||
|
@ -466,7 +471,7 @@ class Reconciliation(Workflow, ModelSQL, ModelView):
|
||||||
|
|
||||||
# set date_to to day of last 'checked'-booking in selected cashbook
|
# set date_to to day of last 'checked'-booking in selected cashbook
|
||||||
lines = Line.search([
|
lines = Line.search([
|
||||||
('cashbook.id', '=', id_cashbook),
|
('cashbook', '=', id_cashbook),
|
||||||
('state', '=', 'check'),
|
('state', '=', 'check'),
|
||||||
('reconciliation', '=', None),
|
('reconciliation', '=', None),
|
||||||
], order=[('date', 'DESC')], limit=1)
|
], order=[('date', 'DESC')], limit=1)
|
||||||
|
|
|
@ -97,7 +97,7 @@ full copyright notices and license terms. -->
|
||||||
<record model="ir.rule" id="rg_recon_write-1">
|
<record model="ir.rule" id="rg_recon_write-1">
|
||||||
<field name="domain" eval="['OR',
|
<field name="domain" eval="['OR',
|
||||||
('cashbook.owner.id', '=', Eval('user', {}).get('id', -1)),
|
('cashbook.owner.id', '=', Eval('user', {}).get('id', -1)),
|
||||||
('cashbook.reviewer.id', 'in', Eval('user', {}).get('groups', [])),
|
('cashbook.reviewer.id', 'in', Eval('groups', [])),
|
||||||
]" pyson="1"/>
|
]" pyson="1"/>
|
||||||
<field name="rule_group" ref="rg_recon_write"/>
|
<field name="rule_group" ref="rg_recon_write"/>
|
||||||
</record>
|
</record>
|
||||||
|
@ -119,7 +119,7 @@ full copyright notices and license terms. -->
|
||||||
</record>
|
</record>
|
||||||
<record model="ir.rule" id="rg_recon_read-1">
|
<record model="ir.rule" id="rg_recon_read-1">
|
||||||
<field name="domain" eval="[
|
<field name="domain" eval="[
|
||||||
('cashbook.observer.id', 'in', Eval('user', {}).get('groups', [])),
|
('cashbook.observer.id', 'in', Eval('groups', [])),
|
||||||
]" pyson="1"/>
|
]" pyson="1"/>
|
||||||
<field name="rule_group" ref="rg_recon_read"/>
|
<field name="rule_group" ref="rg_recon_read"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
68
setup.py
68
setup.py
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Always prefer setuptools over distutils
|
# Always prefer setuptools over distutils
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup
|
||||||
# To use a consistent encoding
|
# To use a consistent encoding
|
||||||
from codecs import open
|
from codecs import open
|
||||||
from os import path
|
from os import path
|
||||||
|
@ -36,11 +36,11 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
|
||||||
l2 = i.strip().split(';')
|
l2 = i.strip().split(';')
|
||||||
if len(l2) < 4:
|
if len(l2) < 4:
|
||||||
continue
|
continue
|
||||||
modversion[l2[0]] = {'min':l2[1], 'max':l2[2], 'prefix':l2[3]}
|
modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]}
|
||||||
|
|
||||||
# tryton-version
|
# tryton-version
|
||||||
major_version = 6
|
major_version = 6
|
||||||
minor_version = 0
|
minor_version = 8
|
||||||
|
|
||||||
requires = ['python-slugify']
|
requires = ['python-slugify']
|
||||||
for dep in info.get('depends', []):
|
for dep in info.get('depends', []):
|
||||||
|
@ -51,19 +51,21 @@ for dep in info.get('depends', []):
|
||||||
prefix = modversion[dep]['prefix']
|
prefix = modversion[dep]['prefix']
|
||||||
|
|
||||||
if len(modversion[dep]['max']) > 0:
|
if len(modversion[dep]['max']) > 0:
|
||||||
requires.append('%s_%s >= %s, <= %s' %
|
requires.append('%s_%s >= %s, <= %s' % (
|
||||||
(prefix, dep, modversion[dep]['min'], modversion[dep]['max']))
|
prefix, dep, modversion[dep]['min'],
|
||||||
else :
|
modversion[dep]['max']))
|
||||||
requires.append('%s_%s >= %s' %
|
else:
|
||||||
(prefix, dep, modversion[dep]['min']))
|
requires.append('%s_%s >= %s' % (
|
||||||
else :
|
prefix, dep, modversion[dep]['min']))
|
||||||
requires.append('%s_%s >= %s.%s, < %s.%s' %
|
else:
|
||||||
('trytond', dep, major_version, minor_version,
|
requires.append('%s_%s >= %s.%s, < %s.%s' % (
|
||||||
|
'trytond', dep, major_version, minor_version,
|
||||||
major_version, minor_version + 1))
|
major_version, minor_version + 1))
|
||||||
requires.append('trytond >= %s.%s, < %s.%s' %
|
requires.append('trytond >= %s.%s, < %s.%s' % (
|
||||||
(major_version, minor_version, major_version, minor_version + 1))
|
major_version, minor_version, major_version, minor_version + 1))
|
||||||
|
|
||||||
setup(name='%s_%s' % (PREFIX, MODULE),
|
setup(
|
||||||
|
name='%s_%s' % (PREFIX, MODULE),
|
||||||
version=info.get('version', '0.0.1'),
|
version=info.get('version', '0.0.1'),
|
||||||
description='Tryton module to add a cashbook.',
|
description='Tryton module to add a cashbook.',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
|
@ -74,21 +76,21 @@ setup(name='%s_%s' % (PREFIX, MODULE),
|
||||||
author_email='service@m-ds.de',
|
author_email='service@m-ds.de',
|
||||||
license='GPL-3',
|
license='GPL-3',
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'Environment :: Plugins',
|
'Environment :: Plugins',
|
||||||
'Framework :: Tryton',
|
'Framework :: Tryton',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'Intended Audience :: Customer Service',
|
'Intended Audience :: Customer Service',
|
||||||
'Intended Audience :: Information Technology',
|
'Intended Audience :: Information Technology',
|
||||||
'Intended Audience :: Financial and Insurance Industry',
|
'Intended Audience :: Financial and Insurance Industry',
|
||||||
'Topic :: Office/Business',
|
'Topic :: Office/Business',
|
||||||
'Topic :: Office/Business :: Financial :: Accounting',
|
'Topic :: Office/Business :: Financial :: Accounting',
|
||||||
'Natural Language :: German',
|
'Natural Language :: German',
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
],
|
],
|
||||||
|
|
||||||
keywords='tryton cashbook',
|
keywords='tryton cashbook',
|
||||||
|
@ -97,10 +99,10 @@ setup(name='%s_%s' % (PREFIX, MODULE),
|
||||||
'trytond.modules.%s' % MODULE,
|
'trytond.modules.%s' % MODULE,
|
||||||
],
|
],
|
||||||
package_data={
|
package_data={
|
||||||
'trytond.modules.%s' % MODULE: (info.get('xml', [])
|
'trytond.modules.%s' % MODULE: (info.get('xml', []) + [
|
||||||
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py',
|
'tryton.cfg', 'locale/*.po', 'tests/*.py',
|
||||||
'view/*.xml', 'icon/*.svg', 'docs/*.txt',
|
'view/*.xml', 'icon/*.svg', 'docs/*.txt',
|
||||||
'report/*.fods', 'versiondep.txt', 'README.rst']),
|
'report/*.fods', 'versiondep.txt', 'README.rst']),
|
||||||
},
|
},
|
||||||
|
|
||||||
install_requires=requires,
|
install_requires=requires,
|
||||||
|
|
|
@ -9,7 +9,7 @@ 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 .line import sel_bookingtype, STATES, DEPENDS
|
from .line import sel_bookingtype, sel_linestate, STATES, DEPENDS
|
||||||
from .book import sel_state_book
|
from .book import sel_state_book
|
||||||
from .mixin import SecondCurrencyMixin, MemCacheIndexMx
|
from .mixin import SecondCurrencyMixin, MemCacheIndexMx
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class SplitLine(SecondCurrencyMixin, MemCacheIndexMx, ModelSQL, ModelView):
|
||||||
selection=sel_bookingtype), 'on_change_with_bookingtype')
|
selection=sel_bookingtype), 'on_change_with_bookingtype')
|
||||||
state = fields.Function(fields.Selection(
|
state = fields.Function(fields.Selection(
|
||||||
string='State', readonly=True,
|
string='State', readonly=True,
|
||||||
selection=sel_linetype), 'on_change_with_state')
|
selection=sel_linestate), 'on_change_with_state')
|
||||||
cashbook = fields.Function(fields.Many2One(
|
cashbook = fields.Function(fields.Many2One(
|
||||||
string='Cashbook',
|
string='Cashbook',
|
||||||
readonly=True, states={'invisible': True}, model_name='cashbook.book'),
|
readonly=True, states={'invisible': True}, model_name='cashbook.book'),
|
||||||
|
|
|
@ -81,7 +81,7 @@ full copyright notices and license terms. -->
|
||||||
<record model="ir.rule" id="rg_split_write-1">
|
<record model="ir.rule" id="rg_split_write-1">
|
||||||
<field name="domain" eval="['OR',
|
<field name="domain" eval="['OR',
|
||||||
('line.cashbook.owner.id', '=', Eval('user', {}).get('id', -1)),
|
('line.cashbook.owner.id', '=', Eval('user', {}).get('id', -1)),
|
||||||
('line.cashbook.reviewer.id', 'in', Eval('user', {}).get('groups', [])),
|
('line.cashbook.reviewer.id', 'in', Eval('groups', [])),
|
||||||
]" pyson="1"/>
|
]" pyson="1"/>
|
||||||
<field name="rule_group" ref="rg_split_write"/>
|
<field name="rule_group" ref="rg_split_write"/>
|
||||||
</record>
|
</record>
|
||||||
|
@ -103,7 +103,7 @@ full copyright notices and license terms. -->
|
||||||
</record>
|
</record>
|
||||||
<record model="ir.rule" id="rg_split_read-1">
|
<record model="ir.rule" id="rg_split_read-1">
|
||||||
<field name="domain" eval="[
|
<field name="domain" eval="[
|
||||||
('line.cashbook.observer.id', 'in', Eval('user', {}).get('groups', [])),
|
('line.cashbook.observer.id', 'in', Eval('groups', [])),
|
||||||
]" pyson="1"/>
|
]" pyson="1"/>
|
||||||
<field name="rule_group" ref="rg_split_read"/>
|
<field name="rule_group" ref="rg_split_read"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
@ -73,6 +73,26 @@ class SplitLineTestCase(object):
|
||||||
book.lines[0].splitlines[1].rec_name,
|
book.lines[0].splitlines[1].rec_name,
|
||||||
'Rev/Sp|6.00 usd|from cashbook [Cat1]')
|
'Rev/Sp|6.00 usd|from cashbook [Cat1]')
|
||||||
|
|
||||||
|
# check function fields
|
||||||
|
self.assertEqual(
|
||||||
|
book.lines[0].splitlines[0].category_view,
|
||||||
|
'Cat1')
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].date, date(2022, 5, 1))
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].target.rec_name, 'Cat1')
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].currency.rec_name, 'usd')
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].currency_digits, 2)
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].bookingtype, 'spin')
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].state, 'edit')
|
||||||
|
self.assertEqual(
|
||||||
|
book.lines[0].splitlines[0].cashbook.rec_name,
|
||||||
|
'Book 1 | 11.00 usd | Open')
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].feature, 'gen')
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].booktransf_feature, None)
|
||||||
|
self.assertEqual(book.lines[0].splitlines[0].state_cashbook, 'open')
|
||||||
|
self.assertEqual(
|
||||||
|
book.lines[0].splitlines[0].owner_cashbook.rec_name,
|
||||||
|
'Administrator')
|
||||||
|
|
||||||
@with_transaction()
|
@with_transaction()
|
||||||
def test_splitline_category_and_transfer(self):
|
def test_splitline_category_and_transfer(self):
|
||||||
""" add book, line, two split-lines,
|
""" add book, line, two split-lines,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[tryton]
|
[tryton]
|
||||||
version=6.0.0
|
version=6.8.32
|
||||||
depends:
|
depends:
|
||||||
res
|
res
|
||||||
currency
|
currency
|
||||||
|
|
17
types.py
17
types.py
|
@ -3,7 +3,7 @@
|
||||||
# The COPYRIGHT file at the top level of this repository contains the
|
# The COPYRIGHT file at the top level of this repository contains the
|
||||||
# full copyright notices and license terms.
|
# full copyright notices and license terms.
|
||||||
|
|
||||||
from trytond.model import ModelView, ModelSQL, fields, Unique
|
from trytond.model import ModelView, ModelSQL, fields, Unique, Index
|
||||||
from trytond.transaction import Transaction
|
from trytond.transaction import Transaction
|
||||||
from trytond.i18n import gettext
|
from trytond.i18n import gettext
|
||||||
|
|
||||||
|
@ -28,8 +28,15 @@ class Type(ModelSQL, ModelView):
|
||||||
cls._order.insert(0, ('name', 'ASC'))
|
cls._order.insert(0, ('name', 'ASC'))
|
||||||
t = cls.__table__()
|
t = cls.__table__()
|
||||||
cls._sql_constraints.extend([
|
cls._sql_constraints.extend([
|
||||||
('code_uniq', Unique(t, t.short), 'cashbook.msg_type_short_unique'),
|
('code_uniq',
|
||||||
|
Unique(t, t.short),
|
||||||
|
'cashbook.msg_type_short_unique'),
|
||||||
])
|
])
|
||||||
|
cls._sql_indexes.update({
|
||||||
|
Index(
|
||||||
|
t,
|
||||||
|
(t.feature, Index.Equality())),
|
||||||
|
})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_feature(cls):
|
def default_feature(cls):
|
||||||
|
@ -48,8 +55,7 @@ class Type(ModelSQL, ModelView):
|
||||||
"""
|
"""
|
||||||
return '%(short)s - %(name)s' % {
|
return '%(short)s - %(name)s' % {
|
||||||
'short': self.short or '-',
|
'short': self.short or '-',
|
||||||
'name': self.name or '-',
|
'name': self.name or '-'}
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_rec_name(cls, name, clause):
|
def search_rec_name(cls, name, clause):
|
||||||
|
@ -58,8 +64,7 @@ class Type(ModelSQL, ModelView):
|
||||||
return [
|
return [
|
||||||
'OR',
|
'OR',
|
||||||
('name',) + tuple(clause[1:]),
|
('name',) + tuple(clause[1:]),
|
||||||
('short',) + tuple(clause[1:]),
|
('short',) + tuple(clause[1:])]
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default_company():
|
def default_company():
|
||||||
|
|
|
@ -5,5 +5,5 @@ full copyright notices and license terms. -->
|
||||||
<tree keyword_open="1">
|
<tree keyword_open="1">
|
||||||
<field name="rec_name" expand="1"/>
|
<field name="rec_name" expand="1"/>
|
||||||
<field name="balance" sum="Balance" symbol="currency"/>
|
<field name="balance" sum="Balance" symbol="currency"/>
|
||||||
<field name="state"/>
|
<field name="state" optional="0"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
|
@ -5,7 +5,7 @@ full copyright notices and license terms. -->
|
||||||
<tree keyword_open="1" tree_state="1">
|
<tree keyword_open="1" tree_state="1">
|
||||||
<field name="name" expand="1"/>
|
<field name="name" expand="1"/>
|
||||||
<field name="balance" symbol="currency"/>
|
<field name="balance" symbol="currency"/>
|
||||||
<field name="state"/>
|
<field name="state" optional="0"/>
|
||||||
<field name="parent" tree_invisible="1"/>
|
<field name="parent" tree_invisible="1"/>
|
||||||
<field name="childs" tree_invisible="1"/>
|
<field name="childs" tree_invisible="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
|
@ -4,14 +4,14 @@ The COPYRIGHT file at the top level of this repository contains the
|
||||||
full copyright notices and license terms. -->
|
full copyright notices and license terms. -->
|
||||||
<tree>
|
<tree>
|
||||||
<field name="cashbook" tree_invisible="1"/>
|
<field name="cashbook" tree_invisible="1"/>
|
||||||
<field name="number"/>
|
<field name="number" optional="0"/>
|
||||||
<field name="date"/>
|
<field name="date"/>
|
||||||
<field name="payee"/>
|
<field name="payee"/>
|
||||||
<field name="category_view"/>
|
<field name="category_view"/>
|
||||||
<field name="descr_short" expand="1"/>
|
<field name="descr_short" expand="1"/>
|
||||||
<field name="credit" sum="Credit"/>
|
<field name="credit" sum="Credit" optional="0"/>
|
||||||
<field name="debit" sum="Debit"/>
|
<field name="debit" sum="Debit" optional="0"/>
|
||||||
<field name="balance"/>
|
<field name="balance" optional="0"/>
|
||||||
<field name="state"/>
|
<field name="state" optional="0"/>
|
||||||
<button name="wfcheck"/>
|
<button name="wfcheck"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
|
@ -4,13 +4,13 @@ The COPYRIGHT file at the top level of this repository contains the
|
||||||
full copyright notices and license terms. -->
|
full copyright notices and license terms. -->
|
||||||
<tree>
|
<tree>
|
||||||
<field name="cashbook" tree_invisible="1"/>
|
<field name="cashbook" tree_invisible="1"/>
|
||||||
<field name="number"/>
|
<field name="number" optional="0"/>
|
||||||
<field name="date"/>
|
<field name="date" optional="0"/>
|
||||||
<field name="payee"/>
|
<field name="payee" optional="0"/>
|
||||||
<field name="category_view"/>
|
<field name="category_view" optional="0"/>
|
||||||
<field name="descr_short" expand="1"/>
|
<field name="descr_short" expand="1" optional="0"/>
|
||||||
<field name="credit" sum="Credit"/>
|
<field name="credit" sum="Credit" optional="0"/>
|
||||||
<field name="debit" sum="Debit"/>
|
<field name="debit" sum="Debit" optional="0"/>
|
||||||
<field name="state"/>
|
<field name="state" optional="0"/>
|
||||||
<button name="wfrecon"/>
|
<button name="wfrecon"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
|
@ -4,11 +4,11 @@ The COPYRIGHT file at the top level of this repository contains the
|
||||||
full copyright notices and license terms. -->
|
full copyright notices and license terms. -->
|
||||||
<tree>
|
<tree>
|
||||||
<field name="cashbook"/>
|
<field name="cashbook"/>
|
||||||
<field name="date"/>
|
<field name="date" optional="0"/>
|
||||||
<field name="date_from"/>
|
<field name="date_from" optional="0"/>
|
||||||
<field name="date_to"/>
|
<field name="date_to" optional="0"/>
|
||||||
<field name="start_amount"/>
|
<field name="start_amount" optional="0"/>
|
||||||
<field name="end_amount"/>
|
<field name="end_amount" optional="0"/>
|
||||||
<field name="lines"/>
|
<field name="lines" optional="0"/>
|
||||||
<field name="state"/>
|
<field name="state" optional="0"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
|
@ -8,7 +8,7 @@ full copyright notices and license terms. -->
|
||||||
<field name="splittype"/>
|
<field name="splittype"/>
|
||||||
<field name="category"/>
|
<field name="category"/>
|
||||||
<field name="booktransf"/>
|
<field name="booktransf"/>
|
||||||
<field name="description" expand="1"/>
|
<field name="description" expand="1" optional="0"/>
|
||||||
<field name="amount" sum="Amount" symbol="currency"/>
|
<field name="amount" sum="Amount" symbol="currency" optional="0"/>
|
||||||
<field name="amount_2nd_currency" symbol="currency2nd"/>
|
<field name="amount_2nd_currency" symbol="currency2nd" optional="0"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
|
@ -50,7 +50,7 @@ class EnterBookingStart(ModelView):
|
||||||
string='Category',
|
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')),
|
||||||
'required': Eval('bookingtype', '').in_(['in', 'out']),
|
'required': Eval('bookingtype', '').in_(['in', 'out']),
|
||||||
'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
|
'invisible': ~Eval('bookingtype', '').in_(['in', 'out']),
|
||||||
},
|
},
|
||||||
|
@ -90,7 +90,8 @@ class EnterBookingStart(ModelView):
|
||||||
|
|
||||||
if self.bookingtype:
|
if self.bookingtype:
|
||||||
if self.category:
|
if self.category:
|
||||||
if self.bookingtype not 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')
|
||||||
|
@ -152,7 +153,7 @@ class EnterBookingWizard(Wizard):
|
||||||
'cashbooks': [x.id for x in Cashbook.search([
|
'cashbooks': [x.id for x in Cashbook.search([
|
||||||
('state', '=', 'open'),
|
('state', '=', 'open'),
|
||||||
('btype', '!=', None),
|
('btype', '!=', None),
|
||||||
('owner.id', '=', Transaction().user),
|
('owner', '=', Transaction().user),
|
||||||
('id', 'in', book_ids),
|
('id', 'in', book_ids),
|
||||||
])],
|
])],
|
||||||
'bookingtype': getattr(self.start, 'bookingtype', 'out'),
|
'bookingtype': getattr(self.start, 'bookingtype', 'out'),
|
||||||
|
|
|
@ -55,7 +55,7 @@ class RunCbReportStart(ModelView):
|
||||||
|
|
||||||
if self.cashbook:
|
if self.cashbook:
|
||||||
recons = Recon2.search([
|
recons = Recon2.search([
|
||||||
('cashbook.id', '=', self.cashbook.id),
|
('cashbook', '=', self.cashbook.id),
|
||||||
], order=[('date_from', 'DESC')])
|
], order=[('date_from', 'DESC')])
|
||||||
return [x.id for x in recons]
|
return [x.id for x in recons]
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ class RunCbReport(Wizard):
|
||||||
result['cashbook'] = result['cashbooks'][0]
|
result['cashbook'] = result['cashbooks'][0]
|
||||||
|
|
||||||
recons = Recon2.search([
|
recons = Recon2.search([
|
||||||
('cashbook.id', '=', result['cashbook']),
|
('cashbook', '=', result['cashbook']),
|
||||||
], order=[('date_from', 'DESC')])
|
], order=[('date_from', 'DESC')])
|
||||||
if len(recons) > 0:
|
if len(recons) > 0:
|
||||||
result['reconciliations'] = [x.id for x in recons]
|
result['reconciliations'] = [x.id for x in recons]
|
||||||
|
|
Loading…
Reference in a new issue