PageRenderTime 26ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/reporters/evangelist.py

https://gitlab.com/made-financial/made-financial-scripts
Python | 114 lines | 73 code | 20 blank | 21 comment | 21 complexity | f8dccbd60c5790dea3570b8c1e166680 MD5 | raw file
  1. # Reports how many memberships each member in a list has paid.
  2. # This is used to prepare a report for our evangelist/outreach contractor.
  3. #
  4. # Supported options:
  5. # - ids: output data for members in this comma-separated list
  6. # Supported formats:
  7. # - text: textual report to send by email
  8. import sys
  9. from collections import namedtuple
  10. import pandas as pd
  11. from .common import get_refunded
  12. sys.path.append("../data/plugins")
  13. from categorize.entities import Entities
  14. entities = Entities("../data/")
  15. sources = dict(PP="Paypal", BK="Bank", CA="Cash", CB="Bitcoin")
  16. Payment = namedtuple('Payment', 'month payee amount source id')
  17. Summary = namedtuple('Summary', 'payments total by_source by_level')
  18. def evangelist(movements, options, format):
  19. items = []
  20. refunded = get_refunded(movements)
  21. ids = options.get('ids').split(",")
  22. print(ids)
  23. for m in movements:
  24. # All movmements should have an id, if not discard them for now.
  25. if not 'movement_id' in m.meta:
  26. continue
  27. id = m.meta['movement_id']
  28. # Discard anything that has zero postings in Income:Membership
  29. payments = [p for p in m.postings if p.account == "Income:Membership"]
  30. if len(payments) == 0:
  31. continue
  32. # If a payment has been refunded, exclude it
  33. if id in refunded:
  34. continue
  35. for p in payments:
  36. # We need to find out which month this payment is for.
  37. # There are three possibilities:
  38. # - pay only for current month: no payment_period meta
  39. # - pay for another month: movement has payment_period meta
  40. # - pay for various months: each posting has a payment_period meta
  41. period = p.meta.get('payment_period', None)
  42. if period is None:
  43. period = m.meta.get('payment_period', None)
  44. if period is None:
  45. period = "{}-{:0>2}".format(m.date.year, m.date.month)
  46. # We also want to know who the payment is from.
  47. # In most cases the parent transaction's payee is what we want.
  48. # However in some cases a single transaction pays for various
  49. # members, identified by their entity_id.
  50. payee = "<< Unknown >>"
  51. member_id = p.meta.get('entity_id', None)
  52. if member_id is None:
  53. member_id = m.meta.get('entity_id', None)
  54. if member_id is not None and member_id != 'unknown':
  55. member = entities.members.get(int(member_id), None)
  56. if member is not None:
  57. payee = member['fullname']
  58. else:
  59. if m.payee is not None:
  60. payee = m.payee
  61. # Exclude this payment if member does not match the filter.
  62. if member_id not in ids:
  63. continue
  64. item = dict(id = m.meta['movement_id'],
  65. month = period,
  66. amount = round(p.units.number * -1),
  67. payee = payee,
  68. source = m.meta['movement_id'][0:2])
  69. items.append(item)
  70. if len(items) == 0:
  71. print("No records for any of the members in the list.")
  72. return
  73. paid = pd.DataFrame().from_records(items).groupby('payee')
  74. # output the data
  75. as_is = lambda v: str(v)
  76. format = lambda f: lambda v: f.format(v)
  77. import calendar
  78. valid = []
  79. invalid = []
  80. for payee, items in paid:
  81. header = "{}: {} payments of {} EUR".format(payee, items['amount'].count(),
  82. items['amount'].sum() / items['amount'].count())
  83. format_month = lambda m: calendar.month_abbr[int(m.split('-')[1])]
  84. mnts = [format_month(m) for m in items['month'].apply(lambda i: i).values]
  85. m = "{} : {}".format(header, ",".join(mnts))
  86. if len(mnts) < 3:
  87. invalid.append(m)
  88. else:
  89. valid.append(m)
  90. print("== Valid")
  91. print("\n".join(valid))
  92. print()
  93. print("== Not Valid")
  94. print("\n".join(invalid))