Merge branch 'feature/eurocredits' into develop
This commit is contained in:
@@ -27,6 +27,9 @@ from rowers.tasks import (
|
||||
#handle_send_email_transaction_notification,
|
||||
)
|
||||
|
||||
from rowers import credits
|
||||
from rowers.utils import dologging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
@@ -68,6 +71,19 @@ def process_webhook(notification):
|
||||
except TypeError:
|
||||
f.write(timestamp+'\n')
|
||||
if notification.kind == 'subscription_charged_successfully':
|
||||
subscription = notification.subscription
|
||||
rs = Rower.objects.filter(subscription_id=subscription.id)
|
||||
if rs.count() == 0:
|
||||
dologging('braintreewebhooks.log','Could not find rowers with subscription ID {id}'.format(
|
||||
id=subscription.id
|
||||
))
|
||||
else:
|
||||
r = rs[0]
|
||||
transactions = subscription.transactions
|
||||
if transactions:
|
||||
amount = int(transactions[0].amount)
|
||||
eurocredits = credits.upgrade(amount,r)
|
||||
eurocredits = credits.upgrade(amount,r)
|
||||
return send_invoice(notification.subscription)
|
||||
if notification.kind == 'subscription_canceled':
|
||||
subscription = notification.subscription
|
||||
@@ -268,6 +284,8 @@ def update_subscription(rower,data,method='up'):
|
||||
if result.is_success:
|
||||
yesterday = (timezone.now()-datetime.timedelta(days=1)).date()
|
||||
rower.paidplan = plan
|
||||
amount_int = int(float(amount))
|
||||
eurocredits = credits.upgrade(amount_int,rower)
|
||||
rower.planexpires = result.subscription.billing_period_end_date
|
||||
rower.teamplanexpires = result.subscription.billing_period_end_date
|
||||
rower.clubsize = plan.clubsize
|
||||
|
||||
41
rowers/credits.py
Normal file
41
rowers/credits.py
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
class InsufficientCreditError(Exception):
|
||||
"""Raised when trying to subtract more than available"""
|
||||
pass
|
||||
|
||||
def upgrade(amount, rower):
|
||||
if rower.eurocredits > amount:
|
||||
return rower.eurocredits
|
||||
else:
|
||||
rower.eurocredits = amount
|
||||
rower.save()
|
||||
return rower.eurocredits
|
||||
return rower.eurocredits
|
||||
|
||||
def withdraw(amount, rower):
|
||||
if rower.eurocredits < amount:
|
||||
rower.eurocredits = 0
|
||||
rower.save()
|
||||
raise InsufficientCreditError
|
||||
else:
|
||||
rower.eurocredits = rower.eurocredits - amount
|
||||
rower.save()
|
||||
return rower.eurocredits
|
||||
|
||||
return rower.eurocredits
|
||||
|
||||
def discount(amount,rower):
|
||||
if amount < rower.eurocredits:
|
||||
return amount
|
||||
else:
|
||||
return rower.eurocredits
|
||||
|
||||
return 0
|
||||
|
||||
def discounted(amount,rower):
|
||||
if amount > rower.eurocredits:
|
||||
return amount-rower.eurocredits
|
||||
else:
|
||||
return 0
|
||||
|
||||
return 0
|
||||
@@ -878,6 +878,8 @@ class Rower(models.Model):
|
||||
null=True,blank=True,
|
||||
default='braintree')
|
||||
|
||||
eurocredits = models.IntegerField(default=0)
|
||||
|
||||
paidplan = models.ForeignKey(PaidPlan,null=True,default=None,on_delete=models.SET_NULL)
|
||||
|
||||
planexpires = models.DateField(default=current_day)
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
|
||||
<p>Plan: {{ plan.name }}</p>
|
||||
<p>Price: {{ plan.price }}€</p>
|
||||
{% if rower.eurocredits > 0 %}
|
||||
<p>Your Discount: {{ plan.price|discount:rower }}</p>
|
||||
<p>You will pay: {{ plan.price|discounted:rower }}</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<th>Plan</th><td>{{ plan.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total</th><td>€ {{ plan.price|currency }}
|
||||
<th>Total</th><td>€ {{ plan.price|discounted:rower|currency }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -70,7 +70,7 @@
|
||||
<label for="amount">
|
||||
<div class="input-wrapper amount-wrapper">
|
||||
<input id="amount" name="amount" type="hidden" min="1" placeholder="Amount"
|
||||
value="{{ plan.price }}" readonly>
|
||||
value="{{ plan.price|discounted:rower }}" readonly>
|
||||
</div>
|
||||
</label>
|
||||
<div class="bt-drop-in-wrapper">
|
||||
@@ -91,7 +91,7 @@
|
||||
Policy and agree with the <a href="/rowers/legal/" target="_blank">Terms of Service</a>.
|
||||
</p>
|
||||
{% csrf_token %}
|
||||
<button type="submit" id="submit-button"><span>Purchase for € {{ plan.price|currency }}</span></button>
|
||||
<button type="submit" id="submit-button"><span>Purchase for € {{ plan.price|discounted:rower|currency }}</span></button>
|
||||
</form>
|
||||
</li>
|
||||
<li class="grid_4">
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
{% else %}
|
||||
<p>Price: {{ plan.price }}€</p>
|
||||
{% endif %}
|
||||
{% if rower.eurocredits and plan.price > 0 %}
|
||||
<p>Your discount: {{ plan.price|discount:rower }}€</p>
|
||||
<p>You pay: {{ plan.price|discounted:rower }}€</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if form %}
|
||||
<li class="grid_2">
|
||||
@@ -47,7 +51,7 @@
|
||||
If you have <a href="/rowers/createplan/">set a training target</a>, you can also ask to plan by that target. Select the
|
||||
target from the targets list, and select "plan by target".
|
||||
</p>
|
||||
{% if plan.price == 0 %}
|
||||
{% if plan.price|discounted:rower == 0 %}
|
||||
<form enctype="multipart/form-data" action="" method="post">
|
||||
{% else %}
|
||||
<form enctype="multipart/form-data" action="/rowers/buyplan/{{ plan.id }}/" method="post">
|
||||
@@ -56,7 +60,7 @@
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
{% if plan.price == 0 %}
|
||||
{% if plan.price|discounted:rower == 0 %}
|
||||
<p><input class="button" type="submit" value="Create Plan and Add Sessions"></p>
|
||||
{% else %}
|
||||
<p><input class="button" type="submit" action="/rowers/buyplan/{{ plan.id }}/" value="BUY NOW and Add Sessions"></p>
|
||||
|
||||
@@ -10,11 +10,19 @@
|
||||
|
||||
<ul class="main-content">
|
||||
<li class="grid_4">
|
||||
<p>
|
||||
On this page, you find trusted training plans that work. If you are looking for a plan, this page is for you.
|
||||
This ever growing list of plans contains a plan for everyone, whether you are a beginning rower or experienced,
|
||||
irrespective of your current fitness level. Click on a plan to read more and see the sessions. If you're
|
||||
on a Self-Coach plan or higher, you can get the plan including even more detailed workout instructions
|
||||
copied straight to your training calendar on this site, with the simple click of a button.
|
||||
</p>
|
||||
{% if rower.eurocredits %}
|
||||
<p>
|
||||
<em>Your have discount vouchers for an mount of {{ rower.eurocredits }}€</em>. You
|
||||
will get discount for up to this amount on any paid plan.
|
||||
</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% for plan in plans %}
|
||||
<li class="rounder">
|
||||
@@ -34,6 +42,10 @@
|
||||
{% else %}
|
||||
<p>Price: {{ plan.price }}€</p>
|
||||
{% endif %}
|
||||
{% if rower.eurocredits and plan.price > 0 %}
|
||||
<p>Your discount: {{ plan.price|discount:rower }}€</p>
|
||||
<p>You pay: {{ plan.price|discounted:rower }}€</p>
|
||||
{% endif %}
|
||||
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
{% endif %}
|
||||
</th><td>{{ rower.planexpires }}</td>
|
||||
</tr>
|
||||
{% if rower.eurocredits %}
|
||||
<tr>
|
||||
<th>Discount voucher for training plans</th>
|
||||
<td>{{ rower.eurocredits }}€</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -19,6 +19,7 @@ from rowers.plannedsessions import (
|
||||
race_can_register, race_can_submit,race_rower_status
|
||||
)
|
||||
|
||||
from rowers import credits
|
||||
from rowers import c2stuff
|
||||
from rowers.c2stuff import c2_open
|
||||
from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispromember
|
||||
@@ -153,6 +154,15 @@ def weekbegin(nr):
|
||||
return True
|
||||
return False
|
||||
|
||||
@register.filter
|
||||
def discount(amount,rower):
|
||||
return credits.discount(amount,rower)
|
||||
|
||||
|
||||
@register.filter
|
||||
def discounted(amount,rower):
|
||||
return credits.discounted(amount,rower)
|
||||
|
||||
@register.filter
|
||||
def weekend(nr):
|
||||
week, day = divmod(nr,7)
|
||||
|
||||
@@ -406,8 +406,8 @@ description: ""
|
||||
|
||||
@patch('rowers.views.braintreestuff.gateway', side_effect=MockBraintreeGateway)
|
||||
@patch('rowers.fakturoid.create_invoice',side_effect=mocked_invoiceid)
|
||||
@patch('rowers.braintreestuff.myqueue')
|
||||
def test_purchase_trainingplan_view(self, mocked_gateway,mocked_invoiceid,mocked_myqueue):
|
||||
@patch('rowers.utils.myqueue')
|
||||
def test_purchase_trainingplan_view(self, mocked_gateway,mocked_invoiceid, mocked_myqueue):
|
||||
u = UserFactory()
|
||||
r = Rower.objects.create(user=u,
|
||||
birthdate=faker.profile()['birthdate'],
|
||||
|
||||
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
Binary file not shown.
@@ -6,6 +6,8 @@ from __future__ import unicode_literals
|
||||
from rowers.views.statements import *
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
from rowers import credits
|
||||
|
||||
@csrf_exempt
|
||||
def braintree_webhook_view(request):
|
||||
with open('braintreewebhooks.log','a') as f:
|
||||
@@ -212,6 +214,11 @@ def purchase_checkouts_view(request):
|
||||
return HttpResponseRedirect(reverse('rower_select_instantplan'))
|
||||
|
||||
amount, success = braintreestuff.make_payment(r,data)
|
||||
diff = plan.price - int(amount)
|
||||
|
||||
eurocredits = credits.withdraw(diff,r)
|
||||
|
||||
|
||||
|
||||
if success:
|
||||
messages.info(request,"Your payment was completed and the sessions are copied to your calendar")
|
||||
@@ -243,7 +250,7 @@ def purchase_checkouts_view(request):
|
||||
p.rowers.add(r)
|
||||
|
||||
create_sessions_from_json(plansteps,r,startdate,r.user)
|
||||
|
||||
|
||||
job = myqueue(queuehigh,handle_send_email_instantplan_notification,
|
||||
r.user.username,
|
||||
r.user.email,
|
||||
@@ -251,7 +258,6 @@ def purchase_checkouts_view(request):
|
||||
plan.name,
|
||||
startdate,
|
||||
enddate)
|
||||
print(job,'noot')
|
||||
|
||||
url = reverse('plannedsessions_view')
|
||||
timeperiod = startdate.strftime('%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d')
|
||||
|
||||
@@ -9,6 +9,7 @@ import json
|
||||
|
||||
from taggit.models import Tag
|
||||
import rowers.garmin_stuff as gs
|
||||
from rowers import credits
|
||||
|
||||
@login_required
|
||||
@permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True)
|
||||
@@ -1653,7 +1654,7 @@ def plannedsessions_manage_view(request,userid=0,
|
||||
user=r,date__gte=startdate,
|
||||
date__lte=enddate
|
||||
).order_by(
|
||||
"date","startdatetime","id"
|
||||
"-date","-startdatetime","-id"
|
||||
)
|
||||
|
||||
|
||||
@@ -2575,7 +2576,7 @@ def rower_view_instantplan(request,id='',userid=0):
|
||||
raise Http404("Plan does not exist")
|
||||
|
||||
plan = InstantPlan.objects.get(uuid=id)
|
||||
|
||||
discountedprice = credits.discounted(plan.price,r)
|
||||
|
||||
authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN
|
||||
url = settings.WORKOUTS_FIT_URL+"/trainingplan/"+id
|
||||
@@ -2621,10 +2622,24 @@ def rower_view_instantplan(request,id='',userid=0):
|
||||
'id':id,
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# check if plan is free or credits are sufficient
|
||||
if plan.price > 0:
|
||||
if plan.price > r.eurocredits:
|
||||
messages.error(request,'You did not have enough credit to purchase this plan')
|
||||
url = reverse('rower_view_instantplan',kwargs={
|
||||
'id':id,
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = InstantPlanSelectForm(request.POST,targets=targets)
|
||||
|
||||
|
||||
if form.is_valid():
|
||||
if plan.price > 0:
|
||||
eurocredits = credits.withdraw(plan.price,r)
|
||||
|
||||
plansteps = response.json()
|
||||
name = form.cleaned_data['name']
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user