app.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import json
  2. import os
  3. from flask import Flask, request, jsonify
  4. from flask_cors import CORS
  5. _url_prefix = os.environ.get("URL_PREFIX", "room").strip("/")
  6. _port = os.environ.get("FLASK_PORT", "5000").strip()
  7. _debug = os.environ.get("FLASK_DEBUG", "").strip()
  8. _here = os.path.dirname(__file__)
  9. DEBUG = True if _debug else False
  10. URL_PREFIX = f"/{_url_prefix}" if _url_prefix else ""
  11. PORT = int(_port)
  12. app = Flask(__name__)
  13. CORS(app)
  14. # Config
  15. with open(os.path.join(_here, "config.json")) as f:
  16. CONFIG = json.load(f)
  17. if not CONFIG or 'services' not in CONFIG or 'global' not in CONFIG:
  18. raise ValueError('CONFIG: Missing services or global')
  19. SERVICES = CONFIG['services']
  20. GLOBAL = CONFIG['global']
  21. if not GLOBAL or 'taxes' not in GLOBAL or 'sales' not in GLOBAL['taxes']:
  22. raise ValueError('GLOBAL: Missing sales tax')
  23. SALES_TAX = GLOBAL['taxes']['sales']
  24. # Formatters
  25. def dollar_fmt(value: float) -> str:
  26. return "{:.2f}".format(round(value, 2))
  27. def title_fmt(value: str) -> str:
  28. return value.replace('_', ' ').title()
  29. def details_fmt(details: dict) -> dict:
  30. fmt = {}
  31. for k, v in details.items():
  32. fmt[title_fmt(k)] = dollar_fmt(v)
  33. return fmt
  34. # Request handlers
  35. def _request(request) -> dict:
  36. _req = request.get_json(silent=True) or request.args
  37. _data = { 'service_key': None }
  38. if 'service' in _req:
  39. _data['service_key'] = str(_req['service']).lower().strip()
  40. if 'amount' in _req:
  41. _data['amount'] = float(_req['amount'])
  42. for k in [ 'service_key', 'amount' ]:
  43. if k not in _data or not _data[k]:
  44. raise ValueError(k)
  45. _data['service'] = SERVICES.get(_data['service_key'])
  46. if _data['service'] is None:
  47. raise ValueError('service')
  48. _data['details'] = {}
  49. return _data
  50. def _response(**data) -> str:
  51. _dict = { 'service_key' : data['service_key'] }
  52. for k in [ 'list', 'retail', 'net' ]:
  53. if data.get(k) is not None:
  54. _dict[k] = dollar_fmt(data.get(k))
  55. if data.get('details') is not None:
  56. _dict['details'] = details_fmt(data.get('details'))
  57. return jsonify(_dict)
  58. def make_handler(*transforms):
  59. def handler():
  60. try:
  61. data = _request(request)
  62. except (TypeError, ValueError):
  63. return jsonify({'error': 'Invalid or missing parameters'}), 400
  64. for transform in transforms:
  65. transform(data)
  66. return _response(**data)
  67. return handler
  68. # Actual math
  69. def _n2l(data: dict) -> None:
  70. svc = data['service']
  71. det = data['details']
  72. net = data['net'] if data.get('net') else data['amount']
  73. comm_rate = svc.get('fees', {}).get('commission', 0)
  74. wrong_rate = svc.get('taxes', {}).get('wrong', SALES_TAX)
  75. list_ = (net / (1 - comm_rate - SALES_TAX + wrong_rate))
  76. if 'fees' in svc and 'commission' in svc['fees']:
  77. det['commission'] = list_ * comm_rate
  78. if 'taxes' in svc and 'wrong' in svc['taxes']:
  79. det['wrong_tax'] = list_ * wrong_rate
  80. det['sales_tax'] = list_ * SALES_TAX
  81. det['wrong_tax_loss'] = det['sales_tax'] - det['wrong_tax']
  82. data['list'] = round(list_, 2)
  83. def _l2n(data: dict) -> None:
  84. svc = data['service']
  85. det = data['details']
  86. net = data['list'] if data.get('list') else data['amount']
  87. if 'fees' in svc and 'commission' in svc['fees']:
  88. det['commission'] = net * svc['fees']['commission']
  89. if 'taxes' in svc and 'wrong' in svc['taxes']:
  90. det['sales_tax'] = net * SALES_TAX
  91. det['wrong_tax'] = net * svc['taxes']['wrong']
  92. det['wrong_tax_loss'] = det['sales_tax'] - det['wrong_tax']
  93. for k in [ 'commission', 'wrong_tax_loss' ]:
  94. if k in det:
  95. net -= det[k]
  96. data['net'] = round(net,2)
  97. def _l2r(data: dict) -> None:
  98. svc = data['service']
  99. det = data['details']
  100. retail = data['list'] if data.get('list') else data['amount']
  101. if 'fees' in svc and 'client_fee' in svc['fees']:
  102. det['client_fee'] = retail * svc['fees']['client_fee']
  103. retail += det['client_fee']
  104. if 'taxes' in svc and 'wrong' in svc['taxes']:
  105. det['wrong_tax'] = retail * svc['taxes']['wrong']
  106. retail += det['wrong_tax']
  107. else:
  108. det['sales_tax'] = retail * SALES_TAX
  109. retail += det['sales_tax']
  110. if 'taxes' in svc and 'flat_tax' in svc['taxes']:
  111. det['service_tax'] = svc['taxes']['flat_tax']
  112. retail += det['service_tax']
  113. data['retail'] = round(retail, 2)
  114. def _r2l(data: dict) -> None:
  115. svc = data['service']
  116. det = data['details']
  117. list_ = data['retail'] if data.get('retail') else data['amount']
  118. if 'taxes' in svc and 'flat_tax' in svc['taxes']:
  119. det['service_tax'] = svc['taxes']['flat_tax']
  120. list_ -= det['service_tax']
  121. if 'taxes' in svc and 'wrong' in svc['taxes']:
  122. det['wrong_tax'] = list_ - (list_ / (1 + svc['taxes']['wrong']))
  123. list_ -= det['wrong_tax']
  124. else:
  125. det['sales_tax'] = list_ - (list_ / (1 + SALES_TAX))
  126. list_ -= det['sales_tax']
  127. if 'fees' in svc and 'client_fee' in svc['fees']:
  128. det['client_fee'] = list_ - (list_ / (1 + svc['fees']['client_fee']))
  129. list_ -= det['client_fee']
  130. data['list'] = round(list_, 2)
  131. # Routing
  132. routes = [
  133. ('net_to_list', _n2l),
  134. ('list_to_net', _l2n),
  135. ('list_to_retail', _l2r),
  136. ('retail_to_list', _r2l),
  137. ('net_to_retail', _n2l, _l2r),
  138. ('retail_to_net', _r2l, _l2n),
  139. ]
  140. for route_def in routes:
  141. endpoint, *transforms = route_def
  142. handler = make_handler(*transforms)
  143. # Flask requires unique function names
  144. handler.__name__ = endpoint
  145. app.add_url_rule(
  146. f"{URL_PREFIX}/{endpoint}",
  147. endpoint=endpoint,
  148. view_func=handler,
  149. methods=['GET', 'POST']
  150. )
  151. @app.route(f"{URL_PREFIX}/services", methods=['GET'])
  152. def list_services():
  153. return jsonify([
  154. {'key': key, 'name': cfg['name']}
  155. for key, cfg in SERVICES.items()
  156. ])
  157. # Main app
  158. if __name__ == '__main__':
  159. app.run(debug=DEBUG, port=PORT)