Successful clickthrough

Finalize booking process
This commit is contained in:
Kumi 2021-04-20 13:37:36 +02:00
parent 570810105e
commit 146ae4b10a
14 changed files with 175 additions and 40 deletions

View file

@ -16,4 +16,8 @@ class InquiryProcessForm(forms.ModelForm):
class Meta:
model = Inquiry
fields = ["gateway"]
fields = ["gateway"]
class OfferSelectionForm(forms.Form):
terms = forms.BooleanField(required=True)
offer = forms.UUIDField(required=True)

View file

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

View file

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

View file

@ -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"))),
]

View file

@ -83,4 +83,11 @@ class ClientBookingsView(InConstructionMixin, ClientProfileRequiredMixin, ListVi
template_name = "clients/bookings.html"
def get_queryset(self):
return Inquiry.objects.filter(client=self.request.user.clientprofile)
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)

View file

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

View file

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

View file

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

View 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"));
}
);

View file

@ -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">&times;</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 %}

View file

@ -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", ""]
}

View 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 %}

View file

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

View file

@ -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 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 %}
</div><!-- end columns -->
</div><!-- end row -->
</div><!-- end container -->
</div><!-- end coming-soon-text -->
</section><!-- end innerpage-wrapper -->
</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 %}