Add print label functionality and enhance asset management actions

This commit is contained in:
2025-11-18 06:33:55 +01:00
parent 901fb91cac
commit b408f7fcf2
4 changed files with 140 additions and 15 deletions

View File

@@ -0,0 +1,52 @@
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin: 0;
padding: 0;
}
.label-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.logo-25 {
max-height: 45px;
margin-bottom: 5px;
}
.logo-50 {
max-height: 70px;
margin-bottom: 10px;
}
.address {
font-size: 8px;
line-height: 1.2;
}
.address-size-50 { font-size: 16px }
.inv-number-25 {
font-size: 14px;
font-weight: bold;
margin-top: 5px;
}
.inv-number-50 {
font-size: 28px;
font-weight: bold;
margin-top: 10px;
}
</style>
<div class="label-container">
<img src="<?php echo BASEDIR ?>/public/assets/images/xinon-full.png" class="logo-<?php echo $size; ?>">
<div class="address address-size-<?php echo $size; ?>">
<?php echo $companyAddress; ?><br>
<?php echo $companyPhone; ?>
</div>
<div class="inv-number-<?php echo $size; ?>">
<?php echo $invNumber; ?>
</div>
</div>

View File

@@ -11,7 +11,6 @@ class AssetManagementController extends TTCrud
['key' => 'currentUser', 'text' => 'Status', 'modal' => false, 'table' => ['sortable' => false, 'filter' => false]], ['key' => 'currentUser', 'text' => 'Status', 'modal' => false, 'table' => ['sortable' => false, 'filter' => false]],
['key' => 'location', 'text' => 'Lagerort', 'required' => true, 'modal' => ['type' => 'text'], 'table' => ['filter' => 'search']], ['key' => 'location', 'text' => 'Lagerort', 'required' => true, 'modal' => ['type' => 'text'], 'table' => ['filter' => 'search']],
['key' => 'serviceDueDate', 'text' => 'Service fällig', 'required' => false, 'modal' => ['type' => 'date'], 'table' => ['filter' => 'date']], ['key' => 'serviceDueDate', 'text' => 'Service fällig', 'required' => false, 'modal' => ['type' => 'date'], 'table' => ['filter' => 'date']],
['key' => 'journal', 'text' => 'Historie', 'modal' => false, 'table' => ['sortable' => false, 'filter' => false]],
['key' => 'actions', 'text' => 'Aktionen', 'modal' => false, 'table' => ['filter' => false, 'sortable' => false]], ['key' => 'actions', 'text' => 'Aktionen', 'modal' => false, 'table' => ['filter' => false, 'sortable' => false]],
]; ];
@@ -21,7 +20,7 @@ class AssetManagementController extends TTCrud
// Restrict actions if the user does not have the 'AssetAdmin' permission. // Restrict actions if the user does not have the 'AssetAdmin' permission.
if (!$this->user->can('AssetAdmin')) { if (!$this->user->can('AssetAdmin')) {
$this->additionalJSVariables['ASSET_ADMIN'] = '0'; $this->additionalJSVariables['ASSET_ADMIN'] = '0';
$this->columns = array_filter($this->columns, fn($col) => !in_array($col['key'], ['actions', 'journal'])); $this->columns = array_filter($this->columns, fn($col) => $col['key'] !== 'actions');
} }
} }
@@ -277,4 +276,40 @@ class AssetManagementController extends TTCrud
AssetManagementReservationModel::delete($post['id']); AssetManagementReservationModel::delete($post['id']);
self::returnJson(['success' => true, 'message' => 'Reservierung gelöscht.']); self::returnJson(['success' => true, 'message' => 'Reservierung gelöscht.']);
} }
protected function printLabelAction() {
if (!$this->user->can('AssetAdmin')) {
self::sendError("Permission denied", 403);
}
$assetId = $this->request->id;
$size = $this->request->size ?? '25'; // Default to 25mm
$asset = AssetManagementModel::get($assetId);
if (!$asset) {
self::sendError("Asset not found", 404);
}
$pdf_vars = [
'companyAddress' => 'Fladnitz 150, 8322 Studenzen',
'companyPhone' => '+43 3115 40800',
'invNumber' => $asset->assetNumber,
'size' => $size
];
$pdf = new PdfForm("AssetManagement/LABEL", $pdf_vars);
if ($size == '50') {
$wkhtmltopdfArgs = "--page-height 50mm --page-width 80mm --margin-top 1mm --margin-bottom 0 --margin-left 0 --margin-right 0 --disable-smart-shrinking --encoding utf-8";
} else { // 25mm
$wkhtmltopdfArgs = "--page-height 25mm --page-width 50mm --margin-top 1mm --margin-bottom 0 --margin-left 0 --margin-right 0 --disable-smart-shrinking --encoding utf-8";
}
$filename = $pdf->render($wkhtmltopdfArgs);
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="label-' . $asset->assetNumber . '.pdf"');
readfile($filename);
die();
}
} }

View File

@@ -1,10 +1,22 @@
window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [ window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [
{
"key": "openHistory",
"title": "Historie",
"class": "fas fa-history text-info",
"condition": (row) => window.TT_CONFIG.ASSET_ADMIN === '1',
},
{ {
"key": "reserve", "key": "reserve",
"title": "Reservieren", "title": "Reservieren",
"class": "fas fa-calendar-alt btn-outline-warning", "class": "fas fa-calendar-alt text-warning",
"condition": (row) => window.TT_CONFIG.ASSET_ADMIN === '1', "condition": (row) => window.TT_CONFIG.ASSET_ADMIN === '1',
}, },
{
"key": "print",
"title": "Label drucken",
"class": "fas fa-print text-secondary",
"condition": (row) => window.TT_CONFIG.ASSET_ADMIN === '1',
}
]; ];
// ================================================================================= // =================================================================================
// Main Asset Management Component // Main Asset Management Component
@@ -24,6 +36,10 @@ Vue.component('asset-management', {
v-if="reservationModalAsset" v-if="reservationModalAsset"
:asset="reservationModalAsset" :asset="reservationModalAsset"
@close="reservationModalAsset = null; $refs.table.$refs.table.refreshTable()"/> @close="reservationModalAsset = null; $refs.table.$refs.table.refreshTable()"/>
<asset-print-label-modal
v-if="printModalAsset"
:asset="printModalAsset"
@close="printModalAsset = null"/>
<button v-if="window.TT_CONFIG.ASSET_ADMIN === '1'" @click="modalId = 'create'" class="btn btn-primary">Anlage erstellen</button> <button v-if="window.TT_CONFIG.ASSET_ADMIN === '1'" @click="modalId = 'create'" class="btn btn-primary">Anlage erstellen</button>
@@ -31,7 +47,9 @@ Vue.component('asset-management', {
ref="table" ref="table"
emit-edit emit-edit
@edit="modalId = $event.id" @edit="modalId = $event.id"
@openHistory="journalModalAssetId = $event.id"
@reserve="reservationModalAsset = $event" @reserve="reservationModalAsset = $event"
@print="printModalAsset = $event"
:crud-config="crudConfig"> :crud-config="crudConfig">
<template v-slot:assetdetails="{ row }"> <template v-slot:assetdetails="{ row }">
@@ -51,15 +69,6 @@ Vue.component('asset-management', {
@update="updateTableWithoutModalsOpening"/> @update="updateTableWithoutModalsOpening"/>
</template> </template>
<template v-slot:journal="{ row }">
<tt-button
sm
icon="fas fa-history"
title="Historie"
@click="journalModalAssetId = row.id"
additional-class="btn-outline-info"/>
</template>
<template v-slot:serviceduedate="{ row }"> <template v-slot:serviceduedate="{ row }">
<span v-if="row.serviceDueDate" :class="{'text-danger font-weight-bold': isDatePast(row.serviceDueDate)}"> <span v-if="row.serviceDueDate" :class="{'text-danger font-weight-bold': isDatePast(row.serviceDueDate)}">
{{ formatDate(row.serviceDueDate, 'DD.MM.YYYY') }} {{ formatDate(row.serviceDueDate, 'DD.MM.YYYY') }}
@@ -75,6 +84,7 @@ Vue.component('asset-management', {
modalId: null, modalId: null,
journalModalAssetId: null, journalModalAssetId: null,
reservationModalAsset: null, reservationModalAsset: null,
printModalAsset: null,
crudConfig: window.TT_CONFIG.CRUD_CONFIG, crudConfig: window.TT_CONFIG.CRUD_CONFIG,
} }
}, },
@@ -97,6 +107,29 @@ Vue.component('asset-management', {
} }
}); });
// =================================================================================
// Asset Print Label Modal
// =================================================================================
Vue.component('asset-print-label-modal', {
props: { asset: { type: Object, required: true } },
template: `
<tt-modal :show="true" :title="'Label für ' + asset.name + ' drucken'" :save="false" :delete="false" @update:show="$emit('close')">
<div class="text-center">
<p>Wählen Sie die gewünschte Label-Größe:</p>
<tt-button text="25mm" @click="printLabel(25)" additional-class="btn-primary mr-2"/>
<tt-button text="50mm" @click="printLabel(50)" additional-class="btn-primary"/>
</div>
</tt-modal>
`,
methods: {
printLabel(size) {
const url = window.TT_CONFIG.BASE_PATH + "/AssetManagement/printLabel?id=" + this.asset.id + "&size=" +size;
window.open(url, '_blank');
this.$emit('close');
}
}
});
// ================================================================================= // =================================================================================
// Asset Image Component // Asset Image Component
// ================================================================================= // =================================================================================
@@ -652,8 +685,8 @@ Vue.component('asset-reservation-modal', {
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Neue Reservierung</h5> <h5 class="card-title">Neue Reservierung</h5>
<tt-autocomplete label="Mitarbeiter" :api-url="userAutoCompleteUrl" v-model="newReservation.userId" sm row/> <tt-autocomplete label="Mitarbeiter" :api-url="userAutoCompleteUrl" v-model="newReservation.userId" sm row/>
<tt-date-picker label="Startdatum" v-model="newReservation.startDate" :date-range="false" sm row/> <tt-date-picker label="Startdatum" v-model="newReservation.startDate" :date-range="false" :time-picker="false" sm row/>
<tt-date-picker label="Enddatum" v-model="newReservation.endDate" :date-range="false" :disabled="isPermanent === 1" sm row/> <tt-date-picker label="Enddatum" v-model="newReservation.endDate" :date-range="false" :time-picker="false" :disabled="isPermanent === 1" sm row/>
<tt-checkbox label="Dauerhaft" v-model="isPermanent" sm row/> <tt-checkbox label="Dauerhaft" v-model="isPermanent" sm row/>
<tt-textarea label="Notizen" v-model="newReservation.notes" sm row/> <tt-textarea label="Notizen" v-model="newReservation.notes" sm row/>
<tt-button text="Reservierung speichern" @click="saveReservation" additional-class="btn-primary float-right"/> <tt-button text="Reservierung speichern" @click="saveReservation" additional-class="btn-primary float-right"/>

View File

@@ -10,6 +10,7 @@ Vue.component('tt-date-picker', {
additionalProps: Object, additionalProps: Object,
sm: { type: Boolean, default: false }, sm: { type: Boolean, default: false },
dateRange: { type: Boolean, default: true }, dateRange: { type: Boolean, default: true },
timePicker: { type: Boolean, default: true },
}, },
template: ` template: `
<div class="form-group" :class="{'row': row}"> <div class="form-group" :class="{'row': row}">
@@ -53,10 +54,14 @@ Vue.component('tt-date-picker', {
await loadScript('/js/jquery.min.js', () => typeof jQuery !== 'undefined'); await loadScript('/js/jquery.min.js', () => typeof jQuery !== 'undefined');
await loadScript('/plugins/daterangepicker/daterangepicker.js', () => typeof $?.fn?.daterangepicker !== 'undefined'); await loadScript('/plugins/daterangepicker/daterangepicker.js', () => typeof $?.fn?.daterangepicker !== 'undefined');
if (!this.timePicker) {
this.locale.format = 'DD.MM.YYYY';
}
const pickerOptions = { const pickerOptions = {
autoUpdateInput: false, autoUpdateInput: false,
singleDatePicker: !this.dateRange, singleDatePicker: !this.dateRange,
timePicker: true, timePicker: this.timePicker,
timePicker24Hour: true, timePicker24Hour: true,
locale: this.locale, locale: this.locale,
startDate: this.dateRange ? (this.value?.from ? this.moment.unix(this.value.from) : this.moment()) : (this.value ? this.moment.unix(this.value) : this.moment()), startDate: this.dateRange ? (this.value?.from ? this.moment.unix(this.value.from) : this.moment()) : (this.value ? this.moment.unix(this.value) : this.moment()),