A webapp/form for people to join pEp coop. Fork of Cultural Commons Collecting Society (C3S) SCE
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

accountants_views.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. # -*- coding: utf-8 -*-
  2. """
  3. This module holds views for accountants to do accounting stuff.
  4. - Log In or Log Out
  5. - Administer Applications for Membership on the Dashboard
  6. - Check reception of signature, send reception confirmation or reminder mails
  7. - Check reception of payment, send reception confirmation or reminder mails
  8. - Check application details
  9. - Deletion of applications
  10. - ReGenerate a PDF for an application
  11. """
  12. import logging
  13. from datetime import (
  14. datetime,
  15. date,
  16. )
  17. from types import NoneType
  18. import deform
  19. from deform import ValidationFailure
  20. from pyramid_mailer.message import Message
  21. from pyramid.httpexceptions import HTTPFound
  22. from pyramid.security import (
  23. remember,
  24. forget,
  25. authenticated_userid,
  26. )
  27. from pyramid.url import route_url
  28. from pyramid.view import view_config
  29. from c3smembership.mail_utils import (
  30. make_payment_confirmation_email,
  31. send_message,
  32. )
  33. from c3smembership.mail_reminders_util import (
  34. make_signature_reminder_email,
  35. make_payment_reminder_email,
  36. )
  37. from c3smembership.models import (
  38. C3sMember,
  39. C3sStaff,
  40. Dues15Invoice,
  41. Dues16Invoice,
  42. Dues17Invoice,
  43. )
  44. from c3smembership.presentation.i18n import _
  45. from c3smembership.presentation.schemas.accountant_login import (
  46. AccountantLogin
  47. )
  48. from c3smembership.presentation.views.dashboard import get_dashboard_redirect
  49. from c3smembership.utils import generate_pdf
  50. DEBUG = False
  51. LOG = logging.getLogger(__name__)
  52. @view_config(renderer='templates/login.pt',
  53. route_name='login')
  54. def accountants_login(request):
  55. """
  56. This view lets accountants log in (using a login form).
  57. If a person is already logged in, she is forwarded to the dashboard.
  58. """
  59. logged_in = authenticated_userid(request)
  60. LOG.info("login by %s", logged_in)
  61. if logged_in is not None:
  62. return get_dashboard_redirect(request)
  63. form = deform.Form(
  64. AccountantLogin(),
  65. buttons=[
  66. deform.Button('submit', _(u'Submit')),
  67. deform.Button('reset', _(u'Reset'))
  68. ],
  69. )
  70. # if the form has been used and SUBMITTED, check contents
  71. if 'submit' in request.POST:
  72. controls = request.POST.items()
  73. try:
  74. appstruct = form.validate(controls)
  75. except ValidationFailure, e_validation_failure:
  76. request.session.flash(
  77. _(u"Please note: There were errors, "
  78. "please check the form below."),
  79. 'message_above_form',
  80. allow_duplicate=False)
  81. return{'form': e_validation_failure.render()}
  82. # get user and check pw...
  83. login = appstruct['login']
  84. password = appstruct['password']
  85. try:
  86. checked = C3sStaff.check_password(login, password)
  87. except AttributeError: # pragma: no cover
  88. checked = False
  89. if checked:
  90. LOG.info("password check for %s: good!", login)
  91. headers = remember(request, login)
  92. LOG.info("logging in %s", login)
  93. return HTTPFound(
  94. request.route_url(
  95. 'dashboard'),
  96. headers=headers)
  97. else:
  98. LOG.info("password check: failed for %s.", login)
  99. else:
  100. request.session.pop('message_above_form')
  101. html = form.render()
  102. return {'form': html, }
  103. @view_config(permission='manage',
  104. route_name='switch_sig')
  105. def switch_sig(request):
  106. """
  107. This view lets accountants switch an applications signature status
  108. once their signature has arrived.
  109. Note:
  110. Expects the object request.registry.membership_application to implement
  111. """
  112. member_id = request.matchdict['memberid']
  113. membership_application = request.registry.membership_application
  114. signature_received = membership_application.get_signature_status(member_id)
  115. new_signature_received = not signature_received
  116. membership_application.set_signature_status(
  117. member_id,
  118. new_signature_received)
  119. LOG.info(
  120. "signature status of member.id %s changed by %s to %s",
  121. member_id,
  122. request.user.login,
  123. new_signature_received
  124. )
  125. if 'dashboard' in request.referrer:
  126. return get_dashboard_redirect(request, member_id)
  127. else:
  128. return HTTPFound(
  129. request.route_url(
  130. 'detail',
  131. memberid=member_id,
  132. _anchor='membership_info'
  133. )
  134. )
  135. @view_config(permission='manage',
  136. route_name='delete_entry')
  137. def delete_entry(request):
  138. """
  139. This view lets accountants delete datasets (e.g. doublettes, test entries).
  140. """
  141. deletion_confirmed = (request.params.get('deletion_confirmed', '0') == '1')
  142. redirection_view = request.params.get('redirect', 'dashboard')
  143. LOG.info('redirect to: ' + str(redirection_view))
  144. if deletion_confirmed:
  145. memberid = request.matchdict['memberid']
  146. member = C3sMember.get_by_id(memberid)
  147. member_lastname = member.lastname
  148. member_firstname = member.firstname
  149. C3sMember.delete_by_id(member.id)
  150. LOG.info(
  151. "member.id %s was deleted by %s",
  152. member.id,
  153. request.user.login,
  154. )
  155. message = "member.id %s was deleted" % member.id
  156. request.session.flash(message, 'messages')
  157. msgstr = u'Member with id {0} \"{1}, {2}\" was deleted.'
  158. return HTTPFound(
  159. request.route_url(
  160. redirection_view,
  161. _query={'message': msgstr.format(
  162. memberid,
  163. member_lastname,
  164. member_firstname)},
  165. _anchor='member_{id}'.format(id=str(memberid))
  166. )
  167. )
  168. else:
  169. return HTTPFound(
  170. request.route_url(
  171. redirection_view,
  172. _query={'message': (
  173. 'Deleting the member was not confirmed'
  174. ' and therefore nothing has been deleted.')}
  175. )
  176. )
  177. @view_config(permission='manage',
  178. route_name='switch_pay')
  179. def switch_pay(request):
  180. """
  181. This view lets accountants switch a member applications payment status
  182. once their payment has arrived.
  183. """
  184. member_id = request.matchdict['memberid']
  185. membership_application = request.registry.membership_application
  186. payment_received = membership_application.get_payment_status(member_id)
  187. new_payment_received = not payment_received
  188. membership_application.set_payment_status(
  189. member_id,
  190. new_payment_received)
  191. LOG.info(
  192. "payment info of member.id %s changed by %s to %s",
  193. member_id,
  194. request.user.login,
  195. new_payment_received
  196. )
  197. if 'dashboard' in request.referrer:
  198. return get_dashboard_redirect(request, member_id)
  199. else:
  200. return HTTPFound(
  201. request.route_url(
  202. 'detail',
  203. memberid=member_id,
  204. _anchor='membership_info')
  205. )
  206. @view_config(renderer='templates/detail.pt',
  207. permission='manage',
  208. route_name='detail')
  209. def member_detail(request):
  210. """
  211. This view lets accountants view member details:
  212. - has their signature arrived?
  213. - how about the payment?
  214. Mostly all the info about an application or membership
  215. in the database can be seen here.
  216. """
  217. from decimal import Decimal as D
  218. logged_in = authenticated_userid(request)
  219. memberid = request.matchdict['memberid']
  220. LOG.info("member details of id %s checked by %s", memberid, logged_in)
  221. member = C3sMember.get_by_id(memberid)
  222. if member is None: # that memberid did not produce good results
  223. request.session.flash(
  224. "A Member with id "
  225. "{} could not be found in the DB. run for the backups!".format(
  226. memberid),
  227. 'message_to_staff'
  228. )
  229. return HTTPFound( # back to base
  230. request.route_url('toolbox'))
  231. # get the members invoices from the DB
  232. invoices15 = Dues15Invoice.get_by_membership_no(member.membership_number)
  233. invoices16 = Dues16Invoice.get_by_membership_no(member.membership_number)
  234. invoices17 = Dues17Invoice.get_by_membership_no(member.membership_number)
  235. shares = request.registry.share_information.get_member_shares(
  236. member.membership_number)
  237. return {
  238. 'today': date.today().strftime('%Y-%m-%d'),
  239. 'D': D,
  240. 'member': member,
  241. 'shares': shares,
  242. 'invoices15': invoices15,
  243. 'invoices16': invoices16,
  244. 'invoices17': invoices17,
  245. # 'form': html
  246. }
  247. @view_config(permission='view',
  248. route_name='logout')
  249. def logout_view(request):
  250. """
  251. Is used to log a user/staffer off. "forget"
  252. """
  253. request.session.invalidate()
  254. request.session.flash(u'Logged out successfully.')
  255. headers = forget(request)
  256. return HTTPFound(
  257. location=route_url('login', request),
  258. headers=headers
  259. )
  260. @view_config(permission='manage',
  261. route_name='regenerate_pdf')
  262. def regenerate_pdf(request):
  263. """
  264. Staffers can regenerate an applicants PDF and send it to her.
  265. """
  266. code = request.matchdict['code']
  267. member = C3sMember.get_by_code(code)
  268. if member is None:
  269. return get_dashboard_redirect(request)
  270. membership_application = request.registry.membership_application.get(
  271. member.id)
  272. appstruct = {
  273. 'firstname': member.firstname,
  274. 'lastname': member.lastname,
  275. 'address1': member.address1,
  276. 'address2': member.address2,
  277. 'postcode': member.postcode,
  278. 'city': member.city,
  279. 'email': member.email,
  280. 'email_confirm_code': membership_application['payment_token'],
  281. 'country': member.country,
  282. 'locale': member.locale,
  283. 'membership_type': membership_application['membership_type'],
  284. 'num_shares': membership_application['shares_quantity'],
  285. 'date_of_birth': member.date_of_birth,
  286. 'date_of_submission': membership_application['date_of_submission'],
  287. }
  288. LOG.info(
  289. "%s regenerated the PDF for code %s",
  290. authenticated_userid(request),
  291. code)
  292. return generate_pdf(appstruct)
  293. @view_config(permission='manage',
  294. route_name='mail_sig_confirmation')
  295. def mail_signature_confirmation(request):
  296. """
  297. Send a mail to a membership applicant
  298. informing her about reception of signature.
  299. """
  300. member_id = request.matchdict['memberid']
  301. membership_application = request.registry.membership_application
  302. membership_application.mail_signature_confirmation(member_id, request)
  303. if 'detail' in request.referrer:
  304. return HTTPFound(request.route_url(
  305. 'detail',
  306. memberid=member_id))
  307. else:
  308. return get_dashboard_redirect(request, member_id)
  309. @view_config(permission='manage',
  310. route_name='mail_pay_confirmation')
  311. def mail_payment_confirmation(request):
  312. """
  313. Send a mail to a membership applicant
  314. informing her about reception of payment.
  315. """
  316. member = request.registry.member_information.get_member_by_id(
  317. request.matchdict['member_id'])
  318. email_subject, email_body = make_payment_confirmation_email(member)
  319. message = Message(
  320. subject=email_subject,
  321. sender='yes@c3s.cc',
  322. recipients=[member.email],
  323. body=email_body,
  324. )
  325. send_message(request, message)
  326. member.payment_confirmed = True
  327. member.payment_confirmed_date = datetime.now()
  328. if 'detail' in request.referrer:
  329. return HTTPFound(request.route_url(
  330. 'detail',
  331. memberid=member.id))
  332. else:
  333. return get_dashboard_redirect(request, member.id)
  334. @view_config(permission='manage',
  335. route_name='mail_sig_reminder')
  336. def mail_signature_reminder(request):
  337. """
  338. Send a mail to a membership applicant
  339. reminding her about lack of *signature*.
  340. Headquarters is still waiting for the *signed form*.
  341. This view can only be used by staff.
  342. To be approved for membership applicants have to
  343. * Transfer money for the shares to acquire (at least one share).
  344. * **Send the signed form** back to headquarters.
  345. """
  346. member_id = request.matchdict['memberid']
  347. member = C3sMember.get_by_id(member_id)
  348. if isinstance(member, NoneType):
  349. request.session.flash(
  350. 'that member was not found! (id: {})'.format(member_id),
  351. 'messages'
  352. )
  353. return get_dashboard_redirect(request, member.id)
  354. email_subject, email_body = make_signature_reminder_email(member)
  355. message = Message(
  356. subject=email_subject,
  357. sender='office@c3s.cc',
  358. recipients=[member.email],
  359. body=email_body
  360. )
  361. send_message(request, message)
  362. try:
  363. member.sent_signature_reminder += 1
  364. except TypeError:
  365. # if value was None (after migration of DB schema)
  366. member.sent_signature_reminder = 1
  367. member.sent_signature_reminder_date = datetime.now()
  368. if 'detail' in request.referrer:
  369. return HTTPFound(request.route_url(
  370. 'detail',
  371. memberid=request.matchdict['memberid']))
  372. else:
  373. return get_dashboard_redirect(request, member.id)
  374. @view_config(permission='manage',
  375. route_name='mail_pay_reminder')
  376. def mail_payment_reminder(request):
  377. """
  378. Send a mail to a membership applicant
  379. reminding her about lack of **payment**.
  380. Headquarters is still waiting for the **bank transfer**.
  381. This view can only be used by staff.
  382. To be approved for membership applicants have to
  383. * **Transfer money** for the shares to acquire (at least one share).
  384. * Send the signed form back to headquarters.
  385. """
  386. member = request.registry.member_information.get_member_by_id(
  387. request.matchdict['memberid'])
  388. email_subject, email_body = make_payment_reminder_email(member)
  389. message = Message(
  390. subject=email_subject,
  391. sender='office@c3s.cc',
  392. recipients=[member.email],
  393. body=email_body
  394. )
  395. send_message(request, message)
  396. try: # if value is int
  397. member.sent_payment_reminder += 1
  398. except TypeError: # pragma: no cover
  399. # if value was None (after migration of DB schema)
  400. member.sent_payment_reminder = 1
  401. member.sent_payment_reminder_date = datetime.now()
  402. if 'detail' in request.referrer:
  403. return HTTPFound(request.route_url(
  404. 'detail',
  405. memberid=request.matchdict['memberid']))
  406. else:
  407. return get_dashboard_redirect(request, member.id)