| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- import json
- import os
- from flask import Flask, request, jsonify
- from flask_cors import CORS
- _url_prefix = os.environ.get("URL_PREFIX", "room").strip("/")
- _port = os.environ.get("FLASK_PORT", "5000").strip()
- _debug = os.environ.get("FLASK_DEBUG", "").strip()
- _here = os.path.dirname(__file__)
- DEBUG = True if _debug else False
- URL_PREFIX = f"/{_url_prefix}" if _url_prefix else ""
- PORT = int(_port)
- app = Flask(__name__)
- CORS(app)
- # Config
- with open(os.path.join(_here, "config.json")) as f:
- CONFIG = json.load(f)
- if not CONFIG or 'services' not in CONFIG or 'global' not in CONFIG:
- raise ValueError('CONFIG: Missing services or global')
- SERVICES = CONFIG['services']
- GLOBAL = CONFIG['global']
- if not GLOBAL or 'taxes' not in GLOBAL or 'sales' not in GLOBAL['taxes']:
- raise ValueError('GLOBAL: Missing sales tax')
- SALES_TAX = GLOBAL['taxes']['sales']
- # Formatters
- def dollar_fmt(value: float) -> str:
- return "{:.2f}".format(round(value, 2))
- def title_fmt(value: str) -> str:
- return value.replace('_', ' ').title()
- def details_fmt(details: dict) -> dict:
- fmt = {}
- for k, v in details.items():
- fmt[title_fmt(k)] = dollar_fmt(v)
- return fmt
- # Request handlers
- def _request(request) -> dict:
- _req = request.get_json(silent=True) or request.args
- _data = { 'service_key': None }
- if 'service' in _req:
- _data['service_key'] = str(_req['service']).lower().strip()
- if 'amount' in _req:
- _data['amount'] = float(_req['amount'])
- for k in [ 'service_key', 'amount' ]:
- if k not in _data or not _data[k]:
- raise ValueError(k)
- _data['service'] = SERVICES.get(_data['service_key'])
- if _data['service'] is None:
- raise ValueError('service')
- _data['details'] = {}
- return _data
- def _response(**data) -> str:
- _dict = { 'service_key' : data['service_key'] }
- for k in [ 'list', 'retail', 'net' ]:
- if data.get(k) is not None:
- _dict[k] = dollar_fmt(data.get(k))
- if data.get('details') is not None:
- _dict['details'] = details_fmt(data.get('details'))
- return jsonify(_dict)
- def make_handler(*transforms):
- def handler():
- try:
- data = _request(request)
- except (TypeError, ValueError):
- return jsonify({'error': 'Invalid or missing parameters'}), 400
- for transform in transforms:
- transform(data)
- return _response(**data)
- return handler
- # Actual math
- def _n2l(data: dict) -> None:
- svc = data['service']
- det = data['details']
- net = data['net'] if data.get('net') else data['amount']
- comm_rate = svc.get('fees', {}).get('commission', 0)
- wrong_rate = svc.get('taxes', {}).get('wrong', SALES_TAX)
- list_ = (net / (1 - comm_rate - SALES_TAX + wrong_rate))
- if 'fees' in svc and 'commission' in svc['fees']:
- det['commission'] = list_ * comm_rate
- if 'taxes' in svc and 'wrong' in svc['taxes']:
- det['wrong_tax'] = list_ * wrong_rate
- det['sales_tax'] = list_ * SALES_TAX
- det['wrong_tax_loss'] = det['sales_tax'] - det['wrong_tax']
- data['list'] = round(list_, 2)
- def _l2n(data: dict) -> None:
- svc = data['service']
- det = data['details']
- net = data['list'] if data.get('list') else data['amount']
- if 'fees' in svc and 'commission' in svc['fees']:
- det['commission'] = net * svc['fees']['commission']
- if 'taxes' in svc and 'wrong' in svc['taxes']:
- det['sales_tax'] = net * SALES_TAX
- det['wrong_tax'] = net * svc['taxes']['wrong']
- det['wrong_tax_loss'] = det['sales_tax'] - det['wrong_tax']
- for k in [ 'commission', 'wrong_tax_loss' ]:
- if k in det:
- net -= det[k]
- data['net'] = round(net,2)
- def _l2r(data: dict) -> None:
- svc = data['service']
- det = data['details']
- retail = data['list'] if data.get('list') else data['amount']
- if 'fees' in svc and 'client_fee' in svc['fees']:
- det['client_fee'] = retail * svc['fees']['client_fee']
- retail += det['client_fee']
- if 'taxes' in svc and 'wrong' in svc['taxes']:
- det['wrong_tax'] = retail * svc['taxes']['wrong']
- retail += det['wrong_tax']
- else:
- det['sales_tax'] = retail * SALES_TAX
- retail += det['sales_tax']
- if 'taxes' in svc and 'flat_tax' in svc['taxes']:
- det['service_tax'] = svc['taxes']['flat_tax']
- retail += det['service_tax']
- data['retail'] = round(retail, 2)
- def _r2l(data: dict) -> None:
- svc = data['service']
- det = data['details']
- list_ = data['retail'] if data.get('retail') else data['amount']
- if 'taxes' in svc and 'flat_tax' in svc['taxes']:
- det['service_tax'] = svc['taxes']['flat_tax']
- list_ -= det['service_tax']
- if 'taxes' in svc and 'wrong' in svc['taxes']:
- det['wrong_tax'] = list_ - (list_ / (1 + svc['taxes']['wrong']))
- list_ -= det['wrong_tax']
- else:
- det['sales_tax'] = list_ - (list_ / (1 + SALES_TAX))
- list_ -= det['sales_tax']
- if 'fees' in svc and 'client_fee' in svc['fees']:
- det['client_fee'] = list_ - (list_ / (1 + svc['fees']['client_fee']))
- list_ -= det['client_fee']
- data['list'] = round(list_, 2)
- # Routing
- routes = [
- ('net_to_list', _n2l),
- ('list_to_net', _l2n),
- ('list_to_retail', _l2r),
- ('retail_to_list', _r2l),
- ('net_to_retail', _n2l, _l2r),
- ('retail_to_net', _r2l, _l2n),
- ]
- for route_def in routes:
- endpoint, *transforms = route_def
- handler = make_handler(*transforms)
- # Flask requires unique function names
- handler.__name__ = endpoint
- app.add_url_rule(
- f"{URL_PREFIX}/{endpoint}",
- endpoint=endpoint,
- view_func=handler,
- methods=['GET', 'POST']
- )
- @app.route(f"{URL_PREFIX}/services", methods=['GET'])
- def list_services():
- return jsonify([
- {'key': key, 'name': cfg['name']}
- for key, cfg in SERVICES.items()
- ])
- # Main app
- if __name__ == '__main__':
- app.run(debug=DEBUG, port=PORT)
|