feat: implement issue #9 #10

Open
Luca wants to merge 1 commits from ai/issue-9 into master
2 changed files with 172 additions and 2 deletions

View File

@@ -342,5 +342,60 @@ class WorkorderAdminController extends WorkorderBaseController
self::returnJson(['success' => true, 'message' => 'Status erfolgreich auf "Zugewiesen" zurückgesetzt.']);
}
protected function scheduleAppointmentAction()
{
if (empty($this->postData['workorderId']) || empty($this->postData['appointmentDate'])) self::sendError("Erforderliche Felder fehlen.");
$workorder = WorkorderModel::get($this->postData['workorderId']);
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
if ((int)date('H', $this->postData['appointmentDate']) >= 23 || (int)date('H', $this->postData['appointmentDate']) < 1) self::sendError("Bitte geben Sie eine Uhrzeit an!");
$workorder->appointmentDate = $this->postData['appointmentDate'];
$workorder->status = 'scheduled';
WorkorderModel::update((array)$workorder);
WorkorderJournalModel::create([
'workorderId' => $workorder->id, 'text' => 'Termin festgelegt auf: ' . date('d.m.Y H:i', $this->postData['appointmentDate']),
'create' => time(), 'createBy' => $this->user->id,
]);
self::returnJson(['success' => true, 'message' => 'Termin erfolgreich gespeichert.']);
}
protected function rescheduleAppointmentAction()
{
if (empty($this->postData['workorderId']) || empty($this->postData['appointmentDate']) || empty($this->postData['reason'])) self::sendError("Erforderliche Felder fehlen.");
$workorder = WorkorderModel::get($this->postData['workorderId']);
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
if ((int)date('H', $this->postData['appointmentDate']) >= 23 || (int)date('H', $this->postData['appointmentDate']) < 1) self::sendError("Bitte geben Sie eine Uhrzeit an!");
$oldDateFormatted = $workorder->appointmentDate ? date('d.m.Y H:i', $workorder->appointmentDate) : 'N/A';
$newDateFormatted = date('d.m.Y H:i', $this->postData['appointmentDate']);
$workorder->appointmentDate = $this->postData['appointmentDate'];
WorkorderModel::update((array)$workorder);
WorkorderJournalModel::create([
'workorderId' => $workorder->id, 'text' => "Termin verschoben von {$oldDateFormatted} auf {$newDateFormatted}. Grund: " . $this->postData['reason'],
'create' => time(), 'createBy' => $this->user->id,
]);
self::returnJson(['success' => true, 'message' => 'Termin erfolgreich verschoben.']);
}
protected function clearAppointmentAction()
{
if (empty($this->postData['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
$workorder = WorkorderModel::get($this->postData['workorderId']);
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
$oldDateFormatted = $workorder->appointmentDate ? date('d.m.Y H:i', $workorder->appointmentDate) : 'N/A';
$workorder->appointmentDate = null;
$workorder->status = 'assigned';
WorkorderModel::update((array)$workorder);
WorkorderJournalModel::create([
'workorderId' => $workorder->id, 'text' => "Termin gelöscht (war: {$oldDateFormatted}).",
'create' => time(), 'createBy' => $this->user->id,
]);
self::returnJson(['success' => true, 'message' => 'Termin erfolgreich gelöscht.']);
}
//endregion
}

View File

@@ -90,7 +90,23 @@ Vue.component('workorder-admin', {
</div>
</template>
<template v-slot:appointmentdate="{ row }">{{ formatDate(row.appointmentDate, true) }}</template>
<template v-slot:appointmentdate="{ row }">
<div v-if="!row.appointmentDate && ['new', 'assigned', 'scheduled', 'correction_requested', 'problem_solved', 'civil_engineering_completed'].includes(row.status)">
<tt-date-picker placeholder="Termin festlegen..." :date-range="false"
@input="setAppointment(row, $event)" sm no-form-group
:additional-props="{ timePicker: true, timePicker24Hour: true, locale: { format: 'DD.MM.YYYY HH:mm' }, drops: 'up' }"/>
</div>
<div v-else-if="row.appointmentDate">
<span>{{ formatDate(row.appointmentDate, true) }}</span>
<tt-button v-if="!['completed', 'cancelled', 'documented', 'charged', 'archived'].includes(row.status)"
icon="fas fa-edit" @click="openRescheduleModal(row)"
additional-class="btn-link btn-sm p-0 ml-2" title="Termin ändern"/>
<tt-button v-if="!['completed', 'cancelled', 'documented', 'charged', 'archived'].includes(row.status)"
icon="fas fa-times" @click="clearAppointment(row)"
additional-class="btn-link btn-sm p-0 ml-1 text-danger" title="Termin löschen"/>
</div>
<span v-else></span>
</template>
<template v-slot:expandedRow="{ row }">
<civil-engineering-manager v-if="row.status === 'civil_engineering_required'" :workorder-id="row.id" :is-admin="true" class="mb-3"/>
@@ -120,13 +136,20 @@ Vue.component('workorder-admin', {
<tt-date-picker label="Deadline" v-model="massAssignModalData.deadline" :date-range="false" sm row/>
</tt-modal>
<tt-modal v-if="rescheduleModalData" :show.sync="rescheduleModalData" title="Termin verschieben" @submit="rescheduleAppointment">
<p><strong>Auftrag:</strong> #{{ rescheduleModalData.workorder.id }}</p>
<tt-date-picker label="Neuer Termin" :date-range="false" v-model="rescheduleModalData.newDate"
:additional-props="{ timePicker: true, timePicker24Hour: true, locale: { format: 'DD.MM.YYYY HH:mm' }, singleDatePicker: true }" sm row/>
<tt-textarea label="Grund" v-model="rescheduleModalData.reason" sm row required/>
</tt-modal>
</tt-card>
`,
data() {
return {
window, workordersToAssign: [], editingWorkorderId: null, editingDeadlineId: null, editingAdditionalInfoId: null,
civilEngineeringData: null, tempAdditionalInfo: '', expandedNotes: [], companiesByTenant: {}, companiesLoading: false, massAssignCompanyId: null,
cancelWorkorderModalData: null, problemSolvedModalData: null, massAssignModalData: null,
cancelWorkorderModalData: null, problemSolvedModalData: null, massAssignModalData: null, rescheduleModalData: null,
crudConfig: {
...window.TT_CONFIG.CRUD_CONFIG, selectable: false, expandable: true,
customRowClass: (row) => {
@@ -321,6 +344,98 @@ Vue.component('workorder-admin', {
this.$refs.table.$refs.table.refreshTable();
this.cancelWorkorderModalData = null;
} else window.notify('error', data.message || 'Stornierung fehlgeschlagen.');
},
getCalendarType(networkOwnerName) {
if (!networkOwnerName) return '1';
const name = networkOwnerName.toLowerCase();
if (name.includes('xinon')) return '2';
if (name.includes('sbidi')) return '7';
if (name.includes('estmk')) return '3';
return '1';
},
getCampaignName(preordercampaignId) {
const col = this.crudConfig.columns.find(c => c.key === 'preordercampaign_id');
const opt = col?.table?.filterOptions?.find(o => o.value == preordercampaignId);
return opt?.text || '';
},
openCalendarWithPrefill(workorder, dateUnix, win) {
const m = window.moment.unix(dateUnix);
const zeitraum = m.hour() < 12 ? 'VM' : 'NM';
const campaignName = this.getCampaignName(workorder.preordercampaign_id);
const locationParts = [workorder.street, workorder.hausnummer];
if (workorder.stiege) locationParts.push('/' + workorder.stiege);
const location = locationParts.join(' ') + ', ' + workorder.plz + ' ' + workorder.city;
const descLines = [];
if (workorder.oaid || campaignName || workorder.city) {
descLines.push([workorder.oaid, campaignName, workorder.city].filter(Boolean).join(' - '));
}
descLines.push('Zeitraum: ' + zeitraum);
if (workorder.phone) descLines.push('Tel.: ' + workorder.phone);
if (workorder.additionalInfo) descLines.push(workorder.additionalInfo);
const calendarData = {
type: this.getCalendarType(workorder.networkOwnerName),
subject: workorder.customerCompany || workorder.customerName || '',
location: location,
cstart: m.format('DD.MM.YYYY HH:mm'),
cend: m.clone().add(90, 'minutes').format('DD.MM.YYYY HH:mm'),
description: descLines.join('<br>'),
customer_phone: workorder.phone || null,
customer_email: workorder.email || null,
calendar_user_name: 'Pusnik',
attendee_names: ['Ziga Harc'],
};
localStorage.setItem('Calendar_create', JSON.stringify(calendarData));
win.location.href = '/Calendar/View';
},
async setAppointment(workorder, date) {
if (!date) return;
if (moment.unix(date).hour() >= 23 || moment.unix(date).hour() < 1) {
this.$refs.table.$refs.table.refreshTable();
return window.notify('error', 'Bitte Uhrzeit angeben!');
}
const calWin = window.open('about:blank', '_blank');
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderAdmin/scheduleAppointment`, {
workorderId: workorder.id, appointmentDate: date
});
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
this.openCalendarWithPrefill(workorder, date, calWin);
} else {
if (calWin) calWin.close();
window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
}
},
openRescheduleModal(row) {
this.rescheduleModalData = { workorder: row, newDate: row.appointmentDate, reason: '' };
},
async rescheduleAppointment() {
const { workorder, newDate, reason } = this.rescheduleModalData;
if (!newDate || !reason) return window.notify('error', 'Bitte geben Sie ein neues Datum und einen Grund an.');
if (moment.unix(newDate).hour() >= 23 || moment.unix(newDate).hour() < 1) return window.notify('error', 'Bitte Uhrzeit angeben!');
const calWin = window.open('about:blank', '_blank');
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderAdmin/rescheduleAppointment`, {
workorderId: workorder.id, appointmentDate: newDate, reason: reason
});
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
this.openCalendarWithPrefill(workorder, newDate, calWin);
this.rescheduleModalData = null;
} else {
if (calWin) calWin.close();
window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
}
},
async clearAppointment(workorder) {
if (!confirm('Möchten Sie den Termin wirklich löschen?')) return;
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderAdmin/clearAppointment`, {
workorderId: workorder.id
});
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
} else window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
}
},
watch: {