Now booking is only for users with memberships. Added method to know if a membership is active or not. Added new formula for booking pricing. handling border cases when an user want to book. Added booking order detail view. Added booking order detail html.

This commit is contained in:
Levi 2016-09-05 19:45:45 -05:00
parent 2eb2c90b1f
commit e3d1761d45
14 changed files with 345 additions and 45 deletions

View file

@ -68,7 +68,7 @@ class BookingDateForm(forms.Form):
except ValueError:
raise forms.ValidationError("Submit valid dates.")
if start_date >= end_date:
if start_date > end_date:
raise forms.ValidationError("Your end date must be greather than your start date.")
return start_date, end_date

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-09-02 03:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('digitalglarus', '0013_remove_booking_membership'),
]
operations = [
migrations.AddField(
model_name='booking',
name='final_price',
field=models.FloatField(default=250),
preserve_default=False,
),
]

View file

@ -1,8 +1,20 @@
from django.db import models
from django.http import HttpResponseRedirect
from membership.models import StripeCustomer
from utils.models import BillingAddress
class MembershipRequired(object):
membership_redirect_url = None
def dispatch(self, request, *args, **kwargs):
from .models import Membership
if not Membership.is_digitalglarus_member(request.user):
return HttpResponseRedirect(self.membership_redirect_url)
return super(MembershipRequired, self).dispatch(request, *args, **kwargs)
class Ordereable(models.Model):
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
@ -22,4 +34,5 @@ class Ordereable(models.Model):
instance.stripe_charge_id = stripe_charge.id
instance.last4 = stripe_charge.source.last4
instance.cc_brand = stripe_charge.source.brand
instance.save()
return instance

View file

@ -1,7 +1,9 @@
import calendar
from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta
from django.db import models
from django.db.models import Q
from cms.models import CMSPlugin
from filer.fields.image import FilerImageField
from django.core.urlresolvers import reverse
@ -59,6 +61,14 @@ class MembershipType(models.Model):
class Membership(models.Model):
type = models.ForeignKey(MembershipType)
@classmethod
def is_digitalglarus_member(cls, user):
past_month = (datetime.today() - relativedelta(months=1)).month
has_booking_current_month = Q(membershiporder__created_at__month=datetime.today().month)
has_booking_past_month = Q(membershiporder__customer__user=user,
membershiporder__created_at__month=past_month)
return cls.objects.filter(has_booking_past_month | has_booking_current_month).exists()
@classmethod
def create(cls, data):
instance = cls.objects.create(**data)
@ -88,6 +98,7 @@ class Booking(models.Model):
end_date = models.DateField()
price = models.FloatField()
free_days = models.IntegerField(default=0)
final_price = models.FloatField()
@classmethod
def create(cls, data):
@ -95,52 +106,50 @@ class Booking(models.Model):
return instance
@classmethod
def get_ramaining_free_days(cls, user):
# ZERO_DAYS = 0
# ONE_DAY = 1
def get_ramaining_free_days(cls, user, start_date, end_date):
TWO_DAYS = 2
start_month = start_date.month
end_month = end_date.month
months = abs(start_month - (end_month + 12) if end_month < start_month
else end_month - start_month)
current_date = datetime.today()
current_month_bookings = cls.objects.filter(bookingorder__customer__user=user,
start_date__month=current_date.month)
free_days = TWO_DAYS - sum(map(lambda x: x.days, current_month_bookings))
return free_days
# free_days = ZERO_DAYS if current_month_bookings.count() > 2 else TWO_DAYS
# if current_month_bookings.count() == 1:
# booking = current_month_bookings.get()
# booked_days = (booking.end_date - booking.start_date).days
# free_days = ONE_DAY if booked_days == 1 else ZERO_DAYS
# return free_days
# free_days = ZERO_DAYS if current_month_bookings.count() > 2 else TWO_DAYS
# return free_days
free_days_this_month = TWO_DAYS - sum(map(lambda x: x.free_days, current_month_bookings))
total_free_days = months * TWO_DAYS + free_days_this_month
return total_free_days
@classmethod
def booking_price(cls, user, start_date, end_date):
"""
Calculate the booking price for requested dates
How it does:
1. Check if the user has booked the current month
2. Get how many days user wants to book
3. Get price per day from BookingPrices instance
4. Get available free days
5. Calculate price by this formula -> (booking_days - free_days) * price_per_day
"""
MAX_MONTH_PRICE = 290
MAX_MONTH_DAYS_PROMOTION = 31
MIN_MONTH_DAYS_PROMOTION = 19
booking_prices = BookingPrice.objects.last()
price_per_day = booking_prices.price_per_day
booking_days = (end_date - start_date).days
booking_days = (end_date - start_date).days + 1
months = booking_days // MAX_MONTH_DAYS_PROMOTION
remanent_days = booking_days % MAX_MONTH_DAYS_PROMOTION
months_prices = months * MAX_MONTH_PRICE
remanent_days_price = remanent_days * price_per_day \
if remanent_days <= MIN_MONTH_DAYS_PROMOTION else MAX_MONTH_PRICE
normal_price = months_prices + remanent_days_price
free_days = cls.get_ramaining_free_days(user)
final_booking_price = (booking_days - free_days) * price_per_day
original_booking_price = (booking_days) * price_per_day
free_days = cls.get_ramaining_free_days(user, start_date, end_date)
final_booking_price = normal_price - (free_days * price_per_day)
return original_booking_price, final_booking_price, free_days
return normal_price, final_booking_price, free_days
class BookingOrder(Ordereable, models.Model):
booking = models.OneToOneField(Booking)
def booking_days(self):
return (self.booking.end_date - self.booking.start_date).days + 1
class Supporter(models.Model):
name = models.CharField(max_length=200)

View file

@ -1,13 +1,16 @@
$( document ).ready(function() {
// $('#booking-date-range').daterangepicker();
var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
// var tomorrow = today.setDate(today.getDate() + 1);
$('#booking-date-range').daterangepicker({
autoUpdateInput: false,
locale: {
cancelLabel: 'Clear'
}
},
minDate: tomorrow,
});

View file

@ -0,0 +1,21 @@
$( document ).ready(function() {
function printDiv(divName) {
var printContents = document.getElementById(divName).innerHTML;
var originalContents = document.body.innerHTML;
document.body.innerHTML = printContents;
window.print();
document.body.innerHTML = originalContents;
}
$('.print').on('click', function(e){
printDiv(e.target.getAttribute("data-print") );
});
});

View file

@ -0,0 +1,88 @@
{% extends "new_base_glarus.html" %}
{% load staticfiles bootstrap3 i18n %}
{% block content %}
<style type="text/css">
.invoice-title{
text-align: center !important;
}
</style>
<script type="text/javascript">
</script>
<section id="price">
<div class="signup-container">
<div class="col-xs-12 col-sm-6 col-lg-8 text-center wow fadeInDown">
<div class="payment-box">
<h2 class="section-heading payment-head">Your Booking Detail</h2>
<hr class="greyline-long">
<h2 class="billing-head">Invoice<btn class="btn btn-primary btn-grey btn-edit print" data-print="price">Get PDF</btn></h2>
<h2 class="order-head">Order Number</h2>
<h2 class="member-name">#{{order.id}}</h2>
<h2 class="order-head">Billed to :</h2>
<h2 class="history-name">{{user.name}}<br>
{{order.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
{{order.billing_address.city}}, {{order.billing_address.country}}.
</h2>
<h2 class="order-head">Payment Method</h2>
<h2 class="history-name">
{{order.cc_brand}} ending **** {{order.last4}}<br>
{{user.email}}
</h2>
<hr class="greyline-long">
<h2 class="order-head">Order Summary</h2>
<h2 class="history-name">
Dates: {{start_date}} - {{end_date}}<br>
</h2>
<h2 class="col-xs-6 payment-total text-left">Total days {{booking_days}}</h2>
<h2 class="order-sum">{{original_price|floatformat}}CHF</h2>
{% if free_days %}
<h2 class="col-xs-6 payment-total text-left">Free days {{free_days}} </h2>
<h2 class="order-sum">-{{total_discount|floatformat}}CHF</h2>
{% endif %}
<hr class="greyline-long">
<h2 class="col-xs-6 payment-total text-left"> Total</h2>
<h2 class="order-result">{{final_price|floatformat}}CHF</h2>
<br>
</div>
</div>
<div class="col-xs-12 col-sm-4 col-lg-4 wow fadeInDown">
<div class="order-summary">
<div class="header text-center">
<h2 class="order-name">Order Summary</h2>
</div>
<div class="order-box">
<h2 class="col-xs-6 order-item" style="padding-bottom:10px">Dates: {{start_date}} - {{end_date}}<br></h2>
<h2 class="col-xs-6 payment-total">Total days {{booking_days}}</h2>
<h2 class="order-sum">{{original_price|floatformat}}CHF</h2>
{% if free_days %}
<h2 class="col-xs-6 payment-total">Free days {{free_days}}</h2>
<h2 class="order-sum">-{{total_discount|floatformat}}CHF</h2>
{% endif %}
<hr class="greyline">
<h2 class="col-xs-6 payment-total">Total</h2>
<h2 class="order-result">{{final_price|floatformat}}CHF</h2>
</div>
</div>
</div>
</section>
<!-- stripe key data -->
{% if stripe_key %}
<script type="text/javascript">
(function () {window.stripeKey = "{{stripe_key}}";})();
</script>
{%endif%}
{% endblock %}

View file

@ -0,0 +1,68 @@
{% extends "new_base_glarus.html" %}
{% load staticfiles bootstrap3 i18n %}
{% block content %}
<!-- Header -->
<!-- Services Section -->
<section id="price">
<div class="signup-container">
<div class="col-xs-12 col-sm-6 col-lg-8 text-center wow fadeInDown">
<div class="payment-box">
<h2 class="section-heading payment-head">Your Order History</h2>
<hr class="greyline-long">
<h2 class="order-head">Member Name</h2>
<h2 class="member-name">{{request.user.name}}</h2>
<hr class="greyline-long">
<h2 class="order-head">Active Membership</h2>
<h2 class="member-name">2016.11.13-2016.12.13</h2>
<hr class="greyline-long">
<h2 class="order-head">Booking history</h2>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Booking dates</th>
<th>Days</th>
<th>Invoice</th>
</tr>
</thead>
<tbody>
{% for order in orders%}
<tr>
<th scope="row">{{order.id}}</th>
<td>{{order.booking.start_date}}-{{order.booking.end_date}}</td>
<td>{{order.booking_days}}</td>
<td><a class="btn btn-xs btn-primary btn-darkgrey" href="{% url 'digitalglarus:booking_orders_detail' order.id %}">View</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<h2 class="order-head">Billing Adress<btn class="btn btn-primary btn-grey btn-edit">Edit</btn></h2>
<h2 class="history-name">Nico Schottelius<br>
In der Au 7 8762 Schwanden<br>
Switzerland
</h2>
</div>
</div>
<div class="col-xs-12 col-sm-4 col-lg-4 wow fadeInDown">
<div class="order-summary">
<h2 class="thankyou">Thank You!</h2>
<div class="order-box">
<span class="glyphicon glyphicon-heart icon-up"></span>
<h2 class="signup-lead text-center">Digital Glarus lives with your love!<br>
Our coworking space is here because of your love and support.</h2>
<hr class="greyline">
<p class="order-bottom-text text-center">This box is here just to thank you</p>
</div>
</div>
</div>
{% endblock %}

View file

@ -28,6 +28,16 @@
<!-- <h2 class="membership-amount">35CHF</h2> -->
<hr class="greyline-long">
<h2 class="billing-head">Billing Adress</h2>
<h2 class="membership-lead">
Your Digital Glarus Membership enables
you to use our coworking space and it includes
2 working days for the month you signed up.
The membership fee is a monthly subscription.
Additional day costs
15CHF per day. More than 17 days a month it
will charge only 290CHF/month.
</h2>
<div class="signup-form form-group row">
<form role="form" id="billing-form" method="post" action="{% url 'digitalglarus:booking_payment' %}" novalidate>
{% for field in form %}

View file

@ -21,7 +21,7 @@
<div class="signup-form form-group row">
<div class="button-booking-box form-inline row">
<button type="submit" class="btn btn-primary btn-blue">Go to Booking</button>
<a class="btn btn-primary btn-blue" href={% url 'digitalglarus:booking' %}>Go to Booking</a>
</div>
<div class="notice-box text-left">
<p class="order-bottom-text">Your membership will be automatically renewed each month. For deactivating go to<a href=#>my page</a></p>

View file

@ -180,6 +180,9 @@
<!-- Booking JavaScript -->
<script src="{% static 'digitalglarus/js/booking.js' %}"></script>
<!-- Utils JavaScript -->
<script src="{% static 'digitalglarus/js/utils.js' %}"></script>
<!-- Custom Fonts -->
<link href="//fonts.googleapis.com/css?family=Raleway" rel="stylesheet" type="text/css">
<link href="{% static 'digitalglarus/font-awesome-4.1.0/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">

View file

@ -4,7 +4,8 @@ from django.utils.translation import ugettext_lazy as _
from . import views
from .views import ContactView, IndexView, AboutView, HistoryView, LoginView, SignupView,\
PasswordResetView, PasswordResetConfirmView, MembershipPaymentView, MembershipActivatedView,\
MembershipPricingView, BookingSelectDatesView, BookingPaymentView
MembershipPricingView, BookingSelectDatesView, BookingPaymentView, OrdersBookingDetailView,\
BookingOrdersListView
# from membership.views import LoginRegistrationView
urlpatterns = [
@ -20,6 +21,10 @@ urlpatterns = [
url(_(r'history/?$'), HistoryView.as_view(), name='history'),
url(_(r'booking/?$'), BookingSelectDatesView.as_view(), name='booking'),
url(_(r'booking/payment/?$'), BookingPaymentView.as_view(), name='booking_payment'),
url(_(r'booking/orders/(?P<pk>\d+)/?$'), OrdersBookingDetailView.as_view(),
name='booking_orders_detail'),
url(_(r'booking/orders/?$'), BookingOrdersListView.as_view(),
name='booking_orders_list'),
url(_(r'membership/payment/?$'), MembershipPaymentView.as_view(), name='membership_payment'),
url(_(r'membership/activated/?$'), MembershipActivatedView.as_view(),
name='membership_activated'),

View file

@ -13,13 +13,13 @@ from django.utils.translation import get_language
from djangocms_blog.models import Post
from django.contrib import messages
from django.http import JsonResponse
from django.views.generic import View, DetailView
from django.views.generic import View, DetailView, ListView
from .models import Supporter
from utils.forms import ContactUsForm
from django.views.generic.edit import FormView
from membership.calendar.calendar import BookCalendar
from membership.models import Calendar as CalendarModel, CustomUser, StripeCustomer
from membership.models import Calendar as CalendarModel, StripeCustomer
from utils.views import LoginViewMixin, SignupViewMixin, \
@ -34,6 +34,8 @@ from .forms import LoginForm, SignupForm, MembershipBillingForm, BookingDateForm
from .models import MembershipType, Membership, MembershipOrder, Booking, BookingPrice,\
BookingOrder
from .mixins import MembershipRequired
class IndexView(TemplateView):
template_name = "digitalglarus/old_index.html"
@ -75,9 +77,10 @@ class HistoryView(TemplateView):
return context
class BookingSelectDatesView(LoginRequiredMixin, FormView):
class BookingSelectDatesView(LoginRequiredMixin, MembershipRequired, FormView):
template_name = "digitalglarus/booking.html"
form_class = BookingDateForm
membership_redirect_url = reverse_lazy('digitalglarus:membership_pricing')
login_url = reverse_lazy('digitalglarus:login')
success_url = reverse_lazy('digitalglarus:booking_payment')
@ -85,7 +88,7 @@ class BookingSelectDatesView(LoginRequiredMixin, FormView):
user = self.request.user
start_date = form.cleaned_data.get('start_date')
end_date = form.cleaned_data.get('end_date')
booking_days = (end_date - start_date).days
booking_days = (end_date - start_date).days + 1
original_price, discount_price, free_days = Booking.\
booking_price(user, start_date, end_date)
self.request.session.update({
@ -99,10 +102,11 @@ class BookingSelectDatesView(LoginRequiredMixin, FormView):
return super(BookingSelectDatesView, self).form_valid(form)
class BookingPaymentView(LoginRequiredMixin, FormView):
class BookingPaymentView(LoginRequiredMixin, MembershipRequired, FormView):
template_name = "digitalglarus/booking_payment.html"
form_class = BookingBillingForm
success_url = reverse_lazy('digitalglarus:booking_payment')
membership_redirect_url = reverse_lazy('digitalglarus:membership_pricing')
# success_url = reverse_lazy('digitalglarus:booking_payment')
booking_needed_fields = ['original_price', 'discount_price', 'booking_days', 'free_days',
'start_date', 'end_date']
@ -114,6 +118,9 @@ class BookingPaymentView(LoginRequiredMixin, FormView):
return super(BookingPaymentView, self).dispatch(request, *args, **kwargs)
def get_success_url(self, order_id):
return reverse('digitalglarus:booking_orders_datail', kwargs={'pk': order_id})
def get_form_kwargs(self):
form_kwargs = super(BookingPaymentView, self).get_form_kwargs()
form_kwargs.update({
@ -147,7 +154,7 @@ class BookingPaymentView(LoginRequiredMixin, FormView):
start_date = data.get('start_date')
end_date = data.get('end_date')
original_price, discount_price, free_days = Booking.\
normal_price, final_price, free_days = Booking.\
booking_price(self.request.user, start_date, end_date)
# Get or create stripe customer
@ -159,7 +166,7 @@ class BookingPaymentView(LoginRequiredMixin, FormView):
# Make stripe charge to a customer
stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(amount=original_price,
charge_response = stripe_utils.make_charge(amount=final_price,
customer=customer.stripe_id)
charge = charge_response.get('response_object')
@ -182,7 +189,8 @@ class BookingPaymentView(LoginRequiredMixin, FormView):
'end_date': end_date,
'start_date': start_date,
'free_days': free_days,
'price': discount_price,
'price': normal_price,
'final_price': final_price,
}
booking = Booking.create(booking_data)
@ -193,12 +201,13 @@ class BookingPaymentView(LoginRequiredMixin, FormView):
'billing_address': billing_address,
'stripe_charge': charge
}
BookingOrder.create(order_data)
order = BookingOrder.create(order_data)
# request.session.update({
# 'membership_price': membership.type.first_month_price,
# 'membership_dates': membership.type.first_month_formated_range
# })
return HttpResponseRedirect(self.get_success_url(order.id))
return super(BookingPaymentView, self).form_valid(form)
# return HttpResponseRedirect(reverse('digitalglarus:membership_activated'))
@ -310,6 +319,53 @@ class MembershipActivatedView(TemplateView):
return context
class OrdersBookingDetailView(LoginRequiredMixin, DetailView):
template_name = "digitalglarus/booking_orders_detail.html"
context_object_name = "order"
login_url = reverse_lazy('digitalglarus:login')
# permission_required = ['view_hostingorder']
model = BookingOrder
def get_context_data(self, *args, **kwargs):
context = super(OrdersBookingDetailView, self).get_context_data(**kwargs)
bookig_order = self.object
booking = bookig_order.booking
start_date = booking.start_date
end_date = booking.end_date
free_days = booking.free_days
booking_days = (end_date - start_date).days + 1
original_price = booking.price
final_price = booking.final_price
context.update({
'original_price': original_price,
'total_discount': original_price - final_price,
'final_price': final_price,
'booking_days': booking_days,
'free_days': free_days,
'start_date': start_date.strftime('%m/%d/%Y'),
'end_date': end_date.strftime('%m/%d/%Y'),
})
return context
class BookingOrdersListView(LoginRequiredMixin, ListView):
template_name = "digitalglarus/booking_orders_list.html"
context_object_name = "orders"
login_url = reverse_lazy('digitalglarus:login')
model = BookingOrder
paginate_by = 10
def get_queryset(self):
queryset = super(BookingOrdersListView, self).get_queryset()
queryset = queryset.filter(customer__user=self.request.user)
return queryset
############## OLD VIEWS
class CalendarApi(View):
def get(self,request,month,year):

View file

@ -1,5 +1,8 @@
from datetime import datetime
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User, AbstractBaseUser, BaseUserManager, AbstractUser, PermissionsMixin