Private
Public Access
1
0

Merge branch 'feature/eurocredits' into develop

This commit is contained in:
Sander Roosendaal
2021-11-11 14:12:39 +01:00
13 changed files with 129 additions and 11 deletions

View File

@@ -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
View 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

View File

@@ -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)

View File

@@ -21,6 +21,10 @@
<p>Plan: {{ plan.name }}</p> <p>Plan: {{ plan.name }}</p>
<p>Price: {{ plan.price }}&euro;</p> <p>Price: {{ plan.price }}&euro;</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>

View File

@@ -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>&euro; {{ plan.price|currency }} <th>Total</th><td>&euro; {{ 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 &euro; {{ plan.price|currency }}</span></button> <button type="submit" id="submit-button"><span>Purchase for &euro; {{ plan.price|discounted:rower|currency }}</span></button>
</form> </form>
</li> </li>
<li class="grid_4"> <li class="grid_4">

View File

@@ -32,6 +32,10 @@
{% else %} {% else %}
<p>Price: {{ plan.price }}&euro;</p> <p>Price: {{ plan.price }}&euro;</p>
{% endif %} {% endif %}
{% if rower.eurocredits and plan.price > 0 %}
<p>Your discount: {{ plan.price|discount:rower }}&euro;</p>
<p>You pay: {{ plan.price|discounted:rower }}&euro;</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>

View File

@@ -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 }}&euro;</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 }}&euro;</p> <p>Price: {{ plan.price }}&euro;</p>
{% endif %} {% endif %}
{% if rower.eurocredits and plan.price > 0 %}
<p>Your discount: {{ plan.price|discount:rower }}&euro;</p>
<p>You pay: {{ plan.price|discounted:rower }}&euro;</p>
{% endif %}
</li> </li>
{% endfor %} {% endfor %}

View File

@@ -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 }}&euro;</td>
</tr>
{% endif %}
{% endif %} {% endif %}
</table> </table>
{% csrf_token %} {% csrf_token %}

View File

@@ -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)

View File

@@ -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'],

Binary file not shown.

View File

@@ -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')

View File

@@ -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: