Merged develop into feature/VBV-707-spinner
This commit is contained in:
commit
13528f5394
|
|
@ -97,6 +97,7 @@ js-linting: &js-linting
|
|||
|
||||
default-steps: &default-steps
|
||||
- parallel:
|
||||
- step: *e2e
|
||||
- step: *e2e
|
||||
- step: *e2e
|
||||
- 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_REGION_NAME": "eu-central-1",
|
||||
"AWS_STORAGE_BUCKET_NAME": "myvbv-dev.iterativ.ch",
|
||||
"DATATRANS_HMAC_KEY": env.str("DATATRANS_HMAC_KEY", ""),
|
||||
"DATATRANS_BASIC_AUTH_KEY": env.str("DATATRANS_BASIC_AUTH_KEY", ""),
|
||||
"DATATRANS_HMAC_KEY": env.str("PIPELINES_DATATRANS_HMAC_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",
|
||||
"IT_DJANGO_DEBUG": "false",
|
||||
"IT_SERVE_VUE": "false",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const orgAddress = computed({
|
|||
for="company-name"
|
||||
class="block text-sm font-medium leading-6 text-gray-900"
|
||||
>
|
||||
{{ $t("a.Name") }}
|
||||
{{ $t("a.Firmenname") }}
|
||||
</label>
|
||||
<div class="mt-2">
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useEntities } from "@/services/entities";
|
||||
import VueDatePicker from "@vuepic/vue-datepicker";
|
||||
import "@vuepic/vue-datepicker/dist/main.css";
|
||||
import { t } from "i18next";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import ItDatePicker from "@/components/ui/ItDatePicker.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: {
|
||||
|
|
@ -25,20 +24,12 @@ const props = defineProps<{
|
|||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const { countries } = useEntities();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const paymentMethods = [
|
||||
{ value: "credit_card", label: t("a.Debit-/Kreditkarte/Twint") },
|
||||
{ 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({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
|
|
@ -234,40 +225,8 @@ const address = computed({
|
|||
{{ $t("a.Geburtsdatum") }}
|
||||
</label>
|
||||
<div class="mt-2">
|
||||
<VueDatePicker
|
||||
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>
|
||||
<ItDatePicker v-model="address.birth_date"></ItDatePicker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 { ref } from "vue";
|
||||
import { type User, useUserStore } from "@/stores/user";
|
||||
import ItDatePicker from "@/components/ui/ItDatePicker.vue";
|
||||
import { normalizeSwissPhoneNumber } from "@/utils/phone";
|
||||
|
||||
const emit = defineEmits(["cancel", "save"]);
|
||||
|
||||
|
|
@ -21,7 +23,12 @@ const formData = ref({
|
|||
postal_code: user.postal_code,
|
||||
city: user.city,
|
||||
country_code: user.country?.country_code,
|
||||
|
||||
phone_number: user.phone_number,
|
||||
birth_date: user.birth_date,
|
||||
|
||||
organisation: user.organisation,
|
||||
organisation_detail_name: user.organisation_detail_name,
|
||||
organisation_street: user.organisation_street,
|
||||
organisation_street_number: user.organisation_street_number,
|
||||
organisation_postal_code: user.organisation_postal_code,
|
||||
|
|
@ -31,7 +38,8 @@ const formData = ref({
|
|||
});
|
||||
|
||||
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 };
|
||||
|
||||
typedProfileData.country = countries.value.find(
|
||||
|
|
@ -41,6 +49,10 @@ async function save() {
|
|||
(c) => c.country_code === organisation_country_code
|
||||
);
|
||||
|
||||
if (phone_number) {
|
||||
typedProfileData.phone_number = normalizeSwissPhoneNumber(phone_number);
|
||||
}
|
||||
|
||||
await user.updateUserProfile(typedProfileData);
|
||||
emit("save");
|
||||
}
|
||||
|
|
@ -126,6 +138,28 @@ async function avatarUpload(e: Event) {
|
|||
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"
|
||||
/>
|
||||
|
||||
<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">
|
||||
{{ $t("a.Profilbild") }}
|
||||
</label>
|
||||
|
|
@ -264,6 +298,22 @@ async function avatarUpload(e: Event) {
|
|||
{{ $t("a.Firmenanschrift") }}
|
||||
</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="w-full md:max-w-sm">
|
||||
<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')">
|
||||
{{ $t("general.cancel") }}
|
||||
</button>
|
||||
<button class="btn btn-primary" @click="save">
|
||||
<button class="btn btn-primary" data-cy="saveButton" @click="save">
|
||||
{{ $t("general.save") }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { useUserStore } from "@/stores/user";
|
|||
import { computed } from "vue";
|
||||
import { useEntities } from "@/services/entities";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import dayjs from "dayjs";
|
||||
import { displaySwissPhoneNumber } from "@/utils/phone";
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -10,21 +12,20 @@ const user = useUserStore();
|
|||
const { organisations } = useEntities();
|
||||
|
||||
const privateAddress = computed(() => {
|
||||
let addressText = `${user.street} ${user.street_number}`.trim();
|
||||
if (user.postal_code || user.city) {
|
||||
if (addressText.length) {
|
||||
addressText += ", ";
|
||||
}
|
||||
addressText += `${user.postal_code} ${user.city}`;
|
||||
}
|
||||
if (user.country) {
|
||||
if (addressText.length) {
|
||||
addressText += ", ";
|
||||
}
|
||||
addressText += user.country.name;
|
||||
const textParts = [];
|
||||
|
||||
if (user.street || user.street_number) {
|
||||
textParts.push(`${user.street} ${user.street_number}`.trim());
|
||||
}
|
||||
|
||||
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(() => {
|
||||
|
|
@ -36,22 +37,25 @@ const organisationName = computed(() => {
|
|||
});
|
||||
|
||||
const orgAddress = computed(() => {
|
||||
let addressText =
|
||||
`${user.organisation_street} ${user.organisation_street_number}`.trim();
|
||||
if (user.organisation_postal_code || user.organisation_city) {
|
||||
if (addressText.length) {
|
||||
addressText += ", ";
|
||||
}
|
||||
addressText += `${user.organisation_postal_code} ${user.organisation_city}`;
|
||||
}
|
||||
if (user.organisation_country) {
|
||||
if (addressText.length) {
|
||||
addressText += ", ";
|
||||
}
|
||||
addressText += user.organisation_country.name;
|
||||
const textParts = [];
|
||||
|
||||
if (user.organisation_detail_name) {
|
||||
textParts.push(user.organisation_detail_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(() => {
|
||||
|
|
@ -67,20 +71,45 @@ const invoiceAddress = computed(() => {
|
|||
<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">
|
||||
<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>
|
||||
<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">
|
||||
{{ $t("a.E-Mail Adresse") }}
|
||||
</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">
|
||||
{{ $t("a.Privatadresse") }}
|
||||
</label>
|
||||
<div class="mb-3 sm:col-span-2 sm:mb-0">
|
||||
<template v-if="privateAddress">
|
||||
{{ privateAddress }}
|
||||
</template>
|
||||
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="privateAddress">
|
||||
<div v-if="privateAddress.length">
|
||||
<span v-for="(line, index) in privateAddress" :key="index">
|
||||
{{ line }}
|
||||
<br />
|
||||
</span>
|
||||
</div>
|
||||
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -89,14 +118,19 @@ const invoiceAddress = computed(() => {
|
|||
<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">
|
||||
<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">
|
||||
{{ $t("a.Firmenanschrift") }}
|
||||
</label>
|
||||
<div class="sm:col-span-2">
|
||||
<template v-if="orgAddress">
|
||||
{{ orgAddress }}
|
||||
</template>
|
||||
<div v-if="orgAddress" data-cy="organisationAddress">
|
||||
<span v-for="(line, index) in orgAddress" :key="index">
|
||||
{{ line }}
|
||||
<br />
|
||||
</span>
|
||||
</div>
|
||||
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||
</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 logger from "loglevel";
|
||||
import { reactive, ref } from "vue";
|
||||
import "@vuepic/vue-datepicker/dist/main.css";
|
||||
|
||||
const state = reactive({
|
||||
checkboxValue: true,
|
||||
|
|
|
|||
|
|
@ -51,10 +51,6 @@ const submissionDeadline = computed(() => {
|
|||
?.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
|
||||
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 { computed, ref, watch } from "vue";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { profileNextRoute } from "@/services/onboarding";
|
||||
import { isOtherOrganisation, profileNextRoute } from "@/services/onboarding";
|
||||
import { useEntities } from "@/services/entities";
|
||||
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
||||
|
||||
|
|
@ -13,9 +13,12 @@ const { t } = useTranslation();
|
|||
|
||||
const user = useUserStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const { organisations } = useEntities();
|
||||
|
||||
const organisationDetailName = ref<string>("");
|
||||
|
||||
const selectedOrganisation = ref({
|
||||
id: 0,
|
||||
name: t("a.Auswählen"),
|
||||
|
|
@ -35,7 +38,11 @@ watch(
|
|||
);
|
||||
|
||||
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);
|
||||
|
|
@ -56,15 +63,21 @@ async function avatarUpload(e: Event) {
|
|||
}
|
||||
}
|
||||
|
||||
watch(selectedOrganisation, async (organisation) => {
|
||||
async function updateUserProfile() {
|
||||
await user.updateUserProfile({
|
||||
organisation: organisation.id,
|
||||
organisation: selectedOrganisation.value.id,
|
||||
organisation_detail_name: organisationDetailName.value.trim(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const nextRoute = computed(() => {
|
||||
return profileNextRoute(route.params.courseType);
|
||||
});
|
||||
|
||||
async function navigateNextRoute() {
|
||||
await updateUserProfile();
|
||||
await router.push({ name: nextRoute.value });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -86,6 +99,19 @@ const nextRoute = computed(() => {
|
|||
|
||||
<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>
|
||||
<h3 class="mb-3">{{ $t("a.Profilbild") }}</h3>
|
||||
|
|
@ -117,18 +143,16 @@ const nextRoute = computed(() => {
|
|||
</template>
|
||||
|
||||
<template #footer>
|
||||
<router-link v-slot="{ navigate }" :to="{ name: nextRoute }" custom>
|
||||
<button
|
||||
:disabled="!validOrganisation"
|
||||
class="btn-blue flex items-center"
|
||||
role="link"
|
||||
data-cy="continue-button"
|
||||
@click="navigate"
|
||||
@click="navigateNextRoute"
|
||||
>
|
||||
{{ $t("general.next") }}
|
||||
<it-icon-arrow-right class="it-icon ml-2 h-6 w-6" />
|
||||
</button>
|
||||
</router-link>
|
||||
</template>
|
||||
</WizardPage>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ import DatatransCembraDeviceFingerprint from "@/components/onboarding/DatatransC
|
|||
import { getLocalSessionKey } from "@/statistics";
|
||||
import log from "loglevel";
|
||||
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({
|
||||
courseType: {
|
||||
|
|
@ -31,11 +37,14 @@ const userOrganisationName = computed(() => {
|
|||
}
|
||||
|
||||
// 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)) {
|
||||
if (
|
||||
[
|
||||
ORGANISATION_OTHER_BROKER_ID,
|
||||
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||
ORGANISATION_NO_COMPANY_ID,
|
||||
].includes(user.organisation)
|
||||
) {
|
||||
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 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") }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
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[]) {
|
||||
if (courseType === "uk") {
|
||||
|
|
@ -10,3 +15,11 @@ export function profileNextRoute(courseType: string | string[]) {
|
|||
}
|
||||
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(
|
||||
"contain",
|
||||
"Konto erstellen"
|
||||
"Konto erstellen",
|
||||
);
|
||||
cy.get('[data-cy="continue-button"]').click();
|
||||
|
||||
cy.get('[data-cy="account-profile-title"]').should(
|
||||
"contain",
|
||||
"Profil ergänzen"
|
||||
"Profil ergänzen",
|
||||
);
|
||||
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.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
|
||||
cy.get('[data-cy="account-checkout-title"]').should(
|
||||
"contain",
|
||||
"Lehrgang kaufen"
|
||||
"Lehrgang kaufen",
|
||||
);
|
||||
cy.get("#street-address").type("Eggersmatt");
|
||||
cy.get("#street-number").type("32");
|
||||
|
|
@ -40,7 +49,7 @@ describe("checkout.cy.js", () => {
|
|||
cy.get("#city").type("Zumholz");
|
||||
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-number").type("16");
|
||||
cy.get("#company-postal-code").type("3012");
|
||||
|
|
@ -60,7 +69,7 @@ describe("checkout.cy.js", () => {
|
|||
expect(ci.country).to.equal("CH");
|
||||
|
||||
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_number).to.equal("16");
|
||||
expect(ci.organisation_postal_code).to.equal("3012");
|
||||
|
|
@ -72,12 +81,32 @@ describe("checkout.cy.js", () => {
|
|||
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
|
||||
cy.get('[data-cy="pay-button"]').click();
|
||||
|
||||
cy.get('[data-cy="checkout-success-title"]').should(
|
||||
"contain",
|
||||
"Gratuliere"
|
||||
"Gratuliere",
|
||||
);
|
||||
// wait for payment callback
|
||||
cy.wait(3000);
|
||||
|
|
@ -86,7 +115,7 @@ describe("checkout.cy.js", () => {
|
|||
// back on dashboard page
|
||||
cy.get('[data-cy="db-course-title"]').should(
|
||||
"contain",
|
||||
"Versicherungsvermittler"
|
||||
"Versicherungsvermittler",
|
||||
);
|
||||
|
||||
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(
|
||||
"contain",
|
||||
"Konto erstellen"
|
||||
"Konto erstellen",
|
||||
);
|
||||
cy.get('[data-cy="continue-button"]').click();
|
||||
|
||||
cy.get('[data-cy="account-profile-title"]').should(
|
||||
"contain",
|
||||
"Profil ergänzen"
|
||||
"Profil ergänzen",
|
||||
);
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
||||
|
|
@ -117,10 +146,10 @@ describe("checkout.cy.js", () => {
|
|||
// Adressdaten ausfüllen
|
||||
cy.get('[data-cy="account-checkout-title"]').should(
|
||||
"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-number").type("32");
|
||||
|
|
@ -132,7 +161,8 @@ describe("checkout.cy.js", () => {
|
|||
|
||||
cy.get('[data-cy="continue-pay"]').click();
|
||||
|
||||
cy.loadExternalApiRequestLog("request_username", "empty@example.com").then((entry) => {
|
||||
cy.loadExternalApiRequestLog("request_username", "empty@example.com").then(
|
||||
(entry) => {
|
||||
// ends with "/v1/transactions""
|
||||
expect(entry.api_url).to.contain("/v1/transactions");
|
||||
expect(entry.request_username).to.contain("empty@example.com");
|
||||
|
|
@ -143,13 +173,17 @@ describe("checkout.cy.js", () => {
|
|||
|
||||
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.street).to.equal("Eggersmatt 32");
|
||||
expect(entry.api_request_data.customer.street).to.equal(
|
||||
"Eggersmatt 32",
|
||||
);
|
||||
expect(entry.api_request_data.customer.zipCode).to.equal("1719");
|
||||
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.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.customer.birthDate).to.equal(
|
||||
"1982-06-09",
|
||||
);
|
||||
|
||||
expect(entry.api_request_data.INT.repaymentType).to.equal(3);
|
||||
expect(entry.api_request_data.INT.riskOwner).to.equal("IJ");
|
||||
|
|
@ -157,7 +191,8 @@ describe("checkout.cy.js", () => {
|
|||
expect(entry.api_request_data.INT.deviceFingerprintId).to.not.be.empty;
|
||||
|
||||
expect(true).to.be.true;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// check that results are stored on server
|
||||
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.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) => {
|
||||
for (let i = 0; i < answers.length; i++) {
|
||||
const answer = answers[i];
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -332,7 +332,6 @@ X_FRAME_OPTIONS = "DENY"
|
|||
# EMAIL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||
# FIXME how to send emails?
|
||||
EMAIL_BACKEND = env(
|
||||
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
|
||||
)
|
||||
|
|
@ -682,10 +681,12 @@ if APP_ENVIRONMENT.startswith("prod"):
|
|||
DATATRANS_PAY_URL = "https://pay.datatrans.com"
|
||||
else:
|
||||
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", default="https://pay.sandbox.datatrans.com"
|
||||
"DATATRANS_PAY_URL",
|
||||
default="http://localhost:8000/server/fakeapi/datatrans/pay",
|
||||
)
|
||||
|
||||
# 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_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
||||
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_ZURICH_ID = -2
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from django.contrib.auth.models import Group, Permission
|
|||
from django.core.files import File
|
||||
from environs import Env
|
||||
|
||||
from vbv_lernwelt.core.model_utils import add_countries
|
||||
from vbv_lernwelt.media_files.models import UserImage
|
||||
|
||||
env = Env()
|
||||
|
|
@ -20,6 +21,7 @@ from vbv_lernwelt.core.constants import (
|
|||
TEST_SUPERVISOR1_USER_ID,
|
||||
TEST_TRAINER1_USER_ID,
|
||||
TEST_TRAINER2_USER_ID,
|
||||
TEST_USER_DATATRANS_HANNA_ID,
|
||||
TEST_USER_EMPTY_ID,
|
||||
)
|
||||
from vbv_lernwelt.core.models import User
|
||||
|
|
@ -78,7 +80,30 @@ default_users = [
|
|||
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):
|
||||
add_countries(small_set=True)
|
||||
|
||||
admin_group, created = Group.objects.get_or_create(name="admin_group")
|
||||
_content_creator_group, _created = Group.objects.get_or_create(
|
||||
name="content_creator_grop"
|
||||
|
|
@ -202,6 +227,8 @@ def create_default_users(default_password="test", set_avatar=False):
|
|||
language="de",
|
||||
)
|
||||
|
||||
hanna = create_datatrans_hanna_user()
|
||||
|
||||
for user_data in default_users:
|
||||
_create_student_user(**user_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ from vbv_lernwelt.core.constants import (
|
|||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||
TEST_STUDENT3_USER_ID,
|
||||
TEST_TRAINER1_USER_ID,
|
||||
TEST_USER_DATATRANS_HANNA_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.course.consts import (
|
||||
COURSE_TEST_ID,
|
||||
|
|
@ -159,6 +161,9 @@ def command(
|
|||
password=make_password("test"),
|
||||
)
|
||||
|
||||
User.objects.filter(id=TEST_USER_DATATRANS_HANNA_ID).delete()
|
||||
create_datatrans_hanna_user()
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("truncate core_securityrequestresponselog;")
|
||||
cursor.execute("truncate core_externalapirequestlog;")
|
||||
|
|
|
|||
|
|
@ -114,8 +114,9 @@ class User(AbstractUser):
|
|||
blank=True,
|
||||
)
|
||||
|
||||
# fields gathered from cembra pay form
|
||||
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="")
|
||||
|
||||
# is only set by abacus invoice export code
|
||||
|
|
|
|||
|
|
@ -131,6 +131,12 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
return instance
|
||||
|
||||
|
||||
class CypressUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class OrganisationSerializer(serializers.ModelSerializer):
|
||||
id = serializers.IntegerField(source="organisation_id", read_only=True)
|
||||
name = serializers.SerializerMethodField()
|
||||
|
|
|
|||
|
|
@ -28,3 +28,10 @@ UK_COURSE_IDS = [
|
|||
COURSE_UK_TRAINING_FR,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import hashlib
|
||||
import hmac
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
import structlog
|
||||
from django.conf import settings
|
||||
|
||||
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.models import CheckoutState
|
||||
|
||||
|
|
@ -73,6 +71,7 @@ def is_signature_valid(
|
|||
|
||||
def init_datatrans_transaction(
|
||||
user: User,
|
||||
refno: str,
|
||||
amount_chf_centimes: int,
|
||||
redirect_url_success: str,
|
||||
redirect_url_error: str,
|
||||
|
|
@ -91,8 +90,8 @@ def init_datatrans_transaction(
|
|||
"amount": amount_chf_centimes,
|
||||
"currency": "CHF",
|
||||
"language": user.language,
|
||||
"refno": str(uuid.uuid4()),
|
||||
"refno2": refno2,
|
||||
"refno": str(refno),
|
||||
"refno2": str(refno2),
|
||||
"webhook": {"url": webhook_url},
|
||||
"redirect": {
|
||||
"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:
|
||||
# payload["paymentMethods"] = ["INT"]
|
||||
if with_cembra_byjuno_invoice:
|
||||
payload["paymentMethods"] = ["INT"]
|
||||
if datatrans_customer_data:
|
||||
payload["customer"] = datatrans_customer_data
|
||||
if datatrans_int_data:
|
||||
|
|
|
|||
|
|
@ -71,9 +71,8 @@ class CheckoutAPITestCase(APITestCase):
|
|||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
f"https://pay.sandbox.datatrans.com/v1/start/1234567890",
|
||||
response.json()["next_step_url"],
|
||||
self.assertTrue(
|
||||
response.json()["next_step_url"].endswith("v1/start/1234567890")
|
||||
)
|
||||
|
||||
ci = CheckoutInformation.objects.first()
|
||||
|
|
@ -154,9 +153,11 @@ class CheckoutAPITestCase(APITestCase):
|
|||
)
|
||||
|
||||
self.assertEqual(
|
||||
0,
|
||||
1,
|
||||
CheckoutInformation.objects.count(),
|
||||
)
|
||||
ci = CheckoutInformation.objects.first()
|
||||
self.assertEqual(ci.state, CheckoutState.FAILED)
|
||||
|
||||
def test_checkout_already_paid(self):
|
||||
# GIVEN
|
||||
|
|
@ -217,9 +218,8 @@ class CheckoutAPITestCase(APITestCase):
|
|||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id_next}",
|
||||
response.json()["next_step_url"],
|
||||
self.assertTrue(
|
||||
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id_next}")
|
||||
)
|
||||
|
||||
# check that we have two checkouts
|
||||
|
|
@ -277,9 +277,8 @@ class CheckoutAPITestCase(APITestCase):
|
|||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
||||
response.json()["next_step_url"],
|
||||
self.assertTrue(
|
||||
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id}")
|
||||
)
|
||||
|
||||
@patch("vbv_lernwelt.shop.views.init_datatrans_transaction")
|
||||
|
|
@ -310,7 +309,6 @@ class CheckoutAPITestCase(APITestCase):
|
|||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
||||
response.json()["next_step_url"],
|
||||
self.assertTrue(
|
||||
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id}")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,10 +24,8 @@ class DatatransServiceTest(TestCase):
|
|||
|
||||
@override_settings(DATATRANS_BASIC_AUTH_KEY="BASIC_AUTH_KEY")
|
||||
@patch("vbv_lernwelt.shop.services.requests.post")
|
||||
@patch("vbv_lernwelt.shop.services.uuid.uuid4")
|
||||
def test_init_transaction_201(self, mock_uuid, mock_post):
|
||||
def test_init_transaction_201(self, mock_post):
|
||||
# GIVEN
|
||||
mock_uuid.return_value = uuid.uuid4()
|
||||
mock_post.return_value.status_code = 201
|
||||
mock_post.return_value.json.return_value = {
|
||||
"transactionId": 1234567890,
|
||||
|
|
@ -38,6 +36,7 @@ class DatatransServiceTest(TestCase):
|
|||
# WHEN
|
||||
transaction_id = init_datatrans_transaction(
|
||||
user=self.user,
|
||||
refno="123321",
|
||||
amount_chf_centimes=324_30,
|
||||
redirect_url_success=f"{REDIRECT_URL}/success",
|
||||
redirect_url_error=f"{REDIRECT_URL}/error",
|
||||
|
|
@ -64,11 +63,12 @@ class DatatransServiceTest(TestCase):
|
|||
with self.assertRaises(InitTransactionException):
|
||||
init_datatrans_transaction(
|
||||
user=self.user,
|
||||
refno="123321",
|
||||
amount_chf_centimes=324_30,
|
||||
redirect_url_success=f"/success",
|
||||
redirect_url_error=f"/error",
|
||||
redirect_url_cancel=f"/cancel",
|
||||
webhook_url=f"/webhook",
|
||||
redirect_url_success="/success",
|
||||
redirect_url_error="/error",
|
||||
redirect_url_cancel="/cancel",
|
||||
webhook_url="/webhook",
|
||||
refno2="",
|
||||
)
|
||||
|
||||
|
|
@ -80,7 +80,4 @@ class DatatransServiceTest(TestCase):
|
|||
url = get_payment_url(transaction_id)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(
|
||||
url,
|
||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
||||
)
|
||||
self.assertTrue(url.endswith(f"v1/start/{transaction_id}"))
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ def checkout_vv(request):
|
|||
sku = request.data["product"]
|
||||
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:
|
||||
product = Product.objects.get(sku=sku)
|
||||
|
|
@ -124,6 +124,38 @@ def checkout_vv(request):
|
|||
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}"
|
||||
|
||||
try:
|
||||
|
|
@ -138,10 +170,10 @@ def checkout_vv(request):
|
|||
"street": f'{request.data["address"]["street"]} {request.data["address"]["street_number"]}',
|
||||
"city": request.data["address"]["city"],
|
||||
"zipCode": request.data["address"]["postal_code"],
|
||||
"country": request.data["address"]["country_code"],
|
||||
"country": request.data["address"]["country_id"],
|
||||
"phone": request.data["address"]["phone_number"],
|
||||
"email": email,
|
||||
"birthDate": request.data["address"]["birth_date"],
|
||||
"birthDate": str(request.data["address"]["birth_date"]),
|
||||
"language": request.user.language,
|
||||
"ipAddress": ip_address,
|
||||
"type": "P",
|
||||
|
|
@ -154,6 +186,7 @@ def checkout_vv(request):
|
|||
}
|
||||
transaction_id = init_datatrans_transaction(
|
||||
user=request.user,
|
||||
refno=str(checkout_info.abacus_order_id),
|
||||
amount_chf_centimes=product.price,
|
||||
redirect_url_success=checkout_success_url(
|
||||
base_url=base_redirect_url, product_sku=sku
|
||||
|
|
@ -170,6 +203,8 @@ def checkout_vv(request):
|
|||
with_cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
||||
)
|
||||
except InitTransactionException as e:
|
||||
checkout_info.state = CheckoutState.FAILED.value
|
||||
checkout_info.save()
|
||||
if not settings.DEBUG:
|
||||
log.error("Transaction initiation failed", exc_info=True, error=str(e))
|
||||
capture_exception(e)
|
||||
|
|
@ -180,36 +215,8 @@ def checkout_vv(request):
|
|||
),
|
||||
)
|
||||
|
||||
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,
|
||||
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"],
|
||||
)
|
||||
checkout_info.transaction_id = transaction_id
|
||||
checkout_info.save()
|
||||
|
||||
return next_step_response(url=get_payment_url(transaction_id))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue