Ejemplo n.º 1
0
def edit_attendee(self, request, form):
    # note: attendees are added in the views/occasion.py file
    assert request.is_admin or self.username == request.current_username

    bookings = BookingCollection(request.session)
    bookings = bookings.for_username(self.username)

    if form.submitted(request):
        form.populate_obj(self)
        request.success(_("Your changes were saved"))

        return request.redirect(request.link(bookings))

    elif not request.POST:
        form.process(obj=self)

    title = _("Edit Attendee")

    layout = BookingCollectionLayout(bookings, request, self.user)
    layout.breadcrumbs.append(Link(title, request.link(self)))

    return {
        'form': form,
        'layout': layout,
        'title': title,
    }
Ejemplo n.º 2
0
def edit_attendee_limit(self, request, form):
    assert request.is_admin or self.username == request.current_username

    bookings = BookingCollection(request.session)
    bookings = bookings.for_username(self.username)

    if form.submitted(request):
        form.populate_obj(self)
        request.success(_("Your changes were saved"))

        return request.redirect(request.link(bookings))

    elif not request.POST:
        form.process(obj=self)

    title = _("Booking Limit of ${name}", mapping={'name': self.name})

    layout = BookingCollectionLayout(bookings, request, self.user)
    layout.breadcrumbs.append(Link(title, request.link(self)))

    return {
        'form': form,
        'layout': layout,
        'title': title,
    }
    def recipients_with_bookings(self):
        bookings = BookingCollection(self.request.session)
        period = self.request.app.active_period

        if period.wishlist_phase:
            return set()

        q = bookings.query()
        q = q.join(Period)

        q = q.filter(Period.active == True)
        q = q.with_entities(distinct(Booking.username).label('username'))

        return {b.username for b in q}
Ejemplo n.º 4
0
def reset_matching(self, request, quiet=False):
    assert self.period.active and not self.period.confirmed

    bookings = BookingCollection(request.session, self.period_id)
    bookings = bookings.query().filter(and_(
        Booking.state != 'cancelled',
        Booking.state != 'open'
    ))

    for booking in bookings:
        booking.state = 'open'
        booking.score = 0

    if not quiet:
        request.success(_("The matching was successfully reset"))
Ejemplo n.º 5
0
def cancel_booking(self, request):
    request.assert_valid_csrf_token()

    if not self.period.wishlist_phase:
        if not request.is_admin:
            if self.occasion.is_past_cancellation(date.today()):
                request.alert(_(
                    "Only admins may cancel bookings at this point"
                ))

                return

    BookingCollection(request.session).cancel_booking(
        booking=self,
        score_function=self.period.scoring,
        cascade=False)

    request.success(_("The booking was cancelled successfully"))

    @request.after
    def update_matching(response):
        response.headers.add('X-IC-Trigger', 'reload-from')
        response.headers.add('X-IC-Trigger-Data', json.dumps({
            'selector': '#{}'.format(self.occasion.id)
        }))
    def recipients_by_occasion_query(self, occasions):
        bookings = BookingCollection(self.request.session)

        q = bookings.query()
        q = q.join(Period)
        q = q.join(Booking.occasion)
        if occasions:
            q = q.filter(Booking.occasion_id.in_(occasions))
        else:
            q = q.filter(Booking.occasion_id == uuid4())
        q = q.filter(
            or_(and_(Occasion.cancelled == False, Booking.state == 'accepted'),
                and_(Occasion.cancelled == True,
                     Booking.state == 'cancelled')))
        q = q.filter(Period.active == True)
        q = q.filter(Period.confirmed == True)

        return q
Ejemplo n.º 7
0
def delete_booking(self, request):
    request.assert_valid_csrf_token()

    if self.period.confirmed and self.state not in DELETABLE_STATES:
        show_error_on_attendee(request, self.attendee, _(
            "Only open, cancelled, denied or blocked bookings may be deleted"))

        return

    BookingCollection(request.session).delete(self)

    @request.after
    def remove_target(response):
        response.headers.add('X-IC-Remove', 'true')
Ejemplo n.º 8
0
def get_my_bookings(request, app, period_id=None, username=None):
    # only admins can actually specify the username
    if not request.is_admin:
        username = request.current_username

    # the default username is the current user
    if not username:
        username = request.current_username

    # the default period is the active period or the first we can find
    if not period_id:
        period = app.default_period

        if period:
            period_id = period.id

    return BookingCollection(app.session(), period_id, username)
Ejemplo n.º 9
0
    def create_invoices(self, all_inclusive_booking_text=None):
        assert not self.period.finalized

        if self.period.all_inclusive and self.period.booking_cost:
            assert all_inclusive_booking_text

        # speed up some lookups
        session = self.session
        period = self.period
        invoices = self.invoices

        # delete all existing invoices
        invoice_ids = invoices.query().with_entities(Invoice.id).subquery()

        def delete_queries():
            yield session.query(InvoiceReference).filter(
                InvoiceReference.invoice_id.in_(invoice_ids))

            yield session.query(InvoiceItem).filter(
                InvoiceItem.invoice_id.in_(invoice_ids))

            yield invoices.query()

        for q in delete_queries():
            q.delete('fetch')

        # preload data to avoid more expensive joins
        activities = {
            r.id: r.title
            for r in session.query(Occasion.id, Activity.title).join(Activity)
        }

        attendees = {
            a.id: (a.name, a.username)
            for a in session.query(Attendee.id, Attendee.name,
                                   Attendee.username)
        }

        # regenerate the invoices
        bookings = BookingCollection(session, period_id=period.id)

        q = bookings.query().with_entities(
            Booking.username,
            Booking.cost,
            Booking.occasion_id,
            Booking.attendee_id,
        )
        q = q.filter(Booking.state == 'accepted')

        # keep track of the attendees which have at least one booking (even
        # if said booking is free)
        actual_attendees = set()

        # keep track of the invoices which were created
        created_invoices = {}

        # easy user_id lookup
        users = dict(session.query(User).with_entities(User.username, User.id))

        for booking in q:
            actual_attendees.add(booking.attendee_id)

            if booking.username not in created_invoices:
                created_invoices[booking.username] = invoices.add(
                    period_id=period.id,
                    user_id=users[booking.username],
                    flush=False,
                    optimistic=True)

            if period.pay_organiser_directly or not booking.cost:
                continue

            created_invoices[booking.username].add(
                group=attendees[booking.attendee_id][0],
                text=activities[booking.occasion_id],
                unit=booking.cost,
                quantity=1,
                flush=False)

        # add the all inclusive booking costs if necessary
        if period.all_inclusive and period.booking_cost:
            for id, (attendee, username) in attendees.items():
                if id in actual_attendees:
                    created_invoices[username].add(
                        group=attendee,
                        text=all_inclusive_booking_text,
                        unit=period.booking_cost,
                        quantity=1,
                        flush=False)
Ejemplo n.º 10
0
 def booking_collection(self):
     return BookingCollection(session=self.request.session,
                              period_id=self.model.period_id)
Ejemplo n.º 11
0
def get_booking(request, app, id):
    return BookingCollection(app.session()).by_id(id)
Ejemplo n.º 12
0
def get_personal_tools(request):
    # for logged-in users show the number of open bookings
    if request.is_logged_in:
        session = request.session
        username = request.current_username

        period = request.app.active_period
        periods = request.app.periods

        invoices = request.app.invoice_collection(
            user_id=request.current_user.id)

        unpaid = invoices.unpaid_count(
            excluded_period_ids={p.id
                                 for p in periods if not p.finalized})

        if unpaid:
            attributes = {
                'data-count': str(unpaid),
                'class': {'with-count', 'alert', 'invoices-count'}
            }
        else:
            attributes = {
                'data-count': '0',
                'class': {'with-count', 'secondary', 'invoices-count'}
            }

        yield Link(text=_("Invoices"),
                   url=request.link(invoices),
                   attrs=attributes)

        bookings = BookingCollection(session)

        if period:
            if period.confirmed:
                states = ('open', 'accepted')
            else:
                # exclude cancelled bookings even during the wish-phase
                states = ('open', 'blocked', 'accepted', 'denied')

            count = bookings.booking_count(username, states)

            if count:
                attributes = {
                    'data-count': str(count),
                    'class':
                    {'with-count', period.confirmed and 'success' or 'info'}
                }
            else:
                attributes = {
                    'data-count': '0',
                    'class': {'with-count', 'secondary'}
                }

            yield Link(text=period.confirmed and _("Bookings")
                       or _("Wishlist"),
                       url=request.link(bookings),
                       attrs=attributes)
        else:
            yield Link(
                text=_("Wishlist"),
                url=request.link(bookings),
                attrs={
                    'data-count': '0',
                    'class': {'with-count', 'secondary'}
                },
            )
Ejemplo n.º 13
0
def test_notification_template_send_form(session):
    activities = ActivityCollection(session, type='vacation')
    attendees = AttendeeCollection(session)
    periods = PeriodCollection(session)
    occasions = OccasionCollection(session)
    bookings = BookingCollection(session)

    users = UserCollection(session)
    admin = users.add(username='******',
                      realname='Robert Baratheon',
                      password='******',
                      role='admin')
    organiser = users.add(username='******',
                          realname=None,
                          password='******',
                          role='editor')
    users.add(username='******',
              realname=None,
              password='******',
              role='member')

    prebooking = tuple(d.date() for d in (datetime.now() - timedelta(days=1),
                                          datetime.now() + timedelta(days=1)))

    execution = tuple(d.date() for d in (datetime.now() + timedelta(days=10),
                                         datetime.now() + timedelta(days=12)))

    period = periods.add(title="Ferienpass 2016",
                         prebooking=prebooking,
                         execution=execution,
                         active=True)

    foo = activities.add("Foo", username='******')
    foo.propose().accept()

    bar = activities.add("Bar", username='******')
    bar.propose().accept()

    o1 = occasions.add(
        start=datetime(2016, 11, 25, 8),
        end=datetime(2016, 11, 25, 16),
        age=(0, 10),
        spots=(0, 2),
        timezone="Europe/Zurich",
        activity=foo,
        period=period,
    )
    o1.username = admin.username

    o2 = occasions.add(
        start=datetime(2016, 11, 25, 17),
        end=datetime(2016, 11, 25, 20),
        age=(0, 10),
        spots=(0, 2),
        timezone="Europe/Zurich",
        activity=bar,
        period=period,
    )
    o2.username = organiser.username

    a1 = attendees.add(admin, 'Dustin', date(2000, 1, 1), 'male')
    a2 = attendees.add(organiser, 'Mike', date(2000, 1, 1), 'female')

    b1 = bookings.add(admin, a1, o1)
    b1.state = 'accepted'
    b1.cost = 100

    b2 = bookings.add(organiser, a2, o2)
    b2.state = 'accepted'
    b2.cost = 100

    transaction.commit()

    # create a mock request
    def invoice_collection(user_id=None, period_id=None):
        return InvoiceCollection(session, user_id=user_id, period_id=period_id)

    def request(admin):
        return Bunch(app=Bunch(
            active_period=periods.active(),
            org=Bunch(geo_provider='geo-mapbox'),
            invoice_collection=invoice_collection,
            periods=periods.query().all(),
        ),
                     session=session,
                     include=lambda *args: None,
                     model=None,
                     is_admin=admin,
                     is_organiser_only=not admin and True or False,
                     is_manager=admin and True or False,
                     translate=lambda text, *args, **kwargs: text,
                     locale='de_CH',
                     current_username=(admin and '*****@*****.**'
                                       or '*****@*****.**'))

    # in the beginning there are no recipients
    form = NotificationTemplateSendForm()
    form.model = None
    form.request = request(admin=True)

    assert form.has_choices  # we still have choices (like send to users)
    assert not form.occasion.choices

    # once the request is processed, the occasions are added
    form.on_request()

    assert form.has_choices
    assert len(form.occasion.choices) == 2
    assert len(form.send_to.choices) == 7

    # if the period is inactive, there are no occasions
    periods.query().one().active = False
    transaction.commit()

    form = NotificationTemplateSendForm()
    form.model = None
    form.request = request(admin=True)

    form.on_request()
    assert len(form.occasion.choices) == 0

    # if the period is active but not confirmed, there are no recipients
    period = periods.query().one()
    period.active = True
    period.confirmed = False
    transaction.commit()

    form = NotificationTemplateSendForm()
    form.model = None
    form.request = request(admin=True)

    form.on_request()

    occasions = [c[0] for c in form.occasion.choices]

    # with organisers
    assert len(form.recipients_by_occasion(occasions, True)) == 2

    # without
    assert len(form.recipients_by_occasion(occasions, False)) == 0

    # the number of users is indepenedent of the period
    assert len(form.recipients_by_role(('admin', 'editor', 'member'))) == 3
    assert len(form.recipients_by_role(('admin', 'editor'))) == 2
    assert len(form.recipients_by_role(('admin', ))) == 1

    # if the period is confirmed, there are accepted recipients
    period = periods.query().one()
    period.active = True
    period.confirmed = True

    transaction.commit()
    assert len(form.recipients_by_occasion(occasions)) == 2

    # only accepted bookings are counted
    parent = admin.username
    bookings.query().filter_by(username=parent).one().state = 'cancelled'
    transaction.commit()

    # without organisers
    assert len(form.recipients_by_occasion(occasions, False)) == 1

    # with
    assert len(form.recipients_by_occasion(occasions, True)) == 2

    # inactive users may be exluded
    form.state.data = ['active']
    assert len(form.recipients_pool) == 3

    form.state.data = ['active', 'inactive']
    assert len(form.recipients_pool) == 3

    form.state.data = ['inactive']
    assert len(form.recipients_pool) == 0

    # bookings count towards the wishlist if the period is active,
    period = periods.query().one()
    period.active = True
    period.confirmed = False

    transaction.commit()

    form.request = request(admin=True)

    # do not count cancelled bookings...
    assert len(form.recipients_with_wishes()) == 2
    assert len(form.recipients_with_bookings()) == 0

    # otherwise they count towards the bookings
    period = periods.query().one()
    period.confirmed = True

    transaction.commit()

    form.request = request(admin=True)
    assert len(form.recipients_with_wishes()) == 0
    assert len(form.recipients_with_bookings()) == 2

    # count the active organisers
    form.request = request(admin=True)
    assert len(form.recipients_which_are_active_organisers()) == 2

    # count the users with unpaid bills
    form.request = request(admin=True)
    assert len(form.recipients_with_unpaid_bills()) == 0

    period = periods.query().one()
    billing = BillingCollection(request=Bunch(
        session=session, app=Bunch(invoice_collection=invoice_collection)),
                                period=period)
    billing.create_invoices()
    transaction.commit()

    form.request = request(admin=True)
    assert len(form.recipients_with_unpaid_bills()) == 1

    # organisers are not counted as active if the occasion has been cancelled
    occasions = OccasionCollection(session)

    occasions.query().first().cancelled = True
    transaction.commit()

    form.request = request(admin=True)
    assert len(form.recipients_which_are_active_organisers()) == 1

    for occasion in occasions.query():
        occasion.cancelled = False

    transaction.commit()

    form.request = request(admin=True)
    assert len(form.recipients_which_are_active_organisers()) == 2
Ejemplo n.º 14
0
def book_occasion(self, request, form):

    # for the "nth. occasion" title
    number = request.session.execute("""
        SELECT count(*) FROM occasions
        WHERE activity_id = :activity_id AND "order" <= :order
    """, {
        'activity_id': self.activity_id,
        'order': self.order
    }).scalar()

    if form.submitted(request):
        attendees = AttendeeCollection(request.session)
        user = form.user

        if form.is_new_attendee:
            attendee = attendees.add(
                user=user,
                name=form.name,
                birth_date=form.birth_date.data,
                gender=form.gender.data,
                notes=form.notes.data
            )
        else:
            attendee = attendees.by_id(form.attendee.data)
            assert attendee.username == form.username

        # should be caught by the form
        assert not (self.full and self.period.confirmed)
        assert self.activity.state == 'accepted'

        bookings = BookingCollection(request.session)

        # if there's a canceled/denied booking blocking the way, reactivate it
        booking = None

        if not form.is_new_attendee:
            booking = bookings.query()\
                .filter(Booking.occasion_id == self.id)\
                .filter(Booking.username == user.username)\
                .filter(Booking.attendee == attendee)\
                .filter(Booking.state.in_((
                    'cancelled',
                    'denied',
                    'blocked',
                )))\
                .first()

            if booking:
                booking.state = 'open'

        if booking is None:
            booking = bookings.add(
                user=user,
                attendee=attendee,
                occasion=self
            )

        # if the TOS have been accepted, record this now
        if hasattr(form, 'accept_tos') and form.accept_tos:
            if form.accept_tos.data:
                request.current_user.data['tos_accepted'] = True

        if self.period.confirmed:
            bookings.accept_booking(booking)
            request.success(
                _("The booking for ${name} was succesfull", mapping={
                    'name': attendee.name
                })
            )
        else:
            request.success(
                _("The occasion was added to ${name}'s wishlist", mapping={
                    'name': attendee.name
                }))

        return request.redirect(request.link(self.activity))

    title = _("Enroll Attendee")

    users = []

    if request.is_admin:
        u = UserCollection(request.session).query()
        u = u.with_entities(User.username, User.title)
        u = u.order_by(User.title)

        users = u

    return {
        'layout': OccasionFormLayout(self.activity, request, title),
        'title': title,
        'form': form,
        'occasion': self,
        'users': users,
        'button_text': _("Enroll"),
        'number': number,
    }