feat: address handling
This commit is contained in:
parent
e9f8b7dadc
commit
bbf4208228
|
|
@ -3,11 +3,12 @@ import { computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: {
|
modelValue: {
|
||||||
street: string;
|
company_name: string;
|
||||||
streetNumber: string;
|
company_street: string;
|
||||||
postalCode: string;
|
company_street_number: string;
|
||||||
city: string;
|
company_postal_code: string;
|
||||||
country: string;
|
company_city: string;
|
||||||
|
company_country: string;
|
||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
@ -25,6 +26,24 @@ const orgAddress = computed({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="my-10 grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
|
<div class="my-10 grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
|
||||||
|
<div class="col-span-full">
|
||||||
|
<label
|
||||||
|
for="company-name"
|
||||||
|
class="block text-sm font-medium leading-6 text-gray-900"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input
|
||||||
|
id="company-name"
|
||||||
|
v-model="orgAddress.company_name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
name="company-name"
|
||||||
|
class="block w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="sm:col-span-5">
|
<div class="sm:col-span-5">
|
||||||
<label
|
<label
|
||||||
for="company-street-address"
|
for="company-street-address"
|
||||||
|
|
@ -35,7 +54,7 @@ const orgAddress = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="company-street-address"
|
id="company-street-address"
|
||||||
v-model="orgAddress.street"
|
v-model="orgAddress.company_street"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
name="street-address"
|
name="street-address"
|
||||||
|
|
@ -55,7 +74,7 @@ const orgAddress = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="company-street-number"
|
id="company-street-number"
|
||||||
v-model="orgAddress.streetNumber"
|
v-model="orgAddress.company_street_number"
|
||||||
name="street-number"
|
name="street-number"
|
||||||
type="text"
|
type="text"
|
||||||
autocomplete="street-number"
|
autocomplete="street-number"
|
||||||
|
|
@ -73,7 +92,7 @@ const orgAddress = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="company-postal-code"
|
id="company-postal-code"
|
||||||
v-model="orgAddress.postalCode"
|
v-model="orgAddress.company_postal_code"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
name="postal-code"
|
name="postal-code"
|
||||||
|
|
@ -93,7 +112,7 @@ const orgAddress = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="company-city"
|
id="company-city"
|
||||||
v-model="orgAddress.city"
|
v-model="orgAddress.company_city"
|
||||||
type="text"
|
type="text"
|
||||||
name="city"
|
name="city"
|
||||||
required
|
required
|
||||||
|
|
@ -113,7 +132,7 @@ const orgAddress = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<select
|
<select
|
||||||
id="company-country"
|
id="company-country"
|
||||||
v-model="orgAddress.country"
|
v-model="orgAddress.company_country"
|
||||||
required
|
required
|
||||||
name="country"
|
name="country"
|
||||||
autocomplete="country-name"
|
autocomplete="country-name"
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import { useEntities } from "@/services/onboarding";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: {
|
modelValue: {
|
||||||
firstName: string;
|
first_name: string;
|
||||||
lastName: string;
|
last_name: string;
|
||||||
street: string;
|
street: string;
|
||||||
streetNumber: string;
|
street_number: string;
|
||||||
postalCode: string;
|
postal_code: string;
|
||||||
city: string;
|
city: string;
|
||||||
country: string;
|
country: string;
|
||||||
};
|
};
|
||||||
|
|
@ -37,7 +37,7 @@ const address = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="first-name"
|
id="first-name"
|
||||||
v-model="address.firstName"
|
v-model="address.first_name"
|
||||||
type="text"
|
type="text"
|
||||||
name="first-name"
|
name="first-name"
|
||||||
required
|
required
|
||||||
|
|
@ -54,7 +54,7 @@ const address = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="last-name"
|
id="last-name"
|
||||||
v-model="address.lastName"
|
v-model="address.last_name"
|
||||||
type="text"
|
type="text"
|
||||||
name="last-name"
|
name="last-name"
|
||||||
required
|
required
|
||||||
|
|
@ -94,7 +94,7 @@ const address = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="street-number"
|
id="street-number"
|
||||||
v-model="address.streetNumber"
|
v-model="address.street_number"
|
||||||
name="street-number"
|
name="street-number"
|
||||||
type="text"
|
type="text"
|
||||||
autocomplete="street-number"
|
autocomplete="street-number"
|
||||||
|
|
@ -113,7 +113,7 @@ const address = computed({
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
id="postal-code"
|
id="postal-code"
|
||||||
v-model="address.postalCode"
|
v-model="address.postal_code"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
name="postal-code"
|
name="postal-code"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import PersonalAddress from "@/components/onboarding/PersonalAddress.vue";
|
||||||
import OrganisationAddress from "@/components/onboarding/OrganisationAddress.vue";
|
import OrganisationAddress from "@/components/onboarding/OrganisationAddress.vue";
|
||||||
import { itPost, itPut, useFetch } from "@/fetchHelpers";
|
import { itPost, itPut, useFetch } from "@/fetchHelpers";
|
||||||
import { useEntities } from "@/services/onboarding";
|
import { useEntities } from "@/services/onboarding";
|
||||||
|
import { useDebounceFn } from "@vueuse/core";
|
||||||
|
|
||||||
type BillingAddressType = {
|
type BillingAddressType = {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
|
|
@ -16,6 +17,7 @@ type BillingAddressType = {
|
||||||
postal_code: string;
|
postal_code: string;
|
||||||
city: string;
|
city: string;
|
||||||
country: string;
|
country: string;
|
||||||
|
company_name: string;
|
||||||
company_street: string;
|
company_street: string;
|
||||||
company_street_number: string;
|
company_street_number: string;
|
||||||
company_postal_code: string;
|
company_postal_code: string;
|
||||||
|
|
@ -26,74 +28,64 @@ type BillingAddressType = {
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
const { organisations } = useEntities();
|
const { organisations } = useEntities();
|
||||||
|
|
||||||
const userOrganisationName = computed(
|
const userOrganisationName = computed(() => {
|
||||||
() => organisations.value?.find((c) => c.id === user.organisation)?.name
|
// Those IDs do not represent a company
|
||||||
);
|
// 1: Other broker
|
||||||
|
// 2: Other insurance
|
||||||
|
// 3: Other private insurance
|
||||||
|
// 31: No company relation
|
||||||
|
if ([1, 2, 3, 31].includes(user.organisation)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return organisations.value?.find((c) => c.id === user.organisation)?.name;
|
||||||
|
});
|
||||||
|
|
||||||
const address = ref({
|
const address = ref({
|
||||||
firstName: user.first_name,
|
first_name: "",
|
||||||
lastName: user.last_name,
|
last_name: "",
|
||||||
street: "",
|
street: "",
|
||||||
streetNumber: "",
|
street_number: "",
|
||||||
postalCode: "",
|
postal_code: "",
|
||||||
city: "",
|
city: "",
|
||||||
country: "",
|
country: "",
|
||||||
|
company_name: "",
|
||||||
|
company_street: "",
|
||||||
|
company_street_number: "",
|
||||||
|
company_postal_code: "",
|
||||||
|
company_city: "",
|
||||||
|
company_country: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const useCompanyAddress = ref(false);
|
const useCompanyAddress = ref(false);
|
||||||
|
|
||||||
const orgAddress = ref({
|
|
||||||
name: "",
|
|
||||||
street: "",
|
|
||||||
streetNumber: "",
|
|
||||||
postalCode: "",
|
|
||||||
city: "",
|
|
||||||
country: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchBillingAddress = useFetch("/api/shop/billing-address/");
|
const fetchBillingAddress = useFetch("/api/shop/billing-address/");
|
||||||
const billingAddressData: Ref<BillingAddressType | null> = fetchBillingAddress.data;
|
const billingAddressData: Ref<BillingAddressType | null> = fetchBillingAddress.data;
|
||||||
|
|
||||||
watch(billingAddressData, (newVal) => {
|
watch(billingAddressData, (newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
address.value = {
|
address.value = newVal;
|
||||||
firstName: newVal.first_name || "",
|
useCompanyAddress.value = !!newVal.company_name;
|
||||||
lastName: newVal.last_name || "",
|
|
||||||
street: newVal.street || "",
|
|
||||||
streetNumber: newVal.street_number || "",
|
|
||||||
postalCode: newVal.postal_code || "",
|
|
||||||
city: newVal.city || "",
|
|
||||||
country: newVal.country || "",
|
|
||||||
};
|
|
||||||
orgAddress.value = {
|
|
||||||
name: userOrganisationName.value || "",
|
|
||||||
street: newVal.company_street || "",
|
|
||||||
streetNumber: newVal.company_street_number || "",
|
|
||||||
postalCode: newVal.company_postal_code || "",
|
|
||||||
city: newVal.company_city || "",
|
|
||||||
country: newVal.company_country || "",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const executePayment = () => {
|
const updateAddress = useDebounceFn(() => {
|
||||||
// FIXME do this as user types, not on submit -> the is the whole point of this ;)
|
itPut("/api/shop/billing-address/update/", address.value);
|
||||||
// FIXME same address and orgaddress too
|
}, 500);
|
||||||
itPut("/api/shop/billing-address/update/", {
|
|
||||||
first_name: address.value.firstName,
|
|
||||||
last_name: address.value.lastName,
|
|
||||||
street: address.value.street,
|
|
||||||
street_number: address.value.streetNumber,
|
|
||||||
postal_code: address.value.postalCode,
|
|
||||||
city: address.value.city,
|
|
||||||
country: address.value.country,
|
|
||||||
company_street: orgAddress.value.street,
|
|
||||||
company_street_number: orgAddress.value.streetNumber,
|
|
||||||
company_postal_code: orgAddress.value.postalCode,
|
|
||||||
company_city: orgAddress.value.city,
|
|
||||||
company_country: orgAddress.value.country,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
address,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
if (Object.values(oldVal).every((x) => x === "")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAddress();
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const executePayment = () => {
|
||||||
// Where the payment page will redirect to after the payment is done:
|
// Where the payment page will redirect to after the payment is done:
|
||||||
// The reason why this is here is convenience: We could also do this in the backend
|
// The reason why this is here is convenience: We could also do this in the backend
|
||||||
// then we'd need to configure this for all environments (including Caprover).
|
// then we'd need to configure this for all environments (including Caprover).
|
||||||
|
|
@ -103,7 +95,6 @@ const executePayment = () => {
|
||||||
|
|
||||||
itPost("/api/shop/vv/checkout/", {
|
itPost("/api/shop/vv/checkout/", {
|
||||||
redirect_url: fullHost,
|
redirect_url: fullHost,
|
||||||
organisation_address: orgAddress.value,
|
|
||||||
address: address.value,
|
address: address.value,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
console.log("Going to next page", res.next_step_url);
|
console.log("Going to next page", res.next_step_url);
|
||||||
|
|
@ -136,7 +127,10 @@ const executePayment = () => {
|
||||||
class="underline"
|
class="underline"
|
||||||
@click="useCompanyAddress = true"
|
@click="useCompanyAddress = true"
|
||||||
>
|
>
|
||||||
|
<template v-if="userOrganisationName">
|
||||||
{{ `Rechnungsadresse von ${userOrganisationName} hinzufügen` }}
|
{{ `Rechnungsadresse von ${userOrganisationName} hinzufügen` }}
|
||||||
|
</template>
|
||||||
|
<template v-else>Rechnungsadresse hinzufügen</template>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<transition
|
<transition
|
||||||
|
|
@ -149,12 +143,15 @@ const executePayment = () => {
|
||||||
>
|
>
|
||||||
<div v-if="useCompanyAddress">
|
<div v-if="useCompanyAddress">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3>{{ `Rechnungsaddresse von ${userOrganisationName}` }}</h3>
|
<h3 v-if="userOrganisationName">
|
||||||
|
{{ `Rechnungsadresse von ${userOrganisationName}` }}
|
||||||
|
</h3>
|
||||||
|
<h3 v-else>Rechnungsadresse</h3>
|
||||||
<button class="underline" @click="useCompanyAddress = false">
|
<button class="underline" @click="useCompanyAddress = false">
|
||||||
Entfernen
|
Entfernen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<OrganisationAddress v-model="orgAddress" />
|
<OrganisationAddress v-model="address" />
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 3.2.20 on 2023-11-17 08:05
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("shop", "0007_checkoutinformation_webhook_history"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="billingaddress",
|
||||||
|
name="id",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="billingaddress",
|
||||||
|
name="user",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -23,7 +23,11 @@ class BillingAddress(models.Model):
|
||||||
Draft of a billing address for a purchase from the shop.
|
Draft of a billing address for a purchase from the shop.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.ForeignKey("core.User", on_delete=models.CASCADE)
|
user = models.OneToOneField(
|
||||||
|
"core.User",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
primary_key=True,
|
||||||
|
)
|
||||||
|
|
||||||
# user
|
# user
|
||||||
first_name = models.CharField(max_length=255, blank=True)
|
first_name = models.CharField(max_length=255, blank=True)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import structlog
|
import structlog
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
@ -41,10 +40,15 @@ def webhook_url(base_url):
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
def get_billing_address(request):
|
def get_billing_address(request):
|
||||||
billing_address = get_object_or_404(BillingAddress, user=request.user)
|
try:
|
||||||
serializer = BillingAddressSerializer(billing_address)
|
billing_address = BillingAddress.objects.get(user=request.user)
|
||||||
|
data = BillingAddressSerializer(billing_address).data
|
||||||
|
except BillingAddress.DoesNotExist:
|
||||||
|
data = BillingAddressSerializer().data
|
||||||
|
data["first_name"] = request.user.first_name
|
||||||
|
data["last_name"] = request.user.last_name
|
||||||
|
|
||||||
return Response(serializer.data)
|
return Response(data)
|
||||||
|
|
||||||
|
|
||||||
@api_view(["PUT"])
|
@api_view(["PUT"])
|
||||||
|
|
@ -143,7 +147,6 @@ def checkout_vv(request):
|
||||||
# )
|
# )
|
||||||
|
|
||||||
address = request.data["address"]
|
address = request.data["address"]
|
||||||
organization = request.data["organisation_address"]
|
|
||||||
base_redirect_url = request.data["redirect_url"]
|
base_redirect_url = request.data["redirect_url"]
|
||||||
|
|
||||||
# not yet initialized at all or canceled/failed
|
# not yet initialized at all or canceled/failed
|
||||||
|
|
@ -166,21 +169,7 @@ def checkout_vv(request):
|
||||||
product_price=product.price,
|
product_price=product.price,
|
||||||
product_name=product.name,
|
product_name=product.name,
|
||||||
product_description=product.description,
|
product_description=product.description,
|
||||||
# address
|
**address,
|
||||||
first_name=address["firstName"],
|
|
||||||
last_name=address["lastName"],
|
|
||||||
street=address["street"],
|
|
||||||
street_number=address["streetNumber"],
|
|
||||||
postal_code=address["postalCode"],
|
|
||||||
city=address["city"],
|
|
||||||
country=address["country"],
|
|
||||||
# organization
|
|
||||||
company_name=organization.get("name"),
|
|
||||||
company_street=organization.get("street"),
|
|
||||||
company_street_number=organization.get("streetNumber"),
|
|
||||||
company_postal_code=organization.get("postalCode"),
|
|
||||||
company_city=organization.get("city"),
|
|
||||||
company_country=organization.get("country"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue