Merge branch 'feature/eurocredits' into develop
This commit is contained in:
@@ -27,6 +27,9 @@ from rowers.tasks import (
|
|||||||
#handle_send_email_transaction_notification,
|
#handle_send_email_transaction_notification,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from rowers import credits
|
||||||
|
from rowers.utils import dologging
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from rowsandall_app.settings import (
|
from rowsandall_app.settings import (
|
||||||
@@ -68,6 +71,19 @@ def process_webhook(notification):
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
f.write(timestamp+'\n')
|
f.write(timestamp+'\n')
|
||||||
if notification.kind == 'subscription_charged_successfully':
|
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)
|
return send_invoice(notification.subscription)
|
||||||
if notification.kind == 'subscription_canceled':
|
if notification.kind == 'subscription_canceled':
|
||||||
subscription = notification.subscription
|
subscription = notification.subscription
|
||||||
@@ -268,6 +284,8 @@ def update_subscription(rower,data,method='up'):
|
|||||||
if result.is_success:
|
if result.is_success:
|
||||||
yesterday = (timezone.now()-datetime.timedelta(days=1)).date()
|
yesterday = (timezone.now()-datetime.timedelta(days=1)).date()
|
||||||
rower.paidplan = plan
|
rower.paidplan = plan
|
||||||
|
amount_int = int(float(amount))
|
||||||
|
eurocredits = credits.upgrade(amount_int,rower)
|
||||||
rower.planexpires = result.subscription.billing_period_end_date
|
rower.planexpires = result.subscription.billing_period_end_date
|
||||||
rower.teamplanexpires = result.subscription.billing_period_end_date
|
rower.teamplanexpires = result.subscription.billing_period_end_date
|
||||||
rower.clubsize = plan.clubsize
|
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,
|
null=True,blank=True,
|
||||||
default='braintree')
|
default='braintree')
|
||||||
|
|
||||||
|
eurocredits = models.IntegerField(default=0)
|
||||||
|
|
||||||
paidplan = models.ForeignKey(PaidPlan,null=True,default=None,on_delete=models.SET_NULL)
|
paidplan = models.ForeignKey(PaidPlan,null=True,default=None,on_delete=models.SET_NULL)
|
||||||
|
|
||||||
planexpires = models.DateField(default=current_day)
|
planexpires = models.DateField(default=current_day)
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
|
|
||||||
<p>Plan: {{ plan.name }}</p>
|
<p>Plan: {{ plan.name }}</p>
|
||||||
<p>Price: {{ plan.price }}€</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>
|
||||||
<li>
|
<li>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<th>Plan</th><td>{{ plan.name }}</td>
|
<th>Plan</th><td>{{ plan.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Total</th><td>€ {{ plan.price|currency }}
|
<th>Total</th><td>€ {{ plan.price|discounted:rower|currency }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<label for="amount">
|
<label for="amount">
|
||||||
<div class="input-wrapper amount-wrapper">
|
<div class="input-wrapper amount-wrapper">
|
||||||
<input id="amount" name="amount" type="hidden" min="1" placeholder="Amount"
|
<input id="amount" name="amount" type="hidden" min="1" placeholder="Amount"
|
||||||
value="{{ plan.price }}" readonly>
|
value="{{ plan.price|discounted:rower }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div class="bt-drop-in-wrapper">
|
<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>.
|
Policy and agree with the <a href="/rowers/legal/" target="_blank">Terms of Service</a>.
|
||||||
</p>
|
</p>
|
||||||
{% csrf_token %}
|
{% 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>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
<li class="grid_4">
|
<li class="grid_4">
|
||||||
|
|||||||
@@ -32,6 +32,10 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p>Price: {{ plan.price }}€</p>
|
<p>Price: {{ plan.price }}€</p>
|
||||||
{% endif %}
|
{% 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>
|
</li>
|
||||||
{% if form %}
|
{% if form %}
|
||||||
<li class="grid_2">
|
<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
|
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".
|
target from the targets list, and select "plan by target".
|
||||||
</p>
|
</p>
|
||||||
{% if plan.price == 0 %}
|
{% if plan.price|discounted:rower == 0 %}
|
||||||
<form enctype="multipart/form-data" action="" method="post">
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
{% else %}
|
{% else %}
|
||||||
<form enctype="multipart/form-data" action="/rowers/buyplan/{{ plan.id }}/" method="post">
|
<form enctype="multipart/form-data" action="/rowers/buyplan/{{ plan.id }}/" method="post">
|
||||||
@@ -56,7 +60,7 @@
|
|||||||
{{ form.as_table }}
|
{{ form.as_table }}
|
||||||
</table>
|
</table>
|
||||||
{% csrf_token %}
|
{% 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>
|
<p><input class="button" type="submit" value="Create Plan and Add Sessions"></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p><input class="button" type="submit" action="/rowers/buyplan/{{ plan.id }}/" value="BUY NOW and Add Sessions"></p>
|
<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">
|
<ul class="main-content">
|
||||||
<li class="grid_4">
|
<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.
|
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,
|
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
|
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
|
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.
|
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>
|
</li>
|
||||||
{% for plan in plans %}
|
{% for plan in plans %}
|
||||||
<li class="rounder">
|
<li class="rounder">
|
||||||
@@ -34,6 +42,10 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p>Price: {{ plan.price }}€</p>
|
<p>Price: {{ plan.price }}€</p>
|
||||||
{% endif %}
|
{% 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>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -52,6 +52,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</th><td>{{ rower.planexpires }}</td>
|
</th><td>{{ rower.planexpires }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if rower.eurocredits %}
|
||||||
|
<tr>
|
||||||
|
<th>Discount voucher for training plans</th>
|
||||||
|
<td>{{ rower.eurocredits }}€</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from rowers.plannedsessions import (
|
|||||||
race_can_register, race_can_submit,race_rower_status
|
race_can_register, race_can_submit,race_rower_status
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from rowers import credits
|
||||||
from rowers import c2stuff
|
from rowers import c2stuff
|
||||||
from rowers.c2stuff import c2_open
|
from rowers.c2stuff import c2_open
|
||||||
from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispromember
|
from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispromember
|
||||||
@@ -153,6 +154,15 @@ def weekbegin(nr):
|
|||||||
return True
|
return True
|
||||||
return False
|
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
|
@register.filter
|
||||||
def weekend(nr):
|
def weekend(nr):
|
||||||
week, day = divmod(nr,7)
|
week, day = divmod(nr,7)
|
||||||
|
|||||||
@@ -406,8 +406,8 @@ description: ""
|
|||||||
|
|
||||||
@patch('rowers.views.braintreestuff.gateway', side_effect=MockBraintreeGateway)
|
@patch('rowers.views.braintreestuff.gateway', side_effect=MockBraintreeGateway)
|
||||||
@patch('rowers.fakturoid.create_invoice',side_effect=mocked_invoiceid)
|
@patch('rowers.fakturoid.create_invoice',side_effect=mocked_invoiceid)
|
||||||
@patch('rowers.braintreestuff.myqueue')
|
@patch('rowers.utils.myqueue')
|
||||||
def test_purchase_trainingplan_view(self, mocked_gateway,mocked_invoiceid,mocked_myqueue):
|
def test_purchase_trainingplan_view(self, mocked_gateway,mocked_invoiceid, mocked_myqueue):
|
||||||
u = UserFactory()
|
u = UserFactory()
|
||||||
r = Rower.objects.create(user=u,
|
r = Rower.objects.create(user=u,
|
||||||
birthdate=faker.profile()['birthdate'],
|
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 rowers.views.statements import *
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
|
|
||||||
|
from rowers import credits
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def braintree_webhook_view(request):
|
def braintree_webhook_view(request):
|
||||||
with open('braintreewebhooks.log','a') as f:
|
with open('braintreewebhooks.log','a') as f:
|
||||||
@@ -212,6 +214,11 @@ def purchase_checkouts_view(request):
|
|||||||
return HttpResponseRedirect(reverse('rower_select_instantplan'))
|
return HttpResponseRedirect(reverse('rower_select_instantplan'))
|
||||||
|
|
||||||
amount, success = braintreestuff.make_payment(r,data)
|
amount, success = braintreestuff.make_payment(r,data)
|
||||||
|
diff = plan.price - int(amount)
|
||||||
|
|
||||||
|
eurocredits = credits.withdraw(diff,r)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
messages.info(request,"Your payment was completed and the sessions are copied to your calendar")
|
messages.info(request,"Your payment was completed and the sessions are copied to your calendar")
|
||||||
@@ -251,7 +258,6 @@ def purchase_checkouts_view(request):
|
|||||||
plan.name,
|
plan.name,
|
||||||
startdate,
|
startdate,
|
||||||
enddate)
|
enddate)
|
||||||
print(job,'noot')
|
|
||||||
|
|
||||||
url = reverse('plannedsessions_view')
|
url = reverse('plannedsessions_view')
|
||||||
timeperiod = startdate.strftime('%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d')
|
timeperiod = startdate.strftime('%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d')
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import json
|
|||||||
|
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
import rowers.garmin_stuff as gs
|
import rowers.garmin_stuff as gs
|
||||||
|
from rowers import credits
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True)
|
@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,
|
user=r,date__gte=startdate,
|
||||||
date__lte=enddate
|
date__lte=enddate
|
||||||
).order_by(
|
).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")
|
raise Http404("Plan does not exist")
|
||||||
|
|
||||||
plan = InstantPlan.objects.get(uuid=id)
|
plan = InstantPlan.objects.get(uuid=id)
|
||||||
|
discountedprice = credits.discounted(plan.price,r)
|
||||||
|
|
||||||
authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN
|
authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN
|
||||||
url = settings.WORKOUTS_FIT_URL+"/trainingplan/"+id
|
url = settings.WORKOUTS_FIT_URL+"/trainingplan/"+id
|
||||||
@@ -2621,10 +2622,24 @@ def rower_view_instantplan(request,id='',userid=0):
|
|||||||
'id':id,
|
'id':id,
|
||||||
})
|
})
|
||||||
return HttpResponseRedirect(url)
|
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)
|
form = InstantPlanSelectForm(request.POST,targets=targets)
|
||||||
|
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
if plan.price > 0:
|
||||||
|
eurocredits = credits.withdraw(plan.price,r)
|
||||||
|
|
||||||
plansteps = response.json()
|
plansteps = response.json()
|
||||||
name = form.cleaned_data['name']
|
name = form.cleaned_data['name']
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user