Merge branch 'develop' into feature/vbv-676-berufsbildner-2
This commit is contained in:
commit
77dce844d3
|
|
@ -6,9 +6,14 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
|||
import { computed, ref, watch } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import _ from "lodash";
|
||||
import type { DashboardPersonCourseSessionType } from "@/services/dashboard";
|
||||
import {
|
||||
type DashboardPersonCourseSessionType,
|
||||
exportPersons,
|
||||
} from "@/services/dashboard";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import type { DashboardPersonsPageMode } from "@/types";
|
||||
import type { DashboardPersonsPageMode, StatisticsFilterItem } from "@/types";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { exportDataAsXls } from "@/utils/export";
|
||||
|
||||
log.debug("DashboardPersonsPage created");
|
||||
|
||||
|
|
@ -28,6 +33,7 @@ type MenuItem = {
|
|||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const { loading, dashboardPersons } = useDashboardPersonsDueDates(props.mode);
|
||||
|
||||
|
|
@ -227,6 +233,32 @@ function personRoleDisplayValue(personCourseSession: DashboardPersonCourseSessio
|
|||
return "";
|
||||
}
|
||||
|
||||
function exportData() {
|
||||
const courseSessionIdsSet = new Set<string>();
|
||||
// get all course session ids from users
|
||||
if (selectedSession.value.id === UNFILTERED) {
|
||||
for (const person of filteredPersons.value) {
|
||||
for (const courseSession of person.course_sessions) {
|
||||
courseSessionIdsSet.add(courseSession.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
courseSessionIdsSet.add(selectedSession.value.id);
|
||||
}
|
||||
|
||||
// construct StatisticsFilterItems for export call
|
||||
const items: StatisticsFilterItem[] = [];
|
||||
for (const csId of courseSessionIdsSet) {
|
||||
items.push({
|
||||
_id: "",
|
||||
course_session_id: csId,
|
||||
generation: "",
|
||||
circle_id: "",
|
||||
});
|
||||
}
|
||||
exportDataAsXls(items, exportPersons, userStore.language);
|
||||
}
|
||||
|
||||
watch(selectedCourse, () => {
|
||||
selectedRegion.value = regions.value[0];
|
||||
});
|
||||
|
|
@ -253,7 +285,18 @@ watch(selectedRegion, () => {
|
|||
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
||||
<span class="inline">{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
<h2 class="my-4">{{ $t("a.Personen") }}</h2>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h2 class="my-4">{{ $t("a.Personen") }}</h2>
|
||||
<button
|
||||
v-if="userStore.course_session_experts.length > 0"
|
||||
class="flex"
|
||||
data-cy="export-button"
|
||||
@click="exportData"
|
||||
>
|
||||
<it-icon-export></it-icon-export>
|
||||
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-2">
|
||||
<section
|
||||
v-if="filtersVisible"
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ const executePayment = async () => {
|
|||
{{ formErrors.personal.join(", ") }}
|
||||
</p>
|
||||
|
||||
<section v-if="address.payment_method !== 'cembra_byjuno'">
|
||||
<section>
|
||||
<div class="mt-4">
|
||||
<button
|
||||
v-if="!withCompanyAddress"
|
||||
|
|
|
|||
|
|
@ -241,6 +241,15 @@ export async function exportCompetenceElements(
|
|||
});
|
||||
}
|
||||
|
||||
export async function exportPersons(
|
||||
data: XlsExportRequestData,
|
||||
language: string
|
||||
): Promise<XlsExportResponseData> {
|
||||
return await itPost("/api/dashboard/export/persons/", data, {
|
||||
headers: { "Accept-Language": language },
|
||||
});
|
||||
}
|
||||
|
||||
export function courseIdForCourseSlug(
|
||||
dashboardConfigs: DashboardCourseConfigType[],
|
||||
courseSlug: string
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ function getCurrentDate() {
|
|||
function verifyExportFileExists(fileName) {
|
||||
const downloadsFolder = Cypress.config("downloadsFolder");
|
||||
cy.readFile(
|
||||
path.join(downloadsFolder, `${fileName}_${getCurrentDate()}.xlsx`)
|
||||
path.join(downloadsFolder, `${fileName}_${getCurrentDate()}.xlsx`),
|
||||
).should("exist");
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ function testExport(url, fileName) {
|
|||
describe("dashboardExport.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days"
|
||||
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -55,13 +55,17 @@ describe("dashboardExport.cy.js", () => {
|
|||
it("should download the competence elements export", () => {
|
||||
testExport(
|
||||
"/statistic/test-lehrgang/assignment",
|
||||
"export_kompetenznachweis_elemente"
|
||||
"export_kompetenznachweis_elemente",
|
||||
);
|
||||
});
|
||||
|
||||
it("should download the feedback export", () => {
|
||||
testExport("/statistic/test-lehrgang/feedback", "export_feedback");
|
||||
});
|
||||
|
||||
it("should download the person export", () => {
|
||||
testExport("/dashboard/persons", "export_personen");
|
||||
});
|
||||
});
|
||||
|
||||
describe("as trainer", () => {
|
||||
|
|
@ -76,12 +80,16 @@ describe("dashboardExport.cy.js", () => {
|
|||
it("should download the competence elements export", () => {
|
||||
testExport(
|
||||
"/statistic/test-lehrgang/assignment",
|
||||
"export_kompetenznachweis_elemente"
|
||||
"export_kompetenznachweis_elemente",
|
||||
);
|
||||
});
|
||||
|
||||
it("should download the feedback export", () => {
|
||||
testExport("/statistic/test-lehrgang/feedback", "export_feedback");
|
||||
});
|
||||
|
||||
it("should download the person export", () => {
|
||||
testExport("/dashboard/persons", "export_personen");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ from vbv_lernwelt.dashboard.views import (
|
|||
export_attendance_as_xsl,
|
||||
export_competence_elements_as_xsl,
|
||||
export_feedback_as_xsl,
|
||||
export_persons_as_xsl,
|
||||
get_dashboard_config,
|
||||
get_dashboard_due_dates,
|
||||
get_dashboard_persons,
|
||||
|
|
@ -143,6 +144,7 @@ urlpatterns = [
|
|||
path(r"api/dashboard/export/competence_elements/", export_competence_elements_as_xsl,
|
||||
name="export_certificate_as_xsl"),
|
||||
path(r"api/dashboard/export/feedback/", export_feedback_as_xsl, name="export_feedback_as_xsl"),
|
||||
path(r"api/dashboard/export/persons/", export_persons_as_xsl, name="export_persons_as_xsl"),
|
||||
|
||||
# course
|
||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ msgstr ""
|
|||
msgid "Lehrgang-Seite"
|
||||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/course/models.py:278
|
||||
#: vbv_lernwelt/dashboard/person_export.py:116
|
||||
msgid "Teilnehmer"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ msgstr ""
|
|||
msgid "Versicherungsvermittler-Lehrgang"
|
||||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/course/models.py:351
|
||||
#: vbv_lernwelt/course/models.py:350
|
||||
msgid "ÜK-Lehrgang"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -155,10 +155,30 @@ msgstr ""
|
|||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: vbv_lernwelt/course_session/services/export_attendance.py:138
|
||||
#: vbv_lernwelt/course_session/services/export_attendance.py:127
|
||||
msgid "Lehrvertragsnummer"
|
||||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:16
|
||||
msgid "export_personen"
|
||||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:68
|
||||
msgid "Telefon"
|
||||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:69
|
||||
msgid "Rolle"
|
||||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:118
|
||||
msgid "Trainer"
|
||||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:120
|
||||
msgid "Regionenleiter"
|
||||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/feedback/export.py:19
|
||||
msgid "export_feedback"
|
||||
msgstr ""
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-07-27 20:59+0200\n"
|
||||
"POT-Creation-Date: 2024-07-30 11:16+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -88,8 +88,9 @@ msgid "Lehrgang-Seite"
|
|||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/course/models.py:278
|
||||
#: vbv_lernwelt/dashboard/person_export.py:116
|
||||
msgid "Teilnehmer"
|
||||
msgstr ""
|
||||
msgstr "Participant"
|
||||
|
||||
#: vbv_lernwelt/course/models.py:279
|
||||
msgid "Experte/Trainer"
|
||||
|
|
@ -120,7 +121,6 @@ msgid "export_anwesenheit"
|
|||
msgstr "export_presence"
|
||||
|
||||
#: vbv_lernwelt/course_session/services/export_attendance.py:86
|
||||
#| msgid "Anwesenheit"
|
||||
msgid "Optionale Anwesenheit"
|
||||
msgstr "Présence facultative"
|
||||
|
||||
|
|
@ -160,6 +160,26 @@ msgstr "E-mail"
|
|||
msgid "Lehrvertragsnummer"
|
||||
msgstr "Numéro de contrat d'apprentissage"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:16
|
||||
msgid "export_personen"
|
||||
msgstr "export_personnes"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:68
|
||||
msgid "Telefon"
|
||||
msgstr "Téléphone"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:69
|
||||
msgid "Rolle"
|
||||
msgstr "Rôle"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:118
|
||||
msgid "Trainer"
|
||||
msgstr "Formateur / Formatrice"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:120
|
||||
msgid "Regionenleiter"
|
||||
msgstr "Responsable CI"
|
||||
|
||||
#: vbv_lernwelt/feedback/export.py:19
|
||||
msgid "export_feedback"
|
||||
msgstr "export_feedback"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-07-27 20:59+0200\n"
|
||||
"POT-Creation-Date: 2024-07-30 11:16+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -88,8 +88,9 @@ msgid "Lehrgang-Seite"
|
|||
msgstr ""
|
||||
|
||||
#: vbv_lernwelt/course/models.py:278
|
||||
#: vbv_lernwelt/dashboard/person_export.py:116
|
||||
msgid "Teilnehmer"
|
||||
msgstr ""
|
||||
msgstr "Partecipante"
|
||||
|
||||
#: vbv_lernwelt/course/models.py:279
|
||||
msgid "Experte/Trainer"
|
||||
|
|
@ -120,7 +121,6 @@ msgid "export_anwesenheit"
|
|||
msgstr "esportazione_presenza"
|
||||
|
||||
#: vbv_lernwelt/course_session/services/export_attendance.py:86
|
||||
#| msgid "Anwesenheit"
|
||||
msgid "Optionale Anwesenheit"
|
||||
msgstr "Presenza opzionale"
|
||||
|
||||
|
|
@ -160,6 +160,28 @@ msgstr "Email"
|
|||
msgid "Lehrvertragsnummer"
|
||||
msgstr "Numero di contratto di tirocinio"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:16
|
||||
#, fuzzy
|
||||
#| msgid "export_anwesenheit"
|
||||
msgid "export_personen"
|
||||
msgstr "esportazione_persone"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:68
|
||||
msgid "Telefon"
|
||||
msgstr "Telefono"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:69
|
||||
msgid "Rolle"
|
||||
msgstr "Ruolo"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:118
|
||||
msgid "Trainer"
|
||||
msgstr "Trainer"
|
||||
|
||||
#: vbv_lernwelt/dashboard/person_export.py:120
|
||||
msgid "Regionenleiter"
|
||||
msgstr "Responsabile CI"
|
||||
|
||||
#: vbv_lernwelt/feedback/export.py:19
|
||||
msgid "export_feedback"
|
||||
msgstr "esportazione_feedback"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import djclick as click
|
||||
import structlog
|
||||
from django.db.models import Count
|
||||
|
||||
from vbv_lernwelt.course.models import CourseSessionUser
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@click.command()
|
||||
def command():
|
||||
VV_COURSE_SESSIONS = [1, 2, 3] # DE, FR, IT
|
||||
|
||||
# Aggregation of users per organisation for a specific course session
|
||||
user_counts = (
|
||||
CourseSessionUser.objects.filter(course_session__id__in=VV_COURSE_SESSIONS)
|
||||
.values("user__organisation__organisation_id", "user__organisation__name_de")
|
||||
.annotate(user_count=Count("id"))
|
||||
.order_by("user__organisation__organisation_id")
|
||||
)
|
||||
|
||||
for entry in user_counts:
|
||||
print(
|
||||
f"Organisation Name: {entry['user__organisation__name_de']}, "
|
||||
f"User Count: {entry['user_count']}"
|
||||
)
|
||||
|
||||
print(
|
||||
f"Total number of users: {sum([entry['user_count'] for entry in user_counts])}"
|
||||
)
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
from io import BytesIO
|
||||
from typing import Optional
|
||||
|
||||
import structlog
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from openpyxl import Workbook
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.course_session.services.export_attendance import (
|
||||
add_user_headers,
|
||||
make_export_filename,
|
||||
sanitize_sheet_name,
|
||||
)
|
||||
from vbv_lernwelt.dashboard.utils import create_person_list_with_roles
|
||||
|
||||
PERSONS_EXPORT_FILENAME = _("export_personen")
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def export_persons(
|
||||
user: User,
|
||||
course_session_ids: list[str],
|
||||
save_as_file: bool = False,
|
||||
) -> Optional[bytes]:
|
||||
if not course_session_ids:
|
||||
return
|
||||
|
||||
wb = Workbook()
|
||||
# remove the first sheet is just easier than keeping track of the active sheet
|
||||
wb.remove(wb.active)
|
||||
|
||||
user_with_roles = create_person_list_with_roles(
|
||||
user, course_session_ids, include_private_data=True
|
||||
)
|
||||
course_sessions = CourseSession.objects.filter(id__in=course_session_ids)
|
||||
|
||||
for cs in course_sessions:
|
||||
_create_sheet(
|
||||
wb,
|
||||
cs.title,
|
||||
cs.id,
|
||||
user_with_roles,
|
||||
)
|
||||
|
||||
if save_as_file:
|
||||
wb.save(make_export_filename(PERSONS_EXPORT_FILENAME))
|
||||
else:
|
||||
output = BytesIO()
|
||||
wb.save(output)
|
||||
|
||||
output.seek(0)
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
def _create_sheet(
|
||||
wb: Workbook,
|
||||
title: str,
|
||||
cs_id: int,
|
||||
user_with_roles,
|
||||
):
|
||||
sheet = wb.create_sheet(title=sanitize_sheet_name(title))
|
||||
|
||||
if len(user_with_roles) == 0:
|
||||
return sheet
|
||||
|
||||
# headers
|
||||
# common user headers, Circle <title> <learningcontenttitle> bestanden, Circle <title> <learningcontenttitle> Resultat, ...
|
||||
col_idx = add_user_headers(sheet)
|
||||
sheet.cell(row=1, column=col_idx, value=str(_("Telefon")))
|
||||
sheet.cell(row=1, column=col_idx + 1, value=str(_("Rolle")))
|
||||
|
||||
_add_rows(sheet, user_with_roles, cs_id)
|
||||
|
||||
return sheet
|
||||
|
||||
|
||||
def _add_rows(
|
||||
sheet,
|
||||
users,
|
||||
course_session_id,
|
||||
):
|
||||
idx_offset = 0
|
||||
|
||||
for row_idx, user in enumerate(users, start=2):
|
||||
|
||||
def get_user_cs_by_id(user_cs, cs_id):
|
||||
return next((cs for cs in user_cs if int(cs.get("id")) == cs_id), None)
|
||||
|
||||
user_cs = get_user_cs_by_id(user["course_sessions"], course_session_id)
|
||||
|
||||
if not user_cs:
|
||||
logger.warning(
|
||||
"User not found in course session",
|
||||
user_id=user["user_id"],
|
||||
course_session_id=course_session_id,
|
||||
)
|
||||
idx_offset += 1
|
||||
continue
|
||||
|
||||
user_role = _role_as_string(user_cs.get("user_role"))
|
||||
idx = row_idx - idx_offset
|
||||
|
||||
sheet.cell(row=idx, column=1, value=user["first_name"])
|
||||
sheet.cell(row=idx, column=2, value=user["last_name"])
|
||||
sheet.cell(row=idx, column=3, value=user["email"])
|
||||
sheet.cell(row=idx, column=4, value=user.get("Lehrvertragsnummer", ""))
|
||||
sheet.cell(
|
||||
row=idx,
|
||||
column=5,
|
||||
value=user.get("phone_number", ""),
|
||||
)
|
||||
sheet.cell(row=idx, column=6, value=user_role)
|
||||
|
||||
|
||||
def _role_as_string(role):
|
||||
if role == "MEMBER":
|
||||
return str(_("Teilnehmer"))
|
||||
elif role == "EXPERT":
|
||||
return str(_("Trainer"))
|
||||
elif role == "SUPERVISOR":
|
||||
return str(_("Regionenleiter"))
|
||||
else:
|
||||
return role
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
import io
|
||||
|
||||
from django.utils.translation import activate
|
||||
from openpyxl import load_workbook
|
||||
|
||||
from vbv_lernwelt.core.constants import (
|
||||
TEST_STUDENT1_USER_ID,
|
||||
TEST_STUDENT2_USER_ID,
|
||||
TEST_STUDENT3_USER_ID,
|
||||
TEST_TRAINER1_USER_ID,
|
||||
TEST_TRAINER2_USER_ID,
|
||||
)
|
||||
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.course_session.tests.test_attendance_export import ExportBaseTestCase
|
||||
from vbv_lernwelt.dashboard.person_export import export_persons
|
||||
from vbv_lernwelt.learnpath.models import Circle
|
||||
|
||||
|
||||
class PersonsExportTestCase(ExportBaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
create_default_users()
|
||||
create_test_course(include_vv=False, with_sessions=True)
|
||||
|
||||
self.course_session_be = CourseSession.objects.get(title="Test Bern 2022 a")
|
||||
self.course_session_zh = CourseSession.objects.get(title="Test Zürich 2022 a")
|
||||
|
||||
self.circle_fahrzeug = Circle.objects.get(
|
||||
slug="test-lehrgang-lp-circle-fahrzeug"
|
||||
)
|
||||
self.circle_reisen = Circle.objects.get(slug="test-lehrgang-lp-circle-fahrzeug")
|
||||
|
||||
self.test_trainer1 = User.objects.get(id=TEST_TRAINER1_USER_ID)
|
||||
self.test_trainer2 = User.objects.get(id=TEST_TRAINER2_USER_ID)
|
||||
self.test_student1 = User.objects.get(id=TEST_STUDENT1_USER_ID)
|
||||
self.test_student2 = User.objects.get(id=TEST_STUDENT2_USER_ID)
|
||||
self.test_student3 = User.objects.get(id=TEST_STUDENT3_USER_ID)
|
||||
|
||||
self.test_student1_row = [
|
||||
self.test_student1.first_name,
|
||||
self.test_student1.last_name,
|
||||
self.test_student1.email,
|
||||
None,
|
||||
None,
|
||||
"Teilnehmer",
|
||||
]
|
||||
self.test_student2_row = [
|
||||
self.test_student2.first_name,
|
||||
self.test_student2.last_name,
|
||||
self.test_student2.email,
|
||||
None,
|
||||
None,
|
||||
"Teilnehmer",
|
||||
]
|
||||
self.test_student3_row = [
|
||||
self.test_student3.first_name,
|
||||
self.test_student3.last_name,
|
||||
self.test_student3.email,
|
||||
None,
|
||||
None,
|
||||
"Teilnehmer",
|
||||
]
|
||||
self.test_trainer1_row = [
|
||||
self.test_trainer1.first_name,
|
||||
self.test_trainer1.last_name,
|
||||
self.test_trainer1.email,
|
||||
None,
|
||||
None,
|
||||
"Trainer",
|
||||
]
|
||||
self.test_trainer2_row = [
|
||||
self.test_trainer2.first_name,
|
||||
self.test_trainer2.last_name,
|
||||
self.test_trainer2.email,
|
||||
None,
|
||||
None,
|
||||
"Trainer",
|
||||
]
|
||||
|
||||
def _generate_expected_data(self, rows):
|
||||
expected_data = [
|
||||
self._make_header(),
|
||||
]
|
||||
for r in rows:
|
||||
expected_data.append(r)
|
||||
|
||||
return expected_data
|
||||
|
||||
def _generate_workbook(self, user, course_session_ids):
|
||||
export_data = io.BytesIO(
|
||||
export_persons(user, course_session_ids, save_as_file=False)
|
||||
)
|
||||
return load_workbook(export_data)
|
||||
|
||||
def _make_header(self):
|
||||
return [
|
||||
"Vorname",
|
||||
"Nachname",
|
||||
"Email",
|
||||
"Lehrvertragsnummer",
|
||||
"Telefon",
|
||||
"Rolle",
|
||||
]
|
||||
|
||||
def test_export_persons(self):
|
||||
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
|
||||
self.assertEqual(len(wb.sheetnames), 1)
|
||||
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
||||
wb.active = wb["Test Bern 2022 a"]
|
||||
|
||||
data = self._generate_expected_data(
|
||||
[
|
||||
self.test_student1_row,
|
||||
self.test_student2_row,
|
||||
self.test_student3_row,
|
||||
self.test_trainer1_row,
|
||||
]
|
||||
)
|
||||
|
||||
self._check_export(wb, data, 4, 6)
|
||||
|
||||
wb = self._generate_workbook(self.test_trainer2, [self.course_session_zh.id])
|
||||
self.assertEqual(len(wb.sheetnames), 1)
|
||||
self.assertEqual(wb.sheetnames[0], "Test Zürich 2022 a")
|
||||
wb.active = wb["Test Zürich 2022 a"]
|
||||
|
||||
data = self._generate_expected_data(
|
||||
[self.test_student2_row, self.test_trainer2_row]
|
||||
)
|
||||
self._check_export(wb, data, 3, 6)
|
||||
|
||||
def test_cannot_export_other_session(self):
|
||||
wb = self._generate_workbook(self.test_trainer1, [self.course_session_zh.id])
|
||||
self.assertEqual(len(wb.sheetnames), 1)
|
||||
self.assertEqual(wb.sheetnames[0], "Test Zürich 2022 a")
|
||||
wb.active = wb["Test Zürich 2022 a"]
|
||||
|
||||
data = self._generate_expected_data([[None] * 6])
|
||||
|
||||
self._check_export(wb, data, 1, 6)
|
||||
|
||||
def test_export_in_fr(self):
|
||||
activate("fr")
|
||||
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
|
||||
self.assertEqual(len(wb.sheetnames), 1)
|
||||
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
||||
wb.active = wb["Test Bern 2022 a"]
|
||||
|
||||
header = [
|
||||
"Prénom",
|
||||
"Nom de famille",
|
||||
"E-mail",
|
||||
"Numéro de contrat d'apprentissage",
|
||||
"Téléphone",
|
||||
"Rôle",
|
||||
]
|
||||
|
||||
self.assertEqual([cell.value for cell in wb.active[1]], header)
|
||||
self.assertEqual(wb.active.cell(row=2, column=6).value, "Participant")
|
||||
self.assertEqual(
|
||||
wb.active.cell(row=5, column=6).value, "Formateur / Formatrice"
|
||||
)
|
||||
|
||||
def test_export_in_it(self):
|
||||
activate("it")
|
||||
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
|
||||
self.assertEqual(len(wb.sheetnames), 1)
|
||||
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
||||
wb.active = wb["Test Bern 2022 a"]
|
||||
|
||||
header = [
|
||||
"Nome",
|
||||
"Cognome",
|
||||
"Email",
|
||||
"Numero di contratto di tirocinio",
|
||||
"Telefono",
|
||||
"Ruolo",
|
||||
]
|
||||
|
||||
self.assertEqual([cell.value for cell in wb.active[1]], header)
|
||||
self.assertEqual(wb.active.cell(row=2, column=6).value, "Partecipante")
|
||||
self.assertEqual(wb.active.cell(row=5, column=6).value, "Trainer")
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import List, Set
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||
from vbv_lernwelt.learning_mentor.models import (
|
||||
AgentParticipantRelation,
|
||||
AgentParticipantRoleType,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CourseSessionWithRoles:
|
||||
_original: CourseSession
|
||||
roles: Set[str]
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
# Delegate attribute access to the _original CourseSession object
|
||||
return getattr(self._original, name)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
raise NotImplementedError("This proxy object cannot be saved.")
|
||||
|
||||
|
||||
def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWithRoles]:
|
||||
result_course_sessions = {}
|
||||
|
||||
# participant/member/expert course sessions
|
||||
csu_qs = CourseSessionUser.objects.filter(user=user).prefetch_related(
|
||||
"course_session", "course_session__course"
|
||||
)
|
||||
for csu in csu_qs:
|
||||
cs = csu.course_session
|
||||
# member/expert is mutually exclusive...
|
||||
cs.roles = {csu.role}
|
||||
result_course_sessions[cs.id] = cs
|
||||
|
||||
# enrich with supervisor course sessions
|
||||
csg_qs = CourseSessionGroup.objects.filter(supervisor=user).prefetch_related(
|
||||
"course_session", "course_session__course"
|
||||
)
|
||||
for csg in csg_qs:
|
||||
for cs in csg.course_session.all():
|
||||
cs.roles = set()
|
||||
cs = result_course_sessions.get(cs.id, cs)
|
||||
|
||||
cs.roles.add("SUPERVISOR")
|
||||
result_course_sessions[cs.id] = cs
|
||||
|
||||
# enrich with mentor course sessions
|
||||
agent_qs = AgentParticipantRelation.objects.filter(agent=user).prefetch_related(
|
||||
"participant__course_session", "participant__course_session__course"
|
||||
)
|
||||
for agent_relation in agent_qs:
|
||||
cs = agent_relation.participant.course_session
|
||||
cs.roles = set()
|
||||
cs = result_course_sessions.get(cs.id, cs)
|
||||
|
||||
cs.roles.add(agent_relation.role)
|
||||
result_course_sessions[cs.id] = cs
|
||||
|
||||
return [
|
||||
CourseSessionWithRoles(cs, cs.roles) for cs in result_course_sessions.values()
|
||||
]
|
||||
|
||||
|
||||
def has_cs_role(roles: Set[str]) -> bool:
|
||||
return bool(roles & {"SUPERVISOR", "EXPERT", "MEMBER"})
|
||||
|
||||
|
||||
def user_role(roles: Set[str]) -> str:
|
||||
if "SUPERVISOR" in roles:
|
||||
return "SUPERVISOR"
|
||||
if "EXPERT" in roles:
|
||||
return "EXPERT"
|
||||
if "MEMBER" in roles:
|
||||
return "MEMBER"
|
||||
return "LEARNING_MENTOR"
|
||||
|
||||
|
||||
def create_course_session_dict(course_session_object, my_role, user_role):
|
||||
return {
|
||||
"id": str(course_session_object.id),
|
||||
"session_title": course_session_object.title,
|
||||
"course_id": str(course_session_object.course.id),
|
||||
"course_title": course_session_object.course.title,
|
||||
"course_slug": course_session_object.course.slug,
|
||||
"region": course_session_object.region,
|
||||
"generation": course_session_object.generation,
|
||||
"my_role": my_role,
|
||||
"user_role": user_role,
|
||||
"is_uk": course_session_object.course.configuration.is_uk,
|
||||
"is_vv": course_session_object.course.configuration.is_vv,
|
||||
}
|
||||
|
||||
|
||||
def create_person_list_with_roles(
|
||||
user, course_session_ids=None, include_private_data=False
|
||||
):
|
||||
def create_user_dict(user_object):
|
||||
def create_user_dict(user_object):
|
||||
return {
|
||||
"user_id": user_object.id,
|
||||
"first_name": user_object.first_name,
|
||||
"last_name": user_object.last_name,
|
||||
"email": user_object.email,
|
||||
"avatar_url_small": user_object.avatar_url_small,
|
||||
"avatar_url": user_object.avatar_url,
|
||||
"course_sessions": [],
|
||||
}
|
||||
|
||||
course_sessions = get_course_sessions_with_roles_for_user(user)
|
||||
|
||||
result_persons = {}
|
||||
for cs in course_sessions:
|
||||
if has_cs_role(cs.roles) and cs.course.configuration.is_uk:
|
||||
course_session_users = CourseSessionUser.objects.filter(
|
||||
course_session=cs.id
|
||||
).select_related("user")
|
||||
my_role = user_role(cs.roles)
|
||||
for csu in course_session_users:
|
||||
person_data = result_persons.get(
|
||||
csu.user.id, create_user_dict(csu.user)
|
||||
)
|
||||
person_data["course_sessions"].append(
|
||||
create_course_session_dict(cs, my_role, csu.role)
|
||||
)
|
||||
result_persons[csu.user.id] = person_data
|
||||
|
||||
# add persons where request.user is mentor
|
||||
for cs in course_sessions:
|
||||
|
||||
def _add_agent_relation(my_role, user_role):
|
||||
course_session_entry = create_course_session_dict(
|
||||
cs, my_role, user_role
|
||||
)
|
||||
participant_user = relation.participant.user
|
||||
|
||||
if participant_user.id not in result_persons:
|
||||
person_data = create_user_dict(participant_user)
|
||||
person_data["course_sessions"] = [course_session_entry]
|
||||
result_persons[participant_user.id] = person_data
|
||||
else:
|
||||
# user is already in result_persons
|
||||
result_persons[participant_user.id]["course_sessions"].append(
|
||||
course_session_entry
|
||||
)
|
||||
|
||||
if "LEARNING_MENTOR" in cs.roles:
|
||||
for relation in AgentParticipantRelation.objects.filter(
|
||||
agent=user,
|
||||
participant__course_session_id=cs.id,
|
||||
role="LEARNING_MENTOR",
|
||||
):
|
||||
_add_agent_relation("LEARNING_MENTOR", "PARTICIPANT")
|
||||
|
||||
if "BERUFSBILDNER" in cs.roles:
|
||||
for relation in AgentParticipantRelation.objects.filter(
|
||||
agent=user,
|
||||
participant__course_session_id=cs.id,
|
||||
role="BERUFSBILDNER",
|
||||
):
|
||||
_add_agent_relation("BERUFSBILDNER", "PARTICIPANT")
|
||||
|
||||
# add persons where request.user is lerning mentee
|
||||
mentor_relation_qs = AgentParticipantRelation.objects.filter(
|
||||
participant__user=user,
|
||||
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
|
||||
).prefetch_related("agent")
|
||||
for mentor_relation in mentor_relation_qs:
|
||||
cs = mentor_relation.participant.course_session
|
||||
course_session_entry = create_course_session_dict(
|
||||
cs,
|
||||
"PARTICIPANT",
|
||||
"LEARNING_MENTOR",
|
||||
)
|
||||
|
||||
if mentor_relation.agent.id not in result_persons:
|
||||
person_data = create_user_dict(mentor_relation.agent)
|
||||
person_data["course_sessions"] = [course_session_entry]
|
||||
result_persons[mentor_relation.agent.id] = person_data
|
||||
else:
|
||||
# user is already in result_persons
|
||||
result_persons[mentor_relation.agent.id]["course_sessions"].append(
|
||||
course_session_entry
|
||||
)
|
||||
return result_persons.values()
|
||||
|
|
@ -2,7 +2,7 @@ import base64
|
|||
from dataclasses import asdict, dataclass
|
||||
from datetime import date
|
||||
from enum import Enum
|
||||
from typing import List, Set, Tuple
|
||||
from typing import List, Tuple
|
||||
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
|
|
@ -24,18 +24,21 @@ from vbv_lernwelt.competence.services import (
|
|||
query_competence_course_session_edoniq_tests,
|
||||
)
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import (
|
||||
CourseConfiguration,
|
||||
CourseSession,
|
||||
CourseSessionUser,
|
||||
)
|
||||
from vbv_lernwelt.course.models import CourseConfiguration, CourseSessionUser
|
||||
from vbv_lernwelt.course.views import logger
|
||||
from vbv_lernwelt.course_session.services.export_attendance import (
|
||||
ATTENDANCE_EXPORT_FILENAME,
|
||||
export_attendance,
|
||||
make_export_filename,
|
||||
)
|
||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||
from vbv_lernwelt.dashboard.person_export import export_persons, PERSONS_EXPORT_FILENAME
|
||||
from vbv_lernwelt.dashboard.utils import (
|
||||
CourseSessionWithRoles,
|
||||
create_course_session_dict,
|
||||
create_person_list_with_roles,
|
||||
get_course_sessions_with_roles_for_user,
|
||||
user_role,
|
||||
)
|
||||
from vbv_lernwelt.duedate.models import DueDate
|
||||
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
||||
from vbv_lernwelt.feedback.export import (
|
||||
|
|
@ -71,19 +74,6 @@ class RoleKeyType(Enum):
|
|||
UNKNOWN_ROLE_KEY = "UnknownRoleKey"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CourseSessionWithRoles:
|
||||
_original: CourseSession
|
||||
roles: Set[str]
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
# Delegate attribute access to the _original CourseSession object
|
||||
return getattr(self._original, name)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
raise NotImplementedError("This proxy object cannot be saved.")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CourseConfig:
|
||||
course_id: str
|
||||
|
|
@ -97,162 +87,6 @@ class CourseConfig:
|
|||
session_to_continue_id: str | None
|
||||
|
||||
|
||||
def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWithRoles]:
|
||||
result_course_sessions = {}
|
||||
|
||||
# participant/member/expert course sessions
|
||||
csu_qs = CourseSessionUser.objects.filter(user=user).prefetch_related(
|
||||
"course_session", "course_session__course"
|
||||
)
|
||||
for csu in csu_qs:
|
||||
cs = csu.course_session
|
||||
# member/expert is mutually exclusive...
|
||||
cs.roles = {csu.role}
|
||||
result_course_sessions[cs.id] = cs
|
||||
|
||||
# enrich with supervisor course sessions
|
||||
csg_qs = CourseSessionGroup.objects.filter(supervisor=user).prefetch_related(
|
||||
"course_session", "course_session__course"
|
||||
)
|
||||
for csg in csg_qs:
|
||||
for cs in csg.course_session.all():
|
||||
cs.roles = set()
|
||||
cs = result_course_sessions.get(cs.id, cs)
|
||||
|
||||
cs.roles.add("SUPERVISOR")
|
||||
result_course_sessions[cs.id] = cs
|
||||
|
||||
# enrich with mentor course sessions
|
||||
agent_qs = AgentParticipantRelation.objects.filter(agent=user).prefetch_related(
|
||||
"participant__course_session", "participant__course_session__course"
|
||||
)
|
||||
for agent_relation in agent_qs:
|
||||
cs = agent_relation.participant.course_session
|
||||
cs.roles = set()
|
||||
cs = result_course_sessions.get(cs.id, cs)
|
||||
|
||||
cs.roles.add(agent_relation.role)
|
||||
result_course_sessions[cs.id] = cs
|
||||
|
||||
return [
|
||||
CourseSessionWithRoles(cs, cs.roles) for cs in result_course_sessions.values()
|
||||
]
|
||||
|
||||
|
||||
def has_cs_role(roles: Set[str]) -> bool:
|
||||
return bool(roles & {"SUPERVISOR", "EXPERT", "MEMBER"})
|
||||
|
||||
|
||||
def user_role(roles: Set[str]) -> str:
|
||||
if "SUPERVISOR" in roles:
|
||||
return "SUPERVISOR"
|
||||
if "EXPERT" in roles:
|
||||
return "EXPERT"
|
||||
if "MEMBER" in roles:
|
||||
return "MEMBER"
|
||||
return "LEARNING_MENTOR"
|
||||
|
||||
|
||||
def _create_course_session_dict(course_session_object, my_role, user_role):
|
||||
return {
|
||||
"id": str(course_session_object.id),
|
||||
"session_title": course_session_object.title,
|
||||
"course_id": str(course_session_object.course.id),
|
||||
"course_title": course_session_object.course.title,
|
||||
"course_slug": course_session_object.course.slug,
|
||||
"region": course_session_object.region,
|
||||
"generation": course_session_object.generation,
|
||||
"my_role": my_role,
|
||||
"user_role": user_role,
|
||||
"is_uk": course_session_object.course.configuration.is_uk,
|
||||
"is_vv": course_session_object.course.configuration.is_vv,
|
||||
}
|
||||
|
||||
|
||||
def _create_person_list_with_roles(user):
|
||||
def create_user_dict(user_object):
|
||||
return {
|
||||
"user_id": user_object.id,
|
||||
"first_name": user_object.first_name,
|
||||
"last_name": user_object.last_name,
|
||||
"email": user_object.email,
|
||||
"avatar_url_small": user_object.avatar_url_small,
|
||||
"avatar_url": user_object.avatar_url,
|
||||
"course_sessions": [],
|
||||
}
|
||||
|
||||
course_sessions = get_course_sessions_with_roles_for_user(user)
|
||||
|
||||
result_persons = {}
|
||||
for cs in course_sessions:
|
||||
if has_cs_role(cs.roles) and cs.course.configuration.is_uk:
|
||||
course_session_users = CourseSessionUser.objects.filter(
|
||||
course_session=cs.id
|
||||
).select_related("user")
|
||||
my_role = user_role(cs.roles)
|
||||
for csu in course_session_users:
|
||||
person_data = result_persons.get(
|
||||
csu.user.id, create_user_dict(csu.user)
|
||||
)
|
||||
person_data["course_sessions"].append(
|
||||
_create_course_session_dict(cs, my_role, csu.role)
|
||||
)
|
||||
result_persons[csu.user.id] = person_data
|
||||
|
||||
# add persons where request.user is mentor
|
||||
for cs in course_sessions:
|
||||
|
||||
def _add_agent_relation(my_role, user_role):
|
||||
course_session_entry = _create_course_session_dict(cs, my_role, user_role)
|
||||
participant_user = relation.participant.user
|
||||
|
||||
if participant_user.id not in result_persons:
|
||||
person_data = create_user_dict(participant_user)
|
||||
person_data["course_sessions"] = [course_session_entry]
|
||||
result_persons[participant_user.id] = person_data
|
||||
else:
|
||||
# user is already in result_persons
|
||||
result_persons[participant_user.id]["course_sessions"].append(
|
||||
course_session_entry
|
||||
)
|
||||
|
||||
if "LEARNING_MENTOR" in cs.roles:
|
||||
for relation in AgentParticipantRelation.objects.filter(
|
||||
agent=user, participant__course_session_id=cs.id, role="LEARNING_MENTOR"
|
||||
):
|
||||
_add_agent_relation("LEARNING_MENTOR", "PARTICIPANT")
|
||||
|
||||
if "BERUFSBILDNER" in cs.roles:
|
||||
for relation in AgentParticipantRelation.objects.filter(
|
||||
agent=user, participant__course_session_id=cs.id, role="BERUFSBILDNER"
|
||||
):
|
||||
_add_agent_relation("BERUFSBILDNER", "PARTICIPANT")
|
||||
|
||||
# add persons where request.user is lerning mentee
|
||||
mentor_relation_qs = AgentParticipantRelation.objects.filter(
|
||||
participant__user=user,
|
||||
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
|
||||
).prefetch_related("agent")
|
||||
for mentor_relation in mentor_relation_qs:
|
||||
cs = mentor_relation.participant.course_session
|
||||
course_session_entry = _create_course_session_dict(
|
||||
cs,
|
||||
"PARTICIPANT",
|
||||
"LEARNING_MENTOR",
|
||||
)
|
||||
|
||||
if mentor_relation.agent.id not in result_persons:
|
||||
person_data = create_user_dict(mentor_relation.agent)
|
||||
person_data["course_sessions"] = [course_session_entry]
|
||||
result_persons[mentor_relation.agent.id] = person_data
|
||||
else:
|
||||
# user is already in result_persons
|
||||
result_persons[mentor_relation.agent.id]["course_sessions"].append(
|
||||
course_session_entry
|
||||
)
|
||||
return result_persons.values()
|
||||
|
||||
|
||||
def _persons_list_add_competence_metrics(persons):
|
||||
course_session_ids = {cs["id"] for p in persons for cs in p["course_sessions"]}
|
||||
competence_assignments = query_competence_course_session_assignments(
|
||||
|
|
@ -295,7 +129,7 @@ def _persons_list_add_competence_metrics(persons):
|
|||
@api_view(["GET"])
|
||||
def get_dashboard_persons(request):
|
||||
try:
|
||||
persons = list(_create_person_list_with_roles(request.user))
|
||||
persons = list(create_person_list_with_roles(request.user))
|
||||
|
||||
if request.GET.get("with_competence_metrics", "") == "true":
|
||||
persons = _persons_list_add_competence_metrics(persons)
|
||||
|
|
@ -333,7 +167,7 @@ def get_dashboard_due_dates(request):
|
|||
cs = course_session_map.get(due_date.course_session_id)
|
||||
|
||||
if cs:
|
||||
data["course_session"] = _create_course_session_dict(
|
||||
data["course_session"] = create_course_session_dict(
|
||||
cs, my_role=user_role(cs.roles), user_role=""
|
||||
)
|
||||
result_due_dates.append(data)
|
||||
|
|
@ -610,6 +444,20 @@ def export_feedback_as_xsl(request):
|
|||
return _make_excel_response(data, FEEDBACK_EXPORT_FILE_NAME)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
def export_persons_as_xsl(request):
|
||||
requested_course_session_ids = request.data.get("courseSessionIds", [])
|
||||
course_sessions_with_roles = _get_permitted_courses_sessions_for_user(
|
||||
request.user, requested_course_session_ids
|
||||
) # noqa
|
||||
|
||||
data = export_persons(
|
||||
request.user,
|
||||
[cswr.id for cswr in course_sessions_with_roles],
|
||||
)
|
||||
return _make_excel_response(data, PERSONS_EXPORT_FILENAME)
|
||||
|
||||
|
||||
def _get_permitted_courses_sessions_for_user(
|
||||
user: User, requested_coursesession_ids: List[str]
|
||||
) -> List[CourseSessionWithRoles]:
|
||||
|
|
|
|||
|
|
@ -78,32 +78,32 @@ def create_customer_xml(checkout_information: CheckoutInformation):
|
|||
first_name=checkout_information.first_name,
|
||||
company_name=(
|
||||
checkout_information.organisation_detail_name
|
||||
if checkout_information.invoice_address == "org"
|
||||
if checkout_information.abacus_use_organisation_data()
|
||||
else ""
|
||||
),
|
||||
street=(
|
||||
checkout_information.organisation_street
|
||||
if checkout_information.invoice_address == "org"
|
||||
if checkout_information.abacus_use_organisation_data()
|
||||
else checkout_information.street
|
||||
),
|
||||
house_number=(
|
||||
checkout_information.organisation_street_number
|
||||
if checkout_information.invoice_address == "org"
|
||||
if checkout_information.abacus_use_organisation_data()
|
||||
else checkout_information.street_number
|
||||
),
|
||||
zip_code=(
|
||||
checkout_information.organisation_postal_code
|
||||
if checkout_information.invoice_address == "org"
|
||||
if checkout_information.abacus_use_organisation_data()
|
||||
else checkout_information.postal_code
|
||||
),
|
||||
city=(
|
||||
checkout_information.organisation_city
|
||||
if checkout_information.invoice_address == "org"
|
||||
if checkout_information.abacus_use_organisation_data()
|
||||
else checkout_information.city
|
||||
),
|
||||
country=(
|
||||
checkout_information.organisation_country_id
|
||||
if checkout_information.invoice_address == "org"
|
||||
if checkout_information.abacus_use_organisation_data()
|
||||
else checkout_information.country_id
|
||||
),
|
||||
language=customer.language,
|
||||
|
|
|
|||
|
|
@ -128,3 +128,15 @@ class CheckoutInformation(models.Model):
|
|||
self.abacus_order_id = new_abacus_order_id
|
||||
self.save()
|
||||
return self
|
||||
|
||||
def abacus_address_type(self) -> str:
|
||||
# always use priv for abacus and CembraPay
|
||||
return (
|
||||
self.INVOICE_ADDRESS_ORGANISATION
|
||||
if self.invoice_address == self.INVOICE_ADDRESS_ORGANISATION
|
||||
and not self.cembra_byjuno_invoice
|
||||
else self.INVOICE_ADDRESS_PRIVATE
|
||||
)
|
||||
|
||||
def abacus_use_organisation_data(self) -> bool:
|
||||
return self.abacus_address_type() == self.INVOICE_ADDRESS_ORGANISATION
|
||||
|
|
|
|||
|
|
@ -163,6 +163,53 @@ class AbacusInvoiceTestCase(TestCase):
|
|||
assert "<Street>Laupenstrasse</Street>" in customer_xml_content
|
||||
assert "<Country>CH</Country>" in customer_xml_content
|
||||
|
||||
def test_create_customer_xml_byjuno_cembra_with_company_address(self):
|
||||
_pat = User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch")
|
||||
_pat.abacus_debitor_number = 60000011
|
||||
_pat.save()
|
||||
_ignore_checkout_information = CheckoutInformationFactory(
|
||||
user=_pat, abacus_order_id=6_000_000_123
|
||||
)
|
||||
|
||||
feuz = User.objects.get(username="andreas.feuz@eiger-versicherungen.ch")
|
||||
feuz_checkout_info = CheckoutInformationFactory(
|
||||
user=feuz,
|
||||
transaction_id="24021508331287484",
|
||||
first_name="Andreas",
|
||||
last_name="Feuz",
|
||||
street="Eggersmatt",
|
||||
street_number="32",
|
||||
postal_code="1719",
|
||||
city="Zumholz",
|
||||
country_id="CH",
|
||||
invoice_address="org",
|
||||
organisation_detail_name="VBV",
|
||||
organisation_street="Laupenstrasse",
|
||||
organisation_street_number="10",
|
||||
organisation_postal_code="3000",
|
||||
organisation_city="Bern",
|
||||
organisation_country_id="CH",
|
||||
cembra_byjuno_invoice=True,
|
||||
)
|
||||
feuz_checkout_info.created_at = datetime(2024, 2, 15, 8, 33, 12, 0)
|
||||
|
||||
customer_xml_filename, customer_xml_content = create_customer_xml(
|
||||
checkout_information=feuz_checkout_info
|
||||
)
|
||||
print(customer_xml_content)
|
||||
print(customer_xml_filename)
|
||||
|
||||
self.assertEqual("myVBV_debi_60000012.xml", customer_xml_filename)
|
||||
assert "<CustomerNumber>60000012</CustomerNumber>" in customer_xml_content
|
||||
assert (
|
||||
"<Email>andreas.feuz@eiger-versicherungen.ch</Email>"
|
||||
in customer_xml_content
|
||||
)
|
||||
assert "<AddressNumber>60000012</AddressNumber>" in customer_xml_content
|
||||
assert "<Name>Feuz</Name>" in customer_xml_content
|
||||
assert "<Text>VBV</Text>" not in customer_xml_content
|
||||
assert "<Street>Eggersmatt</Street>" in customer_xml_content
|
||||
|
||||
def test_render_customer_xml(self):
|
||||
customer_xml = render_customer_xml(
|
||||
abacus_debitor_number=60000012,
|
||||
|
|
|
|||
|
|
@ -1,29 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 120 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
|
||||
<g id="Artboard1" transform="matrix(1,0,0,1.21111,0,0)">
|
||||
<rect x="0" y="0" width="119.93" height="99" style="fill:none;"/>
|
||||
<g transform="matrix(1,0,0,1,11.64,0.93876)">
|
||||
<g transform="matrix(0.805954,0,0,0.670366,0,15.3769)">
|
||||
<ellipse cx="59.96" cy="64.42" rx="59.96" ry="34.58" style="fill:rgb(237,242,246);"/>
|
||||
</g>
|
||||
<g transform="matrix(0.805954,0,0,0.670366,0,15.3769)">
|
||||
<path d="M74.08,46.16C73.64,45.74 72.94,45.76 72.53,46.21C71.3,47.52 69.98,48.8 68.62,50.07C71.35,45.54 73.02,40.58 73.02,36.2C73.02,29.9 69.49,25.83 64.03,25.83C61.53,25.83 58.71,26.69 55.87,28.33C47.37,33.24 40.4,44.09 39.85,53C39.85,53.03 39.83,53.06 39.83,53.09C39.81,53.2 37.88,64.4 29.18,70.46C27.86,70.49 26.64,70.41 25.54,70.18C24.95,70.06 24.37,70.44 24.24,71.04C24.12,71.63 24.5,72.21 25.1,72.34C26.21,72.57 27.41,72.68 28.69,72.68C35.13,72.68 43.62,69.86 52.67,64.63C54.19,63.75 55.7,62.82 57.19,61.84C57.38,61.73 57.57,61.61 57.76,61.49C57.8,61.46 57.85,61.43 57.89,61.41C58.13,61.26 58.36,61.11 58.59,60.95C58.59,60.95 58.61,60.94 58.62,60.93C59.15,60.57 59.67,60.19 60.18,59.79C65.26,56.17 70.05,52.04 74.12,47.73C74.54,47.29 74.52,46.59 74.08,46.18L74.08,46.16ZM43.96,57.17C43.96,51.09 49.79,42.78 56.97,38.64C59.51,37.18 61.93,36.4 63.97,36.4C67.14,36.4 68.88,38.22 68.88,41.52C68.88,44 67.9,46.85 66.26,49.64C66.37,49.06 66.43,48.48 66.43,47.93C66.43,44.64 64.4,42.52 61.26,42.52C59.63,42.52 57.76,43.1 55.87,44.19C50.57,47.25 46.42,53.41 46.42,58.21C46.42,59.88 46.95,61.25 47.88,62.2C47.72,62.18 47.56,62.16 47.4,62.13C45.17,61.63 43.96,59.91 43.96,57.17ZM55.27,60.37C53.93,61.06 52.67,61.42 51.58,61.42C49.64,61.42 48.61,60.31 48.61,58.21C48.61,54.25 52.44,48.7 56.97,46.09C58.53,45.19 60.01,44.72 61.26,44.72C63.2,44.72 64.23,45.83 64.23,47.93C64.23,51.41 61.27,56.12 57.47,58.98C56.92,59.36 56.36,59.72 55.8,60.09C55.62,60.19 55.45,60.28 55.28,60.38L55.27,60.37ZM56.96,30.22C59.46,28.78 61.9,28.01 64.02,28.01C68.28,28.01 70.82,31.06 70.82,36.18C70.82,36.99 70.76,37.82 70.64,38.66C69.71,35.85 67.33,34.19 63.97,34.19C61.54,34.19 58.74,35.07 55.87,36.72C49.03,40.67 43.31,48.24 42.03,54.62C42.03,54.44 42.01,54.26 42.01,54.07C42.01,45.67 48.72,34.97 56.96,30.21L56.96,30.22ZM40.53,58.59C41.62,61.67 43.9,63.72 46.99,64.28C47.42,64.37 47.87,64.43 48.34,64.45C42.87,67.29 37.65,69.19 33.18,69.99C37.03,66.36 39.27,61.95 40.52,58.58L40.53,58.59Z" style="fill:rgb(60,60,59);fill-rule:nonzero;"/>
|
||||
<path d="M27.8,71.52C44.86,71.97 64.39,56.33 74.36,46.36" style="fill-rule:nonzero;stroke:rgb(125,78,42);stroke-width:1px;"/>
|
||||
<path d="M46.7,66.61L51.7,43.79" style="fill:none;fill-rule:nonzero;stroke:rgb(125,78,42);stroke-width:1px;"/>
|
||||
<path d="M66.69,53.27L63.26,39.41" style="fill:none;fill-rule:nonzero;stroke:rgb(125,78,42);stroke-width:1px;"/>
|
||||
<path d="M67.18,26.01L51.54,33.89C50.84,34.29 50.62,34.45 50.22,34.8" style="fill-rule:nonzero;stroke:rgb(125,78,42);stroke-width:1px;"/>
|
||||
<path d="M67.66,78.58L72.58,56.13" style="fill:none;fill-rule:nonzero;"/>
|
||||
<path d="M86.47,66.17L81.41,45.68" style="fill:none;fill-rule:nonzero;"/>
|
||||
<path d="M47.64,47.78L47.78,46.76C47.98,45.31 48.98,43.77 50.14,43.1C50.18,43.07 50.23,43.05 50.27,43.03L60.64,37.7C60.64,37.7 60.73,37.65 60.77,37.63C61.73,37.08 62.6,35.9 62.95,34.66L71.84,2.47C72.08,1.62 72.68,0.82 73.33,0.44C73.39,0.41 73.45,0.37 73.51,0.35L74.01,0.13C74.39,-0.04 74.72,-0.03 74.97,0.11L95.23,11.88C94.98,11.74 94.65,11.73 94.27,11.9L93.77,12.12C93.71,12.15 93.65,12.18 93.59,12.21C92.94,12.59 92.34,13.39 92.1,14.24L83.22,46.43C82.86,47.67 82,48.85 81.04,49.4C81,49.43 80.95,49.45 80.91,49.47L70.54,54.8C70.54,54.8 70.45,54.85 70.41,54.87C69.25,55.54 68.24,57.08 68.05,58.53L67.91,59.55C67.84,60.08 67.99,60.47 68.26,60.63L48,48.86C47.72,48.7 47.57,48.31 47.65,47.78L47.64,47.78Z" style="fill:rgb(175,200,223);fill-rule:nonzero;"/>
|
||||
<path d="M93.59,12.21C92.94,12.59 92.34,13.39 92.1,14.24L83.22,46.43C82.86,47.67 82,48.85 81.04,49.4C81,49.43 80.95,49.45 80.91,49.47L70.54,54.8C70.54,54.8 70.45,54.85 70.41,54.87C69.25,55.54 68.24,57.08 68.05,58.53L67.91,59.55C67.78,60.52 68.38,61 69.17,60.54L84.34,51.78C85.11,51.34 85.8,50.39 86.07,49.4L95.61,13.61C95.97,12.27 95.32,11.43 94.28,11.89L93.78,12.11C93.72,12.14 93.66,12.17 93.6,12.2L93.59,12.21Z" style="fill:rgb(151,179,205);fill-rule:nonzero;"/>
|
||||
<path d="M68.83,56.45L48.57,44.68C48.16,45.32 47.87,46.05 47.78,46.75L47.64,47.77C47.57,48.3 47.72,48.69 47.99,48.85L68.25,60.62C67.97,60.46 67.82,60.07 67.9,59.54L68.04,58.52C68.14,57.81 68.43,57.08 68.83,56.45Z" style="fill:rgb(125,149,172);fill-rule:nonzero;"/>
|
||||
<path d="M61.85,36.69C62.34,36.11 62.74,35.4 62.95,34.65L71.84,2.47C71.94,2.12 72.11,1.79 72.31,1.49L92.57,13.26C92.37,13.57 92.2,13.9 92.1,14.25L83.22,46.44C83.01,47.18 82.61,47.9 82.12,48.48L61.86,36.71L61.85,36.69Z" style="fill:rgb(125,149,172);fill-rule:nonzero;"/>
|
||||
<path d="M94.96,58.5C94.52,58.08 93.82,58.1 93.41,58.55C92.18,59.86 90.86,61.14 89.5,62.41C92.23,57.88 93.9,52.92 93.9,48.54C93.9,42.24 90.37,38.17 84.91,38.17C82.41,38.17 79.59,39.03 76.75,40.67C68.25,45.58 61.28,56.43 60.73,65.34C60.73,65.37 60.71,65.4 60.71,65.43C60.69,65.54 58.76,76.74 50.06,82.8C48.74,82.83 47.52,82.75 46.42,82.52C45.83,82.4 45.25,82.78 45.12,83.38C45,83.97 45.38,84.55 45.98,84.68C47.09,84.91 48.29,85.02 49.57,85.02C56.01,85.02 64.5,82.2 73.55,76.97C75.07,76.09 76.58,75.16 78.07,74.18C78.26,74.07 78.45,73.95 78.64,73.83C78.68,73.8 78.73,73.77 78.77,73.75C79.01,73.6 79.24,73.45 79.47,73.29C79.47,73.29 79.49,73.28 79.5,73.27C80.03,72.91 80.55,72.53 81.06,72.13C86.14,68.51 90.93,64.38 95,60.07C95.42,59.63 95.4,58.93 94.96,58.52L94.96,58.5ZM64.84,69.51C64.84,63.43 70.67,55.12 77.85,50.98C80.39,49.52 82.81,48.74 84.85,48.74C88.02,48.74 89.76,50.56 89.76,53.86C89.76,56.34 88.78,59.19 87.14,61.98C87.25,61.4 87.31,60.82 87.31,60.27C87.31,56.98 85.28,54.86 82.14,54.86C80.51,54.86 78.64,55.44 76.75,56.53C71.45,59.59 67.3,65.75 67.3,70.55C67.3,72.22 67.83,73.59 68.76,74.54C68.6,74.52 68.44,74.5 68.28,74.47C66.05,73.97 64.84,72.25 64.84,69.51ZM76.15,72.71C74.81,73.4 73.55,73.76 72.46,73.76C70.52,73.76 69.49,72.65 69.49,70.55C69.49,66.59 73.32,61.04 77.85,58.43C79.41,57.53 80.89,57.06 82.14,57.06C84.08,57.06 85.11,58.17 85.11,60.27C85.11,63.75 82.15,68.46 78.35,71.32C77.8,71.7 77.24,72.06 76.68,72.43C76.5,72.53 76.33,72.62 76.16,72.72L76.15,72.71ZM77.84,42.56C80.34,41.12 82.78,40.35 84.9,40.35C89.16,40.35 91.7,43.4 91.7,48.52C91.7,49.33 91.64,50.16 91.52,51C90.59,48.19 88.21,46.53 84.85,46.53C82.42,46.53 79.62,47.41 76.75,49.06C69.91,53.01 64.19,60.58 62.91,66.96C62.91,66.78 62.89,66.6 62.89,66.41C62.89,58.01 69.6,47.31 77.84,42.55L77.84,42.56ZM61.41,70.93C62.5,74.01 64.78,76.06 67.87,76.62C68.3,76.71 68.75,76.77 69.22,76.79C63.75,79.63 58.53,81.53 54.06,82.33C57.91,78.7 60.15,74.29 61.4,70.92L61.41,70.93Z" style="fill:rgb(166,102,53);fill-rule:nonzero;"/>
|
||||
<path d="M48.68,83.86C65.74,84.31 85.27,68.67 95.24,58.7" style="fill-rule:nonzero;"/>
|
||||
<path d="M87.32,38.72L72.42,46.23C71.72,46.63 71.5,46.79 71.1,47.14" style="fill-rule:nonzero;"/>
|
||||
<path d="M58.22,82.57L37.41,70.56" style="fill:none;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150"><ellipse cx="75" cy="100.42" rx="59.96" ry="34.58" fill="#edf2f6"/><path d="M42.83 107.52c17.06.45 36.59-15.19 46.56-25.16M61.73 102.61l5.01-22.82M81.72 89.27 78.3 75.41M82.22 62.01l-15.64 7.88c-.7.4-.92.56-1.32.91" fill="none" stroke="#7d4e2a" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2.52"/><path fill="none" stroke="#a66635" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2.52" d="m82.69 114.58 4.93-22.45M101.51 102.17l-5.07-20.49"/><path d="m62.67 83.78.14-1.02c.2-1.45 1.2-2.99 2.36-3.66.04-.03.09-.05.13-.07l10.37-5.33s.09-.05.13-.07c.96-.55 1.83-1.73 2.18-2.97l8.88-32.19c.24-.85.84-1.65 1.49-2.03.06-.03.12-.07.18-.09l.5-.22c.38-.17.71-.16.96-.02l20.26 11.77c-.25-.14-.58-.15-.96.02l-.5.22-.18.09c-.65.38-1.25 1.18-1.49 2.03l-8.88 32.19c-.36 1.24-1.22 2.42-2.18 2.97-.04.03-.09.05-.13.07L85.56 90.8s-.09.05-.13.07c-1.16.67-2.17 2.21-2.36 3.66l-.14 1.02c-.07.53.08.92.35 1.08L63.02 84.86c-.28-.16-.43-.55-.35-1.08Z" fill="#afc8df"/><path d="M108.62 48.21c-.65.38-1.25 1.18-1.49 2.03l-8.88 32.19c-.36 1.24-1.22 2.42-2.18 2.97-.04.03-.09.05-.13.07L85.57 90.8s-.09.05-.13.07c-1.16.67-2.17 2.21-2.36 3.66l-.14 1.02c-.13.97.47 1.45 1.26.99l15.17-8.76c.77-.44 1.46-1.39 1.73-2.38l9.54-35.79c.36-1.34-.29-2.18-1.33-1.72l-.5.22-.18.09Z" fill="#97b3cd"/><path d="M83.87 92.45 63.61 80.68c-.41.64-.7 1.37-.79 2.07l-.14 1.02c-.07.53.08.92.35 1.08l20.26 11.77c-.28-.16-.43-.55-.35-1.08l.14-1.02c.1-.71.39-1.44.79-2.07ZM76.89 72.69c.49-.58.89-1.29 1.1-2.04l8.88-32.19c.1-.35.27-.68.47-.98l20.26 11.77c-.2.31-.37.64-.47.99l-8.88 32.19c-.21.74-.61 1.46-1.1 2.04L76.89 72.7Z" fill="#7d95ac"/><path d="M63.71 119.86c17.06.45 36.59-15.19 46.56-25.16M102.36 74.72l-14.9 7.51c-.7.4-.92.56-1.32.91" fill="none" stroke="#a66635" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2.52"/><path fill="none" stroke="#a66635" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2.29" d="m73.26 118.57-20.81-12.01"/></svg>
|
||||
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 2.0 KiB |
Loading…
Reference in New Issue