Skip to content
This repository has been archived by the owner on Sep 5, 2019. It is now read-only.

Commit

Permalink
Adds the ability to define rules by which allocations are created
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Krienbühl committed Jan 16, 2019
1 parent 933bcac commit caa0f8c
Show file tree
Hide file tree
Showing 11 changed files with 906 additions and 20 deletions.
3 changes: 3 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Changelog
---------

- Adds the ability to define rules by which allocations are created.
[href]

- Adds the ability to skip allocations on holidays.
[href]

Expand Down
9 changes: 9 additions & 0 deletions onegov/org/cronjobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from onegov.reservation import Reservation, Resource, ResourceCollection
from onegov.ticket import Ticket, TicketCollection
from onegov.user import User, UserCollection
from onegov.org.views.allocation import handle_rules_cronjob
from sedate import replace_timezone, to_timezone, utcnow, align_date_to_day
from sqlalchemy import and_
from sqlalchemy.orm import undefer
Expand Down Expand Up @@ -59,6 +60,14 @@ def publish_files(request):
FileCollection(request.session).publish_files()


@OrgApp.cronjob(hour=23, minute=45, timezone='Europe/Zurich')
def process_resource_rules(request):
resources = ResourceCollection(request.app.libres_context)

for resource in resources.query():
handle_rules_cronjob(resources.bind(resource), request)


@OrgApp.cronjob(hour=8, minute=30, timezone='Europe/Zurich')
def send_daily_ticket_statistics(request):

Expand Down
2 changes: 2 additions & 0 deletions onegov/org/forms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from onegov.org.forms.allocation import AllocationRuleForm
from onegov.org.forms.allocation import DaypassAllocationEditForm
from onegov.org.forms.allocation import DaypassAllocationForm
from onegov.org.forms.allocation import RoomAllocationEditForm
Expand Down Expand Up @@ -34,6 +35,7 @@


__all__ = [
'AllocationRuleForm',
'AnalyticsSettingsForm',
'DateRangeForm',
'DaypassAllocationEditForm',
Expand Down
105 changes: 104 additions & 1 deletion onegov/org/forms/allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

from cached_property import cached_property
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from dateutil.rrule import rrule, DAILY, MO, TU, WE, TH, FR, SA, SU
from onegov.form import Form
from onegov.form.fields import MultiCheckboxField
from onegov.org import _
from wtforms.fields import RadioField
from uuid import uuid4
from wtforms.fields import StringField, RadioField
from wtforms.fields.html5 import DateField, IntegerField
from wtforms.validators import DataRequired, NumberRange, InputRequired
from wtforms_components import TimeField
Expand Down Expand Up @@ -85,6 +87,101 @@ def is_excluded(self, date):
return False


class AllocationRuleForm(Form):
""" Base form form allocation rules. """

title = StringField(
label=_("Title"),
description=_("General availability"),
validators=[InputRequired()],
fieldset=_("Rule"))

extend = RadioField(
label=_("Extend"),
validators=[InputRequired()],
fieldset=_("Rule"),
default='daily',
choices=(
('daily', _("Extend by one day at midnight")),
('monthly', _("Extend by one month at the end of the month")),
('yearly', _("Extend by one year at the end of the year"))
))

@cached_property
def rule_id(self):
return uuid4().hex

@cached_property
def iteration(self):
return 0

@cached_property
def last_run(self):
None

@property
def rule(self):
return {
'id': self.rule_id,
'title': self.title.data,
'extend': self.extend.data,
'options': self.options,
'iteration': self.iteration,
'last_run': self.last_run,
}

@rule.setter
def rule(self, value):
self.__dict__['rule_id'] = value['id']
self.__dict__['iteration'] = value['iteration']
self.__dict__['last_run'] = value['last_run']

self.title.data = value['title']
self.extend.data = value['extend']

for k, v in value['options'].items():
if hasattr(self, k):
getattr(self, k).data = v

@property
def options(self):
return {
k: getattr(self, k).data for k in self._fields
if k not in ('title', 'extend', 'csrf_token')
}

def apply(self, resource):
if self.iteration == 0:
dates = self.dates
else:
unit = {
'daily': 'days',
'monthly': 'months',
'yearly': 'years'
}[self.extend.data]

start = self.end.data + timedelta(days=1)
end = self.end.data + relativedelta(**{unit: self.iteration})

dates = self.generate_dates(
start,
end,
weekdays=self.weekdays
)

data = {**(self.data or {}), 'rule': self.rule_id}

return len(resource.scheduler.allocate(
dates=dates,
whole_day=self.whole_day,
quota=self.quota,
quota_limit=self.quota_limit,
data=data,
partly_available=self.partly_available,
skip_overlapping=True
))


class AllocationForm(Form, AllocationFormHelpers):
""" Baseform for all allocation forms. Allocation forms are expected
to implement the methods above (which contain a NotImplementedException).
Expand Down Expand Up @@ -129,6 +226,12 @@ def on_request(self):
if not self.request.app.org.holidays:
self.delete_field('on_holidays')

def ensure_start_before_end(self):
if self.start.data and self.end.data:
if self.start.data > self.end.data:
self.start.errors.append(_("Start date before end date"))
return False

@property
def weekdays(self):
""" The rrule weekdays derived from the except_for field. """
Expand Down
35 changes: 35 additions & 0 deletions onegov/org/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,11 @@ def editbar_links(self):
text=_("Subscribe"),
url=self.request.link(self.model, 'subscribe'),
attrs={'class': 'subscribe-link'}
),
Link(
text=_("Rules"),
url=self.request.link(self.model, 'rules'),
attrs={'class': 'rule-link'}
)
]

Expand All @@ -1231,6 +1236,36 @@ class ReservationLayout(ResourceLayout):
editbar_links = None


class AllocationRulesLayout(ResourceLayout):

@cached_property
def breadcrumbs(self):
return [
Link(_("Homepage"), self.homepage_url),
Link(_("Reservations"), self.request.link(self.collection)),
Link(_(self.model.title), self.request.link(self.model)),
Link(_("Rules"), '#')
]

@cached_property
def editbar_links(self):
return [
LinkGroup(
title=_("Add"),
links=[
Link(
text=_("Rule"),
url=self.request.link(
self.model,
name='new-rule'
),
attrs={'class': 'new-link'}
)
]
),
]


class AllocationEditFormLayout(DefaultLayout):
""" Same as the resource layout, but with different editbar links, because
there's not really an allocation view, but there are allocation forms.
Expand Down
77 changes: 73 additions & 4 deletions onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2019-01-14 16:22+0100\n"
"POT-Creation-Date: 2019-01-16 14:27+0100\n"
"PO-Revision-Date: 2018-12-08 11:01+0100\n"
"Last-Translator: Marc Sommerhalder <[email protected]>\n"
"Language-Team: German\n"
Expand Down Expand Up @@ -123,6 +123,9 @@ msgstr "Reservationen"
msgid "Notifications"
msgstr "Mitteilungen"

msgid "Rules"
msgstr "Regeln"

msgid "Edit allocation"
msgstr "Einteilung bearbeiten"

Expand Down Expand Up @@ -160,6 +163,9 @@ msgstr "Publikationen"
msgid "Delete"
msgstr "Löschen"

msgid "Add"
msgstr "Hinzufügen"

msgid "New"
msgstr "Neu"

Expand All @@ -176,9 +182,6 @@ msgstr ""
"Es existieren Eingaben die mit diesem Formular verknüpft sind. Löschen Sie "
"diese Eingaben bevor Sie das Formular löschen."

msgid "Add"
msgstr "Hinzufügen"

msgid "New Note"
msgstr "Neue Notiz"

Expand Down Expand Up @@ -293,6 +296,9 @@ msgstr "Möchten Sie diese Reservations-Ressource wirklich löschen?"
msgid "Delete resource"
msgstr "Reservations-Ressource löschen"

msgid "Rule"
msgstr "Regel"

msgid "This event can't be editet."
msgstr "Diese Veranstaltung kann nicht bearbeitet werden."

Expand Down Expand Up @@ -1087,6 +1093,12 @@ msgstr "Bitte geben Sie ein Datum pro Zeile ein"
msgid "Please enter only day and month"
msgstr "Bitte geben Sie nur Tag und Monat ein"

msgid "General availability"
msgstr "Generelle Verfügbarkeit"

msgid "Extend"
msgstr "Verlängern"

msgid "Except for"
msgstr "Ausser an"

Expand Down Expand Up @@ -1117,6 +1129,18 @@ msgstr "Optionen"
msgid "Until"
msgstr "Bis zum"

msgid "Extend by one day at midnight"
msgstr "Mitternachts um einen Tag verlängern"

msgid "Extend by one month at the end of the month"
msgstr "Ende Monat um einen Monat verlängern"

msgid "Extend by one year at the end of the year"
msgstr "Ende Jahr um ein Jahr verlängern"

msgid "Start date before end date"
msgstr "Start Datum vor Ende"

msgid "New entries"
msgstr "Neue Einträge"

Expand Down Expand Up @@ -2586,6 +2610,9 @@ msgstr "Noch keine Dateien hochgeladen"
msgid "The OneGov Cloud Team"
msgstr "Das OneGov Cloud Team"

msgid "No rules defined."
msgstr "Keine Regeln definiert."

msgid "Export a vCard of this person"
msgstr "Elektronische Visitenkarte (vCard)"

Expand Down Expand Up @@ -3401,13 +3428,55 @@ msgstr "Ein Konto wurde für Sie erstellt"
msgid "New allocation"
msgstr "Neue Einteilung"

msgid "New Rule"
msgstr "Neue Regel"

msgid ""
"Rules ensure that the allocations between start/end exist and that they are "
"extended beyond those dates at the given intervals. "
msgstr ""
"Regeln stellen sicher dass die Einteilungen zwischen Start und Ende bestehen "
"und dass sie im gewählten Interval verlängert werden."

msgid "The rule was stopped"
msgstr "Die Regel wurde gestoppt"

#, python-format
msgid "The rule was deleted, along with ${n} allocations"
msgstr "Die Regel wurde gelöscht, zusammen mit ${n} Einteilungen"

#, python-format
msgid "New rule active, ${n} allocations created"
msgstr "Regel aktiv, ${n} Einteilungen erstellt"

msgid "Stop"
msgstr "Stop"

msgid "No allocations to add"
msgstr "Keine Einteilungen die hinzugefügt werden können"

#, python-format
msgid "Successfully added ${n} allocations"
msgstr "${n} Einteilungen wurden hinzugefügt"

#, python-format
msgid "Do you really want to stop \"${title}\"?"
msgstr "Möchten Sie \"${title}\" wirklich stoppen?"

msgid "The rule will be removed without affecting existing allocations."
msgstr "Die Regel wird entfernt, bestehende Einteilungen bleiben."

msgid "Stop rule"
msgstr "Regel stoppen"

msgid ""
"All allocations created by the rule will be removed, if they haven't been "
"reserved yet."
msgstr "Alle Einteilungen der Regel werden entfernt, sofern nicht reserviert."

msgid "Delete rule"
msgstr "Regel löschen"

#, python-format
msgid "Search through ${count} indexed documents"
msgstr "Durchsuchen Sie ${count} indizierte Dokumente"
Expand Down
Loading

0 comments on commit caa0f8c

Please sign in to comment.