@@ -423,7 +423,8 @@ export const AbgabeStudentDetail = {
:clearable="false"
:disabled="true"
:enable-time-picker="false"
- :format="formatDate"
+ locale="de"
+ format="dd.MM.yyyy"
:text-input="true"
auto-apply>
@@ -454,7 +455,7 @@ export const AbgabeStudentDetail = {
-
{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}
+
{{$capitalize( $p.t('abgabetool/c4abgabekurzbzv2') )}}
diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js
index 34ddd3fc2..d5caf97a6 100644
--- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js
+++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js
@@ -8,15 +8,8 @@ import ApiStudiensemester from '../../../api/factory/studiensemester.js';
import AbgabeterminStatusLegende from "./StatusLegende.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { splitMailsHelper } from "../../../helpers/EmailHelpers.js"
-
-// spoofed date testing
-// const todayISO = '2025-08-08'
-// const today = new Date(todayISO)
-// const now = luxon.DateTime.fromISO(todayISO)
-
-// prod code
-const today = new Date()
-const now = luxon.DateTime.now()
+import { getDateStyleClass} from "./getDateStyleClass.js";
+import { dateFilter } from '../../../tabulator/filters/Dates.js';
export const AbgabetoolAssistenz = {
name: "AbgabetoolAssistenz",
@@ -187,7 +180,7 @@ export const AbgabetoolAssistenz = {
// frozen: true,
// width: 40
// },
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, tooltip:false, minWidth: 150, cssClass: 'sticky-col'},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', headerFilter: false, headerSort: false, formatter: this.formAction, tooltip:false, minWidth: 150, cssClass: 'sticky-col'},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'student_vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
@@ -199,20 +192,47 @@ export const AbgabetoolAssistenz = {
formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuer'))), field: 'erstbetreuer', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuer'))), field: 'zweitbetreuer', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))), headerFilter: true, field: 'prevTermin', formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), headerFilter: true, field: 'nextTermin', formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))), headerFilter: true, field: 'qgate1Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate2Status'))), headerFilter: true, field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerv2'))), field: 'erstbetreuer', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
+
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerTitelPre'))), field: 'betreuer_titelpre', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerVorname'))), field: 'betreuer_vorname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerNachname'))), field: 'betreuer_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerTitelPost'))), field: 'betreuer_titelpost', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
+
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerv2'))), field: 'zweitbetreuer', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
+
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerTitelPre'))), field: 'zweitbetreuer_titelpre', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerVorname'))), field: 'zweitbetreuer_vorname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerNachname'))), field: 'zweitbetreuer_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerTitelPost'))), field: 'zweitbetreuer_titelpost', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
+
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))),
+ headerFilter: dateFilter,
+ headerFilterFunc: this.headerFilterTerminCol,
+ sorter: this.sortFuncTerminCol,
+ field: 'prevTermin', formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), field: 'nextTermin',
+ headerFilter: dateFilter,
+ headerFilterFunc: this.headerFilterTerminCol,
+ sorter: this.sortFuncTerminCol,
+ formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))),
+ headerFilter: 'list',
+ headerFilterParams: { valuesLookup: this.getQGateStatusList },
+ field: 'qgate1Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate2Status'))),
+ headerFilter: 'list',
+ headerFilterParams: { valuesLookup: this.getQGateStatusList },
+ field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false},
],
persistence: false,
- persistenceID: "abgabetool_2025_12"
+ persistenceID: "abgabetool_2026_02_26"
},
abgabeTableEventHandlers: [
{
event: "rowSelectionChanged",
- handler: async(data) => {
+ handler: async(data) =>
+ {
this.selectedData.filter(sd => !data.includes(sd)).forEach(fsd => {
if(fsd.checkbox) fsd.checkbox.checked = false
})
@@ -220,19 +240,86 @@ export const AbgabetoolAssistenz = {
data.forEach(d => {
if(d.checkbox) d.checkbox.checked = true
})
-
+
this.selectedData = data
}
}
]};
},
methods: {
- sammelMailStudent(param) {
+ handlePaUpdated(projektarbeit) {
+ this.checkAbgabetermineProjektarbeit(projektarbeit)
+ this.$refs.abgabeTable.tabulator.redraw(true)
+ },
+ getQGateStatusList() {
+ return [
+ this.$p.t('abgabetool/c4keinTerminVorhanden'),
+ this.$p.t('abgabetool/c4positivBenotet'),
+ this.$p.t('abgabetool/c4negativBenotet'),
+ this.$p.t('abgabetool/c4notYetGraded'),
+ this.$p.t('abgabetool/c4notSubmitted'),
+ this.$p.t('abgabetool/c4notHappenedYet')
+ ]
+ },
+ sortFuncTerminCol(a, b, aRow, bRow, column, dir, params) {
+ if (a === null || typeof a === "undefined") return 1;
+ if (b === null || typeof b === "undefined") return -1;
- const emails = this.selectedData
- .map(row => `${row.student_uid}@${this.domain}`)
- .join(',');
- const uniqueRecipients = [...new Set(emails)];
+ // try to handle the prev/next interpretation consistently
+ // can only make this wrong UX whise so whatever
+ if(column._column.field == 'prevTermin') {
+ return Math.abs(b.diffMs) - Math.abs(a.diffMs)
+ } else if (column._column.field == 'nextTermin') {
+ return Math.abs(a.diffMs) - Math.abs(b.diffMs)
+ }
+
+ // just in case someone reuses this
+ return Math.abs(b.diffMs) - Math.abs(a.diffMs)
+ },
+ headerFilterTerminCol(filterVal, rowVal) {
+ if (!rowVal || !rowVal.luxonDate || !rowVal.luxonDate.isValid) {
+ return false;
+ }
+
+ const rowDate = rowVal.luxonDate;
+
+ const toLuxon = (val) => {
+ if (!val) return null;
+ let dt;
+ if (val instanceof Date) {
+ dt = luxon.DateTime.fromJSDate(val);
+ } else if (typeof val === "string") {
+ dt = luxon.DateTime.fromISO(val);
+ } else { // fallback
+ dt = luxon.DateTime.fromMillis(Number(val));
+ }
+
+ return dt.isValid ? dt : null;
+ };
+
+ const von = toLuxon(filterVal[0]);
+ const bis = toLuxon(filterVal[1]);
+
+ // specific day
+ if (von && !bis) {
+ return rowDate.hasSame(von, "day");
+ }
+
+ // range case
+ if (von && bis) {
+ return rowDate >= von.startOf("day") && rowDate <= bis.endOf("day");
+ }
+
+ return false
+ },
+ sammelMailStudent(param) {
+
+ const recipientList = [];
+ this.selectedData.forEach(d => {
+ recipientList.push(`${d.student_uid}@${this.domain}`)
+ })
+
+ const uniqueRecipients = [...new Set(recipientList)];
const subject = this.$p.t('abgabetool/c4sammelmailStudentBetreff', [this.selectedStudiengangOption?.bezeichnung]);
splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p)
},
@@ -281,7 +368,6 @@ export const AbgabetoolAssistenz = {
return false;
},
checkQualityGateStatus(projekt) {
- // TODO: might refine the representation of these states and maybe refactor code a little
const qgate1Termine = []
const qgate2Termine = []
@@ -301,7 +387,7 @@ export const AbgabetoolAssistenz = {
// reuse luxon calculated diffMs (termin.datum in relation to today) from previous datestyle check
qgate1Termine.forEach(qgate => {
if(qgate.note != null && projekt.qgate1StatusRank <= 5) {
- const noteOpt = this.notenOptions.find(opt => opt.note == qgate.note)
+ const noteOpt = typeof qgate.note !== 'object' ? this.notenOptions.find(opt => opt.note == qgate.note) : qgate.note
if(noteOpt.positiv) {
projekt.qgate1Status = this.$p.t('abgabetool/c4positivBenotet')
projekt.qgate1StatusRank = 5
@@ -323,7 +409,7 @@ export const AbgabetoolAssistenz = {
qgate2Termine.forEach(qgate => {
if(qgate.note != null && projekt.qgate1StatusRank <= 5) {
- const noteOpt = this.notenOptions.find(opt => opt.note == qgate.note)
+ const noteOpt = typeof qgate.note !== 'object' ? this.notenOptions.find(opt => opt.note == qgate.note) : qgate.note
if(noteOpt.positiv) {
projekt.qgate2Status = this.$p.t('abgabetool/c4positivBenotet')
projekt.qgate2StatusRank = 5
@@ -385,14 +471,18 @@ export const AbgabetoolAssistenz = {
},
checkAbgabetermineProjektarbeit(projekt) {
+ const now = luxon.DateTime.now()
+
// calculate Abgabetermin time diff to now and assign last and next to projekt
projekt.abgabetermine.forEach(termin => {
-
+
+ termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
// while already looping through each termin, calculate datestyle beforehand
- termin.dateStyle = this.getDateStyleClass(termin)
+ termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
- const date = luxon.DateTime.fromISO(termin.datum)
+ const date = luxon.DateTime.fromISO(termin.datum).endOf('day')
+ termin.luxonDate = date
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
if (termin.diffMs < 0) {
@@ -612,6 +702,9 @@ export const AbgabetoolAssistenz = {
},
addSeries() {
const pids = this.selectedData?.map(projekt => projekt.projektarbeit_id)
+
+ const preserveSelected = [...this.selectedData]
+
this.saving = true
this.serienTermin.fixtermin = !this.serienTermin.invertedFixtermin
this.$api.call(ApiAbgabe.postSerientermin(
@@ -644,14 +737,27 @@ export const AbgabetoolAssistenz = {
})
// reset selection to empty
- this.$refs.abgabeTable.tabulator.deselectRow()
-
- const mappedData = this.mapProjekteToTableData(this.projektarbeiten)
+ // this.$refs.abgabeTable.tabulator.deselectRow()
+ const table = this.$refs.abgabeTable.tabulator;
+ const scrollX = table.rowManager.scrollLeft;
+ const scrollY = table.rowManager.scrollTop;
+
+ const mappedData = this.mapProjekteToTableData(this.projektarbeiten)
+
+ table.setData(mappedData)
+ table.redraw(true)
+
+ Vue.nextTick(()=> {
+ const table = this.$refs.abgabeTable?.tabulator.element.querySelector('.tabulator-tableholder')
+ if(table) {
+ table.scrollLeft = scrollX;
+ table.scrollTop = scrollY;
+ }
+ })
- this.$refs.abgabeTable.tabulator.setData(mappedData)
- this.$refs.abgabeTable.tabulator.redraw(true)
}).finally(()=>{
this.saving = false
+ this.selectedData = preserveSelected
})
this.$refs.modalContainerAddSeries.hide()
@@ -705,10 +811,11 @@ export const AbgabetoolAssistenz = {
return str
},
isPastDate(date) {
- return new Date(date) < new Date(Date.now())
+ const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
+ const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
+ return nowInVienna > deadline;
},
setDetailComponent(details){
-
const pa = this.projektarbeiten.find(projektarbeit => projektarbeit.projektarbeit_id == details.projektarbeit_id)
if(pa?.abgabetermine?.length) {
@@ -729,6 +836,11 @@ export const AbgabetoolAssistenz = {
if(typeof termin.note !== 'object') {
termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
}
+
+ // only set this if it has not been set yet and abgabetermin has a note (qgate)
+ if(!termin.noteBackend && termin.note) {
+ termin.noteBackend = termin.note
+ }
termin.file = []
@@ -738,9 +850,7 @@ export const AbgabetoolAssistenz = {
// assistenz are not allowed to delete deadlines with existing submissions
termin.allowedToDelete = paIsBenotet ? false : !termin.abgabedatum
-
- termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
-
+
})
const vorname = pa.vorname ?? pa.student_vorname
@@ -748,52 +858,8 @@ export const AbgabetoolAssistenz = {
pa.student = `${vorname} ${nachname}`
this.selectedProjektarbeit = pa
-
this.$refs.modalContainerAbgabeDetail.show()
},
- dateDiffInDays(datum){
- const dateToday = luxon.DateTime.now().startOf('day');
-
- const dateDatum = luxon.DateTime.fromISO(datum).startOf('day');
-
- const duration = dateDatum.diff(dateToday, 'days');
-
- return duration.values.days;
- },
- getDateStyleClass(termin) {
- const datum = new Date(termin.datum)
- const abgabedatum = new Date(termin.abgabedatum)
-
- termin.diffindays = this.dateDiffInDays(termin.datum)
-
- const isLate = termin.abgabedatum && abgabedatum > datum;
-
- // GRADE STATUS
- if (termin.note) {
- if (termin.note.positiv) return 'bestanden';
- return 'nichtbestanden';
- }
-
- // ACTION REQUIRED FOR GRADE
- if (termin.bezeichnung?.benotbar && datum < today) {
- return 'beurteilungerforderlich';
- }
-
- // SUBMISSION STATUS
- if (termin.upload_allowed) {
- if (termin.abgabedatum) {
- return isLate ? 'verspaetet' : 'abgegeben';
- }
-
- // no submission yet
- if (datum < today) return 'verpasst';
- if (termin.diffindays <= 12) return 'abzugeben';
- return 'standard';
- }
-
- // GENERIC STATUS
- return datum < today ? 'verpasst' : 'standard';
- },
openTimeline(val) {
const projekt = this.projektarbeiten.find(p => p.projektarbeit_id == val.projektarbeit_id)
if(!projekt) {
@@ -864,7 +930,7 @@ export const AbgabetoolAssistenz = {
case 'abgegeben':
icon = '
'
break
- case 'beurteilungerfolderlich':
+ case 'beurteilungerforderlich':
icon = '
'
break
case 'bestanden':
@@ -984,7 +1050,7 @@ export const AbgabetoolAssistenz = {
if(this.ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER) {
menu.push({
- label: this.$p.t('abgabetool/c4sendEmailBetreuerv2', [this.uniqueBetreuerEmailCount]),
+ label: this.$p.t('abgabetool/c4sendEmailBetreuerv3', [this.uniqueBetreuerEmailCount]),
command: this.sammelMailBetreuer
})
}
@@ -1034,6 +1100,24 @@ export const AbgabetoolAssistenz = {
if(this.notenOptionFilter !== null && this.selectedStudiengangOption !== null) {
this.loadProjektarbeiten()
}
+ },
+ selectedData(newVal) {
+ const table = this.$refs.abgabeTable?.tabulator
+ if(!table) return
+
+ const allRows = table.getRows();
+
+ newVal.forEach(selected => {
+ const row = allRows.find(r => {
+ const data = r.getData()
+ if (data.projektarbeit_id == selected.projektarbeit_id) return r
+ })
+
+ row.select()
+ const cb = row.getElement().children[0]?.children[0]?.children[0]
+ if(cb) cb.checked = true
+ })
+
}
},
created() {
@@ -1145,15 +1229,16 @@ export const AbgabetoolAssistenz = {
-
{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}
+
{{$capitalize( $p.t('abgabetool/c4zieldatumv2') )}}
@@ -1186,7 +1271,7 @@ export const AbgabetoolAssistenz = {
-
{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}
+
{{$capitalize( $p.t('abgabetool/c4abgabekurzbzv2') )}}
@@ -1207,7 +1292,13 @@ export const AbgabetoolAssistenz = {
-
+
+
+
@@ -1279,7 +1370,7 @@ export const AbgabetoolAssistenz = {
- {{ $capitalize($p.t('abgabetool/c4zieldatum')) }}:
+ {{ $capitalize($p.t('abgabetool/c4zieldatumv2')) }}:
{{ formatDate(slotProps.item.datum) }}
diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js
index f33333ea3..ee1d18942 100644
--- a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js
+++ b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js
@@ -4,6 +4,9 @@ import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
import FhcOverlay from "../../Overlay/FhcOverlay.js";
+import { getDateStyleClass } from "./getDateStyleClass.js";
+import { dateFilter } from '../../../tabulator/filters/Dates.js';
+import {splitMailsHelper} from "../../../helpers/EmailHelpers.js";
export const AbgabetoolMitarbeiter = {
name: "AbgabetoolMitarbeiter",
@@ -14,6 +17,7 @@ export const AbgabetoolMitarbeiter = {
Checkbox: primevue.checkbox,
Dropdown: primevue.dropdown,
Textarea: primevue.textarea,
+ TieredMenu: primevue.tieredmenu,
VueDatePicker,
FhcOverlay
},
@@ -46,6 +50,7 @@ export const AbgabetoolMitarbeiter = {
phrasenResolved: false,
turnitin_link: null,
old_abgabe_beurteilung_link: null,
+ BETREUER_SAMMELMAIL_BUTTON_STUDENT: null,
saving: false,
loading: false,
abgabeTypeOptions: null,
@@ -79,7 +84,7 @@ export const AbgabetoolMitarbeiter = {
placeholder: Vue.computed(() => this.$p.t('global/noDataAvailable')),
selectable: true,
selectableCheck: this.selectionCheck,
- rowHeight: 80,
+ rowHeight: 40,
columns: [
{
formatter: function (cell, formatterParams, onRendered) {
@@ -135,18 +140,36 @@ export const AbgabetoolMitarbeiter = {
width: 50,
cssClass: 'sticky-col'
},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false, cssClass: 'sticky-col'},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, headerFilter: false, headerSort: false, widthGrow: 1, tooltip: false, cssClass: 'sticky-col'},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4kontakt'))), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, maxWidth: 500, widthGrow: 8},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerart'))), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 1}
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerartv2'))), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 1},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))), field: 'prevTermin',
+ headerFilter: dateFilter,
+ headerFilterFunc: this.headerFilterTerminCol,
+ sorter: this.sortFuncTerminCol,
+ formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), field: 'nextTermin',
+ headerFilter: dateFilter,
+ headerFilterFunc: this.headerFilterTerminCol,
+ sorter: this.sortFuncTerminCol,
+ formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))),
+ headerFilter: 'list',
+ headerFilterParams: { valuesLookup: this.getQGateStatusList },
+ field: 'qgate1Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate2Status'))),
+ headerFilter: 'list',
+ headerFilterParams: { valuesLookup: this.getQGateStatusList },
+ field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false}
],
persistence: false,
+ persistenceID: 'abgabeTableBetreuer2026-02-26'
},
abgabeTableEventHandlers: [{
event: "tableBuilt",
@@ -182,6 +205,331 @@ export const AbgabetoolMitarbeiter = {
]};
},
methods: {
+ handlePaUpdated(projektarbeit) {
+ this.checkAbgabetermineProjektarbeit(projektarbeit)
+ this.$refs.abgabeTable.tabulator.redraw(true)
+ },
+ sammelMailStudent(param) {
+
+ const recipientList = [];
+ this.selectedData.forEach(d => {
+ recipientList.push(`${d.student_uid}@${this.domain}`)
+ })
+ const uniqueRecipients = [...new Set(recipientList)];
+ const subject = ""; // empty subject line
+ splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p)
+ },
+ getQGateStatusList() {
+ return [
+ this.$p.t('abgabetool/c4keinTerminVorhanden'),
+ this.$p.t('abgabetool/c4positivBenotet'),
+ this.$p.t('abgabetool/c4negativBenotet'),
+ this.$p.t('abgabetool/c4notYetGraded'),
+ this.$p.t('abgabetool/c4notSubmitted'),
+ this.$p.t('abgabetool/c4notHappenedYet')
+ ]
+ },
+ sortFuncTerminCol(a, b, aRow, bRow, column, dir, params) {
+ if (a === null || typeof a === "undefined") return 1;
+ if (b === null || typeof b === "undefined") return -1;
+
+ // try to handle the prev/next interpretation consistently
+ // can only make this wrong UX whise so whatever
+ if(column._column.field == 'prevTermin') {
+ return Math.abs(b.diffMs) - Math.abs(a.diffMs)
+ } else if (column._column.field == 'nextTermin') {
+ return Math.abs(a.diffMs) - Math.abs(b.diffMs)
+ }
+
+ // just in case someone reuses this
+ return Math.abs(b.diffMs) - Math.abs(a.diffMs)
+ },
+ headerFilterTerminCol(filterVal, rowVal) {
+ if (!rowVal || !rowVal.luxonDate || !rowVal.luxonDate.isValid) {
+ return false;
+ }
+
+ const rowDate = rowVal.luxonDate;
+
+ const toLuxon = (val) => {
+ if (!val) return null;
+ let dt;
+ if (val instanceof Date) {
+ dt = luxon.DateTime.fromJSDate(val);
+ } else if (typeof val === "string") {
+ dt = luxon.DateTime.fromISO(val);
+ } else { // fallback
+ dt = luxon.DateTime.fromMillis(Number(val));
+ }
+
+ return dt.isValid ? dt : null;
+ };
+
+ const von = toLuxon(filterVal[0]);
+ const bis = toLuxon(filterVal[1]);
+
+ // specific day
+ if (von && !bis) {
+ return rowDate.hasSame(von, "day");
+ }
+
+ // range case
+ if (von && bis) {
+ return rowDate >= von.startOf("day") && rowDate <= bis.endOf("day");
+ }
+
+ return false
+ },
+ loadState() {
+ return JSON.parse(localStorage.getItem(this.abgabeTableOptions.persistenceID) || "null");
+ },
+ saveState(table) {
+ // avoid storing state after first restore part happened
+ if(!this.stateRestored) return
+ const rawLayout = table.getColumnLayout();
+ const state = {
+ columns: rawLayout.map(col => ({
+ field: col.field,
+ visible: col.visible,
+ width: col.width,
+ })),
+ sort: table.getSorters().map(s => ({
+ field: s.field,
+ dir: s.dir,
+ })),
+ filters: table.getFilters(),
+ headerFilters: table.getHeaderFilters()
+ };
+
+ localStorage.setItem(this.abgabeTableOptions.persistenceID, JSON.stringify(state));
+ },
+ handleTableBuilt() {
+ const table = this.$refs.abgabeTable.tabulator
+
+ this.tableBuiltResolve()
+
+ table.on("columnMoved", () => {
+ this.saveState(table);
+ });
+
+ table.on("columnResized", () => {
+ this.saveState(table);
+ });
+
+ table.on("columnVisibilityChanged", () => {
+ this.saveState(table);
+ });
+
+ table.on("filterChanged", () => {
+ this.saveState(table);
+ });
+
+ table.on("headerFilterChanged", () => {
+ this.saveState(table);
+ });
+
+ table.on("dataSorted", () => {
+ this.saveState(table);
+ });
+
+ table.on("columnSorted", () => {
+ this.saveState(table);
+ });
+
+ table.on("sortersChanged", () => {
+ this.saveState(table);
+ });
+
+ const saved = this.loadState();
+
+ table.on("renderComplete", () => {
+ if(!this.stateRestored) {
+
+ if (saved?.columns && !this.colLayoutRestored) {
+ const layout = saved.columns.map(col => ({
+ field: col.field,
+ width: col.width,
+ visible: col.visible,
+ // add more if needed, but keep it simple
+ }));
+
+ table.setColumnLayout(layout);
+
+ this.colLayoutRestored = true;
+ }
+
+ if (saved?.filters && !this.filtersRestored) {
+ this.filtersRestored = true // instantly avoid retriggers
+ table.setFilter(saved.filters);
+ }
+ if (saved?.headerFilters && !this.headerFiltersRestored) {
+ this.headerFiltersRestored = true // instantly avoid retriggers
+ for (let hf of saved.headerFilters) {
+ table.setHeaderFilterValue(hf.field, hf.value);
+ }
+ }
+
+ if (saved?.sort?.length && !this.sortRestored) {
+ this.sortRestored = true;
+
+ setTimeout(() => {
+ const sortList = saved.sort.map(s => {
+ const col = table.columnManager.findColumn(s.field);
+ if (!col) {
+ return null;
+ }
+ return { column: col, dir: s.dir };
+ }).filter(Boolean);
+
+ table.setSort(sortList);
+ }, 100);
+ }
+ this.stateRestored = true
+
+ }
+
+ });
+ },
+ checkQualityGateStatus(projekt) {
+ const qgate1Termine = []
+ const qgate2Termine = []
+
+ projekt.qgate1Status = this.$p.t('abgabetool/c4keinTerminVorhanden')// 'Kein Termin vorhanden'
+ projekt.qgate1StatusRank = 0
+ projekt.qgate2Status = this.$p.t('abgabetool/c4keinTerminVorhanden')
+ projekt.qgate2StatusRank = 0
+
+ projekt.abgabetermine.forEach(termin => {
+ if(termin.paabgabetyp_kurzbz == 'qualgate1') qgate1Termine.push(termin)
+ if(termin.paabgabetyp_kurzbz == 'qualgate2') qgate2Termine.push(termin)
+ })
+
+ // calculate qgateStatusRank and display the highest order status rank of all quality gate termine until one
+ // counts as passed, which is just a positive note no matter if anything has been uploaded
+
+ // reuse luxon calculated diffMs (termin.datum in relation to today) from previous datestyle check
+ qgate1Termine.forEach(qgate => {
+ if(qgate.note != null && projekt.qgate1StatusRank <= 5) {
+ const noteOpt = typeof qgate.note !== 'object' ? this.notenOptions.find(opt => opt.note == qgate.note) : qgate.note
+ if(noteOpt.positiv) {
+ projekt.qgate1Status = this.$p.t('abgabetool/c4positivBenotet')
+ projekt.qgate1StatusRank = 5
+ } else {
+ projekt.qgate1Status = this.$p.t('abgabetool/c4negativBenotet')
+ projekt.qgate1StatusRank = 4
+ }
+ } else if (qgate.note == null && projekt.qgate1StatusRank <= 3) {
+ projekt.qgate1Status = this.$p.t('abgabetool/c4notYetGraded')
+ projekt.qgate1StatusRank = 3
+ } else if(qgate.upload_allowed == true && qgate.abgabedatum == null && projekt.qgate1StatusRank <= 2) {
+ projekt.qgate1Status = this.$p.t('abgabetool/c4notSubmitted')
+ projekt.qgate1StatusRank = 2
+ } else if (qgate.upload_allowed == false && qgate.diffMs <= 0 && projekt.qgate1StatusRank <= 1) {
+ projekt.qgate1Status = this.$p.t('abgabetool/c4notHappenedYet')
+ projekt.qgate1StatusRank = 1
+ }
+ })
+
+ qgate2Termine.forEach(qgate => {
+ if(qgate.note != null && projekt.qgate1StatusRank <= 5) {
+ const noteOpt = typeof qgate.note !== 'object' ? this.notenOptions.find(opt => opt.note == qgate.note) : qgate.note
+ if(noteOpt.positiv) {
+ projekt.qgate2Status = this.$p.t('abgabetool/c4positivBenotet')
+ projekt.qgate2StatusRank = 5
+ } else {
+ projekt.qgate2Status = this.$p.t('abgabetool/c4negativBenotet')
+ projekt.qgate2StatusRank = 4
+ }
+ } else if (qgate.note == null && projekt.qgate2StatusRank <= 3) {
+ projekt.qgate2Status = this.$p.t('abgabetool/c4notYetGraded')
+ projekt.qgate2StatusRank = 3
+ } else if(qgate.upload_allowed == true && qgate.abgabedatum == null && projekt.qgate2StatusRank <= 2) {
+ projekt.qgate2Status = this.$p.t('abgabetool/c4notSubmitted')
+ projekt.qgate2StatusRank = 2
+ } else if (qgate.upload_allowed == false && qgate.diffMs <= 0 && projekt.qgate2StatusRank <= 1) {
+ projekt.qgate2Status = this.$p.t('abgabetool/c4notHappenedYet')
+ projekt.qgate2StatusRank = 1
+ }
+ })
+ },
+ checkAbgabetermineProjektarbeit(projekt) {
+ const now = luxon.DateTime.now()
+ // calculate Abgabetermin time diff to now and assign last and next to projekt
+ projekt.abgabetermine.forEach(termin => {
+
+ // while already looping through each termin, calculate datestyle beforehand
+ termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
+
+ const date = luxon.DateTime.fromISO(termin.datum).endOf('day')
+ termin.luxonDate = date
+ termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
+
+ if (termin.diffMs < 0) {
+ if (!projekt.prevTermin ||
+ termin.diffMs > projekt.prevTermin.diffMs // larger (less negative) = closer to now
+ ) {
+ projekt.prevTermin = termin;
+ }
+ } else if (termin.diffMs > 0) {
+ if (!projekt.nextTermin ||
+ termin.diffMs < projekt.nextTermin.diffMs // smaller positive = closer to now
+ ) {
+ projekt.nextTermin = termin;
+ }
+ }
+ })
+
+ // seperate check for quality gates
+ this.checkQualityGateStatus(projekt)
+ },
+ abgabterminFormatter(cell) {
+ const val = cell.getValue()
+
+ if(val) {
+ let icon = ''
+ switch(val.dateStyle) {
+ case 'verspaetet':
+ icon = '
'
+ break
+ case 'verpasst':
+ icon = '
'
+ break
+ case 'abzugeben':
+ icon = '
'
+ break
+ case 'standard':
+ icon = '
'
+ break
+ case 'abgegeben':
+ icon = '
'
+ break
+ case 'beurteilungerforderlich':
+ icon = '
'
+ break
+ case 'bestanden':
+ icon = '
'
+ break
+ case 'nichtbestanden':
+ icon = '
'
+ break
+ }
+
+ const bezeichnung = val.bezeichnung?.bezeichnung ?? val.bezeichnung
+
+ return '
' +
+ '' +
+ '
' +
+ '
'+bezeichnung+' - '+ this.formatDate(val.datum)+'
' +
+ '
'+
+ '
'
+
+ } else {
+ return ''
+ }
+
+ },
selectHandler(e, cell) {
const row = cell.getRow();
@@ -274,6 +622,24 @@ export const AbgabetoolMitarbeiter = {
)).then(res => {
if (res.meta.status === "success" && res.data) {
this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/serienTerminGespeichert'))
+
+ const oldScrollLeft = this.$refs.abgabeTable?.tabulator.rowManager.scrollLeft
+ const oldScrollTop = this.$refs.abgabeTable?.tabulator.rowManager.scrollTop
+ this.loading = true
+ this.loadProjektarbeiten(this.showAll, () => {
+ this.$refs.abgabeTable?.tabulator.redraw(true)
+ this.$refs.abgabeTable?.tabulator.setSort([]);
+ this.loading = false
+
+ Vue.nextTick(()=> {
+ const table = this.$refs.abgabeTable?.tabulator.element.querySelector('.tabulator-tableholder')
+ if(table) {
+ table.scrollLeft = oldScrollLeft;
+ table.scrollTop = oldScrollTop;
+ }
+ })
+
+ })
} else {
this.$fhcAlert.alertError(this.$p.t('abgabetool/errorSerienterminSpeichern'))
}
@@ -294,48 +660,69 @@ export const AbgabetoolMitarbeiter = {
return str
},
isPastDate(date) {
- return new Date(date) < new Date(Date.now())
+ const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
+ const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
+ return nowInVienna > deadline;
},
setDetailComponent(details){
this.loading=true
- this.loadAbgaben(details).then((res)=> {
- const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
- pa.abgabetermine = res.data[0].retval
- pa.isCurrent = res.data[1]
- let paIsBenotet = false
- if(pa.note !== undefined && pa.note !== null) {
- // check if the note is not defined as a non final projektarbeit note
- const opt = this.notenOptionsNonFinal.find(opt => opt.note)
- // if thats the case allow further work
- if(opt) paIsBenotet = false
- // else the PA is to be considered finished
- paIsBenotet = true
- }
+ const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
- pa.abgabetermine.forEach(termin => {
- termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
- termin.file = []
-
- // update 08-01-2026: everybody is allowed to do everything in client, critical checks happen at backend level
- // termin.allowedToSave = true
-
- // update 21-01-2026: actually blocking operations on finished projektarbeiten seems like a decent idea
- termin.allowedToSave = paIsBenotet ? false : true
-
- // lektoren are not allowed to delete deadlines with existing submissions
- termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum
-
- termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
+ let paIsBenotet = false
+ if(pa.note !== undefined && pa.note !== null) {
+ // check if the note is not defined as a non final projektarbeit note
+ const opt = this.notenOptionsNonFinal.find(opt => opt.note)
+ // if thats the case allow further work
+ if(opt) paIsBenotet = false
+ // else the PA is to be considered finished
+ paIsBenotet = true
+ }
- })
- pa.student_uid = details.student_uid
- pa.student = `${pa.vorname} ${pa.nachname}`
+ if(pa?.abgabetermine?.length) {
+ this.$api.call(ApiAbgabe.getSignaturStatusForProjektarbeitAbgaben(pa.abgabetermine.map(termin => termin.paabgabe_id), pa.student_uid))
+ .then(res => {
+ if(res.meta.status === 'success') {
+ res.data.forEach(paabgabe => {
+ const termin = pa.abgabetermine.find(abgabe => abgabe.paabgabe_id == paabgabe.paabgabe_id)
+ if(termin && paabgabe.signatur !== undefined) termin.signatur = paabgabe.signatur
+ })
+ }
+ })
+ }
+
+ pa.abgabetermine.forEach(termin => {
+ const noteOpt = this.allowedNotenOptions.find(opt => opt.note == termin.note)
+ if(noteOpt) termin.note = noteOpt
+ termin.file = []
- this.selectedProjektarbeit = pa
- this.$refs.modalContainerAbgabeDetail.show()
+ // only set this if it has not been set yet and abgabetermin has a note (qgate)
+ if(!termin.noteBackend && noteOpt) {
+ termin.noteBackend = noteOpt
+ }
+
+ // update 08-01-2026: everybody is allowed to do everything in client, critical checks happen at backend level
+ // termin.allowedToSave = true
+
+ // update 21-01-2026: actually blocking operations on finished projektarbeiten seems like a decent idea
+ termin.allowedToSave = paIsBenotet ? false : true
+
+ // lektoren are not allowed to delete deadlines with existing submissions
+ termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum
+
+ termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
+
+ })
+
+ pa.student_uid = details.student_uid
+ pa.student = `${pa.vorname} ${pa.nachname}`
+
+ this.selectedProjektarbeit = pa
+ this.$refs.modalContainerAbgabeDetail.show()
+
+
+ this.loading = false
- }).finally(()=>{this.loading = false})
},
centeredTextFormatter(cell) {
const val = cell.getValue()
@@ -348,11 +735,6 @@ export const AbgabetoolMitarbeiter = {
return '
'
},
- mailFormatter(cell) {
- const val = cell.getValue()
- return '
'
- },
beurteilungFormatter(cell) {
const val = cell.getValue()
if(val) {
@@ -379,11 +761,13 @@ export const AbgabetoolMitarbeiter = {
return (projekt.typ + projekt.kurzbz)?.toUpperCase()
},
setupData(data){
+
+
this.projektarbeiten = data[0]
this.domain = data[1]
this.tableData = data[0]?.retval?.map(projekt => {
-
+ this.checkAbgabetermineProjektarbeit(projekt)
projekt.selectable = projekt.betreuerart_kurzbz !== 'Zweitbegutachter'
return {
@@ -455,6 +839,29 @@ export const AbgabetoolMitarbeiter = {
},
},
computed: {
+ emailItems() {
+ const menu = []
+
+ if(this.BETREUER_SAMMELMAIL_BUTTON_STUDENT){
+ menu.push({
+ label: this.$p.t('abgabetool/c4sendEmailStudierendev2', [this.uniqueStudentEmailCount]),
+ command: this.sammelMailStudent
+ })
+ }
+
+ return menu
+ },
+ uniqueStudentEmailCount() {
+ const emails = new Set();
+
+ this.selectedData.forEach(row => {
+ if (row.student_uid) {
+ emails.add(row.student_uid); // actually dont need domain for this
+ }
+ });
+
+ return emails.size;
+ },
getAllowedAbgabeTypeOptions() {
return this.abgabeTypeOptions.filter(opt => this.abgabetypenBetreuer.includes(opt.paabgabetyp_kurzbz))
}
@@ -467,6 +874,7 @@ export const AbgabetoolMitarbeiter = {
this.turnitin_link = res.data?.turnitin_link
this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link
this.abgabetypenBetreuer = res.data?.abgabetypenBetreuer
+ this.BETREUER_SAMMELMAIL_BUTTON_STUDENT = res.data?.BETREUER_SAMMELMAIL_BUTTON_STUDENT
}).catch(e => {
this.loading = false
})
@@ -515,7 +923,7 @@ export const AbgabetoolMitarbeiter = {
-
{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}
+
{{$capitalize( $p.t('abgabetool/c4zieldatumv2') )}}
@@ -557,7 +966,7 @@ export const AbgabetoolMitarbeiter = {
-
{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}
+
{{$capitalize( $p.t('abgabetool/c4abgabekurzbzv2') )}}
@@ -578,7 +987,11 @@ export const AbgabetoolMitarbeiter = {
-
+
+
@@ -598,6 +1011,7 @@ export const AbgabetoolMitarbeiter = {
@click:new=openAddSeriesModal
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
+ @tableBuilt="handleTableBuilt"
tableOnly
:sideMenu="false"
:useSelectionSpan="false"
@@ -613,7 +1027,17 @@ export const AbgabetoolMitarbeiter = {
{{ $p.t('abgabetool/showDeadlines') }}
-
+
+
+
diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js
index ff68b680f..e8401d309 100644
--- a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js
+++ b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js
@@ -2,8 +2,8 @@ import AbgabeDetail from "./AbgabeStudentDetail.js";
import ApiAbgabe from '../../../api/factory/abgabe.js'
import BsModal from "../../Bootstrap/Modal.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js";
+import { getDateStyleClass} from "./getDateStyleClass.js";
-const today = new Date()
export const AbgabetoolStudent = {
name: "AbgabetoolStudent",
components: {
@@ -48,61 +48,6 @@ export const AbgabetoolStudent = {
};
},
methods: {
- dateDiffInDays(datumParam) {
- let datum = datumParam
- if(datumParam instanceof Date && !isNaN(datum.getTime()))
- {
- const year = datumParam.getFullYear();
- const month = datumParam.getMonth() + 1; // getMonth() is 0-indexed
- const day = datumParam.getDate();
- const pad = (num) => String(num).padStart(2, '0');
- datum = `${year}-${pad(month)}-${pad(day)}`
- }
-
- const dateToday = luxon.DateTime.now().startOf('day');
- const dateDatum = luxon.DateTime.fromISO(datum).startOf('day');
- const duration = dateDatum.diff(dateToday, 'days');
-
- return duration.values.days;
- },
- getDateStyleClass(termin) {
- const datum = new Date(termin.datum)
- const abgabedatum = new Date(termin.abgabedatum)
-
- termin.diffindays = this.dateDiffInDays(termin.datum)
-
- const isLate = termin.abgabedatum && abgabedatum > datum;
-
- // GRADE STATUS
- if (termin.note) {
- if(Number.isInteger(termin.note)) {
- const opt = this.notenOptions.find(opt => opt.note == termin.note)
- if(opt.positiv) return 'bestanden'
- }
- if (termin.note.positiv) return 'bestanden';
- return 'nichtbestanden';
- }
-
- // ACTION REQUIRED FOR GRADE
- if (termin.bezeichnung?.benotbar && datum < today) {
- return 'beurteilungerforderlich';
- }
-
- // SUBMISSION STATUS
- if (termin.upload_allowed) {
- if (termin.abgabedatum) {
- return isLate ? 'verspaetet' : 'abgegeben';
- }
-
- // no submission yet
- if (datum < today) return 'verpasst';
- if (termin.diffindays <= 12) return 'abzugeben';
- return 'standard';
- }
-
- // GENERIC STATUS
- return datum < today ? 'verpasst' : 'standard';
- },
checkQualityGatesStrict(termine) {
let qgate1Passed = false
let qgate2Passed = false
@@ -155,7 +100,9 @@ export const AbgabetoolStudent = {
return qgate1positiv && qgate2positiv
},
isPastDate(date) {
- return new Date(date) < new Date(Date.now())
+ const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
+ const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
+ return nowInVienna > deadline;
},
setDetailComponent(details){
this.loading = true
@@ -173,8 +120,8 @@ export const AbgabetoolStudent = {
// old assumed production logic when qgates are required
// termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGatesStrict(pa.abgabetermine)
- // new larifari we want qgates but they are optional fhtw mode
- termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGatesOptional(pa.abgabetermine)
+ const inTime = termin.fixtermin ? !this.isPastDate(termin.datum) : true
+ termin.allowedToUpload = inTime && this.checkQualityGatesOptional(pa.abgabetermine)
// development purposes
@@ -193,7 +140,7 @@ export const AbgabetoolStudent = {
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
- termin.dateStyle = this.getDateStyleClass(termin)
+ termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
})
pa.betreuer = this.buildBetreuer(pa)
@@ -383,7 +330,7 @@ export const AbgabetoolStudent = {
-
+
@@ -409,11 +356,11 @@ export const AbgabetoolStudent = {
{{$capitalize( $p.t('abgabetool/c4beurteilung') )}}
@@ -432,13 +379,13 @@ export const AbgabetoolStudent = {
-
{{ projektarbeit?.betreuerart_kurzbz ? $capitalize( $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) ) : $capitalize( $p.t('abgabetool/c4betreuer') ) }}
+
{{ projektarbeit?.betreuerart_kurzbz ? $capitalize( $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) ) : $capitalize( $p.t('abgabetool/c4betreuerv2') ) }}
{{ projektarbeit.betreuerart_kurzbz ? projektarbeit.betreuer : '' }}
-
{{$capitalize( $p.t('abgabetool/c4betreuerEmailKontakt') )}}
+
{{$capitalize( $p.t('abgabetool/c4betreuerEmailKontaktv2') )}}
diff --git a/public/js/components/Cis/Abgabetool/DeadlineOverview.js b/public/js/components/Cis/Abgabetool/DeadlineOverview.js
index 274d94120..6d0ddd02d 100644
--- a/public/js/components/Cis/Abgabetool/DeadlineOverview.js
+++ b/public/js/components/Cis/Abgabetool/DeadlineOverview.js
@@ -34,10 +34,10 @@ export const DeadlineOverview = {
layout: 'fitColumns',
placeholder: Vue.computed(() => this.$p.t('global/noDataAvailable')),
columns: [
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zieldatum'))), field: 'datum', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zieldatumv2'))), field: 'datum', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4fixterminv4'))), field: 'fixterminstring', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabetyp'))), field: 'typ_bezeichnung', formatter: this.centeredTextFormatter, widthGrow: 1},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabekurzbz'))), field: 'kurzbz', formatter: this.centeredTextFormatter, widthGrow: 3},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabekurzbzv2'))), field: 'kurzbz', formatter: this.centeredTextFormatter, widthGrow: 3},
{title: Vue.computed(() => this.$capitalize(this.$p.t('person/studentIn'))), field: 'student', formatter: this.centeredTextFormatter, widthGrow: 2},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'semester', formatter: this.centeredTextFormatter, widthGrow: 1}
diff --git a/public/js/components/Cis/Abgabetool/getDateStyleClass.js b/public/js/components/Cis/Abgabetool/getDateStyleClass.js
new file mode 100644
index 000000000..0449ad40a
--- /dev/null
+++ b/public/js/components/Cis/Abgabetool/getDateStyleClass.js
@@ -0,0 +1,37 @@
+
+const zone = 'Europe/Vienna';
+const today = luxon.DateTime.now().setZone(zone);
+
+export function getDateStyleClass(termin, notenOptions) {
+ const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
+ const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
+ termin.diffindays = datum.diff(today, 'days').days;
+ const isLate = abgabedatum && abgabedatum > datum;
+
+ // GRADE STATUS
+ if (termin.note) {
+ const opt = typeof termin.note === 'object' ? termin.note : notenOptions.find(nopt => nopt.note == termin.note)
+ if (opt?.positiv === true) return 'bestanden';
+ else if (opt?.positiv === false) return 'nichtbestanden';
+ }
+
+ // ACTION REQUIRED FOR GRADE
+ if (termin.bezeichnung?.benotbar && datum <= today) {
+ return 'beurteilungerforderlich';
+ }
+
+ // SUBMISSION STATUS
+ if (termin.upload_allowed) {
+ if (termin.abgabedatum) {
+ return isLate ? 'verspaetet' : 'abgegeben';
+ }
+
+ // no submission yet
+ if (datum < today) return 'verpasst';
+ if (termin.diffindays <= 12) return 'abzugeben';
+ return 'standard';
+ }
+
+ // GENERIC STATUS
+ return datum < today ? 'verpasst' : 'standard';
+}
\ No newline at end of file
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js b/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js
index 43995b918..d075b5a82 100644
--- a/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js
+++ b/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js
@@ -26,7 +26,7 @@ export default {
internMail(event) {
if (this.internMails.length)
{
- splitMailsHelper(this.privateMails, event, null, this.$fhcAlert, this.$p)
+ splitMailsHelper(this.internMails, event, null, this.$fhcAlert, this.$p)
}
},
privateMail(event) {
diff --git a/system/dbupdate_3.4/61164_abgabetool_quality_gates.php b/system/dbupdate_3.4/61164_abgabetool_quality_gates.php
index aa955ab07..21b0335da 100644
--- a/system/dbupdate_3.4/61164_abgabetool_quality_gates.php
+++ b/system/dbupdate_3.4/61164_abgabetool_quality_gates.php
@@ -363,6 +363,21 @@ if($result = $db->db_query("SELECT 1 FROM public.tbl_vorlage WHERE vorlage_kurzb
}
}
+if($result = $db->db_query("SELECT 1 FROM public.tbl_vorlage WHERE vorlage_kurzbz = 'PAANoSigAssSM'"))
+{
+ if($db->db_num_rows($result) === 0)
+ {
+ $qry = "INSERT INTO public.tbl_vorlage (vorlage_kurzbz, bezeichnung, anmerkung, mimetype)
+ VALUES ('PAANoSigAssSM', 'PAANoSigAssSM', null, 'text/html')
+ ON CONFLICT (vorlage_kurzbz) DO NOTHING;";
+
+ if(!$db->db_query($qry))
+ echo '
system.tbl_vorlage: '.$db->db_last_error().'';
+ else
+ echo "
system.tbl_vorlage PAANoSigAssSM hinzugefuegt";
+ }
+}
+
if($result = $db->db_query("SELECT 1 FROM public.tbl_vorlage WHERE vorlage_kurzbz = 'QualGateNegativ'"))
{
if($db->db_num_rows($result) === 0)
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php
index 3c9a3ac47..add5ce79d 100644
--- a/system/phrasesupdate.php
+++ b/system/phrasesupdate.php
@@ -10912,7 +10912,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'First Assessor',
+ 'text' => 'First Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -11552,7 +11552,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'The second assessor’s assessment has been submitted and is part of the final grade.',
+ 'text' => 'The second reviwer’s assessment has been submitted and is part of the final grade.',
'description' => '',
'insertvon' => 'system'
)
@@ -11732,7 +11732,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'Second Assessor',
+ 'text' => 'Second Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -11752,7 +11752,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'Assessor',
+ 'text' => 'Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -12132,7 +12132,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'To assessment of second assessor',
+ 'text' => 'To assessment of second reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -12152,7 +12152,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'The assessment of the second assessor is not available yet.',
+ 'text' => 'The assessment of the second reviewer is not available yet.',
'description' => '',
'insertvon' => 'system'
)
@@ -12232,7 +12232,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'Send token to Second Assessor',
+ 'text' => 'Send token to Second Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -12350,7 +12350,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'Sending only possible after completion of assessment by Second Assessor',
+ 'text' => 'Sending only possible after completion of assessment by Second Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -12590,7 +12590,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'Assessor grade',
+ 'text' => 'Reviewer grade',
'description' => '',
'insertvon' => 'system'
)
@@ -12610,7 +12610,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'Assessor',
+ 'text' => 'Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -12650,7 +12650,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'Assessor type',
+ 'text' => 'Reviewer type',
'description' => '',
'insertvon' => 'system'
)
@@ -12670,7 +12670,7 @@ Any unusual occurrences
),
array(
'sprache' => 'English',
- 'text' => 'secondary assessor',
+ 'text' => 'secondary reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -43481,7 +43481,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4betreuer',
+ 'phrase' => 'c4betreuerv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -43492,7 +43492,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => "Assessor",
+ 'text' => "Reviewer",
'description' => '',
'insertvon' => 'system'
)
@@ -43761,7 +43761,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4zieldatum',
+ 'phrase' => 'c4zieldatumv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -43772,7 +43772,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => "Target date",
+ 'text' => "Deadline",
'description' => '',
'insertvon' => 'system'
)
@@ -43801,7 +43801,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4abgabekurzbz',
+ 'phrase' => 'c4abgabekurzbzv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -43812,7 +43812,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => "Short description of the submitted file",
+ 'text' => "Short description of the submitted document",
'description' => '',
'insertvon' => 'system'
)
@@ -44301,7 +44301,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4betreuerart',
+ 'phrase' => 'c4betreuerartv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -44312,7 +44312,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'Assessor type',
+ 'text' => 'Reviewer type',
'description' => '',
'insertvon' => 'system'
)
@@ -45077,7 +45077,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4betreuerEmailKontakt',
+ 'phrase' => 'c4betreuerEmailKontaktv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45088,7 +45088,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'Email Assessor',
+ 'text' => 'Email Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -45117,7 +45117,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4downloadBeurteilungErstbetreuer',
+ 'phrase' => 'c4downloadBeurteilungErstbetreuerv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45128,7 +45128,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'Download evaluation of first assesor',
+ 'text' => 'Download evaluation of first reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -45137,7 +45137,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4downloadBeurteilungZweitbetreuer',
+ 'phrase' => 'c4downloadBeurteilungZweitbetreuerv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45148,7 +45148,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'Download evaluation of second assesor',
+ 'text' => 'Download evaluation of second reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -45572,7 +45572,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4erstbetreuer',
+ 'phrase' => 'c4erstbetreuerv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45583,7 +45583,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'First Assessor',
+ 'text' => 'First Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -45592,7 +45592,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4zweitbetreuer',
+ 'phrase' => 'c4zweitbetreuerv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45603,7 +45603,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'Second Assessor',
+ 'text' => 'Second Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -45672,7 +45672,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4sendEmailBetreuerv2',
+ 'phrase' => 'c4sendEmailBetreuerv3',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45683,7 +45683,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'Send Email to {0} assessors',
+ 'text' => 'Send Email to {0} reviewers',
'description' => '',
'insertvon' => 'system'
)
@@ -45772,7 +45772,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4fehlerMailBegutachter',
+ 'phrase' => 'c4fehlerMailBegutachterv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45783,7 +45783,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'Error sending E-Mail to first Assessor!',
+ 'text' => 'Error sending E-Mail to first Reviewer!',
'description' => '',
'insertvon' => 'system'
)
@@ -45792,7 +45792,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
- 'phrase' => 'c4fehlerMailZweitBegutachter',
+ 'phrase' => 'c4fehlerMailZweitBegutachterv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45803,7 +45803,7 @@ array(
),
array(
'sprache' => 'English',
- 'text' => 'Error sending E-Mail to second Assessor!',
+ 'text' => 'Error sending E-Mail to second Reviewer!',
'description' => '',
'insertvon' => 'system'
)
@@ -46495,6 +46495,206 @@ array(
)
)
),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4deadlineExceeded',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Nicht rechtzeitig abgegeben!',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Deadline exceeded!',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4missingSignatureNotification',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Abgabetool: Fehlende Signatur bei Endupload',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Submission tool: Missing signature at final upload',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4erstbetreuerTitelPre',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'ErstbetreuerIn Titel Pre',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'First Reviewer Title Pre',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4erstbetreuerVorname',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'ErstbetreuerIn Vorname',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'First Reviewer First Name',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4erstbetreuerNachname',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'ErstbetreuerIn Nachname',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'First Reviewer Last Name',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4erstbetreuerTitelPost',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'ErstbetreuerIn Titel Post',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'First Reviewer Title Post',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4zweitbetreuerTitelPre',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'ZweitbetreuerIn Titel Pre',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Second Reviewer Title Pre',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4zweitbetreuerVorname',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'ZweitbetreuerIn Vorname',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Second Reviewer First Name',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4zweitbetreuerNachname',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'ZweitbetreuerIn Nachname',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Second Reviewer Last Name',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'abgabetool',
+ 'phrase' => 'c4zweitbetreuerTitelPost',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'ZweitbetreuerIn Titel Post',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Second Reviewer Title Post',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
// ABGABETOOL PHRASEN END
array(
'app' => 'core',
@@ -55046,7 +55246,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'assessor',
+ 'text' => 'reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -55066,7 +55266,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'Assessor',
+ 'text' => 'Reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -55086,7 +55286,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'assessor type',
+ 'text' => 'reviewer type',
'description' => '',
'insertvon' => 'system'
)
@@ -55286,7 +55486,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'Deleting not possible, assessors were already assigned to this projekt work',
+ 'text' => 'Deleting not possible, reviewers were already assigned to this projekt work',
'description' => '',
'insertvon' => 'system'
)
@@ -55326,7 +55526,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'Invalid project assessors',
+ 'text' => 'Invalid project reviewers',
'description' => '',
'insertvon' => 'system'
)
@@ -55346,7 +55546,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'Deleting not possible, project assessor has a contract already',
+ 'text' => 'Deleting not possible, project reviewer has a contract already',
'description' => '',
'insertvon' => 'system'
)
@@ -55786,7 +55986,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'Edit assessor',
+ 'text' => 'Edit reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -55806,7 +56006,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'Save assessor',
+ 'text' => 'Save reviewer',
'description' => '',
'insertvon' => 'system'
)
@@ -55946,7 +56146,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'project assessor short name',
+ 'text' => 'project reviewer short name',
'description' => '',
'insertvon' => 'system'
)
@@ -55986,7 +56186,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
- 'text' => 'This project assessor is already assigned',
+ 'text' => 'This project reviewer is already assigned',
'description' => '',
'insertvon' => 'system'
)