Merged develop into fix/optimize-queries
This commit is contained in:
commit
85f0b680b5
|
|
@ -97,6 +97,7 @@ js-linting: &js-linting
|
||||||
|
|
||||||
default-steps: &default-steps
|
default-steps: &default-steps
|
||||||
- parallel:
|
- parallel:
|
||||||
|
- step: *e2e
|
||||||
- step: *e2e
|
- step: *e2e
|
||||||
- step: *e2e
|
- step: *e2e
|
||||||
- step: *python-tests
|
- step: *python-tests
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,12 @@ def main(app_name, image_name, environment_file):
|
||||||
"AWS_S3_SECRET_ACCESS_KEY": env.str("AWS_S3_SECRET_ACCESS_KEY", ""),
|
"AWS_S3_SECRET_ACCESS_KEY": env.str("AWS_S3_SECRET_ACCESS_KEY", ""),
|
||||||
"AWS_S3_REGION_NAME": "eu-central-1",
|
"AWS_S3_REGION_NAME": "eu-central-1",
|
||||||
"AWS_STORAGE_BUCKET_NAME": "myvbv-dev.iterativ.ch",
|
"AWS_STORAGE_BUCKET_NAME": "myvbv-dev.iterativ.ch",
|
||||||
"DATATRANS_HMAC_KEY": env.str("DATATRANS_HMAC_KEY", ""),
|
"DATATRANS_HMAC_KEY": env.str("PIPELINES_DATATRANS_HMAC_KEY", ""),
|
||||||
"DATATRANS_BASIC_AUTH_KEY": env.str("DATATRANS_BASIC_AUTH_KEY", ""),
|
"DATATRANS_BASIC_AUTH_KEY": env.str(
|
||||||
|
"PIPELINES_DATATRANS_BASIC_AUTH_KEY", ""
|
||||||
|
),
|
||||||
|
"DATATRANS_API_ENDPOINT": "https://api.sandbox.datatrans.com",
|
||||||
|
"DATATRANS_PAY_URL": "https://pay.sandbox.datatrans.com",
|
||||||
"FILE_UPLOAD_STORAGE": "s3",
|
"FILE_UPLOAD_STORAGE": "s3",
|
||||||
"IT_DJANGO_DEBUG": "false",
|
"IT_DJANGO_DEBUG": "false",
|
||||||
"IT_SERVE_VUE": "false",
|
"IT_SERVE_VUE": "false",
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const orgAddress = computed({
|
||||||
for="company-name"
|
for="company-name"
|
||||||
class="block text-sm font-medium leading-6 text-gray-900"
|
class="block text-sm font-medium leading-6 text-gray-900"
|
||||||
>
|
>
|
||||||
{{ $t("a.Name") }}
|
{{ $t("a.Firmenname") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useEntities } from "@/services/entities";
|
import { useEntities } from "@/services/entities";
|
||||||
import VueDatePicker from "@vuepic/vue-datepicker";
|
|
||||||
import "@vuepic/vue-datepicker/dist/main.css";
|
import "@vuepic/vue-datepicker/dist/main.css";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useUserStore } from "@/stores/user";
|
import ItDatePicker from "@/components/ui/ItDatePicker.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
@ -25,20 +24,12 @@ const props = defineProps<{
|
||||||
const emit = defineEmits(["update:modelValue"]);
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
const { countries } = useEntities();
|
const { countries } = useEntities();
|
||||||
const userStore = useUserStore();
|
|
||||||
|
|
||||||
const paymentMethods = [
|
const paymentMethods = [
|
||||||
{ value: "credit_card", label: t("a.Debit-/Kreditkarte/Twint") },
|
{ value: "credit_card", label: t("a.Debit-/Kreditkarte/Twint") },
|
||||||
{ value: "cembra_byjuno", label: t("a.Rechnung") },
|
{ value: "cembra_byjuno", label: t("a.Rechnung") },
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: remove after cembra is ready for production
|
|
||||||
const appEnv = import.meta.env.VITE_APP_ENVIRONMENT || "local";
|
|
||||||
if (appEnv.startsWith("prod")) {
|
|
||||||
paymentMethods.splice(1, 1);
|
|
||||||
}
|
|
||||||
// END TODO
|
|
||||||
|
|
||||||
const address = computed({
|
const address = computed({
|
||||||
get() {
|
get() {
|
||||||
return props.modelValue;
|
return props.modelValue;
|
||||||
|
|
@ -234,40 +225,8 @@ const address = computed({
|
||||||
{{ $t("a.Geburtsdatum") }}
|
{{ $t("a.Geburtsdatum") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<VueDatePicker
|
<ItDatePicker v-model="address.birth_date"></ItDatePicker>
|
||||||
v-model="address.birth_date"
|
|
||||||
format="dd.MM.yyyy"
|
|
||||||
no-today
|
|
||||||
model-type="yyyy-MM-dd"
|
|
||||||
name="birth-date"
|
|
||||||
max-date="2007-01-01"
|
|
||||||
prevent-min-max-navigation
|
|
||||||
required
|
|
||||||
:enable-time-picker="false"
|
|
||||||
text-input
|
|
||||||
placeholder="15.06.1982"
|
|
||||||
start-date="1982-01-01"
|
|
||||||
:locale="userStore.language"
|
|
||||||
:cancel-text="$t('a.Abbrechen')"
|
|
||||||
:select-text="$t('a.Auswählen')"
|
|
||||||
></VueDatePicker>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Theming for date picker */
|
|
||||||
.dp__theme_light {
|
|
||||||
--dp-text-color: #585f63;
|
|
||||||
--dp-primary-color: #41b5fa;
|
|
||||||
--dp-border-color-focus: #3d6dcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--dp-font-family: "Buenos Aires" sans-serif;
|
|
||||||
--dp-border-radius: none;
|
|
||||||
--dp-font-size: 0.875rem;
|
|
||||||
--dp-cell-border-radius: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { useEntities } from "@/services/entities";
|
||||||
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { type User, useUserStore } from "@/stores/user";
|
import { type User, useUserStore } from "@/stores/user";
|
||||||
|
import ItDatePicker from "@/components/ui/ItDatePicker.vue";
|
||||||
|
import { normalizeSwissPhoneNumber } from "@/utils/phone";
|
||||||
|
|
||||||
const emit = defineEmits(["cancel", "save"]);
|
const emit = defineEmits(["cancel", "save"]);
|
||||||
|
|
||||||
|
|
@ -21,7 +23,12 @@ const formData = ref({
|
||||||
postal_code: user.postal_code,
|
postal_code: user.postal_code,
|
||||||
city: user.city,
|
city: user.city,
|
||||||
country_code: user.country?.country_code,
|
country_code: user.country?.country_code,
|
||||||
|
|
||||||
|
phone_number: user.phone_number,
|
||||||
|
birth_date: user.birth_date,
|
||||||
|
|
||||||
organisation: user.organisation,
|
organisation: user.organisation,
|
||||||
|
organisation_detail_name: user.organisation_detail_name,
|
||||||
organisation_street: user.organisation_street,
|
organisation_street: user.organisation_street,
|
||||||
organisation_street_number: user.organisation_street_number,
|
organisation_street_number: user.organisation_street_number,
|
||||||
organisation_postal_code: user.organisation_postal_code,
|
organisation_postal_code: user.organisation_postal_code,
|
||||||
|
|
@ -31,7 +38,8 @@ const formData = ref({
|
||||||
});
|
});
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
const { country_code, organisation_country_code, ...profileData } = formData.value;
|
const { country_code, organisation_country_code, phone_number, ...profileData } =
|
||||||
|
formData.value;
|
||||||
const typedProfileData: Partial<User> = { ...profileData };
|
const typedProfileData: Partial<User> = { ...profileData };
|
||||||
|
|
||||||
typedProfileData.country = countries.value.find(
|
typedProfileData.country = countries.value.find(
|
||||||
|
|
@ -41,6 +49,10 @@ async function save() {
|
||||||
(c) => c.country_code === organisation_country_code
|
(c) => c.country_code === organisation_country_code
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (phone_number) {
|
||||||
|
typedProfileData.phone_number = normalizeSwissPhoneNumber(phone_number);
|
||||||
|
}
|
||||||
|
|
||||||
await user.updateUserProfile(typedProfileData);
|
await user.updateUserProfile(typedProfileData);
|
||||||
emit("save");
|
emit("save");
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +138,28 @@ async function avatarUpload(e: Event) {
|
||||||
disabled
|
disabled
|
||||||
class="disabled:bg-gray-50 mb-4 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 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6"
|
class="disabled:bg-gray-50 mb-4 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 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<label for="phone" class="block pb-1.5 leading-6">
|
||||||
|
{{ $t("a.Telefonnummer") }}
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="phone"
|
||||||
|
v-model="formData.phone_number"
|
||||||
|
type="text"
|
||||||
|
name="phone"
|
||||||
|
autocomplete="phone-number"
|
||||||
|
class="disabled:bg-gray-50 mb-4 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 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label for="birth-date" class="block pb-1.5 leading-6">
|
||||||
|
{{ $t("a.Geburtsdatum") }}
|
||||||
|
</label>
|
||||||
|
<div class="mb-4 block w-full py-1.5 sm:max-w-sm sm:text-sm sm:leading-6">
|
||||||
|
<ItDatePicker v-model="formData.birth_date"></ItDatePicker>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label class="block pb-1.5 leading-6">
|
<label class="block pb-1.5 leading-6">
|
||||||
{{ $t("a.Profilbild") }}
|
{{ $t("a.Profilbild") }}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -264,6 +298,22 @@ async function avatarUpload(e: Event) {
|
||||||
{{ $t("a.Firmenanschrift") }}
|
{{ $t("a.Firmenanschrift") }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
|
<div class="flex flex-col justify-start md:flex-row md:space-x-4">
|
||||||
|
<div class="w-full md:max-w-lg">
|
||||||
|
<label for="org-street-address" class="block pb-1.5 leading-6">
|
||||||
|
{{ $t("a.Firmenname") }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="org-detail-name"
|
||||||
|
v-model="formData.organisation_detail_name"
|
||||||
|
type="text"
|
||||||
|
name="org-detail-name"
|
||||||
|
class="disabled:bg-gray-50 mb-4 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 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col justify-start md:flex-row md:space-x-4">
|
<div class="flex flex-col justify-start md:flex-row md:space-x-4">
|
||||||
<div class="w-full md:max-w-sm">
|
<div class="w-full md:max-w-sm">
|
||||||
<label for="org-street-address" class="block pb-1.5 leading-6">
|
<label for="org-street-address" class="block pb-1.5 leading-6">
|
||||||
|
|
@ -389,7 +439,7 @@ async function avatarUpload(e: Event) {
|
||||||
<button class="btn btn-secondary" @click="emit('cancel')">
|
<button class="btn btn-secondary" @click="emit('cancel')">
|
||||||
{{ $t("general.cancel") }}
|
{{ $t("general.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" @click="save">
|
<button class="btn btn-primary" data-cy="saveButton" @click="save">
|
||||||
{{ $t("general.save") }}
|
{{ $t("general.save") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { useUserStore } from "@/stores/user";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useEntities } from "@/services/entities";
|
import { useEntities } from "@/services/entities";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { displaySwissPhoneNumber } from "@/utils/phone";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -10,21 +12,20 @@ const user = useUserStore();
|
||||||
const { organisations } = useEntities();
|
const { organisations } = useEntities();
|
||||||
|
|
||||||
const privateAddress = computed(() => {
|
const privateAddress = computed(() => {
|
||||||
let addressText = `${user.street} ${user.street_number}`.trim();
|
const textParts = [];
|
||||||
if (user.postal_code || user.city) {
|
|
||||||
if (addressText.length) {
|
if (user.street || user.street_number) {
|
||||||
addressText += ", ";
|
textParts.push(`${user.street} ${user.street_number}`.trim());
|
||||||
}
|
|
||||||
addressText += `${user.postal_code} ${user.city}`;
|
|
||||||
}
|
|
||||||
if (user.country) {
|
|
||||||
if (addressText.length) {
|
|
||||||
addressText += ", ";
|
|
||||||
}
|
|
||||||
addressText += user.country.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return addressText.trim();
|
if (user.postal_code || user.city) {
|
||||||
|
textParts.push(`${user.postal_code} ${user.city}`);
|
||||||
|
}
|
||||||
|
if (textParts.length && user.country) {
|
||||||
|
textParts.push(user.country.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textParts;
|
||||||
});
|
});
|
||||||
|
|
||||||
const organisationName = computed(() => {
|
const organisationName = computed(() => {
|
||||||
|
|
@ -36,22 +37,25 @@ const organisationName = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgAddress = computed(() => {
|
const orgAddress = computed(() => {
|
||||||
let addressText =
|
const textParts = [];
|
||||||
`${user.organisation_street} ${user.organisation_street_number}`.trim();
|
|
||||||
if (user.organisation_postal_code || user.organisation_city) {
|
if (user.organisation_detail_name) {
|
||||||
if (addressText.length) {
|
textParts.push(user.organisation_detail_name);
|
||||||
addressText += ", ";
|
|
||||||
}
|
|
||||||
addressText += `${user.organisation_postal_code} ${user.organisation_city}`;
|
|
||||||
}
|
|
||||||
if (user.organisation_country) {
|
|
||||||
if (addressText.length) {
|
|
||||||
addressText += ", ";
|
|
||||||
}
|
|
||||||
addressText += user.organisation_country.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return addressText.trim();
|
if (user.organisation_street || user.organisation_street_number) {
|
||||||
|
textParts.push(
|
||||||
|
`${user.organisation_street} ${user.organisation_street_number}`.trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (user.organisation_postal_code || user.organisation_city) {
|
||||||
|
textParts.push(`${user.organisation_postal_code} ${user.organisation_city}`);
|
||||||
|
}
|
||||||
|
if (textParts.length && user.organisation_country) {
|
||||||
|
textParts.push(user.organisation_country.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textParts;
|
||||||
});
|
});
|
||||||
|
|
||||||
const invoiceAddress = computed(() => {
|
const invoiceAddress = computed(() => {
|
||||||
|
|
@ -67,20 +71,45 @@ const invoiceAddress = computed(() => {
|
||||||
<h3 class="mb-2">{{ $t("a.Persönliche Informationen") }}</h3>
|
<h3 class="mb-2">{{ $t("a.Persönliche Informationen") }}</h3>
|
||||||
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-8 sm:py-6">
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-8 sm:py-6">
|
||||||
<label class="block font-semibold leading-6">{{ $t("a.Vorname") }}</label>
|
<label class="block font-semibold leading-6">{{ $t("a.Vorname") }}</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">{{ user.first_name }}</div>
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="firstName">
|
||||||
|
{{ user.first_name }}
|
||||||
|
</div>
|
||||||
<label class="block font-semibold leading-6">{{ $t("a.Name") }}</label>
|
<label class="block font-semibold leading-6">{{ $t("a.Name") }}</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">{{ user.last_name }}</div>
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="lastName">
|
||||||
|
{{ user.last_name }}
|
||||||
|
</div>
|
||||||
<label class="block font-semibold leading-6">
|
<label class="block font-semibold leading-6">
|
||||||
{{ $t("a.E-Mail Adresse") }}
|
{{ $t("a.E-Mail Adresse") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">{{ user.email }}</div>
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="email">{{ user.email }}</div>
|
||||||
|
<label class="block font-semibold leading-6">
|
||||||
|
{{ $t("a.Telefonnummer") }}
|
||||||
|
</label>
|
||||||
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="phone">
|
||||||
|
<span v-if="user.phone_number">
|
||||||
|
{{ displaySwissPhoneNumber(user.phone_number) }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||||
|
</div>
|
||||||
|
<label class="block font-semibold leading-6">
|
||||||
|
{{ $t("a.Geburtsdatum") }}
|
||||||
|
</label>
|
||||||
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="birthDate">
|
||||||
|
<span v-if="user.birth_date">
|
||||||
|
{{ dayjs(user.birth_date).format("DD.MM.YYYY") }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||||
|
</div>
|
||||||
<label class="block font-semibold leading-6">
|
<label class="block font-semibold leading-6">
|
||||||
{{ $t("a.Privatadresse") }}
|
{{ $t("a.Privatadresse") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="privateAddress">
|
||||||
<template v-if="privateAddress">
|
<div v-if="privateAddress.length">
|
||||||
{{ privateAddress }}
|
<span v-for="(line, index) in privateAddress" :key="index">
|
||||||
</template>
|
{{ line }}
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -89,14 +118,19 @@ const invoiceAddress = computed(() => {
|
||||||
<h3 class="my-2">{{ $t("a.Geschäftsdaten") }}</h3>
|
<h3 class="my-2">{{ $t("a.Geschäftsdaten") }}</h3>
|
||||||
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-8 sm:py-6">
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-8 sm:py-6">
|
||||||
<label class="block font-semibold leading-6">{{ $t("a.Unternehmen") }}</label>
|
<label class="block font-semibold leading-6">{{ $t("a.Unternehmen") }}</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">{{ organisationName }}</div>
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="organisationDetailName">
|
||||||
|
{{ organisationName }}
|
||||||
|
</div>
|
||||||
<label class="block font-semibold leading-6">
|
<label class="block font-semibold leading-6">
|
||||||
{{ $t("a.Firmenanschrift") }}
|
{{ $t("a.Firmenanschrift") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<template v-if="orgAddress">
|
<div v-if="orgAddress" data-cy="organisationAddress">
|
||||||
{{ orgAddress }}
|
<span v-for="(line, index) in orgAddress" :key="index">
|
||||||
</template>
|
{{ line }}
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import VueDatePicker from "@vuepic/vue-datepicker";
|
||||||
|
import "@vuepic/vue-datepicker/dist/main.css";
|
||||||
|
import { defineProps, withDefaults, defineModel } from "vue";
|
||||||
|
|
||||||
|
const model = defineModel<string>();
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
noToday?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
startDate?: string;
|
||||||
|
maxDate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
required: false,
|
||||||
|
placeholder: "15.06.1982",
|
||||||
|
startDate: "1982-01-01",
|
||||||
|
maxDate: "2007-01-01",
|
||||||
|
});
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VueDatePicker
|
||||||
|
v-model="model"
|
||||||
|
format="dd.MM.yyyy"
|
||||||
|
model-type="yyyy-MM-dd"
|
||||||
|
name="date"
|
||||||
|
prevent-min-max-navigation
|
||||||
|
:required="props.required"
|
||||||
|
:enable-time-picker="false"
|
||||||
|
text-input
|
||||||
|
:no-today="props.noToday"
|
||||||
|
:max-date="props.maxDate"
|
||||||
|
:placeholder="props.placeholder"
|
||||||
|
:start-date="props.startDate"
|
||||||
|
:locale="userStore.language"
|
||||||
|
:cancel-text="$t('a.Abbrechen')"
|
||||||
|
:select-text="$t('a.Auswählen')"
|
||||||
|
></VueDatePicker>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dp__theme_light {
|
||||||
|
--dp-text-color: #585f63;
|
||||||
|
--dp-primary-color: #41b5fa;
|
||||||
|
--dp-border-color-focus: #3d6dcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--dp-font-family: "Buenos Aires" sans-serif;
|
||||||
|
--dp-border-radius: none;
|
||||||
|
--dp-font-size: 0.875rem;
|
||||||
|
--dp-cell-border-radius: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Course IDs
|
||||||
|
export const COURSE_TEST_ID = -1;
|
||||||
|
export const COURSE_UK = -3;
|
||||||
|
export const COURSE_VERSICHERUNGSVERMITTLERIN_ID = -4;
|
||||||
|
export const COURSE_UK_FR = -5;
|
||||||
|
export const COURSE_UK_TRAINING = -6;
|
||||||
|
export const COURSE_UK_TRAINING_FR = -7;
|
||||||
|
export const COURSE_UK_IT = -8;
|
||||||
|
export const COURSE_UK_TRAINING_IT = -9;
|
||||||
|
export const COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID = -10;
|
||||||
|
export const COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID = -11;
|
||||||
|
export const COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID = -12;
|
||||||
|
export const COURSE_MOTORFAHRZEUG_PRUEFUNG_ID = -13;
|
||||||
|
|
||||||
|
// Organization IDs
|
||||||
|
export const ORGANISATION_OTHER_BROKER_ID = 1;
|
||||||
|
export const ORGANISATION_OTHER_HEALTH_INSURANCE_ID = 2;
|
||||||
|
export const ORGANISATION_OTHER_PRIVATE_INSURANCE_ID = 3;
|
||||||
|
export const ORGANISATION_NO_COMPANY_ID = 31;
|
||||||
|
|
@ -13,6 +13,7 @@ import VerticalBarChart from "@/components/ui/VerticalBarChart.vue";
|
||||||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||||
import logger from "loglevel";
|
import logger from "loglevel";
|
||||||
import { reactive, ref } from "vue";
|
import { reactive, ref } from "vue";
|
||||||
|
import "@vuepic/vue-datepicker/dist/main.css";
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
checkboxValue: true,
|
checkboxValue: true,
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,6 @@ const submissionDeadline = computed(() => {
|
||||||
?.submission_deadline;
|
?.submission_deadline;
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXME daniel: `useRouteQuery` from usevue is currently the reason that we have to
|
|
||||||
// fix the version of @vueuse/router and @vueuse/core to 10.1.0
|
|
||||||
// it fails with version 10.2.0. I have a reminder to check out the situation
|
|
||||||
// at the end of July 2023
|
|
||||||
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
||||||
const stepIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
const stepIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import WizardPage from "@/components/onboarding/WizardPage.vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import { profileNextRoute } from "@/services/onboarding";
|
import { isOtherOrganisation, profileNextRoute } from "@/services/onboarding";
|
||||||
import { useEntities } from "@/services/entities";
|
import { useEntities } from "@/services/entities";
|
||||||
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
||||||
|
|
||||||
|
|
@ -13,9 +13,12 @@ const { t } = useTranslation();
|
||||||
|
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const { organisations } = useEntities();
|
const { organisations } = useEntities();
|
||||||
|
|
||||||
|
const organisationDetailName = ref<string>("");
|
||||||
|
|
||||||
const selectedOrganisation = ref({
|
const selectedOrganisation = ref({
|
||||||
id: 0,
|
id: 0,
|
||||||
name: t("a.Auswählen"),
|
name: t("a.Auswählen"),
|
||||||
|
|
@ -35,7 +38,11 @@ watch(
|
||||||
);
|
);
|
||||||
|
|
||||||
const validOrganisation = computed(() => {
|
const validOrganisation = computed(() => {
|
||||||
return selectedOrganisation.value.id !== 0;
|
const organisationSelected = selectedOrganisation.value.id !== 0;
|
||||||
|
const organisationNameSet =
|
||||||
|
!isOtherOrganisation(selectedOrganisation.value.id) ||
|
||||||
|
!!organisationDetailName.value.trim();
|
||||||
|
return organisationSelected && organisationNameSet;
|
||||||
});
|
});
|
||||||
|
|
||||||
const avatarError = ref(false);
|
const avatarError = ref(false);
|
||||||
|
|
@ -56,15 +63,21 @@ async function avatarUpload(e: Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(selectedOrganisation, async (organisation) => {
|
async function updateUserProfile() {
|
||||||
await user.updateUserProfile({
|
await user.updateUserProfile({
|
||||||
organisation: organisation.id,
|
organisation: selectedOrganisation.value.id,
|
||||||
|
organisation_detail_name: organisationDetailName.value.trim(),
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
const nextRoute = computed(() => {
|
const nextRoute = computed(() => {
|
||||||
return profileNextRoute(route.params.courseType);
|
return profileNextRoute(route.params.courseType);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function navigateNextRoute() {
|
||||||
|
await updateUserProfile();
|
||||||
|
await router.push({ name: nextRoute.value });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -86,6 +99,19 @@ const nextRoute = computed(() => {
|
||||||
|
|
||||||
<ItDropdownSelect v-model="selectedOrganisation" :items="organisations" />
|
<ItDropdownSelect v-model="selectedOrganisation" :items="organisations" />
|
||||||
|
|
||||||
|
<div v-if="isOtherOrganisation(selectedOrganisation.id)" class="my-8">
|
||||||
|
<label for="organisationDetailName" class="heading-3 block pb-1.5">
|
||||||
|
{{ $t("a.Firmenname") }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="organisationDetailName"
|
||||||
|
v-model="organisationDetailName"
|
||||||
|
type="text"
|
||||||
|
name="phone"
|
||||||
|
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 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-16 flex flex-col justify-between gap-12 lg:flex-row lg:gap-24">
|
<div class="mt-16 flex flex-col justify-between gap-12 lg:flex-row lg:gap-24">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-3">{{ $t("a.Profilbild") }}</h3>
|
<h3 class="mb-3">{{ $t("a.Profilbild") }}</h3>
|
||||||
|
|
@ -117,18 +143,16 @@ const nextRoute = computed(() => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<router-link v-slot="{ navigate }" :to="{ name: nextRoute }" custom>
|
<button
|
||||||
<button
|
:disabled="!validOrganisation"
|
||||||
:disabled="!validOrganisation"
|
class="btn-blue flex items-center"
|
||||||
class="btn-blue flex items-center"
|
role="link"
|
||||||
role="link"
|
data-cy="continue-button"
|
||||||
data-cy="continue-button"
|
@click="navigateNextRoute"
|
||||||
@click="navigate"
|
>
|
||||||
>
|
{{ $t("general.next") }}
|
||||||
{{ $t("general.next") }}
|
<it-icon-arrow-right class="it-icon ml-2 h-6 w-6" />
|
||||||
<it-icon-arrow-right class="it-icon ml-2 h-6 w-6" />
|
</button>
|
||||||
</button>
|
|
||||||
</router-link>
|
|
||||||
</template>
|
</template>
|
||||||
</WizardPage>
|
</WizardPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ import DatatransCembraDeviceFingerprint from "@/components/onboarding/DatatransC
|
||||||
import { getLocalSessionKey } from "@/statistics";
|
import { getLocalSessionKey } from "@/statistics";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { normalizeSwissPhoneNumber, validatePhoneNumber } from "@/utils/phone";
|
import { normalizeSwissPhoneNumber, validatePhoneNumber } from "@/utils/phone";
|
||||||
|
import {
|
||||||
|
ORGANISATION_NO_COMPANY_ID,
|
||||||
|
ORGANISATION_OTHER_BROKER_ID,
|
||||||
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||||
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||||
|
} from "@/consts";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
courseType: {
|
courseType: {
|
||||||
|
|
@ -31,11 +37,14 @@ const userOrganisationName = computed(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Those IDs do not represent a company
|
// Those IDs do not represent a company
|
||||||
// 1: Other broker
|
if (
|
||||||
// 2: Other insurance
|
[
|
||||||
// 3: Other private insurance
|
ORGANISATION_OTHER_BROKER_ID,
|
||||||
// 31: No company relation
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||||
if ([1, 2, 3, 31].includes(user.organisation)) {
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||||
|
ORGANISATION_NO_COMPANY_ID,
|
||||||
|
].includes(user.organisation)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,11 @@ function startEditMode() {
|
||||||
|
|
||||||
<div class="flex flex-grow flex-col space-y-4 px-8 py-8 md:px-16">
|
<div class="flex flex-grow flex-col space-y-4 px-8 py-8 md:px-16">
|
||||||
<div v-if="!editMode" class="flex justify-end space-x-4">
|
<div v-if="!editMode" class="flex justify-end space-x-4">
|
||||||
<button class="btn btn-secondary" @click="startEditMode">
|
<button
|
||||||
|
class="btn btn-secondary"
|
||||||
|
data-cy="editProfileButton"
|
||||||
|
@click="startEditMode"
|
||||||
|
>
|
||||||
{{ $t("a.Profil bearbeiten") }}
|
{{ $t("a.Profil bearbeiten") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
import { isString, startsWith } from "lodash";
|
import { isString, startsWith } from "lodash";
|
||||||
|
import {
|
||||||
|
ORGANISATION_OTHER_BROKER_ID,
|
||||||
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||||
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||||
|
} from "@/consts";
|
||||||
|
|
||||||
export function profileNextRoute(courseType: string | string[]) {
|
export function profileNextRoute(courseType: string | string[]) {
|
||||||
if (courseType === "uk") {
|
if (courseType === "uk") {
|
||||||
|
|
@ -10,3 +15,11 @@ export function profileNextRoute(courseType: string | string[]) {
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isOtherOrganisation(orgId: number) {
|
||||||
|
return [
|
||||||
|
ORGANISATION_OTHER_BROKER_ID,
|
||||||
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||||
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||||
|
].includes(orgId);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,22 +17,31 @@ describe("checkout.cy.js", () => {
|
||||||
|
|
||||||
cy.get('[data-cy="account-confirm-title"]').should(
|
cy.get('[data-cy="account-confirm-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Konto erstellen"
|
"Konto erstellen",
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="continue-button"]').click();
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="account-profile-title"]').should(
|
cy.get('[data-cy="account-profile-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Profil ergänzen"
|
"Profil ergänzen",
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="dropdown-select"]').click();
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
cy.get(
|
||||||
|
'[data-cy="dropdown-select-option-andere Krankenversicherer"]',
|
||||||
|
).click();
|
||||||
|
cy.get("#organisationDetailName").type("FdH GmbH");
|
||||||
cy.get('[data-cy="continue-button"]').click();
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
|
expect(u.organisation_detail_name).to.equal("FdH GmbH");
|
||||||
|
// 2 -> andere Krankenversicherer
|
||||||
|
expect(u.organisation).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
// Adressdaten ausfüllen
|
// Adressdaten ausfüllen
|
||||||
cy.get('[data-cy="account-checkout-title"]').should(
|
cy.get('[data-cy="account-checkout-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Lehrgang kaufen"
|
"Lehrgang kaufen",
|
||||||
);
|
);
|
||||||
cy.get("#street-address").type("Eggersmatt");
|
cy.get("#street-address").type("Eggersmatt");
|
||||||
cy.get("#street-number").type("32");
|
cy.get("#street-number").type("32");
|
||||||
|
|
@ -40,7 +49,7 @@ describe("checkout.cy.js", () => {
|
||||||
cy.get("#city").type("Zumholz");
|
cy.get("#city").type("Zumholz");
|
||||||
cy.get('[data-cy="add-company-address"]').click();
|
cy.get('[data-cy="add-company-address"]').click();
|
||||||
|
|
||||||
cy.get("#company-name").type("Iterativ GmbH");
|
// cy.get("#company-name").clear().type("Iterativ GmbH");
|
||||||
cy.get("#company-street-address").type("Brückfeldstrasse");
|
cy.get("#company-street-address").type("Brückfeldstrasse");
|
||||||
cy.get("#company-street-number").type("16");
|
cy.get("#company-street-number").type("16");
|
||||||
cy.get("#company-postal-code").type("3012");
|
cy.get("#company-postal-code").type("3012");
|
||||||
|
|
@ -60,7 +69,7 @@ describe("checkout.cy.js", () => {
|
||||||
expect(ci.country).to.equal("CH");
|
expect(ci.country).to.equal("CH");
|
||||||
|
|
||||||
expect(ci.invoice_address).to.equal("org");
|
expect(ci.invoice_address).to.equal("org");
|
||||||
expect(ci.organisation_detail_name).to.equal("Iterativ GmbH");
|
expect(ci.organisation_detail_name).to.equal("FdH GmbH");
|
||||||
expect(ci.organisation_street).to.equal("Brückfeldstrasse");
|
expect(ci.organisation_street).to.equal("Brückfeldstrasse");
|
||||||
expect(ci.organisation_street_number).to.equal("16");
|
expect(ci.organisation_street_number).to.equal("16");
|
||||||
expect(ci.organisation_postal_code).to.equal("3012");
|
expect(ci.organisation_postal_code).to.equal("3012");
|
||||||
|
|
@ -72,12 +81,32 @@ describe("checkout.cy.js", () => {
|
||||||
expect(ci.state).to.equal("ongoing");
|
expect(ci.state).to.equal("ongoing");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
|
expect(u.first_name).to.equal("Flasche");
|
||||||
|
expect(u.last_name).to.equal("Leer");
|
||||||
|
|
||||||
|
expect(u.street).to.equal("Eggersmatt");
|
||||||
|
expect(u.street_number).to.equal("32");
|
||||||
|
expect(u.postal_code).to.equal("1719");
|
||||||
|
expect(u.city).to.equal("Zumholz");
|
||||||
|
expect(u.country).to.equal("CH");
|
||||||
|
|
||||||
|
expect(u.invoice_address).to.equal("org");
|
||||||
|
expect(u.organisation_detail_name).to.equal("FdH GmbH");
|
||||||
|
expect(u.organisation_street).to.equal("Brückfeldstrasse");
|
||||||
|
expect(u.organisation_street_number).to.equal("16");
|
||||||
|
expect(u.organisation_postal_code).to.equal("3012");
|
||||||
|
expect(u.organisation_city).to.equal("Bern");
|
||||||
|
// 2 -> andere Krankenversicherer
|
||||||
|
expect(u.organisation).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
// pay
|
// pay
|
||||||
cy.get('[data-cy="pay-button"]').click();
|
cy.get('[data-cy="pay-button"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="checkout-success-title"]').should(
|
cy.get('[data-cy="checkout-success-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Gratuliere"
|
"Gratuliere",
|
||||||
);
|
);
|
||||||
// wait for payment callback
|
// wait for payment callback
|
||||||
cy.wait(3000);
|
cy.wait(3000);
|
||||||
|
|
@ -86,7 +115,7 @@ describe("checkout.cy.js", () => {
|
||||||
// back on dashboard page
|
// back on dashboard page
|
||||||
cy.get('[data-cy="db-course-title"]').should(
|
cy.get('[data-cy="db-course-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Versicherungsvermittler"
|
"Versicherungsvermittler",
|
||||||
);
|
);
|
||||||
|
|
||||||
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
||||||
|
|
@ -102,13 +131,13 @@ describe("checkout.cy.js", () => {
|
||||||
|
|
||||||
cy.get('[data-cy="account-confirm-title"]').should(
|
cy.get('[data-cy="account-confirm-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Konto erstellen"
|
"Konto erstellen",
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="continue-button"]').click();
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="account-profile-title"]').should(
|
cy.get('[data-cy="account-profile-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Profil ergänzen"
|
"Profil ergänzen",
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="dropdown-select"]').click();
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
||||||
|
|
@ -117,10 +146,10 @@ describe("checkout.cy.js", () => {
|
||||||
// Adressdaten ausfüllen
|
// Adressdaten ausfüllen
|
||||||
cy.get('[data-cy="account-checkout-title"]').should(
|
cy.get('[data-cy="account-checkout-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Lehrgang kaufen"
|
"Lehrgang kaufen",
|
||||||
);
|
);
|
||||||
|
|
||||||
cy.get('#paymentMethod').select('cembra_byjuno');
|
cy.get("#paymentMethod").select("cembra_byjuno");
|
||||||
|
|
||||||
cy.get("#street-address").type("Eggersmatt");
|
cy.get("#street-address").type("Eggersmatt");
|
||||||
cy.get("#street-number").type("32");
|
cy.get("#street-number").type("32");
|
||||||
|
|
@ -132,32 +161,38 @@ describe("checkout.cy.js", () => {
|
||||||
|
|
||||||
cy.get('[data-cy="continue-pay"]').click();
|
cy.get('[data-cy="continue-pay"]').click();
|
||||||
|
|
||||||
cy.loadExternalApiRequestLog("request_username", "empty@example.com").then((entry) => {
|
cy.loadExternalApiRequestLog("request_username", "empty@example.com").then(
|
||||||
// ends with "/v1/transactions""
|
(entry) => {
|
||||||
expect(entry.api_url).to.contain("/v1/transactions");
|
// ends with "/v1/transactions""
|
||||||
expect(entry.request_username).to.contain("empty@example.com");
|
expect(entry.api_url).to.contain("/v1/transactions");
|
||||||
|
expect(entry.request_username).to.contain("empty@example.com");
|
||||||
|
|
||||||
expect(entry.api_request_data.amount).to.equal(32400);
|
expect(entry.api_request_data.amount).to.equal(32400);
|
||||||
expect(entry.api_request_data.currency).to.equal("CHF");
|
expect(entry.api_request_data.currency).to.equal("CHF");
|
||||||
expect(entry.api_request_data.autoSettle).to.equal(true);
|
expect(entry.api_request_data.autoSettle).to.equal(true);
|
||||||
|
|
||||||
expect(entry.api_request_data.customer.firstName).to.equal("Flasche");
|
expect(entry.api_request_data.customer.firstName).to.equal("Flasche");
|
||||||
expect(entry.api_request_data.customer.lastName).to.equal("Leer");
|
expect(entry.api_request_data.customer.lastName).to.equal("Leer");
|
||||||
expect(entry.api_request_data.customer.street).to.equal("Eggersmatt 32");
|
expect(entry.api_request_data.customer.street).to.equal(
|
||||||
expect(entry.api_request_data.customer.zipCode).to.equal("1719");
|
"Eggersmatt 32",
|
||||||
expect(entry.api_request_data.customer.city).to.equal("Zumholz");
|
);
|
||||||
expect(entry.api_request_data.customer.country).to.equal("CH");
|
expect(entry.api_request_data.customer.zipCode).to.equal("1719");
|
||||||
expect(entry.api_request_data.customer.type).to.equal("P");
|
expect(entry.api_request_data.customer.city).to.equal("Zumholz");
|
||||||
expect(entry.api_request_data.customer.phone).to.equal("+41792018586");
|
expect(entry.api_request_data.customer.country).to.equal("CH");
|
||||||
expect(entry.api_request_data.customer.birthDate).to.equal("1982-06-09");
|
expect(entry.api_request_data.customer.type).to.equal("P");
|
||||||
|
expect(entry.api_request_data.customer.phone).to.equal("+41792018586");
|
||||||
|
expect(entry.api_request_data.customer.birthDate).to.equal(
|
||||||
|
"1982-06-09",
|
||||||
|
);
|
||||||
|
|
||||||
expect(entry.api_request_data.INT.repaymentType).to.equal(3);
|
expect(entry.api_request_data.INT.repaymentType).to.equal(3);
|
||||||
expect(entry.api_request_data.INT.riskOwner).to.equal("IJ");
|
expect(entry.api_request_data.INT.riskOwner).to.equal("IJ");
|
||||||
expect(entry.api_request_data.INT.subtype).to.equal("INVOICE");
|
expect(entry.api_request_data.INT.subtype).to.equal("INVOICE");
|
||||||
expect(entry.api_request_data.INT.deviceFingerprintId).to.not.be.empty;
|
expect(entry.api_request_data.INT.deviceFingerprintId).to.not.be.empty;
|
||||||
|
|
||||||
expect(true).to.be.true;
|
expect(true).to.be.true;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// check that results are stored on server
|
// check that results are stored on server
|
||||||
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
||||||
|
|
@ -183,5 +218,23 @@ describe("checkout.cy.js", () => {
|
||||||
expect(ci.ip_address).to.not.be.empty;
|
expect(ci.ip_address).to.not.be.empty;
|
||||||
expect(ci.device_fingerprint_session_key).to.not.be.empty;
|
expect(ci.device_fingerprint_session_key).to.not.be.empty;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
|
expect(u.first_name).to.equal("Flasche");
|
||||||
|
expect(u.last_name).to.equal("Leer");
|
||||||
|
|
||||||
|
expect(u.street).to.equal("Eggersmatt");
|
||||||
|
expect(u.street_number).to.equal("32");
|
||||||
|
expect(u.postal_code).to.equal("1719");
|
||||||
|
expect(u.city).to.equal("Zumholz");
|
||||||
|
expect(u.country).to.equal("CH");
|
||||||
|
expect(u.phone_number).to.equal("+41792018586");
|
||||||
|
expect(u.birth_date).to.equal("1982-06-09");
|
||||||
|
|
||||||
|
expect(u.invoice_address).to.equal("prv");
|
||||||
|
|
||||||
|
// 7 -> Baloise
|
||||||
|
expect(u.organisation).to.equal(7);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { TEST_USER_EMPTY_ID } from "../../consts";
|
||||||
|
import { login } from "../helpers";
|
||||||
|
|
||||||
|
describe("personalProfile.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset");
|
||||||
|
|
||||||
|
login("empty@example.com", "test");
|
||||||
|
cy.visit("/profile");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can edit all profile fields", () => {
|
||||||
|
cy.get('[data-cy="editProfileButton"]').click();
|
||||||
|
|
||||||
|
cy.get("#phone").type("079 201 85 86");
|
||||||
|
cy.get('[data-test="dp-input"]').type("09.06.1982{enter}");
|
||||||
|
|
||||||
|
cy.get("#street-address").type("Hafen");
|
||||||
|
cy.get("#street-number").type("123");
|
||||||
|
cy.get("#postal-code").type("DE-20095");
|
||||||
|
cy.get("#city").type("Hamburg");
|
||||||
|
cy.get("#country").select("DE");
|
||||||
|
|
||||||
|
// andere broker
|
||||||
|
cy.get("#organisation").select("1");
|
||||||
|
|
||||||
|
cy.get("#org-detail-name").type("Judihui GmbH");
|
||||||
|
cy.get("#org-street-address").type("Auf der Alm");
|
||||||
|
cy.get("#org-street-number").type("17");
|
||||||
|
cy.get("#org-postal-code").type("AT-6020");
|
||||||
|
cy.get("#org-city").type("Innsbruck");
|
||||||
|
cy.get("#org-country").select("AT");
|
||||||
|
|
||||||
|
cy.get("#invoice-address-organisation").click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="saveButton"]').click();
|
||||||
|
|
||||||
|
// check displayed data
|
||||||
|
cy.get('[data-cy="firstName"]').should("contain", "Flasche");
|
||||||
|
cy.get('[data-cy="lastName"]').should("contain", "Leer");
|
||||||
|
cy.get('[data-cy="email"]').should("contain", "empty@example.com");
|
||||||
|
cy.get('[data-cy="phone"]').should("contain", "079 201 85 86");
|
||||||
|
cy.get('[data-cy="birthDate"]').should("contain", "09.06.1982");
|
||||||
|
|
||||||
|
cy.get('[data-cy="privateAddress"]').should("contain", "Hafen 123");
|
||||||
|
cy.get('[data-cy="privateAddress"]').should("contain", "DE-20095 Hamburg");
|
||||||
|
cy.get('[data-cy="privateAddress"]').should("contain", "Deutschland");
|
||||||
|
|
||||||
|
cy.get('[data-cy="organisationDetailName"]').should(
|
||||||
|
"contain",
|
||||||
|
"andere Broker",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="organisationAddress"]').should("contain", "Judihui GmbH");
|
||||||
|
cy.get('[data-cy="organisationAddress"]').should(
|
||||||
|
"contain",
|
||||||
|
"Auf der Alm 17",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="organisationAddress"]').should(
|
||||||
|
"contain",
|
||||||
|
"AT-6020 Innsbruck",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="organisationAddress"]').should("contain", "Österreich");
|
||||||
|
|
||||||
|
// check stored data
|
||||||
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
|
expect(u.first_name).to.equal("Flasche");
|
||||||
|
expect(u.last_name).to.equal("Leer");
|
||||||
|
|
||||||
|
expect(u.street).to.equal("Hafen");
|
||||||
|
expect(u.street_number).to.equal("123");
|
||||||
|
expect(u.postal_code).to.equal("DE-20095");
|
||||||
|
expect(u.city).to.equal("Hamburg");
|
||||||
|
expect(u.country).to.equal("DE");
|
||||||
|
|
||||||
|
expect(u.invoice_address).to.equal("org");
|
||||||
|
expect(u.organisation_detail_name).to.equal("Judihui GmbH");
|
||||||
|
expect(u.organisation_street).to.equal("Auf der Alm");
|
||||||
|
expect(u.organisation_street_number).to.equal("17");
|
||||||
|
expect(u.organisation_postal_code).to.equal("AT-6020");
|
||||||
|
expect(u.organisation_city).to.equal("Innsbruck");
|
||||||
|
// 1 -> andere Broker
|
||||||
|
expect(u.organisation).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -178,6 +178,17 @@ Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add("loadUser", (key, value) => {
|
||||||
|
return loadObjectJson(
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
"vbv_lernwelt.core.models.User",
|
||||||
|
"vbv_lernwelt.core.serializers.CypressUserSerializer",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("makeSelfEvaluation", (answers) => {
|
Cypress.Commands.add("makeSelfEvaluation", (answers) => {
|
||||||
for (let i = 0; i < answers.length; i++) {
|
for (let i = 0; i < answers.length; i++) {
|
||||||
const answer = answers[i];
|
const answer = answers[i];
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -333,7 +333,6 @@ X_FRAME_OPTIONS = "DENY"
|
||||||
# EMAIL
|
# EMAIL
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||||
# FIXME how to send emails?
|
|
||||||
EMAIL_BACKEND = env(
|
EMAIL_BACKEND = env(
|
||||||
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
|
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
|
||||||
)
|
)
|
||||||
|
|
@ -683,10 +682,12 @@ if APP_ENVIRONMENT.startswith("prod"):
|
||||||
DATATRANS_PAY_URL = "https://pay.datatrans.com"
|
DATATRANS_PAY_URL = "https://pay.datatrans.com"
|
||||||
else:
|
else:
|
||||||
DATATRANS_API_ENDPOINT = env(
|
DATATRANS_API_ENDPOINT = env(
|
||||||
"DATATRANS_API_ENDPOINT", default="https://api.sandbox.datatrans.com"
|
"DATATRANS_API_ENDPOINT",
|
||||||
|
default="http://localhost:8000/server/fakeapi/datatrans/api",
|
||||||
)
|
)
|
||||||
DATATRANS_PAY_URL = env(
|
DATATRANS_PAY_URL = env(
|
||||||
"DATATRANS_PAY_URL", default="https://pay.sandbox.datatrans.com"
|
"DATATRANS_PAY_URL",
|
||||||
|
default="http://localhost:8000/server/fakeapi/datatrans/pay",
|
||||||
)
|
)
|
||||||
|
|
||||||
# default settings for python sftpserver test-server
|
# default settings for python sftpserver test-server
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b"
|
||||||
TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
||||||
TEST_USER_EMPTY_ID = "daecbabe-4ab9-4edf-a71f-4119042ccb02"
|
TEST_USER_EMPTY_ID = "daecbabe-4ab9-4edf-a71f-4119042ccb02"
|
||||||
|
TEST_USER_DATATRANS_HANNA_ID = "6bec1a0d-f852-47aa-a4de-072df6e07ad1"
|
||||||
|
|
||||||
TEST_COURSE_SESSION_BERN_ID = -1
|
TEST_COURSE_SESSION_BERN_ID = -1
|
||||||
TEST_COURSE_SESSION_ZURICH_ID = -2
|
TEST_COURSE_SESSION_ZURICH_ID = -2
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from django.contrib.auth.models import Group, Permission
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from environs import Env
|
from environs import Env
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.model_utils import add_countries
|
||||||
from vbv_lernwelt.media_files.models import UserImage
|
from vbv_lernwelt.media_files.models import UserImage
|
||||||
|
|
||||||
env = Env()
|
env = Env()
|
||||||
|
|
@ -20,6 +21,7 @@ from vbv_lernwelt.core.constants import (
|
||||||
TEST_SUPERVISOR1_USER_ID,
|
TEST_SUPERVISOR1_USER_ID,
|
||||||
TEST_TRAINER1_USER_ID,
|
TEST_TRAINER1_USER_ID,
|
||||||
TEST_TRAINER2_USER_ID,
|
TEST_TRAINER2_USER_ID,
|
||||||
|
TEST_USER_DATATRANS_HANNA_ID,
|
||||||
TEST_USER_EMPTY_ID,
|
TEST_USER_EMPTY_ID,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
|
|
@ -78,7 +80,30 @@ default_users = [
|
||||||
AVATAR_DIR = settings.APPS_DIR / "static" / "avatars"
|
AVATAR_DIR = settings.APPS_DIR / "static" / "avatars"
|
||||||
|
|
||||||
|
|
||||||
|
def create_datatrans_hanna_user():
|
||||||
|
hanna, _ = User.objects.get_or_create(
|
||||||
|
id=TEST_USER_DATATRANS_HANNA_ID,
|
||||||
|
)
|
||||||
|
hanna.username = "datatrans.hanna.vbv@example.com"
|
||||||
|
hanna.email = "datatrans.hanna.vbv@example.com"
|
||||||
|
hanna.language = "de"
|
||||||
|
hanna.first_name = "Hanna"
|
||||||
|
hanna.last_name = "Vbv"
|
||||||
|
hanna.street = "Bahnstrasse"
|
||||||
|
hanna.street_number = "2"
|
||||||
|
hanna.postal_code = "8603"
|
||||||
|
hanna.city = "Schwerzenbach"
|
||||||
|
hanna.country_id = "CH"
|
||||||
|
hanna.birth_date = "1970-01-01"
|
||||||
|
hanna.phone_number = "+41792018586"
|
||||||
|
hanna.password = make_password("test")
|
||||||
|
hanna.save()
|
||||||
|
return hanna
|
||||||
|
|
||||||
|
|
||||||
def create_default_users(default_password="test", set_avatar=False):
|
def create_default_users(default_password="test", set_avatar=False):
|
||||||
|
add_countries(small_set=True)
|
||||||
|
|
||||||
admin_group, created = Group.objects.get_or_create(name="admin_group")
|
admin_group, created = Group.objects.get_or_create(name="admin_group")
|
||||||
_content_creator_group, _created = Group.objects.get_or_create(
|
_content_creator_group, _created = Group.objects.get_or_create(
|
||||||
name="content_creator_grop"
|
name="content_creator_grop"
|
||||||
|
|
@ -202,6 +227,8 @@ def create_default_users(default_password="test", set_avatar=False):
|
||||||
language="de",
|
language="de",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hanna = create_datatrans_hanna_user()
|
||||||
|
|
||||||
for user_data in default_users:
|
for user_data in default_users:
|
||||||
_create_student_user(**user_data)
|
_create_student_user(**user_data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ from vbv_lernwelt.core.constants import (
|
||||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||||
TEST_STUDENT3_USER_ID,
|
TEST_STUDENT3_USER_ID,
|
||||||
TEST_TRAINER1_USER_ID,
|
TEST_TRAINER1_USER_ID,
|
||||||
|
TEST_USER_DATATRANS_HANNA_ID,
|
||||||
TEST_USER_EMPTY_ID,
|
TEST_USER_EMPTY_ID,
|
||||||
)
|
)
|
||||||
|
from vbv_lernwelt.core.create_default_users import create_datatrans_hanna_user
|
||||||
from vbv_lernwelt.core.models import Organisation, User
|
from vbv_lernwelt.core.models import Organisation, User
|
||||||
from vbv_lernwelt.course.consts import (
|
from vbv_lernwelt.course.consts import (
|
||||||
COURSE_TEST_ID,
|
COURSE_TEST_ID,
|
||||||
|
|
@ -159,6 +161,9 @@ def command(
|
||||||
password=make_password("test"),
|
password=make_password("test"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
User.objects.filter(id=TEST_USER_DATATRANS_HANNA_ID).delete()
|
||||||
|
create_datatrans_hanna_user()
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("truncate core_securityrequestresponselog;")
|
cursor.execute("truncate core_securityrequestresponselog;")
|
||||||
cursor.execute("truncate core_externalapirequestlog;")
|
cursor.execute("truncate core_externalapirequestlog;")
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,9 @@ class User(AbstractUser):
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# fields gathered from cembra pay form
|
|
||||||
birth_date = models.DateField(null=True, blank=True)
|
birth_date = models.DateField(null=True, blank=True)
|
||||||
|
|
||||||
|
# phone number should be stored in the format +41792018586 (not validated)
|
||||||
phone_number = models.CharField(max_length=255, blank=True, default="")
|
phone_number = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
|
||||||
# is only set by abacus invoice export code
|
# is only set by abacus invoice export code
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,12 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class CypressUserSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class OrganisationSerializer(serializers.ModelSerializer):
|
class OrganisationSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(source="organisation_id", read_only=True)
|
id = serializers.IntegerField(source="organisation_id", read_only=True)
|
||||||
name = serializers.SerializerMethodField()
|
name = serializers.SerializerMethodField()
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,10 @@ UK_COURSE_IDS = [
|
||||||
COURSE_UK_TRAINING_FR,
|
COURSE_UK_TRAINING_FR,
|
||||||
COURSE_UK_TRAINING_IT,
|
COURSE_UK_TRAINING_IT,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Organization IDs
|
||||||
|
ORGANISATION_OTHER_BROKER_ID = 1
|
||||||
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID = 2
|
||||||
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID = 3
|
||||||
|
ORGANISATION_NO_COMPANY_ID = 31
|
||||||
|
|
|
||||||
|
|
@ -765,7 +765,7 @@ def create_or_update_course_session_assignment(
|
||||||
csa.submission_deadline.save()
|
csa.submission_deadline.save()
|
||||||
csa.evaluation_deadline.start = timezone.make_aware(
|
csa.evaluation_deadline.start = timezone.make_aware(
|
||||||
start
|
start
|
||||||
) + timezone.timedelta(days=45)
|
) + timezone.timedelta(days=60)
|
||||||
csa.evaluation_deadline.end = None
|
csa.evaluation_deadline.end = None
|
||||||
csa.evaluation_deadline.save()
|
csa.evaluation_deadline.save()
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import uuid
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import structlog
|
import structlog
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from vbv_lernwelt.core.admin import User
|
from vbv_lernwelt.core.admin import User
|
||||||
from vbv_lernwelt.shop.const import VV_PRODUCT_NUMBER
|
|
||||||
from vbv_lernwelt.shop.datatrans.datatrans_api_client import DatatransApiClient
|
from vbv_lernwelt.shop.datatrans.datatrans_api_client import DatatransApiClient
|
||||||
from vbv_lernwelt.shop.models import CheckoutState
|
from vbv_lernwelt.shop.models import CheckoutState
|
||||||
|
|
||||||
|
|
@ -73,6 +71,7 @@ def is_signature_valid(
|
||||||
|
|
||||||
def init_datatrans_transaction(
|
def init_datatrans_transaction(
|
||||||
user: User,
|
user: User,
|
||||||
|
refno: str,
|
||||||
amount_chf_centimes: int,
|
amount_chf_centimes: int,
|
||||||
redirect_url_success: str,
|
redirect_url_success: str,
|
||||||
redirect_url_error: str,
|
redirect_url_error: str,
|
||||||
|
|
@ -91,8 +90,8 @@ def init_datatrans_transaction(
|
||||||
"amount": amount_chf_centimes,
|
"amount": amount_chf_centimes,
|
||||||
"currency": "CHF",
|
"currency": "CHF",
|
||||||
"language": user.language,
|
"language": user.language,
|
||||||
"refno": str(uuid.uuid4()),
|
"refno": str(refno),
|
||||||
"refno2": refno2,
|
"refno2": str(refno2),
|
||||||
"webhook": {"url": webhook_url},
|
"webhook": {"url": webhook_url},
|
||||||
"redirect": {
|
"redirect": {
|
||||||
"successUrl": redirect_url_success,
|
"successUrl": redirect_url_success,
|
||||||
|
|
@ -101,9 +100,8 @@ def init_datatrans_transaction(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# FIXME: test with working cembra byjuno invoice customer?
|
if with_cembra_byjuno_invoice:
|
||||||
# if with_cembra_byjuno_invoice:
|
payload["paymentMethods"] = ["INT"]
|
||||||
# payload["paymentMethods"] = ["INT"]
|
|
||||||
if datatrans_customer_data:
|
if datatrans_customer_data:
|
||||||
payload["customer"] = datatrans_customer_data
|
payload["customer"] = datatrans_customer_data
|
||||||
if datatrans_int_data:
|
if datatrans_int_data:
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,8 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertTrue(
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/1234567890",
|
response.json()["next_step_url"].endswith("v1/start/1234567890")
|
||||||
response.json()["next_step_url"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ci = CheckoutInformation.objects.first()
|
ci = CheckoutInformation.objects.first()
|
||||||
|
|
@ -154,9 +153,11 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
0,
|
1,
|
||||||
CheckoutInformation.objects.count(),
|
CheckoutInformation.objects.count(),
|
||||||
)
|
)
|
||||||
|
ci = CheckoutInformation.objects.first()
|
||||||
|
self.assertEqual(ci.state, CheckoutState.FAILED)
|
||||||
|
|
||||||
def test_checkout_already_paid(self):
|
def test_checkout_already_paid(self):
|
||||||
# GIVEN
|
# GIVEN
|
||||||
|
|
@ -217,9 +218,8 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertTrue(
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id_next}",
|
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id_next}")
|
||||||
response.json()["next_step_url"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# check that we have two checkouts
|
# check that we have two checkouts
|
||||||
|
|
@ -277,9 +277,8 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertTrue(
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id}")
|
||||||
response.json()["next_step_url"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch("vbv_lernwelt.shop.views.init_datatrans_transaction")
|
@patch("vbv_lernwelt.shop.views.init_datatrans_transaction")
|
||||||
|
|
@ -310,7 +309,6 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertTrue(
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id}")
|
||||||
response.json()["next_step_url"],
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,8 @@ class DatatransServiceTest(TestCase):
|
||||||
|
|
||||||
@override_settings(DATATRANS_BASIC_AUTH_KEY="BASIC_AUTH_KEY")
|
@override_settings(DATATRANS_BASIC_AUTH_KEY="BASIC_AUTH_KEY")
|
||||||
@patch("vbv_lernwelt.shop.services.requests.post")
|
@patch("vbv_lernwelt.shop.services.requests.post")
|
||||||
@patch("vbv_lernwelt.shop.services.uuid.uuid4")
|
def test_init_transaction_201(self, mock_post):
|
||||||
def test_init_transaction_201(self, mock_uuid, mock_post):
|
|
||||||
# GIVEN
|
# GIVEN
|
||||||
mock_uuid.return_value = uuid.uuid4()
|
|
||||||
mock_post.return_value.status_code = 201
|
mock_post.return_value.status_code = 201
|
||||||
mock_post.return_value.json.return_value = {
|
mock_post.return_value.json.return_value = {
|
||||||
"transactionId": 1234567890,
|
"transactionId": 1234567890,
|
||||||
|
|
@ -38,6 +36,7 @@ class DatatransServiceTest(TestCase):
|
||||||
# WHEN
|
# WHEN
|
||||||
transaction_id = init_datatrans_transaction(
|
transaction_id = init_datatrans_transaction(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
refno="123321",
|
||||||
amount_chf_centimes=324_30,
|
amount_chf_centimes=324_30,
|
||||||
redirect_url_success=f"{REDIRECT_URL}/success",
|
redirect_url_success=f"{REDIRECT_URL}/success",
|
||||||
redirect_url_error=f"{REDIRECT_URL}/error",
|
redirect_url_error=f"{REDIRECT_URL}/error",
|
||||||
|
|
@ -64,11 +63,12 @@ class DatatransServiceTest(TestCase):
|
||||||
with self.assertRaises(InitTransactionException):
|
with self.assertRaises(InitTransactionException):
|
||||||
init_datatrans_transaction(
|
init_datatrans_transaction(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
refno="123321",
|
||||||
amount_chf_centimes=324_30,
|
amount_chf_centimes=324_30,
|
||||||
redirect_url_success=f"/success",
|
redirect_url_success="/success",
|
||||||
redirect_url_error=f"/error",
|
redirect_url_error="/error",
|
||||||
redirect_url_cancel=f"/cancel",
|
redirect_url_cancel="/cancel",
|
||||||
webhook_url=f"/webhook",
|
webhook_url="/webhook",
|
||||||
refno2="",
|
refno2="",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -80,7 +80,4 @@ class DatatransServiceTest(TestCase):
|
||||||
url = get_payment_url(transaction_id)
|
url = get_payment_url(transaction_id)
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(
|
self.assertTrue(url.endswith(f"v1/start/{transaction_id}"))
|
||||||
url,
|
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ def checkout_vv(request):
|
||||||
sku = request.data["product"]
|
sku = request.data["product"]
|
||||||
base_redirect_url = request.data["redirect_url"]
|
base_redirect_url = request.data["redirect_url"]
|
||||||
|
|
||||||
log.info(f"Checkout requested: sku", user_id=request.user.id, sku=sku)
|
log.info("Checkout requested: sku", user_id=request.user.id, sku=sku)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
product = Product.objects.get(sku=sku)
|
product = Product.objects.get(sku=sku)
|
||||||
|
|
@ -124,6 +124,38 @@ def checkout_vv(request):
|
||||||
disable_save="fakeapi" in settings.DATATRANS_API_ENDPOINT
|
disable_save="fakeapi" in settings.DATATRANS_API_ENDPOINT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
address_data = request.data["address"]
|
||||||
|
country_code = address_data.pop("country_code")
|
||||||
|
address_data["country_id"] = country_code
|
||||||
|
|
||||||
|
organisation_country_code = "CH"
|
||||||
|
if "organisation_country_code" in address_data:
|
||||||
|
organisation_country_code = address_data.pop("organisation_country_code")
|
||||||
|
address_data["organisation_country_id"] = organisation_country_code
|
||||||
|
|
||||||
|
if "birth_date" in address_data and address_data["birth_date"]:
|
||||||
|
address_data["birth_date"] = date.fromisoformat(address_data["birth_date"])
|
||||||
|
|
||||||
|
checkout_info = CheckoutInformation.objects.create(
|
||||||
|
user=request.user,
|
||||||
|
state=CheckoutState.ONGOING,
|
||||||
|
# product
|
||||||
|
product_sku=sku,
|
||||||
|
product_price=product.price,
|
||||||
|
product_name=product.name,
|
||||||
|
product_description=product.description,
|
||||||
|
email=email,
|
||||||
|
ip_address=ip_address,
|
||||||
|
cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
||||||
|
device_fingerprint_session_key=request.data.get(
|
||||||
|
"device_fingerprint_session_key", ""
|
||||||
|
),
|
||||||
|
# address
|
||||||
|
**request.data["address"],
|
||||||
|
)
|
||||||
|
|
||||||
|
checkout_info.set_increment_abacus_order_id()
|
||||||
|
|
||||||
refno2 = f"{request.user.abacus_debitor_number}_{VV_PRODUCT_NUMBER}"
|
refno2 = f"{request.user.abacus_debitor_number}_{VV_PRODUCT_NUMBER}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -138,10 +170,10 @@ def checkout_vv(request):
|
||||||
"street": f'{request.data["address"]["street"]} {request.data["address"]["street_number"]}',
|
"street": f'{request.data["address"]["street"]} {request.data["address"]["street_number"]}',
|
||||||
"city": request.data["address"]["city"],
|
"city": request.data["address"]["city"],
|
||||||
"zipCode": request.data["address"]["postal_code"],
|
"zipCode": request.data["address"]["postal_code"],
|
||||||
"country": request.data["address"]["country_code"],
|
"country": request.data["address"]["country_id"],
|
||||||
"phone": request.data["address"]["phone_number"],
|
"phone": request.data["address"]["phone_number"],
|
||||||
"email": email,
|
"email": email,
|
||||||
"birthDate": request.data["address"]["birth_date"],
|
"birthDate": str(request.data["address"]["birth_date"]),
|
||||||
"language": request.user.language,
|
"language": request.user.language,
|
||||||
"ipAddress": ip_address,
|
"ipAddress": ip_address,
|
||||||
"type": "P",
|
"type": "P",
|
||||||
|
|
@ -154,6 +186,7 @@ def checkout_vv(request):
|
||||||
}
|
}
|
||||||
transaction_id = init_datatrans_transaction(
|
transaction_id = init_datatrans_transaction(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
|
refno=str(checkout_info.abacus_order_id),
|
||||||
amount_chf_centimes=product.price,
|
amount_chf_centimes=product.price,
|
||||||
redirect_url_success=checkout_success_url(
|
redirect_url_success=checkout_success_url(
|
||||||
base_url=base_redirect_url, product_sku=sku
|
base_url=base_redirect_url, product_sku=sku
|
||||||
|
|
@ -170,6 +203,8 @@ def checkout_vv(request):
|
||||||
with_cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
with_cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
||||||
)
|
)
|
||||||
except InitTransactionException as e:
|
except InitTransactionException as e:
|
||||||
|
checkout_info.state = CheckoutState.FAILED.value
|
||||||
|
checkout_info.save()
|
||||||
if not settings.DEBUG:
|
if not settings.DEBUG:
|
||||||
log.error("Transaction initiation failed", exc_info=True, error=str(e))
|
log.error("Transaction initiation failed", exc_info=True, error=str(e))
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
|
|
@ -180,36 +215,8 @@ def checkout_vv(request):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
address_data = request.data["address"]
|
checkout_info.transaction_id = transaction_id
|
||||||
country_code = address_data.pop("country_code")
|
checkout_info.save()
|
||||||
address_data["country_id"] = country_code
|
|
||||||
|
|
||||||
organisation_country_code = "CH"
|
|
||||||
if "organisation_country_code" in address_data:
|
|
||||||
organisation_country_code = address_data.pop("organisation_country_code")
|
|
||||||
address_data["organisation_country_id"] = organisation_country_code
|
|
||||||
|
|
||||||
if "birth_date" in address_data and address_data["birth_date"]:
|
|
||||||
address_data["birth_date"] = date.fromisoformat(address_data["birth_date"])
|
|
||||||
|
|
||||||
checkout_info = CheckoutInformation.objects.create(
|
|
||||||
user=request.user,
|
|
||||||
state=CheckoutState.ONGOING,
|
|
||||||
transaction_id=transaction_id,
|
|
||||||
# product
|
|
||||||
product_sku=sku,
|
|
||||||
product_price=product.price,
|
|
||||||
product_name=product.name,
|
|
||||||
product_description=product.description,
|
|
||||||
email=email,
|
|
||||||
ip_address=ip_address,
|
|
||||||
cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
|
||||||
device_fingerprint_session_key=request.data.get(
|
|
||||||
"device_fingerprint_session_key", ""
|
|
||||||
),
|
|
||||||
# address
|
|
||||||
**request.data["address"],
|
|
||||||
)
|
|
||||||
|
|
||||||
return next_step_response(url=get_payment_url(transaction_id))
|
return next_step_response(url=get_payment_url(transaction_id))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue