lino 24.10.2__py3-none-any.whl → 24.10.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lino/__init__.py +1 -1
- lino/api/dd.py +1 -0
- lino/core/store.py +4 -1
- lino/core/utils.py +30 -9
- lino/help_texts.py +9 -0
- lino/mixins/periods.py +1 -1
- lino/modlib/languages/__init__.py +1 -1
- lino/modlib/languages/models.py +1 -1
- lino/modlib/periods/__init__.py +20 -0
- lino/modlib/periods/fixtures/std.py +22 -0
- lino/modlib/periods/mixins.py +126 -0
- lino/modlib/periods/models.py +242 -0
- lino/utils/ranges.py +5 -15
- {lino-24.10.2.dist-info → lino-24.10.3.dist-info}/METADATA +1 -1
- {lino-24.10.2.dist-info → lino-24.10.3.dist-info}/RECORD +18 -14
- {lino-24.10.2.dist-info → lino-24.10.3.dist-info}/WHEEL +0 -0
- {lino-24.10.2.dist-info → lino-24.10.3.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-24.10.2.dist-info → lino-24.10.3.dist-info}/licenses/COPYING +0 -0
lino/__init__.py
CHANGED
lino/api/dd.py
CHANGED
@@ -25,6 +25,7 @@ from lino.core.utils import obj2str
|
|
25
25
|
from lino.core.utils import obj2unicode
|
26
26
|
from lino.core.utils import range_filter
|
27
27
|
from lino.core.utils import inrange_filter
|
28
|
+
from lino.core.utils import overlap_range_filter
|
28
29
|
from lino.core.utils import full_model_name
|
29
30
|
|
30
31
|
from lino.core.model import Model
|
lino/core/store.py
CHANGED
@@ -821,7 +821,10 @@ class DateStoreField(StoreField):
|
|
821
821
|
try:
|
822
822
|
return datetime.date(*settings.SITE.parse_date(v))
|
823
823
|
except Exception as e:
|
824
|
-
|
824
|
+
# The front end is responsible for validating before submitting.
|
825
|
+
# Here it's too late to complain.
|
826
|
+
return None
|
827
|
+
# raise Warning("Invalid date '{}'' : {}".format(v, e))
|
825
828
|
|
826
829
|
def format_value(self, ar, v):
|
827
830
|
"""Return a plain textual representation of this value as a unicode
|
lino/core/utils.py
CHANGED
@@ -23,6 +23,7 @@ from django.core import exceptions
|
|
23
23
|
from django.http import QueryDict
|
24
24
|
|
25
25
|
from lino.utils.html import E, assert_safe, tostring
|
26
|
+
from lino.utils.ranges import isrange
|
26
27
|
|
27
28
|
from django.core.validators import validate_email, ValidationError, URLValidator
|
28
29
|
|
@@ -309,21 +310,41 @@ def range_filter(value, f1, f2):
|
|
309
310
|
q2 = Q(**{f2 + "__isnull": True}) | Q(**{f2 + "__gte": value})
|
310
311
|
return Q(q1, q2)
|
311
312
|
|
312
|
-
|
313
313
|
def inrange_filter(fld, rng, **kw):
|
314
314
|
"""Assuming a database model with a field named `fld`, return a Q
|
315
|
-
object to select
|
316
|
-
|
317
|
-
two items.
|
318
|
-
|
315
|
+
object to select the rows having value for `fld` within the given range `rng`.
|
316
|
+
`rng` must be a tuple or list with two items.
|
319
317
|
"""
|
320
|
-
|
318
|
+
|
319
|
+
# assert rng[0] <= rng[1]
|
321
320
|
kw[fld + "__isnull"] = False
|
322
|
-
|
323
|
-
|
321
|
+
if rng[0] is not None:
|
322
|
+
kw[fld + "__gte"] = rng[0]
|
323
|
+
if rng[1] is not None:
|
324
|
+
kw[fld + "__lte"] = rng[1]
|
324
325
|
return Q(**kw)
|
325
326
|
|
326
327
|
|
328
|
+
def overlap_range_filter(sv, ev, f1, f2, **kw):
|
329
|
+
"""
|
330
|
+
|
331
|
+
Return a Q object to select all objects having fields `f1` and `f2` define a
|
332
|
+
range that overlaps with the range specified by `sv` (start value) and `ev`
|
333
|
+
(end value).
|
334
|
+
|
335
|
+
For example, when I specify filter parameters `Date from` 2024-02-21 and
|
336
|
+
`until` 2024-03-12 in a :class:`lino.modlib.periods.StoredPeriods` table, I
|
337
|
+
want both February and March. Tested examples see
|
338
|
+
:ref:`dg.plugins.periods.period_filter`.
|
339
|
+
|
340
|
+
"""
|
341
|
+
# if not isrange(rng[0], rng[1]):
|
342
|
+
# raise ValueError(f"{rng} is not a valid range")
|
343
|
+
if not ev:
|
344
|
+
ev = sv
|
345
|
+
return Q(**{f1+"__lte" : ev, f2+"__gte" : sv})
|
346
|
+
|
347
|
+
|
327
348
|
def babelkw(*args, **kw):
|
328
349
|
return settings.SITE.babelkw(*args, **kw)
|
329
350
|
|
@@ -679,7 +700,7 @@ class ParameterPanel(object):
|
|
679
700
|
|
680
701
|
Subclassed e.g. by
|
681
702
|
:class:`lino.mixins.periods.ObservedDateRange`.
|
682
|
-
:class:`lino_xl.lib.accounting.
|
703
|
+
:class:`lino_xl.lib.accounting.PeriodRangeParameters`.
|
683
704
|
"""
|
684
705
|
|
685
706
|
def __init__(self, **kw):
|
lino/help_texts.py
CHANGED
@@ -376,6 +376,15 @@ help_texts = {
|
|
376
376
|
'lino.modlib.linod.SystemTask.log_level' : _("""The logging level to apply when running this task."""),
|
377
377
|
'lino.modlib.linod.SystemTask.run' : _("""Performs a routine job."""),
|
378
378
|
'lino.modlib.linod.SystemTasks' : _("""The default actor for the SystemTask model."""),
|
379
|
+
'lino.modlib.periods.StoredPeriod' : _("""An accounting period is the smallest time slice to be declared in accounting reports. Usually it corresponds to one month. But there are except for some small companies which declare per quarter."""),
|
380
|
+
'lino.modlib.periods.StoredYears' : _("""The fiscal years available in this database."""),
|
381
|
+
'lino.modlib.periods.PeriodRange' : _("""Model mixin for objects that cover a range of accounting periods."""),
|
382
|
+
'lino.modlib.periods.PeriodRange.start_period' : _("""The first period of the range to cover."""),
|
383
|
+
'lino.modlib.periods.PeriodRange.end_period' : _("""The last period of the range to cover."""),
|
384
|
+
'lino.modlib.periods.PeriodRangeObservable' : _("""Model mixin for objects that can be filtered by a range of accounting periods. This adds two parameter fields start_period and end_period to every table on this model."""),
|
385
|
+
'lino.modlib.periods.StoredPeriodRange' : _("""A parameter panel with two fields:"""),
|
386
|
+
'lino.modlib.periods.StoredPeriodRange.start_period' : _("""Start of observed period range."""),
|
387
|
+
'lino.modlib.periods.StoredPeriodRange.end_period' : _("""Optional end of observed period range. Leave empty to consider only the Start period."""),
|
379
388
|
'lino.modlib.publisher.Page' : _("""The Django model that represents a content page."""),
|
380
389
|
'lino.modlib.publisher.PublisherBuildMethod' : _("""This deserves better documentation."""),
|
381
390
|
'lino.modlib.publisher.Publishable' : _("""Model mixin to add to models that are potentially publishable."""),
|
lino/mixins/periods.py
CHANGED
lino/modlib/languages/models.py
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Copyright 2008-2024 Rumma & Ko Ltd
|
2
|
+
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
3
|
+
from lino import ad, _
|
4
|
+
|
5
|
+
|
6
|
+
class Plugin(ad.Plugin):
|
7
|
+
|
8
|
+
verbose_name = _("Stored periods")
|
9
|
+
period_name = _("Accounting period")
|
10
|
+
period_name_plural = _("Accounting periods")
|
11
|
+
year_name = _("Fiscal year")
|
12
|
+
year_name_plural = _("Fiscal years")
|
13
|
+
fix_y2k = False
|
14
|
+
start_year = 2012
|
15
|
+
|
16
|
+
def setup_config_menu(self, site, user_type, m, ar=None):
|
17
|
+
p = self.get_menu_group()
|
18
|
+
m = m.add_menu(p.app_label, p.verbose_name)
|
19
|
+
m.add_action("periods.StoredYears")
|
20
|
+
m.add_action("periods.StoredPeriods")
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
# Copyright 2012-2024 Rumma & Ko Ltd
|
3
|
+
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
|
+
|
5
|
+
import datetime
|
6
|
+
from django.conf import settings
|
7
|
+
from lino.api import dd, rt, _
|
8
|
+
|
9
|
+
start_year = dd.get_plugin_setting("periods", "start_year", None)
|
10
|
+
|
11
|
+
def objects():
|
12
|
+
StoredYear = rt.models.periods.StoredYear
|
13
|
+
|
14
|
+
cfg = dd.plugins.periods
|
15
|
+
site = settings.SITE
|
16
|
+
if site.the_demo_date is not None:
|
17
|
+
if start_year > site.the_demo_date.year:
|
18
|
+
raise Exception("plugins.periods.start_year is after the_demo_date")
|
19
|
+
today = site.the_demo_date or datetime.date.today()
|
20
|
+
for y in range(start_year, today.year + 6):
|
21
|
+
yield StoredYear.create_from_year(y)
|
22
|
+
# StoredYears.add_item(StoredYear.year2value(y), str(y))
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
# Copyright 2008-2024 Rumma & Ko Ltd
|
3
|
+
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
|
+
|
5
|
+
from django.db import models
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
7
|
+
|
8
|
+
from lino.api import dd, rt
|
9
|
+
from lino import mixins
|
10
|
+
from lino.mixins.periods import DateRange
|
11
|
+
from lino.mixins import Referrable
|
12
|
+
|
13
|
+
from lino.modlib.office.roles import OfficeStaff
|
14
|
+
|
15
|
+
|
16
|
+
class PeriodRange(dd.Model):
|
17
|
+
|
18
|
+
class Meta:
|
19
|
+
abstract = True
|
20
|
+
|
21
|
+
start_period = dd.ForeignKey(
|
22
|
+
'periods.StoredPeriod',
|
23
|
+
blank=True,
|
24
|
+
verbose_name=_("Start period"),
|
25
|
+
related_name="%(app_label)s_%(class)s_set_by_start_period")
|
26
|
+
|
27
|
+
end_period = dd.ForeignKey(
|
28
|
+
'periods.StoredPeriod',
|
29
|
+
blank=True,
|
30
|
+
null=True,
|
31
|
+
verbose_name=_("End period"),
|
32
|
+
related_name="%(app_label)s_%(class)s_set_by_end_period")
|
33
|
+
|
34
|
+
def get_period_filter(self, fieldname, **kwargs):
|
35
|
+
return rt.models.periods.StoredPeriod.get_period_filter(
|
36
|
+
fieldname, self.start_period, self.end_period, **kwargs)
|
37
|
+
|
38
|
+
|
39
|
+
class PeriodRangeObservable(dd.Model):
|
40
|
+
|
41
|
+
class Meta:
|
42
|
+
abstract = True
|
43
|
+
|
44
|
+
observable_period_prefix = ''
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
def setup_parameters(cls, fields):
|
48
|
+
fields.update(start_period=dd.ForeignKey(
|
49
|
+
'periods.StoredPeriod',
|
50
|
+
blank=True,
|
51
|
+
null=True,
|
52
|
+
help_text=_("Start of observed period range."),
|
53
|
+
verbose_name=_("Period from")))
|
54
|
+
fields.update(end_period=dd.ForeignKey(
|
55
|
+
'periods.StoredPeriod',
|
56
|
+
blank=True,
|
57
|
+
null=True,
|
58
|
+
help_text=_("Optional end of observed period range. "
|
59
|
+
"Leave empty to observe only the start period."),
|
60
|
+
verbose_name=_("Period until")))
|
61
|
+
super().setup_parameters(fields)
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def get_request_queryset(cls, ar, **kwargs):
|
65
|
+
qs = super().get_request_queryset(ar, **kwargs)
|
66
|
+
if (pv := ar.param_values) is None: return qs
|
67
|
+
if pv.start_period is not None:
|
68
|
+
fkw = dict()
|
69
|
+
fkw[cls.observable_period_prefix + "journal__preliminary"] = False
|
70
|
+
flt = rt.models.periods.StoredPeriod.get_period_filter(
|
71
|
+
cls.observable_period_prefix + "accounting_period",
|
72
|
+
pv.start_period, pv.end_period, **fkw)
|
73
|
+
qs = qs.filter(**flt)
|
74
|
+
return qs
|
75
|
+
|
76
|
+
@classmethod
|
77
|
+
def get_title_tags(cls, ar):
|
78
|
+
for t in super().get_title_tags(ar):
|
79
|
+
yield t
|
80
|
+
pv = ar.param_values
|
81
|
+
if pv.start_period is not None:
|
82
|
+
if pv.end_period is None:
|
83
|
+
yield str(pv.start_period)
|
84
|
+
else:
|
85
|
+
yield "{}..{}".format(pv.start_period, pv.end_period)
|
86
|
+
|
87
|
+
|
88
|
+
class PeriodRangeParameters(dd.ParameterPanel):
|
89
|
+
|
90
|
+
def __init__(self,
|
91
|
+
verbose_name_start=_("Period from"),
|
92
|
+
verbose_name_end=_("until"),
|
93
|
+
**kwargs):
|
94
|
+
kwargs.update(
|
95
|
+
start_period=dd.ForeignKey(
|
96
|
+
'periods.StoredPeriod',
|
97
|
+
blank=True,
|
98
|
+
null=True,
|
99
|
+
help_text=_("Start of observed period range"),
|
100
|
+
verbose_name=verbose_name_start),
|
101
|
+
end_period=dd.ForeignKey(
|
102
|
+
'periods.StoredPeriod',
|
103
|
+
blank=True,
|
104
|
+
null=True,
|
105
|
+
help_text=_("Optional end of observed period range. "
|
106
|
+
"Leave empty to consider only the Start period."),
|
107
|
+
verbose_name=verbose_name_end))
|
108
|
+
super().__init__(**kwargs)
|
109
|
+
|
110
|
+
def check_values(self, pv):
|
111
|
+
if not pv.start_period:
|
112
|
+
raise Warning(_("Select at least a start period"))
|
113
|
+
if pv.end_period:
|
114
|
+
if str(pv.start_period) > str(pv.end_period):
|
115
|
+
raise Warning(_("End period must be after start period"))
|
116
|
+
|
117
|
+
def get_title_tags(self, ar):
|
118
|
+
pv = ar.param_values
|
119
|
+
if pv.start_period:
|
120
|
+
if pv.end_period:
|
121
|
+
yield _("Periods {}...{}").format(pv.start_period,
|
122
|
+
pv.end_period)
|
123
|
+
else:
|
124
|
+
yield _("Period {}").format(pv.start_period)
|
125
|
+
else:
|
126
|
+
yield str(_("All periods"))
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
# Copyright 2008-2024 Rumma & Ko Ltd
|
3
|
+
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
|
+
|
5
|
+
import datetime
|
6
|
+
from django.db import models
|
7
|
+
from django.utils.translation import gettext_lazy as _
|
8
|
+
|
9
|
+
from lino.api import dd
|
10
|
+
from lino import mixins
|
11
|
+
from lino.utils import last_day_of_month
|
12
|
+
from lino.mixins.periods import DateRange
|
13
|
+
from lino.mixins import Referrable
|
14
|
+
|
15
|
+
from lino.modlib.office.roles import OfficeStaff
|
16
|
+
|
17
|
+
|
18
|
+
class PeriodStates(dd.Workflow):
|
19
|
+
pass
|
20
|
+
|
21
|
+
add = PeriodStates.add_item
|
22
|
+
add('10', _("Open"), 'open')
|
23
|
+
add('20', _("Closed"), 'closed')
|
24
|
+
|
25
|
+
|
26
|
+
class StoredYear(DateRange, Referrable):
|
27
|
+
|
28
|
+
class Meta:
|
29
|
+
app_label = 'periods'
|
30
|
+
verbose_name = _("Fiscal year")
|
31
|
+
verbose_name_plural = _("Fiscal years")
|
32
|
+
ordering = ['ref']
|
33
|
+
|
34
|
+
preferred_foreignkey_width = 10
|
35
|
+
|
36
|
+
state = PeriodStates.field(default='open')
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def get_simple_parameters(cls):
|
40
|
+
yield super().get_simple_parameters()
|
41
|
+
yield "state"
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def year2ref(cls, year):
|
45
|
+
if dd.plugins.periods.fix_y2k:
|
46
|
+
if year < 2000:
|
47
|
+
return str(year)[-2:]
|
48
|
+
elif year < 3000:
|
49
|
+
return chr(int(str(year)[-3:-1]) + 65) + str(year)[-1]
|
50
|
+
else:
|
51
|
+
raise Exception("20180827")
|
52
|
+
# elif year < 2010:
|
53
|
+
# return "A" + str(year)[-1]
|
54
|
+
# elif year < 2020:
|
55
|
+
# return "B" + str(year)[-1]
|
56
|
+
# elif year < 2030:
|
57
|
+
# return "C" + str(year)[-1]
|
58
|
+
# else:
|
59
|
+
# raise Exception(20160304)
|
60
|
+
# return str(year)[2:]
|
61
|
+
return str(year)
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def from_int(cls, year, *args):
|
65
|
+
ref = cls.year2ref(year)
|
66
|
+
return cls.get_by_ref(ref, *args)
|
67
|
+
|
68
|
+
@classmethod
|
69
|
+
def create_from_year(cls, year):
|
70
|
+
ref = cls.year2ref(year)
|
71
|
+
return cls(ref=ref,
|
72
|
+
start_date=datetime.date(year, 1, 1),
|
73
|
+
end_date=datetime.date(year, 12, 31))
|
74
|
+
# obj.full_clean()
|
75
|
+
# obj.save()
|
76
|
+
# return obj
|
77
|
+
|
78
|
+
@classmethod
|
79
|
+
def get_or_create_from_date(cls, date):
|
80
|
+
obj = cls.from_int(date.year, None)
|
81
|
+
if obj is None:
|
82
|
+
obj = cls.create_from_year(date.year)
|
83
|
+
obj.full_clean()
|
84
|
+
obj.save()
|
85
|
+
return obj
|
86
|
+
|
87
|
+
def __str__(self):
|
88
|
+
return self.ref
|
89
|
+
|
90
|
+
|
91
|
+
class StoredPeriod(DateRange, Referrable):
|
92
|
+
|
93
|
+
class Meta:
|
94
|
+
ordering = ['ref']
|
95
|
+
app_label = 'periods'
|
96
|
+
verbose_name = dd.plugins.periods.period_name
|
97
|
+
verbose_name_plural = dd.plugins.periods.period_name_plural
|
98
|
+
|
99
|
+
preferred_foreignkey_width = 10
|
100
|
+
|
101
|
+
state = PeriodStates.field(default='open')
|
102
|
+
year = dd.ForeignKey('periods.StoredYear', blank=True, null=True)
|
103
|
+
remark = models.CharField(_("Remark"), max_length=250, blank=True)
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
def get_simple_parameters(cls):
|
107
|
+
yield super().get_simple_parameters()
|
108
|
+
yield "state"
|
109
|
+
yield "year"
|
110
|
+
|
111
|
+
@classmethod
|
112
|
+
def get_request_queryset(cls, ar):
|
113
|
+
qs = super().get_request_queryset(ar)
|
114
|
+
if (pv := ar.param_values) is None: return qs
|
115
|
+
|
116
|
+
# if pv.start_date is None or pv.end_date is None:
|
117
|
+
# period = None
|
118
|
+
# else:
|
119
|
+
# period = (pv.start_date, pv.end_date)
|
120
|
+
# if period is not None:
|
121
|
+
# qs = qs.filter(dd.inrange_filter('start_date', period))
|
122
|
+
if pv.start_date or pv.end_date:
|
123
|
+
qs = qs.filter(dd.overlap_range_filter(
|
124
|
+
pv.start_date, pv.end_date, "start_date", "end_date"))
|
125
|
+
# if pv.start_date:
|
126
|
+
# qs = qs.filter(dd.range_filter(pv.start_date, 'start_date', 'end_date'))
|
127
|
+
# # qs = qs.filter(start_date__gte=pv.start_date)
|
128
|
+
# if pv.end_date:
|
129
|
+
# qs = qs.filter(dd.range_filter(pv.end_date, 'start_date', 'end_date'))
|
130
|
+
# qs = qs.filter(end_date__lte=pv.end_date)
|
131
|
+
return qs
|
132
|
+
|
133
|
+
@classmethod
|
134
|
+
def get_available_periods(cls, today):
|
135
|
+
"""Return a queryset of periods available for booking."""
|
136
|
+
if today is None: # added 20160531
|
137
|
+
today = dd.today()
|
138
|
+
fkw = dict(start_date__lte=today, end_date__gte=today)
|
139
|
+
return cls.objects.filter(**fkw)
|
140
|
+
|
141
|
+
@classmethod
|
142
|
+
def get_ref_for_date(cls, d):
|
143
|
+
"""Return a text to be used as :attr:`ref` for a new period.
|
144
|
+
|
145
|
+
Alternative implementation for usage on a site with movements
|
146
|
+
before year 2000::
|
147
|
+
|
148
|
+
@classmethod
|
149
|
+
def get_ref_for_date(cls, d):
|
150
|
+
if d.year < 2000:
|
151
|
+
y = str(d.year - 1900)
|
152
|
+
elif d.year < 2010:
|
153
|
+
y = "A" + str(d.year - 2000)
|
154
|
+
elif d.year < 2020:
|
155
|
+
y = "B" + str(d.year - 2010)
|
156
|
+
elif d.year < 2030:
|
157
|
+
y = "C" + str(d.year - 2020)
|
158
|
+
return y + "{:0>2}".format(d.month)
|
159
|
+
|
160
|
+
"""
|
161
|
+
y = StoredYear.year2ref(d.year)
|
162
|
+
return "{}-{:0>2}".format(y, d.month)
|
163
|
+
|
164
|
+
# if dd.plugins.periods.fix_y2k:
|
165
|
+
# return rt.models.periods.StoredYear.from_int(d.year).ref \
|
166
|
+
# + "{:0>2}".format(d.month)
|
167
|
+
|
168
|
+
# return "{0.year}-{0.month:0>2}".format(d)
|
169
|
+
|
170
|
+
# """The template used for building the :attr:`ref` of an
|
171
|
+
# :class:`StoredPeriod`.
|
172
|
+
#
|
173
|
+
# `Format String Syntax
|
174
|
+
# <https://docs.python.org/2/library/string.html#formatstrings>`_
|
175
|
+
#
|
176
|
+
# """
|
177
|
+
|
178
|
+
@classmethod
|
179
|
+
def get_periods_in_range(cls, p1, p2):
|
180
|
+
return cls.objects.filter(ref__gte=p1.ref, ref__lte=p2.ref)
|
181
|
+
|
182
|
+
@classmethod
|
183
|
+
def get_period_filter(cls, fieldname, p1, p2, **kwargs):
|
184
|
+
if p1 is None:
|
185
|
+
return kwargs
|
186
|
+
|
187
|
+
# ignore preliminary movements if a start_period is given:
|
188
|
+
# kwargs[voucher_prefix + "journal__preliminary"] = False
|
189
|
+
|
190
|
+
# accounting_period = voucher_prefix + "accounting_period"
|
191
|
+
|
192
|
+
if p2 is None:
|
193
|
+
kwargs[fieldname] = p1
|
194
|
+
else:
|
195
|
+
periods = cls.get_periods_in_range(p1, p2)
|
196
|
+
kwargs[fieldname + '__in'] = periods
|
197
|
+
return kwargs
|
198
|
+
|
199
|
+
@classmethod
|
200
|
+
def get_default_for_date(cls, d):
|
201
|
+
ref = cls.get_ref_for_date(d)
|
202
|
+
obj = cls.get_by_ref(ref, None)
|
203
|
+
if obj is None:
|
204
|
+
values = dict(start_date=d.replace(day=1))
|
205
|
+
values.update(end_date=last_day_of_month(d))
|
206
|
+
values.update(ref=ref)
|
207
|
+
obj = StoredPeriod(**values)
|
208
|
+
obj.full_clean()
|
209
|
+
obj.save()
|
210
|
+
return obj
|
211
|
+
|
212
|
+
def full_clean(self, *args, **kwargs):
|
213
|
+
if self.start_date is None:
|
214
|
+
self.start_date = dd.today().replace(day=1)
|
215
|
+
if not self.year:
|
216
|
+
self.year = StoredYear.get_or_create_from_date(self.start_date)
|
217
|
+
super().full_clean(*args, **kwargs)
|
218
|
+
|
219
|
+
def __str__(self):
|
220
|
+
if not self.ref:
|
221
|
+
return dd.obj2str(self)
|
222
|
+
# "{0} {1} (#{0})".format(self.pk, self.year)
|
223
|
+
return self.ref
|
224
|
+
|
225
|
+
|
226
|
+
StoredPeriod.set_widget_options('ref', width=6)
|
227
|
+
|
228
|
+
|
229
|
+
class StoredYears(dd.Table):
|
230
|
+
model = 'periods.StoredYear'
|
231
|
+
required_roles = dd.login_required(OfficeStaff)
|
232
|
+
column_names = "ref start_date end_date state *"
|
233
|
+
# detail_layout = """
|
234
|
+
# ref id
|
235
|
+
# start_date end_date
|
236
|
+
# """
|
237
|
+
|
238
|
+
class StoredPeriods(dd.Table):
|
239
|
+
required_roles = dd.login_required(OfficeStaff)
|
240
|
+
model = 'periods.StoredPeriod'
|
241
|
+
order_by = ["ref", "start_date", "year"]
|
242
|
+
column_names = "ref start_date end_date year state remark *"
|
lino/utils/ranges.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2009-
|
2
|
+
# Copyright 2009-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
"""The :mod:`lino.utils.ranges` module contains utility methods for
|
5
5
|
working with "ranges".
|
@@ -76,10 +76,10 @@ def overlap(a1, a2, b1, b2):
|
|
76
76
|
works with integers or other comparable values. The following
|
77
77
|
examples use integer values to be more readable.
|
78
78
|
|
79
|
-
Unlike the test presented
|
80
|
-
<
|
81
|
-
this works also with "open" ranges (the open end being indicated
|
82
|
-
|
79
|
+
Unlike the test presented in `Determing whether two ranges overlap
|
80
|
+
<https://post.bytes.com/forum/topic/python/398735-determing-whether-two-ranges-overlap?t=457949>`__,
|
81
|
+
this works also with "open" ranges (the open end being indicated by a `None`
|
82
|
+
value).
|
83
83
|
|
84
84
|
Types of constellations::
|
85
85
|
|
@@ -129,13 +129,3 @@ def overlap(a1, a2, b1, b2):
|
|
129
129
|
else:
|
130
130
|
return True
|
131
131
|
return True
|
132
|
-
|
133
|
-
|
134
|
-
def _test():
|
135
|
-
import doctest
|
136
|
-
|
137
|
-
doctest.testmod()
|
138
|
-
|
139
|
-
|
140
|
-
if __name__ == "__main__":
|
141
|
-
_test()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: lino
|
3
|
-
Version: 24.10.
|
3
|
+
Version: 24.10.3
|
4
4
|
Summary: A framework for writing desktop-like web applications using Django and ExtJS or React
|
5
5
|
Project-URL: Homepage, https://www.lino-framework.org
|
6
6
|
Project-URL: Repository, https://gitlab.com/lino-framework/lino
|
@@ -1,13 +1,13 @@
|
|
1
1
|
lino/.cvsignore,sha256=1vrrWoP-WD8hPfCszHHIiJEi8KUMRCt5WvoKB9TSB1k,28
|
2
2
|
lino/SciTEDirectory.properties,sha256=rCYi_e-6h8Yx5DwXhAa6MBPlVINcl6Vv9BQDYZV2_go,28
|
3
|
-
lino/__init__.py,sha256=
|
3
|
+
lino/__init__.py,sha256=xXbC44yjPzSBUvfGWf_PgZxIv2O6_VnDETw6O0AHX_4,5585
|
4
4
|
lino/ad.py,sha256=AQ-vJ4scac1mx3xegXezxnxyOQpV-a0q3VFMJSDbj2s,142
|
5
5
|
lino/apps.py,sha256=ECq-dPARDkuhngwNrcipse3b4Irj70HxJs44uWEZFc4,27
|
6
6
|
lino/hello.py,sha256=7-PJg7PnEiznyETqGjOwXcKh8rda0qLetpbS2gvRYy0,532
|
7
|
-
lino/help_texts.py,sha256=
|
7
|
+
lino/help_texts.py,sha256=jcABL4QqvwyJrzaTIqCZfSdeOd5LBu7QcV3curn3ArI,90338
|
8
8
|
lino/api/__init__.py,sha256=WmzHU-rHdZ68se_nI0SmepQTGE8-cd9tPpovHRH9aag,512
|
9
9
|
lino/api/ad.py,sha256=F6SrcKPRRalHKOZ7QLwsRWGq9hhykQIeo0b85cEk9NQ,314
|
10
|
-
lino/api/dd.py,sha256
|
10
|
+
lino/api/dd.py,sha256=-mjuoflZilnFM97we-N1H_sEf1TQiNs62JwZ5m4XV1s,7813
|
11
11
|
lino/api/doctest.py,sha256=fJ97w6rOO7AdlZ5DmgSHuIq-2LfOIbLT_mmslCdI2do,25561
|
12
12
|
lino/api/rt.py,sha256=auN4YoIthkXsjSN09OR6YeaNxSNfUO7E5KyTAvQtjh8,1400
|
13
13
|
lino/api/selenium.py,sha256=07BQuC6KW2tBUlIUcPGeCQHIfogz-wuHJqPshSzLDp0,9384
|
@@ -60,12 +60,12 @@ lino/core/requests.py,sha256=ZLsiyAI2zNt65nWV3ebrnZWaqarTpZSyS31ba7_a_9A,92177
|
|
60
60
|
lino/core/roles.py,sha256=PXwk436xUupxdbJcygRSYFu7ixfKjAJPQRUQ8sy0lB0,4425
|
61
61
|
lino/core/signals.py,sha256=0JT89mkjSbRm57QZcSI9DoThoKUGkyi-egNhuLUKEds,948
|
62
62
|
lino/core/site.py,sha256=sUuyTNi9IvJ-BwOW1LbAiRKmAE0xOYpHlJqsDH_ID5E,85443
|
63
|
-
lino/core/store.py,sha256=
|
63
|
+
lino/core/store.py,sha256=j3Dk2mSYJLKI6R-SSsuomBcF0cb3dnI87hysqBl5PHU,51499
|
64
64
|
lino/core/tables.py,sha256=FfA3KhGshsR1maNB6B11BbJ4m8cxxIaOhV0jvMxTaJA,25468
|
65
65
|
lino/core/urls.py,sha256=06QlmN1vpxjmb5snO3SPpP6lX1pMdE60bTiBiC77_vQ,2677
|
66
66
|
lino/core/user_types.py,sha256=0iSYmzr2M9v2Mn2y6hzAZeqareUT-gD7l3MfIPyG9ZI,867
|
67
67
|
lino/core/userprefs.py,sha256=cmufIS9xJErKDrw05uoWtQTUR6WRWIGkU1KdAqzNr5M,2850
|
68
|
-
lino/core/utils.py,sha256=
|
68
|
+
lino/core/utils.py,sha256=TW9eKS8Ua6tCGugBzJUS6gXiEAqI7EbB6VAiKwYshw8,35304
|
69
69
|
lino/core/views.py,sha256=b7q9XOjl8nkBV-ZRcR-swFjcC9BGyanRYM2CsB_A5xc,6959
|
70
70
|
lino/core/widgets.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
71
|
lino/core/workflows.py,sha256=9O5INnx3nPW73UmeEPWgN146clQSWwna3Jq_w1MThcg,10567
|
@@ -120,7 +120,7 @@ lino/mixins/__init__.py,sha256=cSFXzy8i0Akp9PmUSJz8E5t3twO3Z2lyCTyTCEqAQSU,8931
|
|
120
120
|
lino/mixins/dupable.py,sha256=lk1Lgqae1PXRP1Ej6AKg5n3tG1MLYu7Ww1Qy5kb46_E,10092
|
121
121
|
lino/mixins/duplicable.py,sha256=lQ5SEQmxPcOU9vETocgQI41uzgvunvXIy7HAnPeKDUI,4224
|
122
122
|
lino/mixins/human.py,sha256=YDIfIHHAaVmzd3uGsJp_vDvkaBWOp09I7i4AGNy9YsQ,13255
|
123
|
-
lino/mixins/periods.py,sha256=
|
123
|
+
lino/mixins/periods.py,sha256=VSxYLfO3b7aJm0ZbGYBwhA3sFuft2-OO4Qv2iR2cKVk,11269
|
124
124
|
lino/mixins/polymorphic.py,sha256=jzpfnzdDChvh1Ua16mdCQQOmoG-7Zyz69UOpcdqIiSA,9391
|
125
125
|
lino/mixins/printable.py,sha256=4U8M1lrTjUeuaPwrcWoanCBo53iAxiNpSTsVctI-gI0,199
|
126
126
|
lino/mixins/ref.py,sha256=gMWewqZjydq02LRDYY30t5EDWBbtecCOAoVU5IX1ayc,5249
|
@@ -3525,8 +3525,8 @@ lino/modlib/jinja/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
3525
3525
|
lino/modlib/jinja/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3526
3526
|
lino/modlib/jinja/management/commands/showsettings.py,sha256=nM5PNWXzbNdhnyH1b05iB__qwBdtPJ14T-Gt5jqGkww,452
|
3527
3527
|
lino/modlib/jinja/management/commands/status.py,sha256=REApUMUqYgQY_50RwfAFbFBGXmn4UZGtLJMOncmOQHg,609
|
3528
|
-
lino/modlib/languages/__init__.py,sha256=
|
3529
|
-
lino/modlib/languages/models.py,sha256=
|
3528
|
+
lino/modlib/languages/__init__.py,sha256=RUPzAiUVGuJXBWVWSomkbMm-f5OXBagZShfx4UyaUIw,1008
|
3529
|
+
lino/modlib/languages/models.py,sha256=tHF14XZKwx1te3DLbdsdogFLeyLuCWz_WExA-OU6VFo,810
|
3530
3530
|
lino/modlib/languages/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3531
3531
|
lino/modlib/languages/fixtures/all_languages.py,sha256=L_EmmcLVPKXcFB8uJITN3rWxNJ43CVNTKj5nbSXPK54,20941
|
3532
3532
|
lino/modlib/languages/fixtures/few_languages.py,sha256=nhhfS4bV9x2yvtGlWFX5j7pC40-c31bFInGvlHxmT4Y,846
|
@@ -3572,6 +3572,10 @@ lino/modlib/odata/views.py,sha256=ssyYYIyGoxXtR3hKnIa-ZBPLb6vxEU5C6XUUQ1L6Z70,72
|
|
3572
3572
|
lino/modlib/odata/config/odata/csdl.xml,sha256=KFDImzUA4YI6PL-i5AHnm22eBMLTw-K2iljslDJFQH8,296
|
3573
3573
|
lino/modlib/office/__init__.py,sha256=rOKD9zKt-VBvFFqoHBioNp299Igb1Sk2n466iCt6ASQ,287
|
3574
3574
|
lino/modlib/office/roles.py,sha256=oQWTZ-_9_vssc9QW5A0YspMjG_V5gCiHSU0eE0AIH30,356
|
3575
|
+
lino/modlib/periods/__init__.py,sha256=Q94txH81IrZR23X00rAbR3BUXDYwfUafnURrWzqEmlg,651
|
3576
|
+
lino/modlib/periods/mixins.py,sha256=IEjeeenFJD9HItK2n1wmsz1KTC9apFb1u87Mz-bhlY4,4266
|
3577
|
+
lino/modlib/periods/models.py,sha256=tk-CyIqii4s5gj9KBatvU_sAaUseeL6hdRbYDTlItVs,7522
|
3578
|
+
lino/modlib/periods/fixtures/std.py,sha256=c7K5xYHgC6AJT59W77MSRM2WS5ut58j55hoO_u9frJ4,788
|
3575
3579
|
lino/modlib/printing/__init__.py,sha256=u1fq44d073-IDH_t8hWs1sQdlAHdsCP85sfEOMSW5L4,689
|
3576
3580
|
lino/modlib/printing/actions.py,sha256=ud56ri8NAK0U8d8vVrhv-GTE8WD8jiUidpdXGrmVPlA,11632
|
3577
3581
|
lino/modlib/printing/choicelists.py,sha256=KzcFD-WjXIXiqLY-gVuuOvE0pueJuV7t5IMgUb4I2P0,9607
|
@@ -4620,7 +4624,7 @@ lino/utils/pdf.py,sha256=caISmnrrADN1MTK_GIiasfkVUyqhTEuK9mtoMf_S7qU,688
|
|
4620
4624
|
lino/utils/pythontest.py,sha256=AA9i3CEWgpbJKNTkxc0Gz94sy1R1MBpyinqrlahKKno,4954
|
4621
4625
|
lino/utils/pyuca.py,sha256=YSLpCYEMqKUhx1y0q-ML8Ryu2ARDFO2HyIFxz1DzYEY,4344
|
4622
4626
|
lino/utils/quantities.py,sha256=m_oi647uCTf2NhhQo6ytphZa2jQq1QXIUcts-cLkEJM,7367
|
4623
|
-
lino/utils/ranges.py,sha256=
|
4627
|
+
lino/utils/ranges.py,sha256=wVsZMe3dlaRYWWELXCGSK2DvjBfW4eLbAwpQVYqzj3g,3303
|
4624
4628
|
lino/utils/report.py,sha256=5l1NkZpcZyd1zpg37MB4Doly9dH9KHBEFarp9Q6vh4w,7983
|
4625
4629
|
lino/utils/requests.py,sha256=vXxsC51FK9FgrqmF9dy3MsmUqJINjSQ_4AmvBiQbGEk,1337
|
4626
4630
|
lino/utils/restify.py,sha256=QMVQXrmjDl6PPC7HJJGeDm125pcvrZKvcMpiscaPrsw,14066
|
@@ -4639,8 +4643,8 @@ lino/utils/xml.py,sha256=4Z44W1e5HvTVrU8erkohgnwqY-5Cr2NHywaAJ5OgRvw,989
|
|
4639
4643
|
lino/utils/mldbc/__init__.py,sha256=QqWRlzeXaOmFfbCk-vTY3SZMn1-FCf67XnpZdd_Nim0,1134
|
4640
4644
|
lino/utils/mldbc/fields.py,sha256=tAX8G5UKigr9c6g0F3ARIjZZtg406mdaZ--PWSbiH9E,2873
|
4641
4645
|
lino/utils/mldbc/mixins.py,sha256=CkYe5jDa7xp9fJq_V8zcZf8ocxgIjUgHc9KZccvA_Yw,1945
|
4642
|
-
lino-24.10.
|
4643
|
-
lino-24.10.
|
4644
|
-
lino-24.10.
|
4645
|
-
lino-24.10.
|
4646
|
-
lino-24.10.
|
4646
|
+
lino-24.10.3.dist-info/METADATA,sha256=OEW_3rFYAWtyG5d4N_-peNOZE4NuzcNH4Tg51ldh6YQ,42535
|
4647
|
+
lino-24.10.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
4648
|
+
lino-24.10.3.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
|
4649
|
+
lino-24.10.3.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
4650
|
+
lino-24.10.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|