Compare commits
	
		
			2 Commits
		
	
	
		
			c767d92442
			...
			0881069b11
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						0881069b11 | |
| 
							
							
								
								 | 
						30173ec87d | 
| 
						 | 
					@ -7,7 +7,3 @@ yarn.lock
 | 
				
			||||||
# Backend
 | 
					# Backend
 | 
				
			||||||
*.egg-info
 | 
					*.egg-info
 | 
				
			||||||
__pycache__
 | 
					__pycache__
 | 
				
			||||||
 | 
					 | 
				
			||||||
# IDE
 | 
					 | 
				
			||||||
.idea
 | 
					 | 
				
			||||||
*.swp
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Provides duty schedule / duty roster functions
 | 
					Provides duty schedule / duty roster functions
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
import pkg_resources
 | 
					 | 
				
			||||||
from flask import Blueprint, current_app
 | 
					from flask import Blueprint, current_app
 | 
				
			||||||
from werkzeug.local import LocalProxy
 | 
					from werkzeug.local import LocalProxy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,28 +9,15 @@ from flaschengeist.plugins import Plugin
 | 
				
			||||||
from . import permissions, models
 | 
					from . import permissions, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__version__ = pkg_resources.get_distribution("flaschengeist_events").version
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class EventPlugin(Plugin):
 | 
					class EventPlugin(Plugin):
 | 
				
			||||||
    # id = "dev.flaschengeist.events"
 | 
					    name = "events"
 | 
				
			||||||
    # provided resources
 | 
					    id = "dev.flaschengeist.plugins.events"
 | 
				
			||||||
    # permissions = permissions.permissions
 | 
					    plugin = LocalProxy(lambda: current_app.config["FG_PLUGINS"][EventPlugin.name])
 | 
				
			||||||
 | 
					    permissions = permissions.permissions
 | 
				
			||||||
 | 
					    blueprint = Blueprint(name, __name__)
 | 
				
			||||||
    models = models
 | 
					    models = models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # def __init__(self, cfg):
 | 
					    def __init__(self, cfg):
 | 
				
			||||||
    #     super(EventPlugin, self).__init__(cfg)
 | 
					        super(EventPlugin, self).__init__(cfg)
 | 
				
			||||||
    #     from . import routes
 | 
					        from . import routes
 | 
				
			||||||
    #     from .event_controller import clear_services
 | 
					        from .event_controller import clear_services
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def load(self):
 | 
					 | 
				
			||||||
        from .routes import blueprint
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.blueprint = blueprint
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def install(self):
 | 
					 | 
				
			||||||
        self.install_permissions(permissions.permissions)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def getPlugin() -> LocalProxy["EventPlugin"]:
 | 
					 | 
				
			||||||
        return LocalProxy(lambda: current_app.config["FG_PLUGINS"]["events"])
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,7 @@
 | 
				
			||||||
from datetime import datetime, timedelta, timezone
 | 
					from datetime import datetime, timedelta, timezone
 | 
				
			||||||
from enum import IntEnum
 | 
					from enum import IntEnum
 | 
				
			||||||
from typing import Optional, Tuple, Union
 | 
					from typing import Optional, Tuple
 | 
				
			||||||
from flaschengeist.controller import userController
 | 
					from flaschengeist.models import UtcDateTime
 | 
				
			||||||
from flaschengeist.models import Notification, UtcDateTime
 | 
					 | 
				
			||||||
from flaschengeist.models.user import User
 | 
					from flaschengeist.models.user import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from werkzeug.exceptions import BadRequest, Conflict, NotFound
 | 
					from werkzeug.exceptions import BadRequest, Conflict, NotFound
 | 
				
			||||||
| 
						 | 
					@ -17,7 +16,6 @@ from flaschengeist.plugins.scheduler import scheduled
 | 
				
			||||||
from . import EventPlugin
 | 
					from . import EventPlugin
 | 
				
			||||||
from .models import EventType, Event, Invitation, Job, JobType, Service
 | 
					from .models import EventType, Event, Invitation, Job, JobType, Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
# STUB
 | 
					# STUB
 | 
				
			||||||
def _(x):
 | 
					def _(x):
 | 
				
			||||||
    return x
 | 
					    return x
 | 
				
			||||||
| 
						 | 
					@ -28,11 +26,8 @@ class NotifyType(IntEnum):
 | 
				
			||||||
    INVITE = 0x01
 | 
					    INVITE = 0x01
 | 
				
			||||||
    TRANSFER = 0x02
 | 
					    TRANSFER = 0x02
 | 
				
			||||||
    # Invitation responsed 0x10..0x1F
 | 
					    # Invitation responsed 0x10..0x1F
 | 
				
			||||||
    INVITATION_ACCEPTED = 0x11
 | 
					    INVITATION_ACCEPTED = 0x10
 | 
				
			||||||
    INVITATION_REJECTED = 0x12
 | 
					    INVITATION_REJECTED = 0x11
 | 
				
			||||||
    # Information responses 0x20..0x2F
 | 
					 | 
				
			||||||
    INFO_ACCEPTED = 0x21
 | 
					 | 
				
			||||||
    INFO_REJECTED = 0x22
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@before_delete_user
 | 
					@before_delete_user
 | 
				
			||||||
| 
						 | 
					@ -48,8 +43,8 @@ def clear_services(user):
 | 
				
			||||||
    db.session.commit()
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# def update():
 | 
					def update():
 | 
				
			||||||
#    db.session.commit()
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_event_types():
 | 
					def get_event_types():
 | 
				
			||||||
| 
						 | 
					@ -239,10 +234,10 @@ def create_event(event_type, start, end=None, jobs=[], is_template=None, name=No
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_job(job_id, event_id=None) -> Job:
 | 
					def get_job(job_id, event_id=None) -> Job:
 | 
				
			||||||
    query = db.select(Job).where(Job.id == job_id)
 | 
					    query = Job.query.filter(Job.id == job_id)
 | 
				
			||||||
    if event_id is not None:
 | 
					    if event_id is not None:
 | 
				
			||||||
        query = query.where(Job.event_id_ == event_id)
 | 
					        query = query.filter(Job.event_id_ == event_id)
 | 
				
			||||||
    job = db.session.execute(query).scalar_one_or_none()
 | 
					    job = query.one_or_none()
 | 
				
			||||||
    if job is None:
 | 
					    if job is None:
 | 
				
			||||||
        raise NotFound
 | 
					        raise NotFound
 | 
				
			||||||
    return job
 | 
					    return job
 | 
				
			||||||
| 
						 | 
					@ -299,7 +294,7 @@ def delete_job(job: Job):
 | 
				
			||||||
    db.session.commit()
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def assign_job(job: Job, user, value, is_backup=False, notify=False):
 | 
					def assign_job(job: Job, user, value, is_backup=False):
 | 
				
			||||||
    assert value > 0
 | 
					    assert value > 0
 | 
				
			||||||
    service = Service.query.get((job.id, user.id_))
 | 
					    service = Service.query.get((job.id, user.id_))
 | 
				
			||||||
    if service:
 | 
					    if service:
 | 
				
			||||||
| 
						 | 
					@ -307,17 +302,10 @@ def assign_job(job: Job, user, value, is_backup=False, notify=False):
 | 
				
			||||||
        service.is_backup = is_backup
 | 
					        service.is_backup = is_backup
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        job.services.append(Service(user_=user, value=value, is_backup=is_backup, job_=job))
 | 
					        job.services.append(Service(user_=user, value=value, is_backup=is_backup, job_=job))
 | 
				
			||||||
    if notify:
 | 
					 | 
				
			||||||
        EventPlugin.getPlugin().notify(
 | 
					 | 
				
			||||||
            user,
 | 
					 | 
				
			||||||
            f"You were assigned to a job\n{job.start.strftime('%d.%m.%Y')}",
 | 
					 | 
				
			||||||
            {"type": NotifyType.INFO_ACCEPTED, "event_id": job.event_id_},
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    db.session.commit()
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def unassign_job(job: Job = None, user=None, service=None, notify=False):
 | 
					def unassign_job(job: Job = None, user=None, service=None, notify=False):
 | 
				
			||||||
    _date = job.start.strftime("%d.%m.%Y")
 | 
					 | 
				
			||||||
    if service is None:
 | 
					    if service is None:
 | 
				
			||||||
        assert job is not None and user is not None
 | 
					        assert job is not None and user is not None
 | 
				
			||||||
        service = Service.query.get((job.id, user.id_))
 | 
					        service = Service.query.get((job.id, user.id_))
 | 
				
			||||||
| 
						 | 
					@ -331,24 +319,17 @@ def unassign_job(job: Job = None, user=None, service=None, notify=False):
 | 
				
			||||||
    db.session.delete(service)
 | 
					    db.session.delete(service)
 | 
				
			||||||
    db.session.commit()
 | 
					    db.session.commit()
 | 
				
			||||||
    if notify:
 | 
					    if notify:
 | 
				
			||||||
        EventPlugin.getPlugin().notify(
 | 
					        EventPlugin.plugin.notify(user, "Your assignmet was cancelled", {"event_id": event_id})
 | 
				
			||||||
            user, f"Your assignmet was cancelled\n{_date}", {"type": NotifyType.INFO_REJECTED, "event_id": event_id}
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def invite(job: Job, invitee, inviter, transferee=None):
 | 
					def invite(job: Job, invitee, inviter, transferee=None):
 | 
				
			||||||
    inv = Invitation(job_=job, inviter_=inviter, invitee_=invitee, transferee_=transferee)
 | 
					    inv = Invitation(job_=job, inviter_=inviter, invitee_=invitee, transferee_=transferee)
 | 
				
			||||||
    db.session.add(inv)
 | 
					    db.session.add(inv)
 | 
				
			||||||
    update()
 | 
					    update()
 | 
				
			||||||
    _date = job.start.strftime("%d.%m.%Y")
 | 
					 | 
				
			||||||
    if transferee is None:
 | 
					    if transferee is None:
 | 
				
			||||||
        EventPlugin.getPlugin().notify(
 | 
					        EventPlugin.plugin.notify(invitee, _("Job invitation"), {"type": NotifyType.INVITE, "invitation": inv.id})
 | 
				
			||||||
            invitee, _(f"Job invitation\n{_date}"), {"type": NotifyType.INVITE, "invitation": inv.id}
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        EventPlugin.getPlugin().notify(
 | 
					        EventPlugin.plugin.notify(invitee, _("Job transfer"), {"type": NotifyType.TRANSFER, "invitation": inv.id})
 | 
				
			||||||
            invitee, _(f"Job transfer\n{_date}"), {"type": NotifyType.TRANSFER, "invitation": inv.id}
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    return inv
 | 
					    return inv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -365,21 +346,9 @@ def get_invitations(user: User):
 | 
				
			||||||
    ).all()
 | 
					    ).all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cleanup_notifications(inv: Invitation):
 | 
					 | 
				
			||||||
    notifications = tuple(
 | 
					 | 
				
			||||||
        filter(
 | 
					 | 
				
			||||||
            lambda notification: notification.data.get("invitation") == inv.id, EventPlugin.getPlugin().notifications
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    for notification in notifications:
 | 
					 | 
				
			||||||
        db.session.delete(notification)
 | 
					 | 
				
			||||||
        db.session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def cancel_invitation(inv: Invitation):
 | 
					def cancel_invitation(inv: Invitation):
 | 
				
			||||||
    db.session.delete(inv)
 | 
					    db.session.delete(inv)
 | 
				
			||||||
    db.session.commit()
 | 
					    db.session.commit()
 | 
				
			||||||
    cleanup_notifications(inv)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def respond_invitation(invite: Invitation, accepted=True):
 | 
					def respond_invitation(invite: Invitation, accepted=True):
 | 
				
			||||||
| 
						 | 
					@ -392,7 +361,7 @@ def respond_invitation(invite: Invitation, accepted=True):
 | 
				
			||||||
        raise Conflict
 | 
					        raise Conflict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not accepted:
 | 
					    if not accepted:
 | 
				
			||||||
        EventPlugin.getPlugin().notify(
 | 
					        EventPlugin.plugin.notify(
 | 
				
			||||||
            inviter,
 | 
					            inviter,
 | 
				
			||||||
            _("Invitation rejected"),
 | 
					            _("Invitation rejected"),
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					@ -406,12 +375,12 @@ def respond_invitation(invite: Invitation, accepted=True):
 | 
				
			||||||
        if invite.transferee_id is None:
 | 
					        if invite.transferee_id is None:
 | 
				
			||||||
            assign_job(job, invite.invitee_, 1)
 | 
					            assign_job(job, invite.invitee_, 1)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            service = tuple(filter(lambda s: s.userid == invite.transferee_id, job.services))
 | 
					            service = filter(lambda s: s.userid == invite.transferee_id, job.services)
 | 
				
			||||||
            if not service:
 | 
					            if not service:
 | 
				
			||||||
                raise Conflict
 | 
					                raise Conflict
 | 
				
			||||||
            unassign_job(job, invite.transferee_, service[0], True)
 | 
					            unassign_job(job, invite.transferee_, service[0], True)
 | 
				
			||||||
            assign_job(job, invite.invitee_, service[0].value)
 | 
					            assign_job(job, invite.invitee_, service[0].value)
 | 
				
			||||||
        EventPlugin.getPlugin().notify(
 | 
					        EventPlugin.plugin.notify(
 | 
				
			||||||
            inviter,
 | 
					            inviter,
 | 
				
			||||||
            _("Invitation accepted"),
 | 
					            _("Invitation accepted"),
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					@ -432,7 +401,7 @@ def assign_backups():
 | 
				
			||||||
    services = Service.query.filter(Service.is_backup == True).join(Job).filter(Job.start <= start).all()
 | 
					    services = Service.query.filter(Service.is_backup == True).join(Job).filter(Job.start <= start).all()
 | 
				
			||||||
    for service in services:
 | 
					    for service in services:
 | 
				
			||||||
        if service.job_.start <= now or service.job_.is_full():
 | 
					        if service.job_.start <= now or service.job_.is_full():
 | 
				
			||||||
            EventPlugin.getPlugin().notify(
 | 
					            EventPlugin.plugin.notify(
 | 
				
			||||||
                service.user_,
 | 
					                service.user_,
 | 
				
			||||||
                "Your backup assignment was cancelled.",
 | 
					                "Your backup assignment was cancelled.",
 | 
				
			||||||
                {"event_id": service.job_.event_id_},
 | 
					                {"event_id": service.job_.event_id_},
 | 
				
			||||||
| 
						 | 
					@ -442,7 +411,7 @@ def assign_backups():
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            service.is_backup = False
 | 
					            service.is_backup = False
 | 
				
			||||||
            logger.debug(f"Service not full, assigning backup. {service.serialize()}")
 | 
					            logger.debug(f"Service not full, assigning backup. {service.serialize()}")
 | 
				
			||||||
            EventPlugin.getPlugin().notify(
 | 
					            EventPlugin.plugin.notify(
 | 
				
			||||||
                service.user_,
 | 
					                service.user_,
 | 
				
			||||||
                "Your backup assignment was accepted.",
 | 
					                "Your backup assignment was accepted.",
 | 
				
			||||||
                {"event_id": service.job_.event_id_},
 | 
					                {"event_id": service.job_.event_id_},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,105 +0,0 @@
 | 
				
			||||||
"""init events
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Revision ID: e70508bd8cb4
 | 
					 | 
				
			||||||
Revises: 20482a003db8
 | 
					 | 
				
			||||||
Create Date: 2023-04-10 14:21:47.007251
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
from alembic import op
 | 
					 | 
				
			||||||
import sqlalchemy as sa
 | 
					 | 
				
			||||||
import flaschengeist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# revision identifiers, used by Alembic.
 | 
					 | 
				
			||||||
revision = "e70508bd8cb4"
 | 
					 | 
				
			||||||
down_revision = None
 | 
					 | 
				
			||||||
branch_labels = ("events",)
 | 
					 | 
				
			||||||
depends_on = "flaschengeist"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def upgrade():
 | 
					 | 
				
			||||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
					 | 
				
			||||||
    op.create_table(
 | 
					 | 
				
			||||||
        "events_event_type",
 | 
					 | 
				
			||||||
        sa.Column("id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("name", sa.String(length=30), nullable=False),
 | 
					 | 
				
			||||||
        sa.PrimaryKeyConstraint("id", name=op.f("pk_events_event_type")),
 | 
					 | 
				
			||||||
        sa.UniqueConstraint("name", name=op.f("uq_events_event_type_name")),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    op.create_table(
 | 
					 | 
				
			||||||
        "events_job_type",
 | 
					 | 
				
			||||||
        sa.Column("id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("name", sa.String(length=30), nullable=False),
 | 
					 | 
				
			||||||
        sa.PrimaryKeyConstraint("id", name=op.f("pk_events_job_type")),
 | 
					 | 
				
			||||||
        sa.UniqueConstraint("name", name=op.f("uq_events_job_type_name")),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    op.create_table(
 | 
					 | 
				
			||||||
        "events_event",
 | 
					 | 
				
			||||||
        sa.Column("id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("start", flaschengeist.database.types.UtcDateTime(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("end", flaschengeist.database.types.UtcDateTime(), nullable=True),
 | 
					 | 
				
			||||||
        sa.Column("name", sa.String(length=255), nullable=True),
 | 
					 | 
				
			||||||
        sa.Column("description", sa.String(length=512), nullable=True),
 | 
					 | 
				
			||||||
        sa.Column("is_template", sa.Boolean(), nullable=True),
 | 
					 | 
				
			||||||
        sa.Column("type_id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(
 | 
					 | 
				
			||||||
            ["type_id"],
 | 
					 | 
				
			||||||
            ["events_event_type.id"],
 | 
					 | 
				
			||||||
            name=op.f("fk_events_event_type_id_events_event_type"),
 | 
					 | 
				
			||||||
            ondelete="CASCADE",
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        sa.PrimaryKeyConstraint("id", name=op.f("pk_events_event")),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    op.create_table(
 | 
					 | 
				
			||||||
        "events_job",
 | 
					 | 
				
			||||||
        sa.Column("id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("start", flaschengeist.database.types.UtcDateTime(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("end", flaschengeist.database.types.UtcDateTime(), nullable=True),
 | 
					 | 
				
			||||||
        sa.Column("comment", sa.String(length=256), nullable=True),
 | 
					 | 
				
			||||||
        sa.Column("type_id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("locked", sa.Boolean(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("required_services", sa.Numeric(precision=4, scale=2, asdecimal=False), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("event_id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(["event_id"], ["events_event.id"], name=op.f("fk_events_job_event_id_events_event")),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(
 | 
					 | 
				
			||||||
            ["type_id"], ["events_job_type.id"], name=op.f("fk_events_job_type_id_events_job_type")
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        sa.PrimaryKeyConstraint("id", name=op.f("pk_events_job")),
 | 
					 | 
				
			||||||
        sa.UniqueConstraint("type_id", "start", "event_id", name="_type_start_uc"),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    op.create_table(
 | 
					 | 
				
			||||||
        "events_invitation",
 | 
					 | 
				
			||||||
        sa.Column("id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("time", flaschengeist.database.types.UtcDateTime(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("job_id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("invitee_id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("inviter_id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("transferee_id", flaschengeist.database.types.Serial(), nullable=True),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(["invitee_id"], ["user.id"], name=op.f("fk_events_invitation_invitee_id_user")),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(["inviter_id"], ["user.id"], name=op.f("fk_events_invitation_inviter_id_user")),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(["job_id"], ["events_job.id"], name=op.f("fk_events_invitation_job_id_events_job")),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(["transferee_id"], ["user.id"], name=op.f("fk_events_invitation_transferee_id_user")),
 | 
					 | 
				
			||||||
        sa.PrimaryKeyConstraint("id", name=op.f("pk_events_invitation")),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    op.create_table(
 | 
					 | 
				
			||||||
        "events_service",
 | 
					 | 
				
			||||||
        sa.Column("is_backup", sa.Boolean(), nullable=True),
 | 
					 | 
				
			||||||
        sa.Column("value", sa.Numeric(precision=3, scale=2, asdecimal=False), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("job_id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.Column("user_id", flaschengeist.database.types.Serial(), nullable=False),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(["job_id"], ["events_job.id"], name=op.f("fk_events_service_job_id_events_job")),
 | 
					 | 
				
			||||||
        sa.ForeignKeyConstraint(["user_id"], ["user.id"], name=op.f("fk_events_service_user_id_user")),
 | 
					 | 
				
			||||||
        sa.PrimaryKeyConstraint("job_id", "user_id", name=op.f("pk_events_service")),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    # ### end Alembic commands ###
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def downgrade():
 | 
					 | 
				
			||||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
					 | 
				
			||||||
    op.drop_table("events_service")
 | 
					 | 
				
			||||||
    op.drop_table("events_invitation")
 | 
					 | 
				
			||||||
    op.drop_table("events_job")
 | 
					 | 
				
			||||||
    op.drop_table("events_event")
 | 
					 | 
				
			||||||
    op.drop_table("events_job_type")
 | 
					 | 
				
			||||||
    op.drop_table("events_event_type")
 | 
					 | 
				
			||||||
    # ### end Alembic commands ###
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
from __future__ import annotations  # TODO: Remove if python requirement is >= 3.10
 | 
					from __future__ import annotations  # TODO: Remove if python requirement is >= 3.10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from typing import Optional, Union, List
 | 
					from typing import Optional, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from sqlalchemy import UniqueConstraint
 | 
					from sqlalchemy import UniqueConstraint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,6 @@ _table_prefix_ = "events_"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EventType(db.Model, ModelSerializeMixin):
 | 
					class EventType(db.Model, ModelSerializeMixin):
 | 
				
			||||||
    __allow_unmapped__ = True
 | 
					 | 
				
			||||||
    __tablename__ = _table_prefix_ + "event_type"
 | 
					    __tablename__ = _table_prefix_ + "event_type"
 | 
				
			||||||
    id: int = db.Column(Serial, primary_key=True)
 | 
					    id: int = db.Column(Serial, primary_key=True)
 | 
				
			||||||
    name: str = db.Column(db.String(30), nullable=False, unique=True)
 | 
					    name: str = db.Column(db.String(30), nullable=False, unique=True)
 | 
				
			||||||
| 
						 | 
					@ -35,7 +34,6 @@ class JobType(db.Model, ModelSerializeMixin):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Service(db.Model, ModelSerializeMixin):
 | 
					class Service(db.Model, ModelSerializeMixin):
 | 
				
			||||||
    __allow_unmapped__ = True
 | 
					 | 
				
			||||||
    __tablename__ = _table_prefix_ + "service"
 | 
					    __tablename__ = _table_prefix_ + "service"
 | 
				
			||||||
    userid: str = ""
 | 
					    userid: str = ""
 | 
				
			||||||
    is_backup: bool = db.Column(db.Boolean, default=False)
 | 
					    is_backup: bool = db.Column(db.Boolean, default=False)
 | 
				
			||||||
| 
						 | 
					@ -59,7 +57,6 @@ class Service(db.Model, ModelSerializeMixin):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Job(db.Model, ModelSerializeMixin):
 | 
					class Job(db.Model, ModelSerializeMixin):
 | 
				
			||||||
    __allow_unmapped__ = True
 | 
					 | 
				
			||||||
    __tablename__ = _table_prefix_ + "job"
 | 
					    __tablename__ = _table_prefix_ + "job"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    id: int = db.Column(Serial, primary_key=True)
 | 
					    id: int = db.Column(Serial, primary_key=True)
 | 
				
			||||||
| 
						 | 
					@ -68,7 +65,7 @@ class Job(db.Model, ModelSerializeMixin):
 | 
				
			||||||
    comment: Optional[str] = db.Column(db.String(256))
 | 
					    comment: Optional[str] = db.Column(db.String(256))
 | 
				
			||||||
    type: int = db.Column("type_id", Serial, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False)
 | 
					    type: int = db.Column("type_id", Serial, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False)
 | 
				
			||||||
    locked: bool = db.Column(db.Boolean(), default=False, nullable=False)
 | 
					    locked: bool = db.Column(db.Boolean(), default=False, nullable=False)
 | 
				
			||||||
    services: List[Service] = db.relationship(
 | 
					    services: list[Service] = db.relationship(
 | 
				
			||||||
        "Service", back_populates="job_", cascade="save-update, merge, delete, delete-orphan"
 | 
					        "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)
 | 
				
			||||||
| 
						 | 
					@ -88,7 +85,6 @@ class Job(db.Model, ModelSerializeMixin):
 | 
				
			||||||
class Event(db.Model, ModelSerializeMixin):
 | 
					class Event(db.Model, ModelSerializeMixin):
 | 
				
			||||||
    """Model for an Event"""
 | 
					    """Model for an Event"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __allow_unmapped__ = True
 | 
					 | 
				
			||||||
    __tablename__ = _table_prefix_ + "event"
 | 
					    __tablename__ = _table_prefix_ + "event"
 | 
				
			||||||
    id: int = db.Column(Serial, primary_key=True)
 | 
					    id: int = db.Column(Serial, primary_key=True)
 | 
				
			||||||
    start: datetime = db.Column(UtcDateTime, nullable=False)
 | 
					    start: datetime = db.Column(UtcDateTime, nullable=False)
 | 
				
			||||||
| 
						 | 
					@ -97,7 +93,7 @@ class Event(db.Model, ModelSerializeMixin):
 | 
				
			||||||
    description: Optional[str] = db.Column(db.String(512))
 | 
					    description: Optional[str] = db.Column(db.String(512))
 | 
				
			||||||
    type: Union[EventType, int] = db.relationship("EventType")
 | 
					    type: Union[EventType, int] = db.relationship("EventType")
 | 
				
			||||||
    is_template: bool = db.Column(db.Boolean, default=False)
 | 
					    is_template: bool = db.Column(db.Boolean, default=False)
 | 
				
			||||||
    jobs: List[Job] = db.relationship(
 | 
					    jobs: list[Job] = db.relationship(
 | 
				
			||||||
        "Job",
 | 
					        "Job",
 | 
				
			||||||
        back_populates="event_",
 | 
					        back_populates="event_",
 | 
				
			||||||
        cascade="all,delete,delete-orphan",
 | 
					        cascade="all,delete,delete-orphan",
 | 
				
			||||||
| 
						 | 
					@ -113,7 +109,6 @@ class Event(db.Model, ModelSerializeMixin):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Invitation(db.Model, ModelSerializeMixin):
 | 
					class Invitation(db.Model, ModelSerializeMixin):
 | 
				
			||||||
    __allow_unmapped__ = True
 | 
					 | 
				
			||||||
    __tablename__ = _table_prefix_ + "invitation"
 | 
					    __tablename__ = _table_prefix_ + "invitation"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    id: int = db.Column(Serial, primary_key=True)
 | 
					    id: int = db.Column(Serial, primary_key=True)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
from http.client import NO_CONTENT
 | 
					from http.client import NO_CONTENT
 | 
				
			||||||
from flask import request, jsonify, Blueprint
 | 
					from flask import request, jsonify
 | 
				
			||||||
from werkzeug.exceptions import BadRequest, NotFound, Forbidden
 | 
					from werkzeug.exceptions import BadRequest, NotFound, Forbidden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from flaschengeist.models.session import Session
 | 
					from flaschengeist.models.session import Session
 | 
				
			||||||
| 
						 | 
					@ -7,14 +7,10 @@ from flaschengeist.controller import userController
 | 
				
			||||||
from flaschengeist.utils.decorators import login_required
 | 
					from flaschengeist.utils.decorators import login_required
 | 
				
			||||||
from flaschengeist.utils.datetime import from_iso_format
 | 
					from flaschengeist.utils.datetime import from_iso_format
 | 
				
			||||||
from flaschengeist.utils.HTTP import get_filter_args, no_content
 | 
					from flaschengeist.utils.HTTP import get_filter_args, no_content
 | 
				
			||||||
from flaschengeist import logger
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import event_controller, permissions, EventPlugin
 | 
					from . import event_controller, permissions, EventPlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
blueprint = Blueprint("events", __name__)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def dict_get(self, key, default=None, type=None):
 | 
					def dict_get(self, key, default=None, type=None):
 | 
				
			||||||
    """Same as .get from MultiDict"""
 | 
					    """Same as .get from MultiDict"""
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
| 
						 | 
					@ -24,20 +20,19 @@ def dict_get(self, key, default=None, type=None):
 | 
				
			||||||
    if type is not None:
 | 
					    if type is not None:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            rv = type(rv)
 | 
					            rv = type(rv)
 | 
				
			||||||
        except (ValueError, TypeError):
 | 
					        except ValueError:
 | 
				
			||||||
            rv = default
 | 
					            rv = default
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return rv
 | 
					    return rv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/templates", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/templates", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_templates(current_session):
 | 
					def get_templates(current_session):
 | 
				
			||||||
    return jsonify(event_controller.get_templates())
 | 
					    return jsonify(event_controller.get_templates())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/event-types", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/event-types", methods=["GET"])
 | 
				
			||||||
@blueprint.route("/events/event-types/<int:identifier>", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/event-types/<int:identifier>", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_event_types(current_session, identifier=None):
 | 
					def get_event_types(current_session, identifier=None):
 | 
				
			||||||
    """Get EventType(s)
 | 
					    """Get EventType(s)
 | 
				
			||||||
| 
						 | 
					@ -59,7 +54,7 @@ def get_event_types(current_session, identifier=None):
 | 
				
			||||||
    return jsonify(result)
 | 
					    return jsonify(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/event-types", methods=["POST"])
 | 
					@EventPlugin.blueprint.route("/events/event-types", methods=["POST"])
 | 
				
			||||||
@login_required(permission=permissions.EVENT_TYPE)
 | 
					@login_required(permission=permissions.EVENT_TYPE)
 | 
				
			||||||
def new_event_type(current_session):
 | 
					def new_event_type(current_session):
 | 
				
			||||||
    """Create a new EventType
 | 
					    """Create a new EventType
 | 
				
			||||||
| 
						 | 
					@ -81,7 +76,7 @@ def new_event_type(current_session):
 | 
				
			||||||
    return jsonify(event_type)
 | 
					    return jsonify(event_type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/event-types/<int:identifier>", methods=["PUT", "DELETE"])
 | 
					@EventPlugin.blueprint.route("/events/event-types/<int:identifier>", methods=["PUT", "DELETE"])
 | 
				
			||||||
@login_required(permission=permissions.EVENT_TYPE)
 | 
					@login_required(permission=permissions.EVENT_TYPE)
 | 
				
			||||||
def modify_event_type(identifier, current_session):
 | 
					def modify_event_type(identifier, current_session):
 | 
				
			||||||
    """Rename or delete an event type
 | 
					    """Rename or delete an event type
 | 
				
			||||||
| 
						 | 
					@ -107,7 +102,7 @@ def modify_event_type(identifier, current_session):
 | 
				
			||||||
    return "", NO_CONTENT
 | 
					    return "", NO_CONTENT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/job-types", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/job-types", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_job_types(current_session):
 | 
					def get_job_types(current_session):
 | 
				
			||||||
    """Get all JobTypes
 | 
					    """Get all JobTypes
 | 
				
			||||||
| 
						 | 
					@ -124,7 +119,7 @@ def get_job_types(current_session):
 | 
				
			||||||
    return jsonify(types)
 | 
					    return jsonify(types)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/job-types", methods=["POST"])
 | 
					@EventPlugin.blueprint.route("/events/job-types", methods=["POST"])
 | 
				
			||||||
@login_required(permission=permissions.JOB_TYPE)
 | 
					@login_required(permission=permissions.JOB_TYPE)
 | 
				
			||||||
def new_job_type(current_session):
 | 
					def new_job_type(current_session):
 | 
				
			||||||
    """Create a new JobType
 | 
					    """Create a new JobType
 | 
				
			||||||
| 
						 | 
					@ -146,7 +141,7 @@ def new_job_type(current_session):
 | 
				
			||||||
    return jsonify(jt)
 | 
					    return jsonify(jt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/job-types/<int:type_id>", methods=["PUT", "DELETE"])
 | 
					@EventPlugin.blueprint.route("/events/job-types/<int:type_id>", methods=["PUT", "DELETE"])
 | 
				
			||||||
@login_required(permission=permissions.JOB_TYPE)
 | 
					@login_required(permission=permissions.JOB_TYPE)
 | 
				
			||||||
def modify_job_type(type_id, current_session):
 | 
					def modify_job_type(type_id, current_session):
 | 
				
			||||||
    """Rename or delete a JobType
 | 
					    """Rename or delete a JobType
 | 
				
			||||||
| 
						 | 
					@ -172,7 +167,7 @@ def modify_job_type(type_id, current_session):
 | 
				
			||||||
    return "", NO_CONTENT
 | 
					    return "", NO_CONTENT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/<int:event_id>", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/<int:event_id>", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_event(event_id, current_session):
 | 
					def get_event(event_id, current_session):
 | 
				
			||||||
    """Get event by id
 | 
					    """Get event by id
 | 
				
			||||||
| 
						 | 
					@ -193,7 +188,7 @@ def get_event(event_id, current_session):
 | 
				
			||||||
    return jsonify(event)
 | 
					    return jsonify(event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_events(current_session):
 | 
					def get_events(current_session):
 | 
				
			||||||
    count, result = event_controller.get_events(
 | 
					    count, result = event_controller.get_events(
 | 
				
			||||||
| 
						 | 
					@ -209,39 +204,21 @@ def _add_job(event, data):
 | 
				
			||||||
        end = dict_get(data, "end", None, type=from_iso_format)
 | 
					        end = dict_get(data, "end", None, type=from_iso_format)
 | 
				
			||||||
        required_services = data["required_services"]
 | 
					        required_services = data["required_services"]
 | 
				
			||||||
        job_type = int(data["type"])
 | 
					        job_type = int(data["type"])
 | 
				
			||||||
 | 
					 | 
				
			||||||
        job_type = event_controller.get_job_type(job_type)
 | 
					 | 
				
			||||||
        job_id = dict_get(data, "id", None, int)
 | 
					 | 
				
			||||||
        if job_id:
 | 
					 | 
				
			||||||
            job = next(job for job in event.jobs if job.id == job_id)
 | 
					 | 
				
			||||||
            job.event = event
 | 
					 | 
				
			||||||
            job.job_type = job_type
 | 
					 | 
				
			||||||
            job.start = start
 | 
					 | 
				
			||||||
            job.end = end
 | 
					 | 
				
			||||||
            job.required_services = required_services
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            event_controller.add_job(
 | 
					 | 
				
			||||||
                event,
 | 
					 | 
				
			||||||
                job_type,
 | 
					 | 
				
			||||||
                required_services,
 | 
					 | 
				
			||||||
                start,
 | 
					 | 
				
			||||||
                end,
 | 
					 | 
				
			||||||
                comment=dict_get(data, "comment", None, str),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    except (KeyError, ValueError):
 | 
					    except (KeyError, ValueError):
 | 
				
			||||||
        raise BadRequest("Missing or invalid POST parameter")
 | 
					        raise BadRequest("Missing or invalid POST parameter")
 | 
				
			||||||
    except StopIteration:
 | 
					
 | 
				
			||||||
        raise BadRequest("Job not in event")
 | 
					    job_type = event_controller.get_job_type(job_type)
 | 
				
			||||||
 | 
					    event_controller.add_job(
 | 
				
			||||||
 | 
					        event,
 | 
				
			||||||
 | 
					        job_type,
 | 
				
			||||||
 | 
					        required_services,
 | 
				
			||||||
 | 
					        start,
 | 
				
			||||||
 | 
					        end,
 | 
				
			||||||
 | 
					        comment=dict_get(data, "comment", None, str),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _delete_jobs_from_event(event, data):
 | 
					@EventPlugin.blueprint.route("/events", methods=["POST"])
 | 
				
			||||||
    job_ids = [x["id"] for x in data if "id" in x]
 | 
					 | 
				
			||||||
    for job in event.jobs:
 | 
					 | 
				
			||||||
        if job.id not in job_ids:
 | 
					 | 
				
			||||||
            event.jobs.remove(job)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@blueprint.route("/events", methods=["POST"])
 | 
					 | 
				
			||||||
@login_required(permission=permissions.CREATE)
 | 
					@login_required(permission=permissions.CREATE)
 | 
				
			||||||
def create_event(current_session):
 | 
					def create_event(current_session):
 | 
				
			||||||
    """Create an new event
 | 
					    """Create an new event
 | 
				
			||||||
| 
						 | 
					@ -282,7 +259,7 @@ def create_event(current_session):
 | 
				
			||||||
        raise BadRequest("Invalid parameter")
 | 
					        raise BadRequest("Invalid parameter")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/<int:event_id>", methods=["PUT"])
 | 
					@EventPlugin.blueprint.route("/events/<int:event_id>", methods=["PUT"])
 | 
				
			||||||
@login_required(permission=permissions.EDIT)
 | 
					@login_required(permission=permissions.EDIT)
 | 
				
			||||||
def modify_event(event_id, current_session):
 | 
					def modify_event(event_id, current_session):
 | 
				
			||||||
    """Modify an event
 | 
					    """Modify an event
 | 
				
			||||||
| 
						 | 
					@ -300,7 +277,6 @@ def modify_event(event_id, current_session):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    event = event_controller.get_event(event_id)
 | 
					    event = event_controller.get_event(event_id)
 | 
				
			||||||
    data = request.get_json()
 | 
					    data = request.get_json()
 | 
				
			||||||
    logger.debug("PUT data: %s", data)
 | 
					 | 
				
			||||||
    event.start = dict_get(data, "start", event.start, type=from_iso_format)
 | 
					    event.start = dict_get(data, "start", event.start, type=from_iso_format)
 | 
				
			||||||
    event.end = dict_get(data, "end", event.end, type=from_iso_format)
 | 
					    event.end = dict_get(data, "end", event.end, type=from_iso_format)
 | 
				
			||||||
    event.name = dict_get(data, "name", event.name, type=str)
 | 
					    event.name = dict_get(data, "name", event.name, type=str)
 | 
				
			||||||
| 
						 | 
					@ -308,15 +284,12 @@ def modify_event(event_id, current_session):
 | 
				
			||||||
    if "type" in data:
 | 
					    if "type" in data:
 | 
				
			||||||
        event_type = event_controller.get_event_type(data["type"])
 | 
					        event_type = event_controller.get_event_type(data["type"])
 | 
				
			||||||
        event.type = event_type
 | 
					        event.type = event_type
 | 
				
			||||||
    if "jobs" in data:
 | 
					
 | 
				
			||||||
        _delete_jobs_from_event(event, data["jobs"])
 | 
					 | 
				
			||||||
        for job in data["jobs"]:
 | 
					 | 
				
			||||||
            _add_job(event, job)
 | 
					 | 
				
			||||||
    event_controller.update()
 | 
					    event_controller.update()
 | 
				
			||||||
    return jsonify(event)
 | 
					    return jsonify(event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/<int:event_id>", methods=["DELETE"])
 | 
					@EventPlugin.blueprint.route("/events/<int:event_id>", methods=["DELETE"])
 | 
				
			||||||
@login_required(permission=permissions.DELETE)
 | 
					@login_required(permission=permissions.DELETE)
 | 
				
			||||||
def delete_event(event_id, current_session):
 | 
					def delete_event(event_id, current_session):
 | 
				
			||||||
    """Delete an event
 | 
					    """Delete an event
 | 
				
			||||||
| 
						 | 
					@ -334,7 +307,7 @@ def delete_event(event_id, current_session):
 | 
				
			||||||
    return "", NO_CONTENT
 | 
					    return "", NO_CONTENT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/<int:event_id>/jobs", methods=["POST"])
 | 
					@EventPlugin.blueprint.route("/events/<int:event_id>/jobs", methods=["POST"])
 | 
				
			||||||
@login_required(permission=permissions.EDIT)
 | 
					@login_required(permission=permissions.EDIT)
 | 
				
			||||||
def add_job(event_id, current_session):
 | 
					def add_job(event_id, current_session):
 | 
				
			||||||
    """Add an new Job to an Event / EventSlot
 | 
					    """Add an new Job to an Event / EventSlot
 | 
				
			||||||
| 
						 | 
					@ -355,7 +328,7 @@ def add_job(event_id, current_session):
 | 
				
			||||||
    return jsonify(event)
 | 
					    return jsonify(event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["DELETE"])
 | 
					@EventPlugin.blueprint.route("/events/<int:event_id>/jobs/<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
 | 
				
			||||||
| 
						 | 
					@ -375,7 +348,7 @@ def delete_job(event_id, job_id, current_session):
 | 
				
			||||||
    return no_content()
 | 
					    return no_content()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["PUT"])
 | 
					@EventPlugin.blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["PUT"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def update_job(event_id, job_id, current_session: Session):
 | 
					def update_job(event_id, job_id, current_session: Session):
 | 
				
			||||||
    """Edit Job
 | 
					    """Edit Job
 | 
				
			||||||
| 
						 | 
					@ -417,20 +390,20 @@ def update_job(event_id, job_id, current_session: Session):
 | 
				
			||||||
    return jsonify(job)
 | 
					    return jsonify(job)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/jobs", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/jobs", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_jobs(current_session: Session):
 | 
					def get_jobs(current_session: Session):
 | 
				
			||||||
    count, result = event_controller.get_jobs(current_session.user_, *get_filter_args())
 | 
					    count, result = event_controller.get_jobs(current_session.user_, *get_filter_args())
 | 
				
			||||||
    return jsonify({"count": count, "result": result})
 | 
					    return jsonify({"count": count, "result": result})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/jobs/<int:job_id>", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/jobs/<int:job_id>", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_job(job_id, current_session: Session):
 | 
					def get_job(job_id, current_session: Session):
 | 
				
			||||||
    return jsonify(event_controller.get_job(job_id))
 | 
					    return jsonify(event_controller.get_job(job_id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/jobs/<int:job_id>/assign", methods=["POST"])
 | 
					@EventPlugin.blueprint.route("/events/jobs/<int:job_id>/assign", methods=["POST"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def assign_job(job_id, current_session: Session):
 | 
					def assign_job(job_id, current_session: Session):
 | 
				
			||||||
    """Assign / unassign user to the Job
 | 
					    """Assign / unassign user to the Job
 | 
				
			||||||
| 
						 | 
					@ -458,9 +431,7 @@ def assign_job(job_id, current_session: Session):
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            raise Forbidden
 | 
					            raise Forbidden
 | 
				
			||||||
        if value > 0:
 | 
					        if value > 0:
 | 
				
			||||||
            event_controller.assign_job(
 | 
					            event_controller.assign_job(job, user, value, data.get("is_backup", False))
 | 
				
			||||||
                job, user, value, data.get("is_backup", False), notify=user != current_session.user_
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            event_controller.unassign_job(job, user, notify=user != current_session.user_)
 | 
					            event_controller.unassign_job(job, user, notify=user != current_session.user_)
 | 
				
			||||||
    except (TypeError, KeyError, ValueError):
 | 
					    except (TypeError, KeyError, ValueError):
 | 
				
			||||||
| 
						 | 
					@ -468,7 +439,7 @@ def assign_job(job_id, current_session: Session):
 | 
				
			||||||
    return jsonify(job)
 | 
					    return jsonify(job)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/jobs/<int:job_id>/lock", methods=["POST"])
 | 
					@EventPlugin.blueprint.route("/events/jobs/<int:job_id>/lock", methods=["POST"])
 | 
				
			||||||
@login_required(permissions.LOCK_JOBS)
 | 
					@login_required(permissions.LOCK_JOBS)
 | 
				
			||||||
def lock_job(job_id, current_session: Session):
 | 
					def lock_job(job_id, current_session: Session):
 | 
				
			||||||
    """Lock / unlock the Job
 | 
					    """Lock / unlock the Job
 | 
				
			||||||
| 
						 | 
					@ -495,7 +466,7 @@ def lock_job(job_id, current_session: Session):
 | 
				
			||||||
    return no_content()
 | 
					    return no_content()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/invitations", methods=["POST"])
 | 
					@EventPlugin.blueprint.route("/events/invitations", methods=["POST"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def invite(current_session: Session):
 | 
					def invite(current_session: Session):
 | 
				
			||||||
    """Invite an user to a job or transfer job
 | 
					    """Invite an user to a job or transfer job
 | 
				
			||||||
| 
						 | 
					@ -533,13 +504,13 @@ def invite(current_session: Session):
 | 
				
			||||||
        raise BadRequest
 | 
					        raise BadRequest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/invitations", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/invitations", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_invitations(current_session: Session):
 | 
					def get_invitations(current_session: Session):
 | 
				
			||||||
    return jsonify(event_controller.get_invitations(current_session.user_))
 | 
					    return jsonify(event_controller.get_invitations(current_session.user_))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/invitations/<int:invitation_id>", methods=["GET"])
 | 
					@EventPlugin.blueprint.route("/events/invitations/<int:invitation_id>", methods=["GET"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_invitation(invitation_id: int, current_session: Session):
 | 
					def get_invitation(invitation_id: int, current_session: Session):
 | 
				
			||||||
    inv = event_controller.get_invitation(invitation_id)
 | 
					    inv = event_controller.get_invitation(invitation_id)
 | 
				
			||||||
| 
						 | 
					@ -548,7 +519,7 @@ def get_invitation(invitation_id: int, current_session: Session):
 | 
				
			||||||
    return jsonify(inv)
 | 
					    return jsonify(inv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route("/events/invitations/<int:invitation_id>", methods=["DELETE", "PUT"])
 | 
					@EventPlugin.blueprint.route("/events/invitations/<int:invitation_id>", methods=["DELETE", "PUT"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def respond_invitation(invitation_id: int, current_session: Session):
 | 
					def respond_invitation(invitation_id: int, current_session: Session):
 | 
				
			||||||
    inv = event_controller.get_invitation(invitation_id)
 | 
					    inv = event_controller.get_invitation(invitation_id)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,14 +82,13 @@
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <template v-for="(job, index) in event.jobs" :key="index">
 | 
					        <template v-for="(job, index) in event.jobs" :key="index">
 | 
				
			||||||
          <!--:ref="active === index ? 'activeJob' : undefined"-->
 | 
					 | 
				
			||||||
          <edit-job-slot
 | 
					          <edit-job-slot
 | 
				
			||||||
            ref="activeJob"
 | 
					            :ref="active === index ? 'activeJob' : undefined"
 | 
				
			||||||
            v-model="event.jobs[index]"
 | 
					            v-model="event.jobs[index]"
 | 
				
			||||||
            :active="index === active"
 | 
					            :active="index === active"
 | 
				
			||||||
            class="q-mb-md"
 | 
					            class="q-mb-md"
 | 
				
			||||||
            @remove-job="removeJob(index)"
 | 
					 | 
				
			||||||
            @activate="activate(index)"
 | 
					            @activate="activate(index)"
 | 
				
			||||||
 | 
					            @remove-job="removeJob(index)"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
      </q-card-section>
 | 
					      </q-card-section>
 | 
				
			||||||
| 
						 | 
					@ -114,7 +113,7 @@ import { IsoDateInput } from '@flaschengeist/api/components';
 | 
				
			||||||
import { useEventStore } from '../../store';
 | 
					import { useEventStore } from '../../store';
 | 
				
			||||||
import { emptyEvent, Job, EditableEvent } from '../../store/models';
 | 
					import { emptyEvent, Job, EditableEvent } from '../../store/models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { date, DateOptions } from 'quasar';
 | 
					import { date, ModifyDateOptions } from 'quasar';
 | 
				
			||||||
import { computed, defineComponent, PropType, ref, onBeforeMount, watch } from 'vue';
 | 
					import { computed, defineComponent, PropType, ref, onBeforeMount, watch } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import EditJobSlot from './EditJobSlot.vue';
 | 
					import EditJobSlot from './EditJobSlot.vue';
 | 
				
			||||||
| 
						 | 
					@ -143,7 +142,7 @@ export default defineComponent({
 | 
				
			||||||
    const store = useEventStore();
 | 
					    const store = useEventStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const active = ref(0);
 | 
					    const active = ref(0);
 | 
				
			||||||
    const activeJob = ref<{ validate: () => Promise<boolean> }[]>([]);
 | 
					    const activeJob = ref<{ validate: () => Promise<boolean> }>();
 | 
				
			||||||
    const templates = computed(() => store.templates);
 | 
					    const templates = computed(() => store.templates);
 | 
				
			||||||
    const template = ref<FG.Event>();
 | 
					    const template = ref<FG.Event>();
 | 
				
			||||||
    const event = ref<EditableEvent>(props.modelValue || emptyEvent());
 | 
					    const event = ref<EditableEvent>(props.modelValue || emptyEvent());
 | 
				
			||||||
| 
						 | 
					@ -165,10 +164,9 @@ export default defineComponent({
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function addJob() {
 | 
					    function addJob() {
 | 
				
			||||||
      if (!activeJob.value[active.value]) {
 | 
					      if (!activeJob.value) event.value.jobs.push(new Job());
 | 
				
			||||||
        event.value.jobs.push(new Job());
 | 
					      else
 | 
				
			||||||
      } else
 | 
					        void activeJob.value.validate().then((success) => {
 | 
				
			||||||
        void activeJob.value[active.value].validate().then((success) => {
 | 
					 | 
				
			||||||
          if (success) {
 | 
					          if (success) {
 | 
				
			||||||
            event.value.jobs.push(new Job());
 | 
					            event.value.jobs.push(new Job());
 | 
				
			||||||
            active.value = event.value.jobs.length - 1;
 | 
					            active.value = event.value.jobs.length - 1;
 | 
				
			||||||
| 
						 | 
					@ -205,11 +203,14 @@ export default defineComponent({
 | 
				
			||||||
    async function save(template = false) {
 | 
					    async function save(template = false) {
 | 
				
			||||||
      event.value.is_template = template;
 | 
					      event.value.is_template = template;
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
 | 
					        if (event.value?.id !== undefined) {
 | 
				
			||||||
 | 
					          //fix
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        await store.addEvent(event.value);
 | 
					        await store.addEvent(event.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (props.modelValue === undefined && recurrent.value && !event.value.is_template) {
 | 
					        if (props.modelValue === undefined && recurrent.value && !event.value.is_template) {
 | 
				
			||||||
          let count = 0;
 | 
					          let count = 0;
 | 
				
			||||||
          const options: DateOptions = {};
 | 
					          const options: ModifyDateOptions = {};
 | 
				
			||||||
          switch (recurrenceRule.value.frequency) {
 | 
					          switch (recurrenceRule.value.frequency) {
 | 
				
			||||||
            case 'daily':
 | 
					            case 'daily':
 | 
				
			||||||
              options['days'] = 1 * recurrenceRule.value.interval;
 | 
					              options['days'] = 1 * recurrenceRule.value.interval;
 | 
				
			||||||
| 
						 | 
					@ -262,7 +263,7 @@ export default defineComponent({
 | 
				
			||||||
      !d || event.value.start <= d || 'Das Veranstaltungsende muss vor dem Beginn liegen';
 | 
					      !d || event.value.start <= d || 'Das Veranstaltungsende muss vor dem Beginn liegen';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function activate(idx: number) {
 | 
					    function activate(idx: number) {
 | 
				
			||||||
      void activeJob.value[active.value]?.validate().then((s) => {
 | 
					      void activeJob.value?.validate().then((s) => {
 | 
				
			||||||
        if (s) active.value = idx;
 | 
					        if (s) active.value = idx;
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@
 | 
				
			||||||
          input-debounce="0"
 | 
					          input-debounce="0"
 | 
				
			||||||
          class="col-xs-12 col-sm-6 q-pa-sm"
 | 
					          class="col-xs-12 col-sm-6 q-pa-sm"
 | 
				
			||||||
          :options="jobtypes"
 | 
					          :options="jobtypes"
 | 
				
			||||||
          :option-label="(jobtype) => (typeof jobtype === 'number' ? '' : jobtype.name)"
 | 
					          option-label="name"
 | 
				
			||||||
          option-value="id"
 | 
					          option-value="id"
 | 
				
			||||||
          map-options
 | 
					          map-options
 | 
				
			||||||
          clearable
 | 
					          clearable
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,21 +28,31 @@
 | 
				
			||||||
      </q-card>
 | 
					      </q-card>
 | 
				
			||||||
    </q-dialog>
 | 
					    </q-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <q-table :title="title" :rows="rows" row-key="id" :columns="columns">
 | 
					    <q-card>
 | 
				
			||||||
      <template #top-right>
 | 
					      <q-card-section>
 | 
				
			||||||
        <q-input ref="input" v-model="actualType.name" :rules="rules" dense placeholder="Neuer Typ">
 | 
					        <q-table :title="title" :rows="rows" row-key="id" :columns="columns">
 | 
				
			||||||
          <slot name="after"
 | 
					          <template #top-right>
 | 
				
			||||||
            ><q-btn color="primary" icon="mdi-plus" title="Hinzufügen" @click="addType"
 | 
					            <q-input
 | 
				
			||||||
          /></slot>
 | 
					              ref="input"
 | 
				
			||||||
        </q-input>
 | 
					              v-model="actualType.name"
 | 
				
			||||||
      </template>
 | 
					              :rules="rules"
 | 
				
			||||||
      <template #body-cell-actions="props">
 | 
					              dense
 | 
				
			||||||
        <q-td :props="props" align="right" :auto-width="true">
 | 
					              placeholder="Neuer Typ"
 | 
				
			||||||
          <q-btn round icon="mdi-pencil" @click="editType(props.row.id)" />
 | 
					            >
 | 
				
			||||||
          <q-btn round icon="mdi-delete" @click="deleteType(props.row.id)" />
 | 
					              <slot name="after"
 | 
				
			||||||
        </q-td>
 | 
					                ><q-btn color="primary" icon="mdi-plus" title="Hinzufügen" @click="addType"
 | 
				
			||||||
      </template>
 | 
					              /></slot>
 | 
				
			||||||
    </q-table>
 | 
					            </q-input>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          <template #body-cell-actions="props">
 | 
				
			||||||
 | 
					            <q-td :props="props" align="right" :auto-width="true">
 | 
				
			||||||
 | 
					              <q-btn round icon="mdi-pencil" @click="editType(props.row.id)" />
 | 
				
			||||||
 | 
					              <q-btn round icon="mdi-delete" @click="deleteType(props.row.id)" />
 | 
				
			||||||
 | 
					            </q-td>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					        </q-table>
 | 
				
			||||||
 | 
					      </q-card-section>
 | 
				
			||||||
 | 
					    </q-card>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,31 +16,31 @@
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </q-card>
 | 
					    </q-card>
 | 
				
			||||||
  </q-dialog>
 | 
					  </q-dialog>
 | 
				
			||||||
  <!-- <div class="q-pa-md"> -->
 | 
					  <div class="q-pa-md">
 | 
				
			||||||
  <!-- <q-card style="height: 70vh; max-width: 1800px" class="q-pa-md"> -->
 | 
					    <q-card style="height: 70vh; max-width: 1800px" class="q-pa-md">
 | 
				
			||||||
  <div ref="scrollDiv" class="scroll" style="height: 100%">
 | 
					      <div ref="scrollDiv" class="scroll" style="height: 100%">
 | 
				
			||||||
    <q-infinite-scroll :offset="250" @load="load">
 | 
					        <q-infinite-scroll :offset="250" @load="load">
 | 
				
			||||||
      <q-list>
 | 
					          <q-list>
 | 
				
			||||||
        <q-item id="bbb">
 | 
					            <q-item id="bbb">
 | 
				
			||||||
          <q-btn label="Ältere Veranstaltungen laden" @click="load(-1)" />
 | 
					              <q-btn label="Ältere Veranstaltungen laden" @click="load(-1)" />
 | 
				
			||||||
        </q-item>
 | 
					            </q-item>
 | 
				
			||||||
        <template v-for="(events, index) in agendas" :key="index">
 | 
					            <template v-for="(events, index) in agendas" :key="index">
 | 
				
			||||||
          <q-separator />
 | 
					              <q-separator />
 | 
				
			||||||
          <q-item-label header>{{ asDate(index) }}</q-item-label>
 | 
					              <q-item-label overline>{{ index }}</q-item-label>
 | 
				
			||||||
          <q-item v-for="(event, idx) in events" :key="idx">
 | 
					              <q-item v-for="(event, idx) in events" :key="idx"
 | 
				
			||||||
            <event-slot :model-value="event" />
 | 
					                ><event-slot :model-value="event" />{{ idx }}</q-item
 | 
				
			||||||
          </q-item>
 | 
					              >
 | 
				
			||||||
        </template>
 | 
					            </template>
 | 
				
			||||||
      </q-list>
 | 
					          </q-list>
 | 
				
			||||||
      <template #loading>
 | 
					          <template #loading>
 | 
				
			||||||
        <div class="row justify-center q-my-md">
 | 
					            <div class="row justify-center q-my-md">
 | 
				
			||||||
          <q-spinner-dots color="primary" size="40px" />
 | 
					              <q-spinner-dots color="primary" size="40px" />
 | 
				
			||||||
        </div>
 | 
					            </div>
 | 
				
			||||||
      </template>
 | 
					          </template>
 | 
				
			||||||
    </q-infinite-scroll>
 | 
					        </q-infinite-scroll>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </q-card>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <!-- </q-card> -->
 | 
					 | 
				
			||||||
  <!-- </div> -->
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
| 
						 | 
					@ -157,15 +157,6 @@ export default defineComponent({
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function asDate(value: string) {
 | 
					 | 
				
			||||||
      if (value) {
 | 
					 | 
				
			||||||
        const year = parseInt(value.substring(0, 4));
 | 
					 | 
				
			||||||
        const month = parseInt(value.substring(4, 6)) - 1;
 | 
					 | 
				
			||||||
        const day = parseInt(value.substring(6, 8));
 | 
					 | 
				
			||||||
        return date.formatDate(new Date(year, month, day), 'DD.MM.YYYY');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      agendas,
 | 
					      agendas,
 | 
				
			||||||
      asYear,
 | 
					      asYear,
 | 
				
			||||||
| 
						 | 
					@ -176,7 +167,6 @@ export default defineComponent({
 | 
				
			||||||
      editDone,
 | 
					      editDone,
 | 
				
			||||||
      load,
 | 
					      load,
 | 
				
			||||||
      remove,
 | 
					      remove,
 | 
				
			||||||
      asDate,
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/index.ts
								
								
								
								
							
							
						
						
									
										14
									
								
								src/index.ts
								
								
								
								
							| 
						 | 
					@ -10,11 +10,8 @@ const EventTypes = {
 | 
				
			||||||
  invite: 0x01,
 | 
					  invite: 0x01,
 | 
				
			||||||
  transfer: 0x02,
 | 
					  transfer: 0x02,
 | 
				
			||||||
  invitation_response: 0x10,
 | 
					  invitation_response: 0x10,
 | 
				
			||||||
  invitation_accepted: 0x11,
 | 
					  invitation_accepted: 0x10,
 | 
				
			||||||
  invitation_rejected: 0x12,
 | 
					  invitation_rejected: 0x11,
 | 
				
			||||||
  info: 0x20,
 | 
					 | 
				
			||||||
  info_accepted: 0x21,
 | 
					 | 
				
			||||||
  info_rejected: 0x22,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function transpile(msg: FG_Plugin.Notification) {
 | 
					function transpile(msg: FG_Plugin.Notification) {
 | 
				
			||||||
| 
						 | 
					@ -32,10 +29,7 @@ function transpile(msg: FG_Plugin.Notification) {
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    message.link = { name: 'events-requests' };
 | 
					    message.link = { name: 'events-requests' };
 | 
				
			||||||
  } else if (
 | 
					  } else if ((message.data.type & EventTypes._mask_) === EventTypes.invitation_response) {
 | 
				
			||||||
    (message.data.type & EventTypes._mask_) === EventTypes.invitation_response ||
 | 
					 | 
				
			||||||
    (message.data.type & EventTypes._mask_) === EventTypes.info
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    message.link = {
 | 
					    message.link = {
 | 
				
			||||||
      name: 'events-single-view',
 | 
					      name: 'events-single-view',
 | 
				
			||||||
      params: { id: (<InvitationResponseData>message.data).event },
 | 
					      params: { id: (<InvitationResponseData>message.data).event },
 | 
				
			||||||
| 
						 | 
					@ -45,7 +39,7 @@ function transpile(msg: FG_Plugin.Notification) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const plugin: FG_Plugin.Plugin = {
 | 
					const plugin: FG_Plugin.Plugin = {
 | 
				
			||||||
  id: 'events',
 | 
					  id: 'dev.flaschengeist.events',
 | 
				
			||||||
  name: 'Event schedule',
 | 
					  name: 'Event schedule',
 | 
				
			||||||
  innerRoutes,
 | 
					  innerRoutes,
 | 
				
			||||||
  internalRoutes: privateRoutes,
 | 
					  internalRoutes: privateRoutes,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,21 +43,15 @@
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { computed, defineComponent, ref, onBeforeMount } from 'vue';
 | 
					import { computed, defineComponent, ref } from 'vue';
 | 
				
			||||||
import { useQuasar } from 'quasar';
 | 
					import { useQuasar } from 'quasar';
 | 
				
			||||||
import AgendaView from '../components/overview/AgendaView.vue';
 | 
					import AgendaView from '../components/overview/AgendaView.vue';
 | 
				
			||||||
import ListView from '../components/overview/ListView.vue';
 | 
					import ListView from '../components/overview/ListView.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useEventStore } from '../store';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  name: 'EventOverview',
 | 
					  name: 'EventOverview',
 | 
				
			||||||
  components: { AgendaView, ListView },
 | 
					  components: { AgendaView, ListView },
 | 
				
			||||||
  setup() {
 | 
					  setup() {
 | 
				
			||||||
    const store = useEventStore();
 | 
					 | 
				
			||||||
    onBeforeMount(() => {
 | 
					 | 
				
			||||||
      void store.getJobTypes();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const quasar = useQuasar();
 | 
					    const quasar = useQuasar();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const tabs = computed(() => [
 | 
					    const tabs = computed(() => [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,55 +1,40 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <q-page padding>
 | 
					  <q-page padding>
 | 
				
			||||||
    <q-table
 | 
					    <q-card>
 | 
				
			||||||
      v-model:pagination="pagination"
 | 
					      <q-card-section>
 | 
				
			||||||
      title="Dienstanfragen"
 | 
					        <q-table
 | 
				
			||||||
      :rows="rows"
 | 
					          v-model:pagination="pagination"
 | 
				
			||||||
      :columns="columns"
 | 
					          title="Dienstanfragen"
 | 
				
			||||||
      row-key="id"
 | 
					          :rows="rows"
 | 
				
			||||||
      :loading="loading"
 | 
					          :columns="columns"
 | 
				
			||||||
      binary-state-sort
 | 
					          row-key="id"
 | 
				
			||||||
      @request="onRequest"
 | 
					          :loading="loading"
 | 
				
			||||||
    >
 | 
					          binary-state-sort
 | 
				
			||||||
      <template #top-right>
 | 
					          @request="onRequest"
 | 
				
			||||||
        <q-toggle v-model="showSent" dense label="Gesendete anzeigen" />
 | 
					        >
 | 
				
			||||||
      </template>
 | 
					          <template #top-right>
 | 
				
			||||||
      <template #body-cell-inviter="props">
 | 
					            <q-toggle v-model="showSent" dense label="Gesendete anzeigen" />
 | 
				
			||||||
        <q-td :props="props">
 | 
					          </template>
 | 
				
			||||||
          <div>
 | 
					          <template #body-cell-inviter="props">
 | 
				
			||||||
            {{ props.value.with
 | 
					            <q-td :props="props">
 | 
				
			||||||
            }}<q-icon v-if="props.value.sender" name="mdi-account-alert">
 | 
					              <div>
 | 
				
			||||||
              <q-tooltip>Gesendet von {{ props.value.sender }}</q-tooltip>
 | 
					                {{ props.value.with
 | 
				
			||||||
            </q-icon>
 | 
					                }}<q-icon v-if="props.value.sender" name="mdi-account-alert">
 | 
				
			||||||
          </div>
 | 
					                  <q-tooltip>Gesendet von {{ props.value.sender }}</q-tooltip>
 | 
				
			||||||
        </q-td>
 | 
					                </q-icon>
 | 
				
			||||||
      </template>
 | 
					              </div>
 | 
				
			||||||
      <template #body-cell-type="props">
 | 
					            </q-td>
 | 
				
			||||||
        <q-td :props="props">
 | 
					          </template>
 | 
				
			||||||
          <q-icon size="sm" :name="types[props.value].icon">
 | 
					          <template #body-cell-type="props">
 | 
				
			||||||
            <q-tooltip>{{ types[props.value].tooltip }}</q-tooltip>
 | 
					            <q-td :props="props">
 | 
				
			||||||
          </q-icon>
 | 
					              <q-icon size="sm" :name="types[props.value].icon">
 | 
				
			||||||
        </q-td>
 | 
					                <q-tooltip>{{ types[props.value].tooltip }}</q-tooltip>
 | 
				
			||||||
      </template>
 | 
					              </q-icon>
 | 
				
			||||||
      <template #body-cell-actions="props">
 | 
					            </q-td>
 | 
				
			||||||
        <q-td :props="props">
 | 
					          </template>
 | 
				
			||||||
          <!-- <q-btn v-for="action in props.value" :key="action.icon" :icon="action.icon" dense /> -->
 | 
					        </q-table>
 | 
				
			||||||
          <div class="row justify-end">
 | 
					      </q-card-section>
 | 
				
			||||||
            <div v-for="action in props.value" :key="action.icon">
 | 
					    </q-card>
 | 
				
			||||||
              <q-btn
 | 
					 | 
				
			||||||
                class="q-mx-xs"
 | 
					 | 
				
			||||||
                :icon="action.icon"
 | 
					 | 
				
			||||||
                dense
 | 
					 | 
				
			||||||
                @click="action.onClick"
 | 
					 | 
				
			||||||
                round
 | 
					 | 
				
			||||||
                :color="action.color"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <q-tooltip>{{ action.tooltip }}</q-tooltip>
 | 
					 | 
				
			||||||
              </q-btn>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </q-td>
 | 
					 | 
				
			||||||
      </template>
 | 
					 | 
				
			||||||
    </q-table>
 | 
					 | 
				
			||||||
  </q-page>
 | 
					  </q-page>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,7 +44,6 @@ import { computed, defineComponent, ref, onBeforeMount, watch } from 'vue';
 | 
				
			||||||
import { QTableProps } from 'quasar';
 | 
					import { QTableProps } from 'quasar';
 | 
				
			||||||
import { Job } from '../store/models';
 | 
					import { Job } from '../store/models';
 | 
				
			||||||
import { useEventStore } from '../store';
 | 
					import { useEventStore } from '../store';
 | 
				
			||||||
import { EventNotification, InvitationData, InvitationResponseData } from '../events';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  name: 'PageEventRequests',
 | 
					  name: 'PageEventRequests',
 | 
				
			||||||
| 
						 | 
					@ -93,9 +77,6 @@ export default defineComponent({
 | 
				
			||||||
        ? store.invitations
 | 
					        ? store.invitations
 | 
				
			||||||
        : store.invitations.filter((i) => i.inviter_id !== mainStore.currentUser.userid)
 | 
					        : store.invitations.filter((i) => i.inviter_id !== mainStore.currentUser.userid)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const all_notifications = computed<EventNotification[]>(() => {
 | 
					 | 
				
			||||||
      return mainStore.notifications.filter((n) => n.plugin === 'events') as EventNotification[];
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const showSent = ref(false);
 | 
					    const showSent = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function fillRows(data: FG.Invitation[]) {
 | 
					    async function fillRows(data: FG.Invitation[]) {
 | 
				
			||||||
| 
						 | 
					@ -237,57 +218,10 @@ export default defineComponent({
 | 
				
			||||||
        align: 'right',
 | 
					        align: 'right',
 | 
				
			||||||
        name: 'actions',
 | 
					        name: 'actions',
 | 
				
			||||||
        classes: dimmed,
 | 
					        classes: dimmed,
 | 
				
			||||||
        field: (row: RowData) => {
 | 
					        field: (row: RowData) => ({
 | 
				
			||||||
          const sender = row.inviter_id === mainStore.currentUser.userid;
 | 
					          job: row.job_id,
 | 
				
			||||||
          let actions = [];
 | 
					          sender: row.inviter_id === mainStore.currentUser.userid,
 | 
				
			||||||
          const reject = {
 | 
					        }),
 | 
				
			||||||
            icon: 'mdi-delete',
 | 
					 | 
				
			||||||
            tooltip: 'Einladung löschen',
 | 
					 | 
				
			||||||
            color: 'negative',
 | 
					 | 
				
			||||||
            onClick: () => {
 | 
					 | 
				
			||||||
              void store.rejectInvitation(row.id).then(() => {
 | 
					 | 
				
			||||||
                onRequest({
 | 
					 | 
				
			||||||
                  pagination: pagination.value,
 | 
					 | 
				
			||||||
                  filter: () => [],
 | 
					 | 
				
			||||||
                  getCellValue: () => [],
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                const notification = all_notifications.value.find(
 | 
					 | 
				
			||||||
                  (n) => (<InvitationData>n.data).invitation === row.id
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                if (notification !== undefined) {
 | 
					 | 
				
			||||||
                  void mainStore.removeNotification(notification.id);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
          const accept = {
 | 
					 | 
				
			||||||
            icon: 'mdi-check',
 | 
					 | 
				
			||||||
            tooltip: 'Einladung annehmen',
 | 
					 | 
				
			||||||
            color: 'primary',
 | 
					 | 
				
			||||||
            onClick: () => {
 | 
					 | 
				
			||||||
              void store.acceptInvitation(row.id).then(() => {
 | 
					 | 
				
			||||||
                onRequest({
 | 
					 | 
				
			||||||
                  pagination: pagination.value,
 | 
					 | 
				
			||||||
                  filter: () => [],
 | 
					 | 
				
			||||||
                  getCellValue: () => [],
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                const notification = all_notifications.value.find(
 | 
					 | 
				
			||||||
                  (n) => (<InvitationData>n.data).invitation === row.id
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                if (notification !== undefined) {
 | 
					 | 
				
			||||||
                  void mainStore.removeNotification(notification.id);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
          if (sender) {
 | 
					 | 
				
			||||||
            actions.push(reject);
 | 
					 | 
				
			||||||
          } else if (row.invitee_id === mainStore.currentUser.userid) {
 | 
					 | 
				
			||||||
            actions.push(accept);
 | 
					 | 
				
			||||||
            actions.push({ ...reject, icon: 'mdi-close' });
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return actions;
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
import { api, isAxiosError } from '@flaschengeist/api';
 | 
					import { api, isAxiosError } from '@flaschengeist/api';
 | 
				
			||||||
import { defineStore } from 'pinia';
 | 
					import { defineStore } from 'pinia';
 | 
				
			||||||
import { EditableEvent } from './models';
 | 
					import { EditableEvent } from './models';
 | 
				
			||||||
import { Notify } from 'quasar';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Convert JSON decoded Job to real job (fix Date object)
 | 
					 * Convert JSON decoded Job to real job (fix Date object)
 | 
				
			||||||
| 
						 | 
					@ -147,16 +146,17 @@ export const useEventStore = defineStore({
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async addEvent(event: EditableEvent) {
 | 
					    async addEvent(event: EditableEvent) {
 | 
				
			||||||
 | 
					      console.log('addEvent', event);
 | 
				
			||||||
      if (event?.id === undefined) {
 | 
					      if (event?.id === undefined) {
 | 
				
			||||||
        const { data } = await api.post<FG.Event>('/events', event);
 | 
					        const { data } = await api.post<FG.Event>('/events', event);
 | 
				
			||||||
        if (data.is_template) this.templates.push(data);
 | 
					        if (data.is_template) this.templates.push(data);
 | 
				
			||||||
        return data;
 | 
					        return data;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        if (typeof event.type === 'object') event.type = event.type.id;
 | 
					        if (typeof event.type === 'object') event.type = event.type.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const { data } = await api.put<FG.Event>(
 | 
					        const { data } = await api.put<FG.Event>(
 | 
				
			||||||
          `/events/${event.id}`,
 | 
					          `/events/${event.id}`,
 | 
				
			||||||
          // Object.assign(event, { jobs: undefined })
 | 
					          Object.assign(event, { jobs: undefined })
 | 
				
			||||||
          event
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        if (data.is_template) this.templates.push(data);
 | 
					        if (data.is_template) this.templates.push(data);
 | 
				
			||||||
        return data;
 | 
					        return data;
 | 
				
			||||||
| 
						 | 
					@ -205,47 +205,13 @@ export const useEventStore = defineStore({
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async rejectInvitation(invite: FG.Invitation | number) {
 | 
					    async rejectInvitation(invite: FG.Invitation | number) {
 | 
				
			||||||
      try {
 | 
					      return api.delete(`/events/invitations/${typeof invite === 'number' ? invite : invite.id}`);
 | 
				
			||||||
        await api.delete(`/events/invitations/${typeof invite === 'number' ? invite : invite.id}`);
 | 
					 | 
				
			||||||
        const idx = this.invitations.findIndex((v) => v.id === (invite.id || invite));
 | 
					 | 
				
			||||||
        if (idx >= 0) this.invitations.splice(idx, 1);
 | 
					 | 
				
			||||||
        notify_success('Einladung für erfolgreich abgelehnt');
 | 
					 | 
				
			||||||
      } catch (e) {
 | 
					 | 
				
			||||||
        notify_failure();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async acceptInvitation(invite: FG.Invitation | number) {
 | 
					    async acceptInvitation(invite: FG.Invitation | number) {
 | 
				
			||||||
      try {
 | 
					      return api.put(`/events/invitations/${typeof invite === 'number' ? invite : invite.id}`, {
 | 
				
			||||||
        await api.put(`/events/invitations/${typeof invite === 'number' ? invite : invite.id}`, {
 | 
					        accept: true,
 | 
				
			||||||
          accept: true,
 | 
					      });
 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        const idx = this.invitations.findIndex((v) => v.id === (invite.id || invite));
 | 
					 | 
				
			||||||
        if (idx >= 0) this.invitations.splice(idx, 1);
 | 
					 | 
				
			||||||
        notify_success('Einladung für erfolgreich angenommen');
 | 
					 | 
				
			||||||
      } catch (e) {
 | 
					 | 
				
			||||||
        notify_failure();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					 | 
				
			||||||
function notify_failure() {
 | 
					 | 
				
			||||||
  Notify.create({
 | 
					 | 
				
			||||||
    message: 'Es ist ein Fehler aufgetreten.',
 | 
					 | 
				
			||||||
    color: 'negative',
 | 
					 | 
				
			||||||
    group: false,
 | 
					 | 
				
			||||||
    timeout: 10000,
 | 
					 | 
				
			||||||
    actions: [{ icon: 'mdi-close', color: 'white' }],
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function notify_success(msg: string) {
 | 
					 | 
				
			||||||
  Notify.create({
 | 
					 | 
				
			||||||
    message: msg,
 | 
					 | 
				
			||||||
    color: 'positive',
 | 
					 | 
				
			||||||
    group: false,
 | 
					 | 
				
			||||||
    timeout: 5000,
 | 
					 | 
				
			||||||
    actions: [{ icon: 'mdi-close', color: 'white' }],
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,14 +30,6 @@ export class Job implements FG.Job {
 | 
				
			||||||
        milliseconds: 0,
 | 
					        milliseconds: 0,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    else this.start = new Date(); // <-- make TS happy "no initalizer"
 | 
					    else this.start = new Date(); // <-- make TS happy "no initalizer"
 | 
				
			||||||
    if (!iJob || iJob.end === undefined) {
 | 
					 | 
				
			||||||
      this.end = date.buildDate({
 | 
					 | 
				
			||||||
        hours: new Date().getHours() + 4,
 | 
					 | 
				
			||||||
        minutes: 0,
 | 
					 | 
				
			||||||
        seconds: 0,
 | 
					 | 
				
			||||||
        milliseconds: 0,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (iJob !== undefined) Object.assign(this, iJob);
 | 
					    if (iJob !== undefined) Object.assign(this, iJob);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "extends": "@quasar/app-webpack/tsconfig-preset",
 | 
					  "extends": "@quasar/app/tsconfig-preset",
 | 
				
			||||||
  "target": "esnext",
 | 
					  "target": "esnext",
 | 
				
			||||||
  "compilerOptions": {
 | 
					  "compilerOptions": {
 | 
				
			||||||
    "baseUrl": "./src/",
 | 
					    "baseUrl": "./src/",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue