Successful clickthrough
Finalize booking process
This commit is contained in:
parent
570810105e
commit
146ae4b10a
14 changed files with 175 additions and 40 deletions
|
@ -17,3 +17,7 @@ class InquiryProcessForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = Inquiry
|
||||
fields = ["gateway"]
|
||||
|
||||
class OfferSelectionForm(forms.Form):
|
||||
terms = forms.BooleanField(required=True)
|
||||
offer = forms.UUIDField(required=True)
|
|
@ -58,10 +58,10 @@ class Inquiry(models.Model):
|
|||
|
||||
@property
|
||||
def accepted(self):
|
||||
qset = Offer.objects.filter(inquiry=self, accepted=True)
|
||||
|
||||
if qset:
|
||||
return qset[0]
|
||||
try:
|
||||
return Offer.objects.get(inquiry=self, accepted__isnull=False)
|
||||
except Offer.DoesNotExist:
|
||||
return False
|
||||
|
||||
class Offer(models.Model):
|
||||
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
||||
|
@ -69,7 +69,7 @@ class Offer(models.Model):
|
|||
roomcategory = models.ForeignKey(RoomCategory, models.PROTECT)
|
||||
departure = models.DateField()
|
||||
comment = models.TextField(null=True, blank=True)
|
||||
accepted = models.BooleanField(default=False)
|
||||
accepted = models.DateTimeField(null=True, blank=True)
|
||||
hidden = models.BooleanField(default=False)
|
||||
|
||||
@property
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
from django.views.generic import CreateView, UpdateView, View, ListView, DetailView
|
||||
from django.views.generic import CreateView, UpdateView, View, ListView, DetailView, FormView
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.contrib import messages
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.contrib.gis.db.models.functions import Distance
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from public.mixins import InConstructionMixin
|
||||
from partners.mixins import PartnerProfileRequiredMixin
|
||||
from clients.mixins import ClientProfileRequiredMixin
|
||||
from localauth.helpers import name_to_coords
|
||||
from partners.models import Establishment
|
||||
from payment.models import BillingAddress, Invoice, InvoiceItem, InvoicePayment
|
||||
from clients.models import ClientProfile
|
||||
|
||||
from .models import Inquiry, Offer
|
||||
from .forms import InquiryProcessForm
|
||||
from .forms import InquiryProcessForm, OfferSelectionForm
|
||||
|
||||
class InquiryCreateView(CreateView):
|
||||
model = Inquiry
|
||||
|
@ -96,20 +98,31 @@ class InquiryPaymentView(InConstructionMixin, View):
|
|||
|
||||
return redirect(payment_url)
|
||||
|
||||
class OfferSelectionView(InConstructionMixin, DetailView):
|
||||
class OfferSelectionView(InConstructionMixin, ClientProfileRequiredMixin, FormView, DetailView):
|
||||
model = Inquiry
|
||||
template_name = "auction/offer_select.html"
|
||||
form_class = OfferSelectionForm
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(Inquiry, uuid=self.kwargs["uuid"])
|
||||
return get_object_or_404(Inquiry, uuid=self.kwargs["uuid"], client=self.request.user.clientprofile)
|
||||
|
||||
class OfferSelectionTableView(InConstructionMixin, ListView):
|
||||
def form_valid(self, form):
|
||||
inquiry = self.get_object()
|
||||
offer = get_object_or_404(Offer, inquiry=inquiry, uuid=form.cleaned_data["offer"])
|
||||
offer.accepted = timezone.now()
|
||||
offer.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("clients:booking_view", args=[self.kwargs["uuid"]])
|
||||
|
||||
class OfferSelectionTableView(InConstructionMixin, ClientProfileRequiredMixin, ListView):
|
||||
model = Offer
|
||||
content_type = "text/javascript"
|
||||
template_name = "auction/offer_table.js"
|
||||
|
||||
def get_queryset(self):
|
||||
inquiry = get_object_or_404(Inquiry, uuid=self.kwargs["uuid"])
|
||||
inquiry = get_object_or_404(Inquiry, uuid=self.kwargs["uuid"], client=self.request.user.clientprofile)
|
||||
return inquiry.offer_set.all()
|
||||
|
||||
class OfferCreationView(InConstructionMixin, PartnerProfileRequiredMixin, CreateView):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.urls import path, reverse_lazy
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from .views import ClientRegistrationView, ClientProfileView, ClientDashboardView, ClientBookingsView
|
||||
from .views import ClientRegistrationView, ClientProfileView, ClientDashboardView, ClientBookingsView, ClientBookingView
|
||||
|
||||
app_name = "clients"
|
||||
|
||||
|
@ -10,5 +10,6 @@ urlpatterns = [
|
|||
path('register/', ClientRegistrationView.as_view(), name="register"),
|
||||
path('profile/', ClientProfileView.as_view(), name="profile"),
|
||||
path('bookings/', ClientBookingsView.as_view(), name="bookings"),
|
||||
path('bookings/<slug:uuid>/', ClientBookingView.as_view(), name="booking_view"),
|
||||
path('', RedirectView.as_view(url=reverse_lazy("clients:dashboard"))),
|
||||
]
|
|
@ -84,3 +84,10 @@ class ClientBookingsView(InConstructionMixin, ClientProfileRequiredMixin, ListVi
|
|||
|
||||
def get_queryset(self):
|
||||
return Inquiry.objects.filter(client=self.request.user.clientprofile)
|
||||
|
||||
class ClientBookingView(InConstructionMixin, ClientProfileRequiredMixin, DetailView):
|
||||
model = Inquiry
|
||||
template_name = "clients/booking.html"
|
||||
|
||||
def get_object(self):
|
||||
return Inquiry.objects.get(uuid=self.kwargs["uuid"], client=self.request.user.clientprofile)
|
|
@ -73,7 +73,7 @@ class OffersListView(InConstructionMixin, PartnerProfileRequiredMixin, ListView)
|
|||
template_name = "partners/offer_list.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return Offer.objects.filter(establishment__in=self.request.user.partnerprofile.establishment_set.all())
|
||||
return Offer.objects.filter(roomcategory__in=self.request.user.partnerprofile.roomcategory_set.all())
|
||||
|
||||
class EstablishmentsListView(InConstructionMixin, PartnerProfileRequiredMixin, ListView):
|
||||
model = Establishment
|
||||
|
|
|
@ -15,7 +15,7 @@ class DemoInvoicePayment(InvoicePayment):
|
|||
@classmethod
|
||||
def initiate(cls, invoice):
|
||||
payment = cls.objects.create(invoice=invoice, amount=invoice.balance * -1, gateway_id=uuid.uuid4())
|
||||
invoice.generate_invoice()
|
||||
invoice.finalize()
|
||||
return reverse_lazy("payment:status", args=[payment.uuid])
|
||||
|
||||
@property
|
||||
|
|
|
@ -217,13 +217,11 @@ class InvoicePayment(PolymorphicModel):
|
|||
|
||||
@classmethod
|
||||
def from_invoice(cls, invoice, gateway):
|
||||
if invoice.balance < 0:
|
||||
if not invoice.is_paid:
|
||||
responses = initiate_payment.send_robust(sender=cls, invoice=invoice, gateway=gateway)
|
||||
|
||||
for handler, response in responses:
|
||||
try:
|
||||
return response["redirect"]
|
||||
except:
|
||||
continue
|
||||
|
||||
return False
|
6
static/auction/js/offer-selection.js
Normal file
6
static/auction/js/offer-selection.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
$(".accept-button").click(
|
||||
function() {
|
||||
$("#id_offer").val($(this).attr("data-offer-uuid"));
|
||||
$("#hotel_name").text($(this).attr("data-offer-name"));
|
||||
}
|
||||
);
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "frontend/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block "styles" %}
|
||||
<link rel="stylesheet" href="{% static "vendor/css/tabella.css" %}">
|
||||
{% endblock %}
|
||||
|
@ -29,7 +29,35 @@
|
|||
</section><!-- end innerpage-wrapper -->
|
||||
{% endblock %}
|
||||
|
||||
{% block "modal" %}
|
||||
<div id="accept-offer" class="modal custom-modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">Angebot akzeptieren?</h3>
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div><!-- end modal-header -->
|
||||
|
||||
<div class="modal-body">
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<p>Bist du sicher, dass du das Angebot von <span id="hotel_name"></span> annehmen möchtest?</p>
|
||||
<p>Deine Sicherheitsleistung von € {{ object.budget }} wird zur Bezahlung der Buchung verwendet und kann damit nicht mehr rückerstattet werden.</p>
|
||||
<p>Die von dir angegebenen Daten werden dem Reiseanbieter zur Bearbeitung deiner Buchung zur Verfügung gestellt.</p>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" required id="id_terms" name="terms"> {% trans "Ich erkläre mich einverstanden mit den" %} <a href="#">{% trans "Allgemeinen Geschäftsbedingungen" %}</a> {% trans "und der" %} <a href="#">{% trans "Datenschutzerklärung" %}</a>.</label>
|
||||
</div>
|
||||
<input type="hidden" required id="id_offer" name="offer" />
|
||||
<button class="btn btn-orange btn-block">Zahlungspflichtig buchen!</button>
|
||||
</form>
|
||||
</div><!-- end modal-bpdy -->
|
||||
</div><!-- end modal-content -->
|
||||
</div><!-- end modal-dialog -->
|
||||
</div><!-- end edit-profile -->
|
||||
{% endblock %}
|
||||
|
||||
{% block "scripts" %}
|
||||
<script src="{% static "vendor/js/tabella.min.js" %}"></script>
|
||||
<script src="{% url "auction:offer_table" object.uuid %}"></script>
|
||||
<script src="{% static "auction/js/offer-selection.js" %}"></script>
|
||||
{% endblock %}
|
|
@ -39,7 +39,7 @@
|
|||
{
|
||||
rowVal: [
|
||||
[{% for offer in object_list %}'€ {{ offer.inquiry.budget }}',{% endfor %}],
|
||||
[{% for offer in object_list %}'<a href="#{{ offer.uuid }}" class="btn btn-orange btn-block">Zahlungspflichtig buchen!</button>',{% endfor %}]
|
||||
[{% for offer in object_list %}'<button data-offer-uuid="{{ offer.uuid }}" data-offer-name="{{ offer.roomcategory.establishment.name }}" data-toggle="modal" data-target="#accept-offer" class="btn btn-orange btn-block accept-button">Angebot annehmen!</button>',{% endfor %}]
|
||||
],
|
||||
rowDesc: ["Fixpreis", ""]
|
||||
}
|
||||
|
|
40
templates/clients/booking.html
Normal file
40
templates/clients/booking.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{% extends "frontend/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block "content" %}
|
||||
<!--===== INNERPAGE-WRAPPER ====-->
|
||||
<section class="innerpage-wrapper">
|
||||
<div id="payment-success" class="section-padding">
|
||||
<div class="container text-center">
|
||||
<div class="row d-flex justify-content-center">
|
||||
<div class="col-md-12 col-lg-8 col-lg-offset-2">
|
||||
<div class="payment-success-text">
|
||||
<h2>{% blocktrans with booking=object.id %}Buchung #{{ booking }} erfolgreich{% endblocktrans %}</h2>
|
||||
<p>{% blocktrans with currency=object.invoice.currency|upper amount=object.budget %}Die Zahlung über {{ currency }} {{ amount }} wurde erfolgreich freigegeben!{% endblocktrans %}</p>
|
||||
|
||||
<span><i class="fa fa-check-circle"></i></span>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><span><i class="fa fa-clone"></i></span>{% trans "Kategorie" %}</td>
|
||||
<td><span><i class="fa fa-diamond"></i></span>{% trans "Beschreibung" %}</td>
|
||||
<td><span><i class="fa fa-dollar"></i></span>{% trans "Preis" %}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span><i class="fa fa-building"></i></span>Buchung</td>
|
||||
<td>{{ object.accepted.roomcategory.establishment.name }}<br/>{{ object.arrival }} – {{ object.accepted.departure }}</td>
|
||||
<td>{{ object.invoice.currency|upper }} {{ object.budget }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a href="{% url "auction:offer_selection" object.invoice.inquiry.uuid %}" class="btn btn-orange">Buchungsbestätigung drucken</a><br /><br />
|
||||
</div>
|
||||
</div><!-- end columns -->
|
||||
</div><!-- end row -->
|
||||
</div><!-- end container -->
|
||||
</div><!-- end coming-soon-text -->
|
||||
</section><!-- end innerpage-wrapper -->
|
||||
{% endblock %}
|
|
@ -32,7 +32,7 @@
|
|||
<tr>
|
||||
<td class="dash-list-icon booking-list-date"><div class="b-date"><h3>{{ inquiry.arrival.day }}</h3><p>{{ inquiry.arrival|date:"E Y" }}</p></div></td>
|
||||
<td class="dash-list-text booking-list-detail">
|
||||
<h3>{% if inquiry.accepted %}{{ inquiry.accepted.name }} ({{ inquiry.destination_name }}){% else %}{{ inquiry.destination_name }}{% endif %}</h3>
|
||||
<h3>{% if inquiry.accepted %}{{ inquiry.accepted.roomcategory.establishment.name }} ({{ inquiry.destination_name }}){% else %}{{ inquiry.destination_name }}{% endif %}</h3>
|
||||
<ul class="list-unstyled booking-info">
|
||||
<li><span>Ankunft:</span> {{ inquiry.arrival }}</li>
|
||||
<li><span>{% if inquiry.accepted %}Abreise{% else %}Dauer{% endif %}:</span> {% if inquiry.accepted %}{{ inquiry.accepted.departure }}{% elif inquiry.min_nights == 0 %}Egal!{% elif inquiry.min_nights == 1 %}Kurztrip (2 – 3 Nächte){% elif inquiry.min_nights == 2 %}min. 3 Nächte{% endif %}</li>
|
||||
|
@ -40,11 +40,11 @@
|
|||
<li><span>Status:</span> {% if inquiry.accepted %}Fixiert{% elif inquiry.active %}In Gebotsphase ({{ inquiry.offer_set.all|length }} Gebote){% else %}Inaktiv{% endif %}</li>
|
||||
</ul>
|
||||
{% if inquiry.accepted %}
|
||||
<button class="btn btn-orange">Buchungsdetails</button>
|
||||
<a href="{% url "clients:booking_view" inquiry.uuid %}" class="btn btn-orange">Buchungsdetails</a>
|
||||
{% elif inquiry.active %}
|
||||
<a href="{% url "auction:offer_selection" inquiry.uuid %}" class="btn btn-orange">Gebote ansehen</a>
|
||||
{% elif inquiry.expired %}
|
||||
{% else %}
|
||||
{% elif not inquiry.is_paid %}
|
||||
<a href="{% url "auction:process_inquiry" inquiry.uuid %}" class="btn btn-orange">Zahlung abschließen</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
|
|
@ -1,18 +1,56 @@
|
|||
{% extends "frontend/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap4 %}
|
||||
{% block "content" %}
|
||||
<!--===== INNERPAGE-WRAPPER ====-->
|
||||
<section class="innerpage-wrapper">
|
||||
<div id="payment-success" class="section-padding">
|
||||
<div class="container text-center">
|
||||
<div class="row d-flex justify-content-center">
|
||||
<div class="col-md-12 col-lg-8 col-lg-offset-2">
|
||||
<h2>{% trans "Deine Angebote" %}</h2>
|
||||
{% extends "clients/base.html" %}
|
||||
{% block "dashboardcontent" %}
|
||||
<div class="col-12 col-md-10 col-lg-10 dashboard-content booking-trips">
|
||||
<h2 class="dash-content-title">Deine Angebote</h2>
|
||||
<div class="dashboard-listing booking-listing">
|
||||
<div class="dash-listing-heading">
|
||||
<div class="custom-radio">
|
||||
<input type="radio" id="radio01" name="radio" checked/>
|
||||
<label for="radio01"><span></span>Alle Angebote</label>
|
||||
</div><!-- end custom-radio -->
|
||||
|
||||
</div><!-- end columns -->
|
||||
</div><!-- end row -->
|
||||
</div><!-- end container -->
|
||||
</div><!-- end coming-soon-text -->
|
||||
</section><!-- end innerpage-wrapper -->
|
||||
<div class="custom-radio">
|
||||
<input type="radio" id="radio02" name="radio" />
|
||||
<label for="radio02"><span></span>Fixiert</label>
|
||||
</div><!-- end custom-radio -->
|
||||
|
||||
<div class="custom-radio">
|
||||
<input type="radio" id="radio03" name="radio" />
|
||||
<label for="radio03"><span></span>In Gebotsphase</label>
|
||||
</div><!-- end custom-radio -->
|
||||
|
||||
<div class="custom-radio">
|
||||
<input type="radio" id="radio04" name="radio" />
|
||||
<label for="radio04"><span></span>Inaktiv</label>
|
||||
</div><!-- end custom-radio -->
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<tbody>
|
||||
{% for offer in object_list %}
|
||||
<tr>
|
||||
<td class="dash-list-icon booking-list-date"><div class="b-date"><h3>{{ offer.inquiry.arrival.day }}</h3><p>{{ offer.inquiry.arrival|date:"E Y" }}</p></div></td>
|
||||
<td class="dash-list-text booking-list-detail">
|
||||
<h3>{% if offer.accepted %}Buchung{% else %}Angebot{% endif %}: {{ offer.roomcategory.establishment.name }} ({{ offer.roomcategory.name }})</h3>
|
||||
<ul class="list-unstyled booking-info">
|
||||
<li><span>Ankunft:</span> {{ offer.inquiry.arrival }}</li>
|
||||
<li><span>Abreise:</span> {{ offer.departure }}</li>
|
||||
<li><span>Preis:</span> € {{ offer.inquiry.budget }}</li>
|
||||
<li><span>Status:</span> {% if offer.accepted %}Fixiert{% elif offer.inquiry.active and not offer.inquiry.accepted %}In Gebotsphase{% else %}Inaktiv{% endif %}</li>
|
||||
</ul>
|
||||
{% if offer.accepted %}
|
||||
<button class="btn btn-orange">Buchungsdetails</button>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td class="dash-list-btn"><button class="btn btn-orange">Stornieren</button><button class="btn">PDF-Bestätigung</button></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div><!-- end table-responsive -->
|
||||
</div><!-- end booking-listings -->
|
||||
|
||||
</div><!-- end columns -->
|
||||
{% endblock %}
|
Loading…
Reference in a new issue