[events] Initial work for job transfer
This commit is contained in:
parent
03aa7a3231
commit
ce074bb51a
|
@ -1,31 +1,82 @@
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from werkzeug.exceptions import BadRequest, NotFound
|
from werkzeug.exceptions import BadRequest, NotFound, Conflict
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from flaschengeist import logger
|
from flaschengeist import logger
|
||||||
from flaschengeist.database import db
|
from flaschengeist.database import db
|
||||||
from flaschengeist.plugins.events import EventPlugin
|
from flaschengeist.plugins.events import EventPlugin
|
||||||
from flaschengeist.plugins.events.models import EventType, Event, Job, JobType, Service
|
from flaschengeist.plugins.events.models import EventType, Event, Job, JobType, JobTransfer, Service
|
||||||
from flaschengeist.utils.scheduler import scheduled
|
from flaschengeist.utils.scheduler import scheduled
|
||||||
|
|
||||||
|
|
||||||
|
TRANSFER_REQUEST = 0x10
|
||||||
|
TRANSFER_ACCEPTED = 0x11
|
||||||
|
TRANSFER_REJECTED = 0x12
|
||||||
|
|
||||||
|
|
||||||
def update():
|
def update():
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def invite(job, invitee, sender):
|
||||||
|
EventPlugin.plugin.notify(invitee, "Neue Diensteinladung", {"type": 0, "sender": sender.userid, "job": job.id})
|
||||||
|
|
||||||
|
|
||||||
|
def get_transfer(id):
|
||||||
|
return JobTransfer.query.get(id)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer(job: Job, new, old):
|
||||||
|
service = Service.query.get((job.id, old.id_))
|
||||||
|
if service is None:
|
||||||
|
raise BadRequest
|
||||||
|
|
||||||
|
jt = JobTransfer(old_user=old, new_user=new, job=job)
|
||||||
|
db.session.add(jt)
|
||||||
|
db.session.commit()
|
||||||
|
EventPlugin.plugin.notify(
|
||||||
|
new,
|
||||||
|
"Neue Dienstübergabe",
|
||||||
|
{"type": TRANSFER_REQUEST, "sender": old.userid, "event": job.event_id_, "job": job.id, "id": jt.id},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def accept_transfer(jt: JobTransfer, accept=True):
|
||||||
|
try:
|
||||||
|
if accept:
|
||||||
|
service = Service.query.get((jt.job.id, jt.old_user.id_))
|
||||||
|
if service is not None:
|
||||||
|
service.user_ = jt.new_user
|
||||||
|
else:
|
||||||
|
raise Conflict
|
||||||
|
EventPlugin.plugin.notify(
|
||||||
|
jt.old_user,
|
||||||
|
"Dienstübergabe akzeptiert",
|
||||||
|
{"type": TRANSFER_ACCEPTED, "sender": jt.new_user.userid, "event": jt.job.event_id_, "job": jt.job_id_},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
EventPlugin.plugin.notify(
|
||||||
|
jt.old_user,
|
||||||
|
"Dienstübergabe abgelehnt",
|
||||||
|
{"type": TRANSFER_REJECTED, "sender": jt.new_user.userid, "event": jt.job.event_id_, "job": jt.job_id_},
|
||||||
|
)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
db.session.delete(jt)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def get_event_types():
|
def get_event_types():
|
||||||
return EventType.query.all()
|
return EventType.query.all()
|
||||||
|
|
||||||
|
|
||||||
def get_event_type(identifier):
|
def get_event_type(identifier):
|
||||||
"""Get EventType by ID (int) or name (string)"""
|
"""Get EventType by ID (int)"""
|
||||||
|
|
||||||
if isinstance(identifier, int):
|
if isinstance(identifier, int):
|
||||||
et = EventType.query.get(identifier)
|
et = EventType.query.get(identifier)
|
||||||
elif isinstance(identifier, str):
|
|
||||||
et = EventType.query.filter(EventType.name == identifier).one_or_none()
|
|
||||||
else:
|
else:
|
||||||
logger.debug("Invalid identifier type for EventType")
|
logger.debug("Invalid identifier type for EventType")
|
||||||
raise BadRequest
|
raise BadRequest
|
||||||
|
@ -102,11 +153,11 @@ def delete_job_type(name):
|
||||||
raise BadRequest("Type still in use")
|
raise BadRequest("Type still in use")
|
||||||
|
|
||||||
|
|
||||||
def clear_backup(event: Event):
|
def clear_backup(event: Event, backup):
|
||||||
for job in event.jobs:
|
for job in event.jobs:
|
||||||
services = []
|
services = []
|
||||||
for service in job.services:
|
for service in job.services:
|
||||||
if not service.is_backup:
|
if not service.is_backup or service.userid == backup:
|
||||||
services.append(service)
|
services.append(service)
|
||||||
job.services = services
|
job.services = services
|
||||||
|
|
||||||
|
@ -115,8 +166,6 @@ def get_event(event_id, with_backup=False) -> Event:
|
||||||
event = Event.query.get(event_id)
|
event = Event.query.get(event_id)
|
||||||
if event is None:
|
if event is None:
|
||||||
raise NotFound
|
raise NotFound
|
||||||
if not with_backup:
|
|
||||||
return clear_backup(event)
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,9 +188,9 @@ def get_events(start: Optional[datetime] = None, end=None, with_backup=False):
|
||||||
if end is not None:
|
if end is not None:
|
||||||
query = query.filter(Event.start < end)
|
query = query.filter(Event.start < end)
|
||||||
events = query.all()
|
events = query.all()
|
||||||
if not with_backup:
|
if not (with_backup is True):
|
||||||
for event in events:
|
for event in events:
|
||||||
clear_backup(event)
|
clear_backup(event, with_backup)
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
|
||||||
|
@ -205,7 +254,7 @@ def delete_job(job: Job):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def assign_to_job(job: Job, user, value):
|
def assign_to_job(job: Job, user, value, backup=False):
|
||||||
service = Service.query.get((job.id, user.id_))
|
service = Service.query.get((job.id, user.id_))
|
||||||
if value < 0:
|
if value < 0:
|
||||||
if not service:
|
if not service:
|
||||||
|
@ -214,8 +263,9 @@ def assign_to_job(job: Job, user, value):
|
||||||
else:
|
else:
|
||||||
if service:
|
if service:
|
||||||
service.value = value
|
service.value = value
|
||||||
|
service.is_backup = backup
|
||||||
else:
|
else:
|
||||||
service = Service(user_=user, value=value, job_=job)
|
service = Service(user_=user, value=value, job_=job, is_backup=backup)
|
||||||
db.session.add(service)
|
db.session.add(service)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,9 @@ class Job(db.Model, ModelSerializeMixin):
|
||||||
end: Optional[datetime] = db.Column(UtcDateTime)
|
end: Optional[datetime] = db.Column(UtcDateTime)
|
||||||
type: Union[JobType, int] = db.relationship("JobType")
|
type: Union[JobType, int] = db.relationship("JobType")
|
||||||
comment: Optional[str] = db.Column(db.String(256))
|
comment: Optional[str] = db.Column(db.String(256))
|
||||||
services: list[Service] = db.relationship("Service", back_populates="job_")
|
services: list[Service] = db.relationship(
|
||||||
|
"Service", back_populates="job_", cascade="save-update, merge, delete, delete-orphan"
|
||||||
|
)
|
||||||
required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False)
|
required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False)
|
||||||
|
|
||||||
event_ = db.relationship("Event", back_populates="jobs")
|
event_ = db.relationship("Event", back_populates="jobs")
|
||||||
|
@ -68,6 +70,17 @@ class Job(db.Model, ModelSerializeMixin):
|
||||||
__table_args__ = (UniqueConstraint("type_id", "start", "event_id", name="_type_start_uc"),)
|
__table_args__ = (UniqueConstraint("type_id", "start", "event_id", name="_type_start_uc"),)
|
||||||
|
|
||||||
|
|
||||||
|
class JobTransfer(db.Model, ModelSerializeMixin):
|
||||||
|
__tablename__ = _table_prefix_ + "job_transfer"
|
||||||
|
id: int = db.Column(Serial, primary_key=True)
|
||||||
|
old_id_ = db.Column("old_id", Serial, db.ForeignKey("user.id"), nullable=False)
|
||||||
|
new_id_ = db.Column("new_id", Serial, db.ForeignKey("user.id"), nullable=False)
|
||||||
|
job_id_ = db.Column("job_id", Serial, db.ForeignKey(f"{_table_prefix_}job.id"), nullable=False)
|
||||||
|
old_user = db.relationship("User", foreign_keys=[old_id_])
|
||||||
|
new_user = db.relationship("User", foreign_keys=[new_id_])
|
||||||
|
job = db.relationship("Job")
|
||||||
|
|
||||||
|
|
||||||
##########
|
##########
|
||||||
# Events #
|
# Events #
|
||||||
##########
|
##########
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime
|
||||||
from http.client import NO_CONTENT
|
from http.client import NO_CONTENT
|
||||||
from flask import request, jsonify
|
from flask import request, jsonify
|
||||||
from werkzeug.exceptions import BadRequest, NotFound, Forbidden
|
from werkzeug.exceptions import BadRequest, NotFound, Forbidden
|
||||||
|
@ -9,6 +9,7 @@ from flaschengeist.utils.datetime import from_iso_format
|
||||||
from flaschengeist.controller import userController
|
from flaschengeist.controller import userController
|
||||||
|
|
||||||
from . import event_controller, permissions, EventPlugin
|
from . import event_controller, permissions, EventPlugin
|
||||||
|
from ... import logger
|
||||||
from ...utils.HTTP import no_content
|
from ...utils.HTTP import no_content
|
||||||
|
|
||||||
|
|
||||||
|
@ -169,14 +170,15 @@ def get_event(event_id, current_session):
|
||||||
JSON encoded event object
|
JSON encoded event object
|
||||||
"""
|
"""
|
||||||
event = event_controller.get_event(
|
event = event_controller.get_event(
|
||||||
event_id, with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP)
|
event_id,
|
||||||
|
with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP) or current_session.user_.userid,
|
||||||
)
|
)
|
||||||
return jsonify(event)
|
return jsonify(event)
|
||||||
|
|
||||||
|
|
||||||
@EventPlugin.blueprint.route("/events", methods=["GET"])
|
@EventPlugin.blueprint.route("/events", methods=["GET"])
|
||||||
@login_required()
|
@login_required()
|
||||||
def get_filtered_events(current_session):
|
def get_events(current_session):
|
||||||
begin = request.args.get("from")
|
begin = request.args.get("from")
|
||||||
if begin is not None:
|
if begin is not None:
|
||||||
begin = from_iso_format(begin)
|
begin = from_iso_format(begin)
|
||||||
|
@ -187,48 +189,13 @@ def get_filtered_events(current_session):
|
||||||
begin = datetime.now()
|
begin = datetime.now()
|
||||||
return jsonify(
|
return jsonify(
|
||||||
event_controller.get_events(
|
event_controller.get_events(
|
||||||
begin, end, with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP)
|
begin,
|
||||||
|
end,
|
||||||
|
with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP) or current_session.user_.userid,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@EventPlugin.blueprint.route("/events/<int:year>/<int:month>", methods=["GET"])
|
|
||||||
@EventPlugin.blueprint.route("/events/<int:year>/<int:month>/<int:day>", methods=["GET"])
|
|
||||||
@login_required()
|
|
||||||
def get_events(current_session, year=datetime.now().year, month=datetime.now().month, day=None):
|
|
||||||
"""Get Event objects for specified date (or month or year),
|
|
||||||
if nothing set then events for current month are returned
|
|
||||||
|
|
||||||
Route: ``/events[/<year>/<month>[/<int:day>]]`` | Method: ``GET``
|
|
||||||
|
|
||||||
Args:
|
|
||||||
year (int, optional): year to query, defaults to current year
|
|
||||||
month (int, optional): month to query (if set), defaults to current month
|
|
||||||
day (int, optional): day to query events for (if set)
|
|
||||||
current_session: Session sent with Authorization Header
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
JSON encoded list containing events found or HTTP-error
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
begin = datetime(year=year, month=month, day=1, tzinfo=timezone.utc)
|
|
||||||
if day:
|
|
||||||
begin += timedelta(days=day - 1)
|
|
||||||
end = begin + timedelta(days=1)
|
|
||||||
else:
|
|
||||||
if month == 12:
|
|
||||||
end = datetime(year=year + 1, month=1, day=1, tzinfo=timezone.utc)
|
|
||||||
else:
|
|
||||||
end = datetime(year=year, month=month + 1, day=1, tzinfo=timezone.utc)
|
|
||||||
|
|
||||||
events = event_controller.get_events(
|
|
||||||
begin, end, with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP)
|
|
||||||
)
|
|
||||||
return jsonify(events)
|
|
||||||
except ValueError:
|
|
||||||
raise BadRequest("Invalid date given")
|
|
||||||
|
|
||||||
|
|
||||||
def _add_job(event, data):
|
def _add_job(event, data):
|
||||||
try:
|
try:
|
||||||
start = from_iso_format(data["start"])
|
start = from_iso_format(data["start"])
|
||||||
|
@ -316,8 +283,10 @@ def modify_event(event_id, current_session):
|
||||||
if "description" in data:
|
if "description" in data:
|
||||||
event.description = data["description"]
|
event.description = data["description"]
|
||||||
if "type" in data:
|
if "type" in data:
|
||||||
event_type = event_controller.get_event_type(data["type"])
|
event_type = data["type"]
|
||||||
event.type = event_type
|
if isinstance(event_type, dict) and "id" in event_type:
|
||||||
|
event_type = data["type"]["id"]
|
||||||
|
event.type = event_controller.get_event_type(event_type)
|
||||||
event_controller.update()
|
event_controller.update()
|
||||||
return jsonify(event)
|
return jsonify(event)
|
||||||
|
|
||||||
|
@ -361,12 +330,12 @@ def add_job(event_id, current_session):
|
||||||
return jsonify(event)
|
return jsonify(event)
|
||||||
|
|
||||||
|
|
||||||
@EventPlugin.blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["DELETE"])
|
@EventPlugin.blueprint.route("/events/<int:event_id>/<int:job_id>", methods=["DELETE"])
|
||||||
@login_required(permission=permissions.DELETE)
|
@login_required(permission=permissions.DELETE)
|
||||||
def delete_job(event_id, job_id, current_session):
|
def delete_job(event_id, job_id, current_session):
|
||||||
"""Delete a Job
|
"""Delete a Job
|
||||||
|
|
||||||
Route: ``/events/<event_id>/jobs/<job_id>`` | Method: ``DELETE``
|
Route: ``/events/<event_id>/<job_id>`` | Method: ``DELETE``
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event_id: Identifier of the event
|
event_id: Identifier of the event
|
||||||
|
@ -381,12 +350,12 @@ def delete_job(event_id, job_id, current_session):
|
||||||
return no_content()
|
return no_content()
|
||||||
|
|
||||||
|
|
||||||
@EventPlugin.blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["PUT"])
|
@EventPlugin.blueprint.route("/events/<int:event_id>/<int:job_id>", methods=["PUT"])
|
||||||
@login_required()
|
@login_required(permission=permissions.ASSIGN)
|
||||||
def update_job(event_id, job_id, current_session: Session):
|
def update_job(event_id, job_id, current_session: Session):
|
||||||
"""Edit Job or assign user to the Job
|
"""Edit Job or assign user to the Job
|
||||||
|
|
||||||
Route: ``/events/<event_id>/jobs/<job_id>`` | Method: ``PUT``
|
Route: ``/events/<event_id>/<job_id>`` | Method: ``PUT``
|
||||||
|
|
||||||
POST-data: See TS interface for Job or ``{user: {userid: string, value: number}}``
|
POST-data: See TS interface for Job or ``{user: {userid: string, value: number}}``
|
||||||
|
|
||||||
|
@ -401,31 +370,52 @@ def update_job(event_id, job_id, current_session: Session):
|
||||||
job = event_controller.get_job(job_id, event_id)
|
job = event_controller.get_job(job_id, event_id)
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data:
|
if not data or ("user" not in data and "required_services" not in data):
|
||||||
raise BadRequest
|
raise BadRequest
|
||||||
|
|
||||||
if ("user" not in data or len(data) > 1) and not current_session.user_.has_permission(permissions.EDIT):
|
# Check if number of services changed, check permission if so
|
||||||
raise Forbidden
|
if "required_services" in data:
|
||||||
|
if not current_session.user_.has_permission(permissions.EDIT):
|
||||||
|
raise Forbidden
|
||||||
|
job.required_services = data["required_services"]
|
||||||
|
|
||||||
if "user" in data:
|
if "user" in data:
|
||||||
try:
|
try:
|
||||||
user = userController.get_user(data["user"]["userid"])
|
user = userController.get_user(data["user"]["userid"])
|
||||||
value = data["user"]["value"]
|
value = data["user"].get("value", None)
|
||||||
if (user == current_session.user_ and not user.has_permission(permissions.ASSIGN)) or (
|
replace = data["user"].get("replace", None)
|
||||||
user != current_session.user_ and not current_session.user_.has_permission(permissions.ASSIGN_OTHER)
|
if replace is not None:
|
||||||
):
|
replace = userController.get_user(replace)
|
||||||
raise Forbidden
|
event_controller.transfer(job, user, replace)
|
||||||
event_controller.assign_to_job(job, user, value)
|
else:
|
||||||
except (KeyError, ValueError):
|
if value is not None:
|
||||||
|
if user != current_session.user_ and not current_session.user_.has_permission(
|
||||||
|
permissions.ASSIGN_OTHER
|
||||||
|
):
|
||||||
|
raise Forbidden
|
||||||
|
event_controller.assign_to_job(job, user, value, data["user"].get("is_backup", False))
|
||||||
|
else:
|
||||||
|
logger.debug("Invite user")
|
||||||
|
event_controller.invite(job, user, current_session.user_)
|
||||||
|
except (KeyError, ValueError, NotFound):
|
||||||
raise BadRequest
|
raise BadRequest
|
||||||
|
|
||||||
if "required_services" in data:
|
|
||||||
job.required_services = data["required_services"]
|
|
||||||
if "type" in data:
|
|
||||||
job.type = event_controller.get_job_type(data["type"])
|
|
||||||
event_controller.update()
|
event_controller.update()
|
||||||
|
|
||||||
return jsonify(job)
|
return jsonify(job)
|
||||||
|
|
||||||
|
|
||||||
# TODO: JobTransfer
|
@EventPlugin.blueprint.route("/events/transfer/<int:transfer_id>", methods=["PUT", "DELETE"])
|
||||||
|
@login_required(permission=permissions.ASSIGN)
|
||||||
|
def transfer_accepted(transfer_id, current_session: Session):
|
||||||
|
jt = event_controller.get_transfer(transfer_id)
|
||||||
|
if jt is None:
|
||||||
|
raise NotFound
|
||||||
|
if request.method == "PUT":
|
||||||
|
if jt.new_user.userid != current_session.userid:
|
||||||
|
raise Forbidden
|
||||||
|
event_controller.accept_transfer(jt)
|
||||||
|
else:
|
||||||
|
if jt.new_user.userid != current_session.userid or jt.old_user.userid != current_session.userid:
|
||||||
|
raise Forbidden
|
||||||
|
event_controller.accept_transfer(jt, False)
|
||||||
|
return no_content()
|
||||||
|
|
Loading…
Reference in New Issue