def make_dues17_reversal_invoice_pdf(request): """ This view checks supplied information (in URL) against info in database -- especially the invoice number -- and conditionally returns - an error message or - a PDF """ token = request.matchdict['code'] invoice_number = request.matchdict['no'] try: member = C3sMember.get_by_dues17_token(token) assert member is not None assert member.dues17_token == token except AssertionError: request.session.flash( u"This member and token did not match!", 'message_to_user' # message queue for user ) return HTTPFound(request.route_url('error_page')) try: invoice = Dues17Invoice.get_by_invoice_no(invoice_number) assert invoice is not None except AssertionError: request.session.flash( u"No invoice found!", 'message_to_user' # message queue for user ) return HTTPFound(request.route_url('error_page')) # sanity check: invoice token must match with token try: assert (invoice.token == token) except AssertionError: request.session.flash( u"Token did not match!", 'message_to_user' # message queue for user ) return HTTPFound(request.route_url('error_page')) # sanity check: reversal invoice token must be reversal try: assert (invoice.is_reversal) except AssertionError: request.session.flash( u"No reversal invoice found!", 'message_to_user' # message queue for user ) return HTTPFound(request.route_url('error_page')) # return a pdf file pdf_file = make_reversal_pdf_pdflatex(member, invoice) response = Response(content_type='application/pdf') pdf_file.seek(0) # rewind to beginning response.app_iter = open(pdf_file.name, "r") return response
def stats_view(request): """ This view lets accountants view statistics: how many membership applications, real members, shares, etc. """ # countries_dict = C3sMember.get_countries_list() _cl = C3sMember.get_countries_list() _cl_sorted = _cl.items() # print "die liste: {}".format(_cl_sorted) import operator _cl_sorted.sort(key=operator.itemgetter(1), reverse=True) # print "sortiert: {}".format(_cl_sorted) share_information = request.registry.share_information return { # form submissions '_number_of_datasets': C3sMember.get_number(), 'afm_shares_unpaid': C3sMember.afm_num_shares_unpaid(), 'afm_shares_paid': C3sMember.afm_num_shares_paid(), # shares 'num_shares_members': share_information.get_share_count(), # 'num_shares_mem_norm': Shares.get_sum_norm(), # 'num_shares_mem_inv': Shares.get_sum_inv(), # memberships 'num_members_accepted': C3sMember.get_num_members_accepted(), 'num_non_accepted': C3sMember.get_num_non_accepted(), 'num_nonmember_listing': C3sMember.nonmember_listing_count(), 'num_duplicates': len(C3sMember.get_duplicates()), # 'num_empty_slots': C3sMember.get_num_empty_slots(), # normal persons vs. legal entities 'num_ms_nat_acc': C3sMember.get_num_mem_nat_acc(), 'num_ms_jur_acc': C3sMember.get_num_mem_jur_acc(), # normal vs. investing memberships 'num_ms_norm': C3sMember.get_num_mem_norm(), 'num_ms_inves': C3sMember.get_num_mem_invest(), 'num_ms_features': C3sMember.get_num_mem_other_features(), 'num_membership_lost': C3sMember.get_num_membership_lost(), # membership_numbers 'num_memnums': C3sMember.get_num_membership_numbers(), 'max_memnum': C3sMember.get_highest_membership_number(), 'next_memnum': C3sMember.get_next_free_membership_number(), # countries 'num_countries': C3sMember.get_num_countries(), 'countries_list': _cl_sorted, # key=lambda x: x[1] # ), # XXX TODO: sorte # dues stats 'dues15_stats': Dues15Invoice.get_monthly_stats(), 'dues16_stats': Dues16Invoice.get_monthly_stats(), 'dues17_stats': Dues17Invoice.get_monthly_stats(), # staff figures 'num_staff': len(C3sStaff.get_all()) }
def dues17_listing(request): """ a listing of all invoices for the 2017 dues run. shall show both active/valid and cancelled/invalid invoices. """ # get them all from the DB dues17_invoices = Dues17Invoice.get_all() return { 'count': len(dues17_invoices), '_today': date.today(), 'invoices': dues17_invoices, }
def member_detail(request): """ This view lets accountants view member details: - has their signature arrived? - how about the payment? Mostly all the info about an application or membership in the database can be seen here. """ from decimal import Decimal as D logged_in = authenticated_userid(request) memberid = request.matchdict['memberid'] LOG.info("member details of id %s checked by %s", memberid, logged_in) member = C3sMember.get_by_id(memberid) if member is None: # that memberid did not produce good results request.session.flash( "A Member with id " "{} could not be found in the DB. run for the backups!".format( memberid), 'message_to_staff') return HTTPFound( # back to base request.route_url('toolbox')) # get the members invoices from the DB invoices15 = Dues15Invoice.get_by_membership_no(member.membership_number) invoices16 = Dues16Invoice.get_by_membership_no(member.membership_number) invoices17 = Dues17Invoice.get_by_membership_no(member.membership_number) shares = request.registry.share_information.get_member_shares( member.membership_number) return { 'today': date.today().strftime('%Y-%m-%d'), 'D': D, 'member': member, 'shares': shares, 'invoices15': invoices15, 'invoices16': invoices16, 'invoices17': invoices17, # 'form': html }
def dues17_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", 'dues17_message_to_staff' # message queue for staff ) return HTTPFound( request.route_url('detail', memberid=member.id) + '#dues17') # 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'])), 'dues17_message_to_staff' # message queue for user ) return HTTPFound( request.route_url('detail', memberid=member.id) + '#dues17') if DEBUG: print("DEBUG: member.dues17_amount: {}".format(member.dues17_amount)) print("DEBUG: type(member.dues17_amount): {}".format( type(member.dues17_amount))) print("DEBUG: member.dues17_reduced: {}".format(member.dues17_reduced)) print("DEBUG: member.dues17_amount_reduced: {}".format( member.dues17_amount_reduced)) print("DEBUG: type(member.dues17_amount_reduced): {}".format( type(member.dues17_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.', 'dues17_message_to_staff' # message queue for staff ) return HTTPFound( request.route_url('detail', memberid=member.id) + '#dues17') # check the reduction amount: same as default calculated amount? if ((member.dues17_reduced is False) and (member.dues17_amount == reduced_amount)): request.session.flash( u"Dieser Beitrag ist der default-Beitrag!", 'dues17_message_to_staff' # message queue for staff ) return HTTPFound( request.route_url('detail', memberid=member.id) + '#dues17') if reduced_amount == member.dues17_amount_reduced: request.session.flash( u"Auf diesen Beitrag wurde schon reduziert!", 'dues17_message_to_staff' # message queue for staff ) return HTTPFound( request.route_url('detail', memberid=member.id) + '#dues17') if member.dues17_reduced \ and reduced_amount > member.dues17_amount_reduced \ or reduced_amount > member.dues17_amount: request.session.flash( u'Beitrag darf nicht über den berechneten oder bereits' u'reduzierten Wert gesetzt werden.', 'dues17_message_to_staff' # message queue for staff ) return HTTPFound( request.route_url('detail', memberid=member.id) + '#dues17') # prepare: get highest invoice no from db max_invoice_no = Dues17Invoice.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_dues17_reduced_amount(reduced_amount) request.session.flash('reduction to {}'.format(reduced_amount), 'dues17_message_to_staff') old_invoice = Dues17Invoice.get_by_invoice_no(member.dues17_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 = Dues17Invoice( invoice_no=new_invoice_no, invoice_no_string=(u'C3S-dues2017-' + 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.dues17_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 = Dues17Invoice( invoice_no=new_invoice_no + 1, invoice_no_string=(u'C3S-dues2017-' + 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.dues17_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.dues17_invoice_no = new_invoice_no + 1 DBSession.flush() # persist newer invoices reversal_url = (request.route_url( 'make_dues17_reversal_invoice_pdf', email=member.email, code=member.dues17_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_dues17_invoice_no_pdf', email=member.email, code=member.dues17_token, i=str(new_invoice_no + 1).zfill(4))) email_subject, email_body = make_dues17_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!', 'dues17_message_to_staff') else: request.session.flash('update email was sent to user!', 'dues17_message_to_staff') send_message(request, message) return HTTPFound( request.route_url('detail', memberid=member_id) + '#dues17')
def make_dues17_invoice_no_pdf(request): """ Create invoice PDFs on-the-fly. This view checks supplied information (in URL) against info in database and returns - an error message OR - a PDF as receipt === =========================================================== URL http://app:port/dues_invoice_no/EMAIL/CAQJGCGUFW/C3S-dues17-0001.pdf === =========================================================== """ token = request.matchdict['code'] invoice_number = request.matchdict['i'] try: member = C3sMember.get_by_dues17_token(token) assert member is not None assert member.dues17_token == token except AssertionError: request.session.flash( u"This member and token did not match!", 'message_to_user' # message queue for user ) return HTTPFound(request.route_url('error_page')) try: invoice = Dues17Invoice.get_by_invoice_no(invoice_number.lstrip('0')) assert invoice is not None except AssertionError: request.session.flash( u"No invoice found!", 'message_to_user' # message queue for user ) return HTTPFound(request.route_url('error_page')) # sanity check: invoice token must match with token try: assert (invoice.token == token) except AssertionError: request.session.flash( u"Token did not match!", 'message_to_user' # message queue for user ) return HTTPFound(request.route_url('error_page')) # sanity check: invoice must not be reversal try: assert (not invoice.is_reversal) except AssertionError: request.session.flash( u"Token did not match!", 'message_to_user' # message queue for user ) return HTTPFound(request.route_url('error_page')) # return a pdf file pdf_file = make_invoice_pdf_pdflatex(member, invoice) response = Response(content_type='application/pdf') pdf_file.seek(0) # rewind to beginning response.app_iter = open(pdf_file.name, "r") return response
def send_dues17_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(2018,1,1) or ( \ member.membership_loss_date is not None and member.membership_loss_date < date(2017,1,1) ): request.session.flash( 'Member {0} was not a member in 2017. Therefore, you cannot send ' 'an invoice for 2017.'.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.dues17_invoice is True: invoice = Dues17Invoice.get_by_invoice_no(member.dues17_invoice_no) member.dues17_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 (Dues17Invoice.check_for_existing_dues17_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.dues17_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 = Dues17Invoice.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_dues17(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.dues17_invoice = True member.dues17_invoice_no = new_invoice_no # irrelevant for investing member.dues17_invoice_date = datetime.now() member.dues17_token = randomstring member.dues17_start = dues_start if 'normal' in member.membership_type: # only for normal members member.set_dues17_amount(dues_amount) # store some more info about invoice in invoice table invoice = Dues17Invoice( invoice_no=member.dues17_invoice_no, invoice_no_string=(u'C3S-dues2017-' + str(member.dues17_invoice_no).zfill(4)), invoice_date=member.dues17_invoice_date, invoice_amount=u'' + str(member.dues17_amount), member_id=member.id, membership_no=member.membership_number, email=member.email, token=member.dues17_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 'investing' not in member.membership_type: start_quarter = string_start_quarter_dues17(member) invoice_url = (request.route_url( 'make_dues17_invoice_no_pdf', email=member.email, code=member.dues17_token, i=str(member.dues17_invoice_no).zfill(4))) email_subject, email_body = make_dues17_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) + '#dues17') if 'toolbox' in request.referrer: return HTTPFound(request.route_url('toolbox')) else: return get_memberhip_listing_redirect(request, member.id)
def test_dues17_reduction(self): """ test the dues17_reduction functionality """ # have to accept their membersip first m1 = C3sMember.get_by_id(1) # german normal member m1.membership_accepted = True m2 = C3sMember.get_by_id(2) # english normal member m2.membership_accepted = True self.config.add_route('make_dues17_invoice_no_pdf', '/') self.config.add_route('make_dues17_reversal_invoice_pdf', '/') self.config.add_route('detail', '/detail/') self.config.add_route('error_page', '/error_page') self.config.add_route('toolbox', '/toolbox') req = testing.DummyRequest() req.referrer = 'toolbox' from c3smembership.views.membership_dues_2017 import ( send_dues17_invoice_batch, ) # send out invoices. this is a prerequisite for reductions res = send_dues17_invoice_batch(req) res """ test reduction of dues """ # pre-check self.assertFalse(m1.dues17_reduced) # not reduced yet! _m1_amount_reduced = m1.dues17_amount_reduced # is Decimal('0') _number_of_invoices_before_reduction = len(Dues17Invoice.get_all()) # print("_number_of_invoices_before_reduction: {}".format( # _number_of_invoices_before_reduction)) # we have 2 invoices as of now self.assertEqual(len(Dues17Invoice.get_all()), 2) # import the function under test from c3smembership.views.membership_dues_2017 import dues17_reduction ############################################################# # try to reduce to the given calculated amount (edge case coverage) # this will not work, produce no new invoices req_reduce = testing.DummyRequest( # prepare request post={ 'confirmed': 'yes', 'submit': True, 'amount': 50, }, ) req_reduce.matchdict['member_id'] = 1 # do it for member with id 1 res_reduce = dues17_reduction(req_reduce) # call reduce on her self.assertEqual(len(Dues17Invoice.get_all()), 2) # no new invoice ############################################################# # try to reduce above the given calculated amount # this will not work, produce no new invoices req_reduce = testing.DummyRequest( # prepare request post={ 'confirmed': 'yes', 'submit': True, 'amount': 500, }, ) req_reduce.matchdict['member_id'] = 1 # do it for member with id 1 res_reduce = dues17_reduction(req_reduce) # call reduce on her self.assertEqual(len(Dues17Invoice.get_all()), 2) # no new invoice ############################################################# # valid reduction but without confirmation req_reduce = testing.DummyRequest(post={ 'confirmed': 'no', 'submit': True, 'amount': 42, }, ) req_reduce.matchdict['member_id'] = 1 res_reduce = dues17_reduction(req_reduce) self.assertEqual(len(Dues17Invoice.get_all()), 2) # no new invoice ############################################################# # valid reduction req_reduce = testing.DummyRequest(post={ 'confirmed': 'yes', 'submit': True, 'amount': 42, }, ) req_reduce.matchdict['member_id'] = 1 res_reduce = dues17_reduction(req_reduce) _number_of_invoices_after_reduction = len(Dues17Invoice.get_all()) assert ( # two new invoices must have been issued (_number_of_invoices_before_reduction + 2) == _number_of_invoices_after_reduction) assert (_number_of_invoices_after_reduction == 4) assert ('detail' in res_reduce.headers['Location']) # 302 to detail p. assert (_m1_amount_reduced != m1.dues17_amount_reduced) # changed! assert (m1.dues17_amount_reduced == 42) # changed to 42! # check the invoice created _rev_inv = Dues17Invoice.get_by_invoice_no( _number_of_invoices_before_reduction + 1) _new_inv = Dues17Invoice.get_by_invoice_no( _number_of_invoices_before_reduction + 2) # print(_rev_inv.invoice_amount) # print(type(_rev_inv.invoice_amount)) assert (_rev_inv.invoice_amount == D('-50')) assert (_new_inv.invoice_amount == D('42')) # we have 4 invoices as of now self.assertEqual(len(Dues17Invoice.get_all()), 4) ############################################################# # now try to raise above the previous reduction req_reduce = testing.DummyRequest(post={ 'confirmed': 'yes', 'submit': True, 'amount': 50, }, ) req_reduce.matchdict['member_id'] = 1 res_reduce = dues17_reduction(req_reduce) _number_of_invoices_after_reduction = len(Dues17Invoice.get_all()) # no new invoices were created, we still have 4 invoices self.assertEqual(len(Dues17Invoice.get_all()), 4) ############################################################# # try to reduce to the same amount again (edge case coverage) req_reduce = testing.DummyRequest( post={ 'confirmed': 'yes', 'submit': True, 'amount': 42, # lots of values missing }, ) req_reduce.matchdict['member_id'] = 1 res_reduce = dues17_reduction(req_reduce) ############################################################# # try to reduce to zero (edge case coverage) # print("------------- reduction to zero ahead!!!") req_reduce = testing.DummyRequest( post={ 'confirmed': 'yes', 'submit': True, 'amount': 0, # lots of values missing }, ) req_reduce.matchdict['member_id'] = 1 res_reduce = dues17_reduction(req_reduce) req_reduce = testing.DummyRequest( post={ 'confirmed': 'yes', 'submit': True, 'amount': 0, # lots of values missing }, ) req_reduce.matchdict['member_id'] = 2 res_reduce = dues17_reduction(req_reduce) ############################################################# # try to reduce to zero with english member (edge case coverage) # how to do this if you already reduced to zero? reduce to more first! req_reduce = testing.DummyRequest( post={ 'confirmed': 'yes', 'submit': True, 'amount': 1, # lots of values missing }, ) req_reduce.matchdict['member_id'] = 1 res_reduce = dues17_reduction(req_reduce) m1.locale = u'en' req_reduce = testing.DummyRequest( post={ 'confirmed': 'yes', 'submit': True, 'amount': 0, # lots of values missing }, ) req_reduce.matchdict['member_id'] = 1 res_reduce = dues17_reduction(req_reduce) ############################################################# """ test reversal invoice PDF generation """ from c3smembership.views.membership_dues_2017 import ( make_dues17_reversal_invoice_pdf, ) req2 = testing.DummyRequest() # wrong token: must fail! req2.matchdict = { 'email': m1.email, 'code': m1.dues17_token + 'false!!!', # must fail 'no': u'0006', } res = make_dues17_reversal_invoice_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error # wrong invoice number: must fail! req2.matchdict = { 'email': m1.email, 'code': m1.dues17_token, 'no': u'1234', # must fail } res = make_dues17_reversal_invoice_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error # wrong invoice token: must fail! i2 = Dues17Invoice.get_by_invoice_no('2') i2.token = u'not_matching' req2.matchdict = { 'email': m2.email, 'code': m2.dues17_token, 'no': u'2', # must fail } res = make_dues17_reversal_invoice_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error ###################################################################### # wrong invoice type (not a reversal): must fail! (edge case coverage) assert (not i2.is_reversal) # i2 is not a reversal i2.token = m2.dues17_token # we give it a valid token req2.matchdict = { 'email': m2.email, 'code': m2.dues17_token, 'no': u'0002', } res = make_dues17_reversal_invoice_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error ###################################################################### # retry with valid token: req2.matchdict = { 'email': m1.email, 'code': m1.dues17_token, 'no': u'0003', } res = make_dues17_reversal_invoice_pdf(req2) # print("length of the result: {}".format(len(res.body))) # print("headers of the result: {}".format((res.headers))) assert (60000 < len(res.body) < 80000) assert ('application/pdf' in res.headers['Content-Type'])
def test_send_dues17_invoice_email_via_BATCH(self): """ test the send_dues17_invoice_batch function for batch processing """ # from pyramid_mailer import get_mailer from c3smembership.views.membership_dues_2017 import ( send_dues17_invoice_batch, ) self.config.add_route('make_dues17_invoice_no_pdf', '/') self.config.add_route('make_dues17_reversal_invoice_pdf', '/') self.config.add_route('detail', '/detail/') self.config.add_route('error_page', '/error_page') self.config.add_route('toolbox', '/toolbox') # have to accept their membersip first m1 = C3sMember.get_by_id(1) # german normal member m1.membership_accepted = True m2 = C3sMember.get_by_id(2) # english normal member m2.membership_accepted = True m3 = C3sMember.get_by_id(3) # german investing member m3.membership_accepted = True m4 = C3sMember.get_by_id(4) # english investing member m4.membership_accepted = True m5 = C3sMember.get_by_id(5) # german investing member m5.membership_accepted = True m5 = C3sMember.get_by_id(6) # english investing member m5.membership_accepted = True # check number of invoices: should be 0 _number_of_invoices_before_batch = len(Dues17Invoice.get_all()) # print("_number_of_invoices_before_batch: {}".format( # _number_of_invoices_before_batch)) assert (_number_of_invoices_before_batch == 0) req = testing.DummyRequest() req.referrer = 'toolbox' res = send_dues17_invoice_batch(req) # print res # check number of invoices: should be 2 _number_of_invoices_batch = len(Dues17Invoice.get_all()) # print("number of invoices after batch: {}".format( # _number_of_invoices_batch)) assert (_number_of_invoices_batch == 2) # try to post a number for batch processing req_post = testing.DummyRequest( post={ 'submit': True, 'number': 24 # lots of values missing }, ) req.referrer = 'toolbox' res = send_dues17_invoice_batch(req_post) assert ('sent out 5 mails (to members with ids [1, 2, 3, 4, 5])' in req.session.pop_flash('message_to_staff')) # try to batch-send once more: # this will respond with a redirect and tell # that there are no invitees left res2 = send_dues17_invoice_batch(req) # print res2 self.assertEquals(res2.status, '302 Found') self.assertEquals(res2.status_code, 302) assert ('no invoicees left. all done!' in req.session.pop_flash('message_to_staff')) """ and now some tests for make_dues17_invoice_no_pdf """ from c3smembership.views.membership_dues_2017 import ( make_dues17_invoice_no_pdf, ) req2 = testing.DummyRequest() # wrong token: must fail! req2.matchdict = { 'email': m1.email, 'code': m1.dues17_token + 'false!!!', # must fail 'i': u'0001', } res = make_dues17_invoice_no_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error # wrong invoice number: must fail! req2.matchdict = { 'email': m1.email, 'code': m1.dues17_token, 'i': u'1234', # must fail } res = make_dues17_invoice_no_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error # wrong invoice token: must fail! i2 = Dues17Invoice.get_by_invoice_no(2) i2.token = u'not_matching' req2.matchdict = { 'email': m2.email, 'code': m2.dues17_token, 'i': u'3', # must fail } res = make_dues17_invoice_no_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error ####################################################################### # one more edge case: # check _inv.token must match code, or else!!! # first, set inv_code to something wrong: i1 = Dues17Invoice.get_by_invoice_no(1) _old_i1_token = i1.token i1.token = u'not_right' req2.matchdict = { 'email': m1.email, 'code': m1.dues17_token, 'i': u'0001', } res = make_dues17_invoice_no_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error # reset it to what was there before i1.token = _old_i1_token ####################################################################### # one more edge case: # check this invoice is not a reversal, or else no PDF!!! # first, set is_reversal to something wrong: i1 = Dues17Invoice.get_by_invoice_no(1) _old_i1_reversal_status = i1.is_reversal # False i1.is_reversal = True req2.matchdict = { 'email': m1.email, 'code': m1.dues17_token, 'i': u'0001', } res = make_dues17_invoice_no_pdf(req2) assert ('application/pdf' not in res.headers['Content-Type']) # no PDF assert ('error_page' in res.headers['Location']) # but error # reset it to what was there before i1.is_reversal = _old_i1_reversal_status ####################################################################### # retry with valid token: req2.matchdict = { 'email': m1.email, 'code': m1.dues17_token, 'i': u'0001', } res = make_dues17_invoice_no_pdf(req2) # m1. # print("length of the result: {}".format(len(res.body))) # print("headers of the result: {}".format((res.headers))) assert (60000 < len(res.body) < 80000) assert ('application/pdf' in res.headers['Content-Type']) """ test dues listing """ from c3smembership.views.membership_dues_2017 import dues17_listing req_list = testing.DummyRequest() resp_list = dues17_listing(req_list) # print resp_list # {'count': 5, # 'invoices': [ # <c3smembership.models.Dues17Invoice object at 0x7f95df761a50>, # <c3smembership.models.Dues17Invoice object at 0x7f95df761690>, # <c3smembership.models.Dues17Invoice object at 0x7f95df817a50>, # <c3smembership.models.Dues17Invoice object at 0x7f95df761c90>, # <c3smembership.models.Dues17Invoice object at 0x7f95df761c10>], # '_today': datetime.date(2017, 9, 1)} assert (resp_list['count'] == 2)
def test_send_dues17_invoice_email_single(self): """ test the send_dues17_invoice_email view * calculate invoice amount and send invoice email ** to not accepted member ** to accepted member ** to non-existing member (wrong id) ** to same member (just send email, don't create new invoice) ... and also check email texts for * german normal member * english normal member * german investing member * english investing member * german investing legal entity * english investing legal entity """ from pyramid_mailer import get_mailer from c3smembership.views.membership_dues_2017 import ( send_dues17_invoice_email, ) from c3smembership.models import Dues17Invoice _number_of_invoices = len(Dues17Invoice.get_all()) self.config.add_route('toolbox', '/') self.config.add_route('detail', '/') self.config.add_route('make_dues17_invoice_no_pdf', '/') req = testing.DummyRequest() req.matchdict = { 'member_id': '1', } req.referrer = 'detail' res = send_dues17_invoice_email(req) self.assertTrue(res.status_code == 302) self.assertTrue('http://example.com/' in res.headers['Location']) # member 1 not accepted by the board. problem! _number_of_invoices_2 = len(Dues17Invoice.get_all()) assert (_number_of_invoices == _number_of_invoices_2 == 0) # print("_number_of_invoices: {}".format(_number_of_invoices)) m1 = C3sMember.get_by_id(1) m1.membership_accepted = True res = send_dues17_invoice_email(req) # print('#'*60) # print res # print('#'*60) _number_of_invoices_3 = len(Dues17Invoice.get_all()) # print("_number_of_invoices 3: {}".format(_number_of_invoices_3)) assert (_number_of_invoices_3 == 1) # check for outgoing email mailer = get_mailer(req) # print mailer # print mailer.outbox self.assertEqual(len(mailer.outbox), 1) # print mailer.outbox[0].body self.assertTrue( 'Verwendungszweck: C3S-dues2017-0001' in mailer.outbox[0].body) """ now we try to get an id that does not exist """ req2 = testing.DummyRequest() req2.matchdict = { 'member_id': '1234', } req2.referrer = 'detail' res2 = send_dues17_invoice_email(req2) self.assertTrue(res2.status_code == 302) self.assertTrue('http://example.com/' in res2.headers['Location']) """ what if we call that function (and send email) twice? test that no second invoice is created in DB. """ req3 = testing.DummyRequest() req3.matchdict = { 'member_id': '1', } req3.referrer = 'detail' res3 = send_dues17_invoice_email(req3) self.assertTrue(res3.status_code == 302) self.assertTrue('http://example.com/' in res3.headers['Location']) _number_of_invoices_4 = len(Dues17Invoice.get_all()) self.assertEqual(_number_of_invoices_3, _number_of_invoices_4) """ check for email texts """ self.assertEqual(len(mailer.outbox), 2) self.assertTrue( (u'Dein Mitgliedsbeitrag für das ganze Jahr beträgt also 50 Euro.' ) in mailer.outbox[0].body) # print(mailer.outbox[0].body) self.assertTrue( (u'Dein Mitgliedsbeitrag für das ganze Jahr beträgt also 50 Euro.' ) in mailer.outbox[1].body) # print(mailer.outbox[1].body) """ send email to * english member, * investing members (de/en), * legal entities (de/en) """ # english normal ##################################################### m2 = C3sMember.get_by_id(2) m2.membership_accepted = True req_en_normal = testing.DummyRequest() req_en_normal.matchdict = { 'member_id': '2', } req_en_normal.referrer = 'detail' res_en_normal = send_dues17_invoice_email(req_en_normal) self.assertTrue(res_en_normal.status_code == 302) self.assertEqual(len(mailer.outbox), 3) # print(mailer.outbox[2].body) self.assertTrue((u'Please transfer 50 Euro') in mailer.outbox[2].body) # german investing ################################################### m3 = C3sMember.get_by_id(3) m3.membership_accepted = True req_de_investing = testing.DummyRequest() req_de_investing.matchdict = { 'member_id': '3', } req_de_investing.referrer = 'detail' res_de_investing = send_dues17_invoice_email(req_de_investing) self.assertTrue(res_de_investing.status_code == 302) self.assertEqual(len(mailer.outbox), 4) # print(mailer.outbox[3].body) self.assertTrue( (u'Da Du investierendes Mitglied bist') in mailer.outbox[3].body) # english investing ################################################## m4 = C3sMember.get_by_id(4) m4.membership_accepted = True req_en_investing = testing.DummyRequest() req_en_investing.matchdict = { 'member_id': '4', } req_en_investing.referrer = 'detail' res_en_investing = send_dues17_invoice_email(req_en_investing) self.assertTrue(res_en_investing.status_code == 302) self.assertEqual(len(mailer.outbox), 5) # print(mailer.outbox[4].body) self.assertTrue( (u'Since you are an investing member') in mailer.outbox[4].body) # german legal entity ################################################ m5 = C3sMember.get_by_id(5) m5.membership_accepted = True req_de_legalentity = testing.DummyRequest() req_de_legalentity.matchdict = { 'member_id': '5', } req_de_legalentity.referrer = 'detail' res_de_legalentity = send_dues17_invoice_email(req_de_legalentity) self.assertTrue(res_de_legalentity.status_code == 302) self.assertEqual(len(mailer.outbox), 6) # print(mailer.outbox[5].body) self.assertTrue((u'') in mailer.outbox[5].body) # english legal entity ############################################### m6 = C3sMember.get_by_id(6) m6.membership_accepted = True req_en_legalentity = testing.DummyRequest() req_en_legalentity.matchdict = { 'member_id': '6', } req_en_legalentity.referrer = 'detail' res_en_legalentity = send_dues17_invoice_email(req_en_legalentity) self.assertTrue(res_en_legalentity.status_code == 302) self.assertEqual(len(mailer.outbox), 7) # print(mailer.outbox[6].body) self.assertTrue((u'Da Musikverlag investierendes Mitglied ist' ) in mailer.outbox[6].body) self.assertTrue((u'Für juristische Personen wird empfohlen' ) in mailer.outbox[6].body)