lino 24.10.2__py3-none-any.whl → 24.11.0__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/api/doctest.py +11 -10
- lino/api/rt.py +2 -3
- lino/config/admin_main_base.html +2 -2
- lino/core/actors.py +68 -33
- lino/core/choicelists.py +2 -2
- lino/core/dashboard.py +2 -1
- lino/core/dbtables.py +3 -3
- lino/core/elems.py +8 -4
- lino/core/fields.py +11 -3
- lino/core/kernel.py +4 -5
- lino/core/layouts.py +1 -1
- lino/core/model.py +1 -9
- lino/core/plugin.py +1 -0
- lino/core/renderer.py +19 -19
- lino/core/requests.py +73 -38
- lino/core/site.py +5 -45
- lino/core/store.py +16 -16
- lino/core/tables.py +0 -17
- lino/core/utils.py +58 -10
- lino/core/views.py +2 -1
- lino/help_texts.py +13 -0
- lino/management/commands/show.py +2 -4
- lino/mixins/periods.py +10 -4
- lino/mixins/polymorphic.py +3 -3
- lino/mixins/ref.py +6 -3
- lino/modlib/checkdata/__init__.py +3 -3
- lino/modlib/extjs/ext_renderer.py +1 -1
- lino/modlib/languages/__init__.py +1 -1
- lino/modlib/languages/models.py +1 -1
- lino/modlib/linod/consumers.py +2 -3
- lino/modlib/memo/mixins.py +1 -1
- lino/modlib/periods/__init__.py +31 -0
- lino/modlib/periods/fixtures/std.py +23 -0
- lino/modlib/periods/mixins.py +126 -0
- lino/modlib/periods/models.py +246 -0
- lino/modlib/printing/actions.py +2 -0
- lino/modlib/publisher/ui.py +2 -2
- lino/modlib/system/__init__.py +0 -2
- lino/modlib/system/choicelists.py +55 -1
- lino/modlib/system/models.py +1 -0
- lino/modlib/users/models.py +2 -2
- lino/modlib/weasyprint/__init__.py +2 -0
- lino/utils/__init__.py +8 -9
- lino/utils/djangotest.py +2 -1
- lino/utils/html.py +31 -0
- lino/utils/ranges.py +5 -15
- {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/METADATA +1 -1
- {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/RECORD +53 -49
- {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/WHEEL +0 -0
- {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/licenses/COPYING +0 -0
lino/mixins/periods.py
CHANGED
@@ -214,11 +214,11 @@ class DateRangeObservable(Model):
|
|
214
214
|
def setup_parameters(cls, fields):
|
215
215
|
fields.update(
|
216
216
|
start_date=models.DateField(
|
217
|
-
_("
|
217
|
+
_("Date range from"),
|
218
218
|
blank=True,
|
219
219
|
null=True,
|
220
220
|
default=cls.get_default_start_date,
|
221
|
-
help_text=_("Start date of observed
|
221
|
+
help_text=_("Start date of observed date range."),
|
222
222
|
)
|
223
223
|
)
|
224
224
|
fields.update(
|
@@ -227,7 +227,7 @@ class DateRangeObservable(Model):
|
|
227
227
|
blank=True,
|
228
228
|
null=True,
|
229
229
|
default=cls.get_default_end_date,
|
230
|
-
help_text=_("End date of observed
|
230
|
+
help_text=_("End date of observed date range."),
|
231
231
|
)
|
232
232
|
)
|
233
233
|
super().setup_parameters(fields)
|
@@ -267,6 +267,12 @@ class DateRange(DateRangeObservable):
|
|
267
267
|
def get_period_text(self):
|
268
268
|
return rangetext(self, fdl, self.empty_period_text)
|
269
269
|
|
270
|
+
def covers_date(self, d):
|
271
|
+
if self.start_date and self.start_date > d:
|
272
|
+
return False
|
273
|
+
if self.end_date and self.end_date < d:
|
274
|
+
return False
|
275
|
+
return True
|
270
276
|
|
271
277
|
DateRange.set_widget_options("start_date", width=10)
|
272
278
|
DateRange.set_widget_options("end_date", width=10)
|
@@ -372,4 +378,4 @@ class Today(ParameterPanel):
|
|
372
378
|
help_text=_("Date of observation"),
|
373
379
|
),
|
374
380
|
)
|
375
|
-
super(
|
381
|
+
super().__init__(**kw)
|
lino/mixins/polymorphic.py
CHANGED
@@ -247,16 +247,16 @@ class Polymorphic(model.Model):
|
|
247
247
|
buttons = self.get_mti_buttons(ar)
|
248
248
|
return E.span(*buttons)
|
249
249
|
|
250
|
-
def as_summary_item(self, ar,
|
250
|
+
def as_summary_item(self, ar, text=None, **kwargs):
|
251
251
|
# a = super(Polymorphic, self).get_detail_action(ar)
|
252
252
|
a = self.get_detail_action(ar)
|
253
253
|
if a is not None:
|
254
|
-
return super().as_summary_item(ar,
|
254
|
+
return super().as_summary_item(ar, text, **kwargs)
|
255
255
|
for m in self._mtinav_models:
|
256
256
|
if m is not self.__class__:
|
257
257
|
obj = mti.get_child(self, m)
|
258
258
|
if obj is not None:
|
259
259
|
if obj.get_detail_action(ar) is not None:
|
260
|
-
return obj.as_summary_item(ar,
|
260
|
+
return obj.as_summary_item(ar, text, **kwargs)
|
261
261
|
return str(self)
|
262
262
|
# return "?!? {} has no detail_action for {}".format(self, ar)
|
lino/mixins/ref.py
CHANGED
@@ -47,7 +47,7 @@ class Referrable(model.Model):
|
|
47
47
|
@classmethod
|
48
48
|
def on_analyze(cls, site):
|
49
49
|
cls.set_widget_options("ref", width=cls.ref_max_length)
|
50
|
-
super(
|
50
|
+
super().on_analyze(site)
|
51
51
|
|
52
52
|
def on_duplicate(self, ar, master):
|
53
53
|
"""
|
@@ -56,7 +56,10 @@ class Referrable(model.Model):
|
|
56
56
|
"""
|
57
57
|
if self.ref:
|
58
58
|
self.ref += " (DUP)"
|
59
|
-
super(
|
59
|
+
super().on_duplicate(ar, master)
|
60
|
+
|
61
|
+
def __str__(self):
|
62
|
+
return self.ref or super().__str__()
|
60
63
|
|
61
64
|
@staticmethod
|
62
65
|
def ref_prefix(obj, ar=None):
|
@@ -96,7 +99,7 @@ class Referrable(model.Model):
|
|
96
99
|
# if search_text.isdigit():
|
97
100
|
if search_text.startswith("*"):
|
98
101
|
return models.Q(**{prefix + "ref__icontains": search_text[1:]})
|
99
|
-
return super(
|
102
|
+
return super().quick_search_filter(search_text, prefix)
|
100
103
|
|
101
104
|
|
102
105
|
class StructuredReferrable(Referrable):
|
@@ -20,7 +20,8 @@ class Plugin(ad.Plugin):
|
|
20
20
|
"The config descriptor for this plugin."
|
21
21
|
|
22
22
|
verbose_name = _("Checkdata")
|
23
|
-
needs_plugins = ["lino.modlib.users", "lino.modlib.gfks",
|
23
|
+
needs_plugins = ["lino.modlib.users", "lino.modlib.gfks",
|
24
|
+
"lino.modlib.office", "lino.modlib.linod"]
|
24
25
|
|
25
26
|
# plugin settings
|
26
27
|
responsible_user = None # the username (a string)
|
@@ -50,8 +51,7 @@ class Plugin(ad.Plugin):
|
|
50
51
|
User = self.site.models.users.User
|
51
52
|
try:
|
52
53
|
self._responsible_user = User.objects.get(
|
53
|
-
username=self.responsible_user
|
54
|
-
)
|
54
|
+
username=self.responsible_user)
|
55
55
|
except User.DoesNotExist:
|
56
56
|
msg = "Invalid username '{0}' in `responsible_user` "
|
57
57
|
msg = msg.format(self.responsible_user)
|
@@ -986,7 +986,7 @@ class ExtRenderer(JsCacheRenderer):
|
|
986
986
|
tbl = dh.layout._datasource
|
987
987
|
yield ""
|
988
988
|
yield "Lino.%s = Ext.extend(Lino.ActionFormPanel,{" % dh.layout._formpanel_name
|
989
|
-
for k, v in
|
989
|
+
for k, v in dh.main.ext_options().items():
|
990
990
|
if k != "items":
|
991
991
|
yield " %s: %s," % (k, py2js(v))
|
992
992
|
assert tbl.action_name is not None
|
lino/modlib/languages/models.py
CHANGED
lino/modlib/linod/consumers.py
CHANGED
@@ -57,9 +57,8 @@ class LinodConsumer(AsyncConsumer):
|
|
57
57
|
async def run_background_tasks(self, event: dict):
|
58
58
|
# 'run.background.tasks' in `pm linod`
|
59
59
|
from lino.modlib.linod.mixins import start_task_runner
|
60
|
-
|
61
|
-
|
62
|
-
ar = settings.SITE.login()
|
60
|
+
from lino.core.requests import BaseRequest
|
61
|
+
ar = BaseRequest()
|
63
62
|
asyncio.ensure_future(start_task_runner(ar))
|
64
63
|
|
65
64
|
async def send_push(self, event):
|
lino/modlib/memo/mixins.py
CHANGED
@@ -286,7 +286,7 @@ class MemoReferrable(dd.Model):
|
|
286
286
|
def memo2html(self, ar, txt, **kwargs):
|
287
287
|
if txt:
|
288
288
|
kwargs.update(title=txt)
|
289
|
-
e = self.as_summary_item(ar
|
289
|
+
e = self.as_summary_item(ar)
|
290
290
|
return tostring(e)
|
291
291
|
# return ar.obj2str(self, **kwargs)
|
292
292
|
|
@@ -0,0 +1,31 @@
|
|
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
|
+
start_year = 2012
|
14
|
+
start_month = 1
|
15
|
+
period_type = "month"
|
16
|
+
fix_y2k = False
|
17
|
+
short_ref = False
|
18
|
+
|
19
|
+
def setup_config_menu(self, site, user_type, m, ar=None):
|
20
|
+
p = self.get_menu_group()
|
21
|
+
m = m.add_menu(p.app_label, p.verbose_name)
|
22
|
+
m.add_action("periods.StoredYears")
|
23
|
+
m.add_action("periods.StoredPeriods")
|
24
|
+
|
25
|
+
def before_analyze(self):
|
26
|
+
if self.fix_y2k and self.start_month != 1:
|
27
|
+
raise Exception("When fix_y2k is set, start_month must be 1")
|
28
|
+
if isinstance(self.period_type, str):
|
29
|
+
self.period_type = self.site.models.periods.PeriodTypes.get_by_name(
|
30
|
+
self.period_type)
|
31
|
+
super().before_analyze()
|
@@ -0,0 +1,23 @@
|
|
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
|
+
yield StoredYear.get_or_create_from_date(datetime.date(y, today.month, today.day))
|
23
|
+
# 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,246 @@
|
|
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, ONE_DAY
|
12
|
+
from lino.mixins.periods import DateRange
|
13
|
+
from lino.mixins import Referrable
|
14
|
+
|
15
|
+
from lino.modlib.office.roles import OfficeStaff
|
16
|
+
|
17
|
+
NEXT_YEAR_SEP = "/"
|
18
|
+
YEAR_PERIOD_SEP = "-"
|
19
|
+
|
20
|
+
class PeriodType(dd.Choice):
|
21
|
+
ref_template = None
|
22
|
+
ref_template = None
|
23
|
+
|
24
|
+
def __init__(self, value, text, duration, ref_template):
|
25
|
+
super().__init__(value, text, value)
|
26
|
+
self.ref_template = ref_template
|
27
|
+
self.duration = duration
|
28
|
+
|
29
|
+
class PeriodTypes(dd.ChoiceList):
|
30
|
+
item_class = PeriodType
|
31
|
+
verbose_name = _("Period type")
|
32
|
+
verbose_name_plural = _("Period types")
|
33
|
+
column_names = "value text duration ref_template"
|
34
|
+
|
35
|
+
@dd.displayfield(_("Duration"))
|
36
|
+
def duration(cls, p, ar):
|
37
|
+
return str(p.duration)
|
38
|
+
|
39
|
+
@dd.displayfield(_("Template for reference"))
|
40
|
+
def ref_template(cls, p, ar):
|
41
|
+
return p.ref_template
|
42
|
+
|
43
|
+
add = PeriodTypes.add_item
|
44
|
+
# value/names, text, duration, ref_template
|
45
|
+
add("month", _("Month"), 1, "{month:0>2}")
|
46
|
+
add("quarter", _("Quarter"), 3, "Q{period}")
|
47
|
+
add("trimester", _("Trimester"), 4, "T{period}")
|
48
|
+
add("semester", _("Semester"), 6, "S{period}")
|
49
|
+
|
50
|
+
|
51
|
+
class PeriodStates(dd.Workflow):
|
52
|
+
pass
|
53
|
+
|
54
|
+
add = PeriodStates.add_item
|
55
|
+
add('10', _("Open"), 'open')
|
56
|
+
add('20', _("Closed"), 'closed')
|
57
|
+
|
58
|
+
|
59
|
+
class StoredYear(DateRange, Referrable):
|
60
|
+
|
61
|
+
class Meta:
|
62
|
+
app_label = 'periods'
|
63
|
+
verbose_name = dd.plugins.periods.year_name
|
64
|
+
verbose_name_plural = dd.plugins.periods.year_name_plural
|
65
|
+
ordering = ['ref']
|
66
|
+
|
67
|
+
preferred_foreignkey_width = 10
|
68
|
+
|
69
|
+
state = PeriodStates.field(default='open')
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def get_simple_parameters(cls):
|
73
|
+
yield super().get_simple_parameters()
|
74
|
+
yield "state"
|
75
|
+
|
76
|
+
@classmethod
|
77
|
+
def get_ref_for_date(cls, date):
|
78
|
+
year = date.year
|
79
|
+
if date.month < dd.plugins.periods.start_month:
|
80
|
+
year -= 1
|
81
|
+
if dd.plugins.periods.fix_y2k:
|
82
|
+
if year < 2000:
|
83
|
+
return str(year)[-2:]
|
84
|
+
elif year < 3000:
|
85
|
+
return chr(int(str(year)[-3:-1]) + 65) + str(year)[-1]
|
86
|
+
else:
|
87
|
+
raise Exception("fix_y2k not supported after 2999")
|
88
|
+
elif dd.plugins.periods.short_ref:
|
89
|
+
if dd.plugins.periods.start_month == 1:
|
90
|
+
return str(year)[-2:]
|
91
|
+
return str(year)[-2:] + NEXT_YEAR_SEP + str(year+1)[-2:]
|
92
|
+
elif dd.plugins.periods.start_month == 1:
|
93
|
+
return str(year)
|
94
|
+
return str(year) + NEXT_YEAR_SEP + str(year+1)[-2:]
|
95
|
+
|
96
|
+
@classmethod
|
97
|
+
def get_or_create_from_date(cls, date):
|
98
|
+
ref = cls.get_ref_for_date(date)
|
99
|
+
obj = cls.get_by_ref(ref, None)
|
100
|
+
if obj is None:
|
101
|
+
sd = datetime.date(date.year, dd.plugins.periods.start_month, 1)
|
102
|
+
ed = sd.replace(year=date.year+1) - ONE_DAY
|
103
|
+
obj = cls(ref=ref, start_date=sd, end_date=ed)
|
104
|
+
obj.full_clean()
|
105
|
+
obj.save()
|
106
|
+
return obj
|
107
|
+
|
108
|
+
def __str__(self):
|
109
|
+
return self.ref
|
110
|
+
|
111
|
+
|
112
|
+
class StoredPeriod(DateRange, Referrable):
|
113
|
+
|
114
|
+
class Meta:
|
115
|
+
ordering = ['ref']
|
116
|
+
app_label = 'periods'
|
117
|
+
verbose_name = dd.plugins.periods.period_name
|
118
|
+
verbose_name_plural = dd.plugins.periods.period_name_plural
|
119
|
+
|
120
|
+
preferred_foreignkey_width = 10
|
121
|
+
|
122
|
+
state = PeriodStates.field(default='open')
|
123
|
+
year = dd.ForeignKey('periods.StoredYear', blank=True, null=True)
|
124
|
+
remark = models.CharField(_("Remark"), max_length=250, blank=True)
|
125
|
+
|
126
|
+
@classmethod
|
127
|
+
def get_simple_parameters(cls):
|
128
|
+
yield super().get_simple_parameters()
|
129
|
+
yield "state"
|
130
|
+
yield "year"
|
131
|
+
|
132
|
+
@classmethod
|
133
|
+
def get_request_queryset(cls, ar):
|
134
|
+
qs = super().get_request_queryset(ar)
|
135
|
+
if (pv := ar.param_values) is None: return qs
|
136
|
+
|
137
|
+
# if pv.start_date is None or pv.end_date is None:
|
138
|
+
# period = None
|
139
|
+
# else:
|
140
|
+
# period = (pv.start_date, pv.end_date)
|
141
|
+
# if period is not None:
|
142
|
+
# qs = qs.filter(dd.inrange_filter('start_date', period))
|
143
|
+
if pv.start_date or pv.end_date:
|
144
|
+
qs = qs.filter(dd.overlap_range_filter(
|
145
|
+
pv.start_date, pv.end_date, "start_date", "end_date"))
|
146
|
+
# if pv.start_date:
|
147
|
+
# qs = qs.filter(dd.range_filter(pv.start_date, 'start_date', 'end_date'))
|
148
|
+
# # qs = qs.filter(start_date__gte=pv.start_date)
|
149
|
+
# if pv.end_date:
|
150
|
+
# qs = qs.filter(dd.range_filter(pv.end_date, 'start_date', 'end_date'))
|
151
|
+
# qs = qs.filter(end_date__lte=pv.end_date)
|
152
|
+
return qs
|
153
|
+
|
154
|
+
@classmethod
|
155
|
+
def get_available_periods(cls, today):
|
156
|
+
"""Return a queryset of periods available for booking."""
|
157
|
+
if today is None: # added 20160531
|
158
|
+
today = dd.today()
|
159
|
+
fkw = dict(start_date__lte=today, end_date__gte=today)
|
160
|
+
return cls.objects.filter(**fkw)
|
161
|
+
|
162
|
+
@classmethod
|
163
|
+
def get_periods_in_range(cls, p1, p2):
|
164
|
+
return cls.objects.filter(ref__gte=p1.ref, ref__lte=p2.ref)
|
165
|
+
|
166
|
+
@classmethod
|
167
|
+
def get_period_filter(cls, fieldname, p1, p2, **kwargs):
|
168
|
+
if p1 is None:
|
169
|
+
return kwargs
|
170
|
+
|
171
|
+
# ignore preliminary movements if a start_period is given:
|
172
|
+
# kwargs[voucher_prefix + "journal__preliminary"] = False
|
173
|
+
|
174
|
+
# accounting_period = voucher_prefix + "accounting_period"
|
175
|
+
|
176
|
+
if p2 is None:
|
177
|
+
kwargs[fieldname] = p1
|
178
|
+
else:
|
179
|
+
periods = cls.get_periods_in_range(p1, p2)
|
180
|
+
kwargs[fieldname + '__in'] = periods
|
181
|
+
return kwargs
|
182
|
+
|
183
|
+
@classmethod
|
184
|
+
def get_ref_for_date(cls, date):
|
185
|
+
pt = dd.plugins.periods.period_type
|
186
|
+
month = date.month
|
187
|
+
month_offset = month - dd.plugins.periods.start_month
|
188
|
+
if month_offset < 0:
|
189
|
+
month_offset += 12
|
190
|
+
period = int(month_offset / pt.duration) + 1
|
191
|
+
# periods_per_year = int(12 / p.duration)
|
192
|
+
# period = (month_offset % (periods_per_year-1)) + 1
|
193
|
+
return pt.ref_template.format(**locals())
|
194
|
+
|
195
|
+
@classmethod
|
196
|
+
def get_or_create_from_date(cls, date): # get_default_for_date until 20241020
|
197
|
+
ref = date2ref(date)
|
198
|
+
obj = cls.get_by_ref(ref, None)
|
199
|
+
if obj is None:
|
200
|
+
obj = cls(
|
201
|
+
ref=ref,
|
202
|
+
start_date=date.replace(day=1),
|
203
|
+
end_date=last_day_of_month(date))
|
204
|
+
obj.full_clean()
|
205
|
+
obj.save()
|
206
|
+
return obj
|
207
|
+
|
208
|
+
def full_clean(self, *args, **kwargs):
|
209
|
+
if self.start_date is None:
|
210
|
+
self.start_date = dd.today().replace(day=1)
|
211
|
+
if not self.year:
|
212
|
+
self.year = StoredYear.get_or_create_from_date(self.start_date)
|
213
|
+
super().full_clean(*args, **kwargs)
|
214
|
+
|
215
|
+
def __str__(self):
|
216
|
+
if not self.ref:
|
217
|
+
return dd.obj2str(self)
|
218
|
+
# "{0} {1} (#{0})".format(self.pk, self.year)
|
219
|
+
return self.ref
|
220
|
+
|
221
|
+
@property
|
222
|
+
def nickname(self):
|
223
|
+
if self.year.covers_date(dd.today()):
|
224
|
+
if len(parts := self.ref.split(YEAR_PERIOD_SEP)) == 2:
|
225
|
+
return parts[1]
|
226
|
+
return self.ref
|
227
|
+
|
228
|
+
StoredPeriod.set_widget_options('ref', width=6)
|
229
|
+
|
230
|
+
def date2ref(d):
|
231
|
+
return StoredYear.get_ref_for_date(d) + YEAR_PERIOD_SEP + StoredPeriod.get_ref_for_date(d)
|
232
|
+
|
233
|
+
class StoredYears(dd.Table):
|
234
|
+
model = 'periods.StoredYear'
|
235
|
+
required_roles = dd.login_required(OfficeStaff)
|
236
|
+
column_names = "ref start_date end_date state *"
|
237
|
+
# detail_layout = """
|
238
|
+
# ref id
|
239
|
+
# start_date end_date
|
240
|
+
# """
|
241
|
+
|
242
|
+
class StoredPeriods(dd.Table):
|
243
|
+
required_roles = dd.login_required(OfficeStaff)
|
244
|
+
model = 'periods.StoredPeriod'
|
245
|
+
order_by = ["ref", "start_date", "year"]
|
246
|
+
column_names = "ref start_date end_date year state remark *"
|
lino/modlib/printing/actions.py
CHANGED
@@ -119,6 +119,7 @@ class BasePrintAction(Action):
|
|
119
119
|
class DirectPrintAction(BasePrintAction):
|
120
120
|
url_action_name = None
|
121
121
|
icon_name = "printer"
|
122
|
+
# button_text = "🖶" # 1F5B6
|
122
123
|
tplname = None
|
123
124
|
|
124
125
|
def __init__(self, label=None, tplname=None, build_method=None, **kw):
|
@@ -162,6 +163,7 @@ class CachedPrintAction(BasePrintAction):
|
|
162
163
|
# select_rows = False
|
163
164
|
http_method = "POST"
|
164
165
|
icon_name = "printer"
|
166
|
+
# button_text = "🖶" # 1F5B6
|
165
167
|
|
166
168
|
def before_build(self, bm, elem):
|
167
169
|
if elem.build_time:
|
lino/modlib/publisher/ui.py
CHANGED
@@ -172,6 +172,6 @@ class TranslationsByPage(Pages):
|
|
172
172
|
default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
|
173
173
|
|
174
174
|
@classmethod
|
175
|
-
def row_as_summary(cls, ar, obj, **kwargs):
|
175
|
+
def row_as_summary(cls, ar, obj, text=None, **kwargs):
|
176
176
|
# return format_html("({}) {}", obj.language, obj.as_summary_row(ar, **kwargs))
|
177
|
-
return E.span("({}) ".format(obj.language), obj.as_summary_item(ar, **kwargs))
|
177
|
+
return E.span("({}) ".format(obj.language), obj.as_summary_item(ar, text, **kwargs))
|
lino/modlib/system/__init__.py
CHANGED