コード例 #1
0
def dues15_reduction(request):
    """
    reduce a members dues upon valid request to do so.

    * change payable amount for member
    * cancel old invoice by issuing a cancellation
    * issue a new invoice with the new amount (if new amount != 0)

    this will only work for *normal* members.
    """
    # member: sanity checks
    try:
        member_id = request.matchdict['member_id']
        member = C3sMember.get_by_id(member_id)  # is in database
        assert member.membership_accepted  # is a member
        assert 'investing' not in member.membership_type  # is normal member
    except (KeyError, AssertionError):  # pragma: no cover
        request.session.flash(
            u"No member OR not accepted OR not normal member",
            'dues15_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues15')

    # sanity check: the given amount is a positive decimal
    try:
        reduced_amount = D(request.POST['amount'])
        assert not reduced_amount.is_signed()
        if DEBUG:
            print("DEBUG: reduction to {}".format(reduced_amount))
    except (KeyError, AssertionError):  # pragma: no cover
        request.session.flash(
            (u"Invalid amount to reduce to: '{}' "
             u"Use the dot ('.') as decimal mark, e.g. '23.42'".format(
                 request.POST['amount'])),
            'dues15_message_to_staff'  # message queue for user
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues15')

    if DEBUG:
        print("DEBUG: member.dues15_amount: {}".format(member.dues15_amount))
        print("DEBUG: type(member.dues15_amount): {}".format(
            type(member.dues15_amount)))
        print("DEBUG: member.dues15_reduced: {}".format(member.dues15_reduced))
        print("DEBUG: member.dues15_amount_reduced: {}".format(
            member.dues15_amount_reduced))
        print("DEBUG: type(member.dues15_amount_reduced): {}".format(
            type(member.dues15_amount_reduced)))

    # The hidden input 'confirmed' must have the value 'yes' which is set by
    # the confirmation dialog.
    reduction_confirmed = request.POST['confirmed']
    if reduction_confirmed != 'yes':
        request.session.flash(
            u'Die Reduktion wurde nicht bestätigt.',
            'dues15_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues15')

    # check the reduction amount: same as default calculated amount?
    if ((member.dues15_reduced is False)
            and (member.dues15_amount == reduced_amount)):
        request.session.flash(
            u"Dieser Beitrag ist der default-Beitrag!",
            'dues15_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues15')

    if reduced_amount == member.dues15_amount_reduced:
        request.session.flash(
            u"Auf diesen Beitrag wurde schon reduziert!",
            'dues15_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues15')

    if member.dues15_reduced \
            and reduced_amount > member.dues15_amount_reduced \
            or reduced_amount > member.dues15_amount:
        request.session.flash(
            u'Beitrag darf nicht über den berechneten oder bereits'
            u'reduzierten Wert gesetzt werden.',
            'dues15_message_to_staff'  # message queue for staff
        )
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues15')

    # prepare: get highest invoice no from db
    max_invoice_no = Dues15Invoice.get_max_invoice_no()

    # things to be done:
    # * change dues amount for that member
    # * cancel old invoice by issuing a reversal invoice
    # * issue a new invoice with the new amount

    member.set_dues15_reduced_amount(reduced_amount)
    request.session.flash('reduction to {}'.format(reduced_amount),
                          'dues15_message_to_staff')

    old_invoice = Dues15Invoice.get_by_invoice_no(member.dues15_invoice_no)
    old_invoice.is_cancelled = True

    reversal_invoice_amount = -D(old_invoice.invoice_amount)

    # prepare reversal invoice number
    new_invoice_no = max_invoice_no + 1
    # create reversal invoice
    reversal_invoice = Dues15Invoice(
        invoice_no=new_invoice_no,
        invoice_no_string=(u'C3S-dues2015-' + str(new_invoice_no).zfill(4)) +
        '-S',
        invoice_date=datetime.today(),
        invoice_amount=reversal_invoice_amount.to_eng_string(),
        member_id=member.id,
        membership_no=member.membership_number,
        email=member.email,
        token=member.dues15_token,
    )
    reversal_invoice.preceding_invoice_no = old_invoice.invoice_no
    reversal_invoice.is_reversal = True
    DBSession.add(reversal_invoice)
    DBSession.flush()
    old_invoice.succeeding_invoice_no = new_invoice_no

    # check if this is an exemption (reduction to zero)
    is_exemption = False  # sane default
    # check if reduction to zero
    if reduced_amount.is_zero():
        is_exemption = True
        if DEBUG:
            print("this is an exemption: reduction to zero")
    else:
        if DEBUG:
            print("this is a reduction to {}".format(reduced_amount))

    if not is_exemption:
        # create new invoice
        new_invoice = Dues15Invoice(
            invoice_no=new_invoice_no + 1,
            invoice_no_string=(u'C3S-dues2015-' +
                               str(new_invoice_no + 1).zfill(4)),
            invoice_date=datetime.today(),
            invoice_amount=u'' + str(reduced_amount),
            member_id=member.id,
            membership_no=member.membership_number,
            email=member.email,
            token=member.dues15_token,
        )
        new_invoice.is_altered = True
        new_invoice.preceding_invoice_no = reversal_invoice.invoice_no
        reversal_invoice.succeeding_invoice_no = new_invoice_no + 1
        DBSession.add(new_invoice)

        # in the members record, store the current invoice no
        member.dues15_invoice_no = new_invoice_no + 1

        DBSession.flush()  # persist newer invoices

    reversal_url = (request.route_url(
        'make_dues15_reversal_invoice_pdf',
        email=member.email,
        code=member.dues15_token,
        no=str(reversal_invoice.invoice_no).zfill(4)))
    if is_exemption:
        email_subject, email_body = make_dues_exemption_email(
            member, reversal_url)
    else:
        invoice_url = (request.route_url('make_dues15_invoice_no_pdf',
                                         email=member.email,
                                         code=member.dues15_token,
                                         i=str(new_invoice_no + 1).zfill(4)))
        email_subject, email_body = make_dues_reduction_email(
            member, new_invoice, invoice_url, reversal_url)

    message = Message(subject=email_subject,
                      sender='*****@*****.**',
                      recipients=[member.email],
                      body=email_body,
                      extra_headers={
                          'Reply-To': '*****@*****.**',
                      })
    if is_exemption:
        request.session.flash('exemption email was sent to user!',
                              'dues15_message_to_staff')
    else:
        request.session.flash('update email was sent to user!',
                              'dues15_message_to_staff')
    send_message(request, message)
    return HTTPFound(
        request.route_url('detail', memberid=member_id) + '#dues15')
コード例 #2
0
def send_dues15_invoice_email(request, m_id=None):
    """
    Send email to a member to prompt her to pay the membership dues.
    - For normal members, also send link to invoice.
    - For investing members that are legal entities,
      ask for additional support depending on yearly turnover.

    This view function works both if called via URL, e.g. /dues_invoice/123
    and if called as a function with a member id as parameter.
    The latter is useful for batch processing.

    When this function is used for the first time for one member,
    some database fields are filled:
    - Invoice number
    - Invoice amount (calculated from date of membership approval by the board)
    - Invoice token
    Also, the database table of invoices (and cancellations) is appended.

    If this function gets called the second time for a member,
    no new invoice is produced, but the same mail sent again.
    """
    # either we are given a member id via url or function parameter
    try:  # view was called via http/s
        member_id = request.matchdict['member_id']
        batch = False
    except KeyError:  # ...or was called as function with parameter (see batch)
        member_id = m_id
        batch = True

    try:  # get member from DB
        member = C3sMember.get_by_id(member_id)
        assert (member is not None)
    except AssertionError:
        if not batch:
            request.session.flash(
                "member with id {} not found in DB!".format(member_id),
                'message_to_staff')
            return HTTPFound(request.route_url('toolbox'))

    # sanity check:is this a member?
    try:
        assert (member.membership_accepted)  # must be accepted member!
    except AssertionError:
        request.session.flash(
            "member {} not accepted by the board!".format(member_id),
            'message_to_staff')
        return HTTPFound(request.route_url('toolbox'))

    if 'normal' not in member.membership_type and \
            'investing' not in member.membership_type:
        request.session.flash(
            'The membership type of member {0} is not specified! The '
            'membership type must either be "normal" or "investing" in order '
            'to be able to send an invoice email.'.format(member.id),
            'message_to_staff')
        return get_memberhip_listing_redirect(request)
    if member.membership_date >= date(2016, 1, 1):
        request.session.flash(
            'Member {0} was not a member in 2015. Therefore, you cannot send '
            'an invoice for 2015.'.format(member.id), 'message_to_staff')
        return get_memberhip_listing_redirect(request)

    # check if invoice no already exists.
    #     if yes: just send that email again!
    #     also: offer staffers to cancel this invoice

    if member.dues15_invoice is True:
        invoice = Dues15Invoice.get_by_invoice_no(member.dues15_invoice_no)
        member.dues15_invoice_date = datetime.now()

    else:  # if no invoice already exists:
        # make dues token and ...
        randomstring = make_random_string()
        # check if dues token is already used
        while (Dues15Invoice.check_for_existing_dues15_token(randomstring)):
            # create a new one, if the new one already exists in the database
            randomstring = make_random_string()  # pragma: no cover

        # prepare invoice number
        try:
            # either we already have an invoice number for that client...
            invoice_no = member.dues15_invoice_no
            assert invoice_no is not None
        except AssertionError:
            # ... or we create a new one and save it
            # get max invoice no from db
            max_invoice_no = Dues15Invoice.get_max_invoice_no()
            # use the next free number, save it to db
            new_invoice_no = int(max_invoice_no) + 1
            DBSession.flush()  # save dataset to DB

        # calculate dues amount (maybe partial, depending on quarter)
        dues_start, dues_amount = calculate_partial_dues15(member)

        # now we have enough info to update the member info
        # and persist invoice info for bookkeeping
        # store some info in DB/member table
        member.dues15_invoice = True
        member.dues15_invoice_no = new_invoice_no  # irrelevant for investing
        member.dues15_invoice_date = datetime.now()
        member.dues15_token = randomstring
        member.dues15_start = dues_start

        if 'normal' in member.membership_type:  # only for normal members
            member.set_dues15_amount(dues_amount)
            # store some more info about invoice in invoice table
            invoice = Dues15Invoice(
                invoice_no=member.dues15_invoice_no,
                invoice_no_string=(u'C3S-dues2015-' +
                                   str(member.dues15_invoice_no).zfill(4)),
                invoice_date=member.dues15_invoice_date,
                invoice_amount=u'' + str(member.dues15_amount),
                member_id=member.id,
                membership_no=member.membership_number,
                email=member.email,
                token=member.dues15_token,
            )
            DBSession.add(invoice)
        DBSession.flush()

    # now: prepare that email
    # only normal (not investing) members *have to* pay the dues.
    # only the normal members get an invoice link and PDF produced for them.
    # only investing legalentities are asked for more support.
    if 'normal' in member.membership_type:
        start_quarter = string_start_quarter(member)
        invoice_url = (request.route_url(
            'make_dues15_invoice_no_pdf',
            email=member.email,
            code=member.dues15_token,
            i=str(member.dues15_invoice_no).zfill(4)))
        email_subject, email_body = make_dues_invoice_email(
            member, invoice, invoice_url, start_quarter)
        message = Message(subject=email_subject,
                          sender='*****@*****.**',
                          recipients=[member.email],
                          body=email_body,
                          extra_headers={
                              'Reply-To': '*****@*****.**',
                          })
    elif 'investing' in member.membership_type:
        if member.is_legalentity:
            email_subject, email_body = \
                make_dues_invoice_legalentity_email(member)
        else:
            email_subject, email_body = \
                make_dues_invoice_investing_email(member)
        message = Message(subject=email_subject,
                          sender='*****@*****.**',
                          recipients=[member.email],
                          body=email_body,
                          extra_headers={
                              'Reply-To': '*****@*****.**',
                          })

    # print to console or send mail
    if 'true' in request.registry.settings['testing.mail_to_console']:
        print(message.body.encode('utf-8'))  # pragma: no cover
    else:
        send_message(request, message)

    # now choose where to redirect
    if 'detail' in request.referrer:
        return HTTPFound(
            request.route_url('detail', memberid=member.id) + '#dues15')
    if 'toolbox' in request.referrer:
        return HTTPFound(request.route_url('toolbox'))
    else:
        return get_memberhip_listing_redirect(request, member.id)