window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [ { "key": "status_to_progress", "title": "In Bearbeitung", "class": "fas fa-cog text-warning", "condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['new', 'on_hold'].includes(row.status), }, { "key": "status_to_accepted", "title": "Akzeptieren", "class": "fas fa-check text-success", "condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['new', 'in_progress', 'on_hold'].includes(row.status), }, { "key": "status_to_invoiced", "title": "Verrechnet", "class": "fas fa-file-invoice text-info", "condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['in_progress', 'accepted', 'on_hold'].includes(row.status), }, { "key": "status_to_on_hold", "title": "On Hold", "class": "fas fa-pause text-warning", "condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['accepted', 'new', 'in_progress'].includes(row.status), }, { "key": "status_to_cancelled", "title": "Storniert", "class": "fas fa-ban text-danger", "condition": (row) => (window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['new', 'in_progress', 'accepted'].includes(row.status)) || (row.status === 'new' && row.signature === null), }, { "key": "status_to_new", "title": "Lieferschein wiedereröffnen", "class": "fas fa-redo-alt text-success", "condition": (row) => (window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.status === 'cancelled'), }, { "key": "add_log", "title": "Log Eintrag hinzufügen", "class": "fas fa-plus text-primary", }, { "key": "print", "title": "Drucken", "class": "fas fa-print text-primary", } ] // normal regie 50% 100% window.TT_CONFIG["CRUD_CONFIG"]["editCondition"] = (row) => row.status === 'new' && row.signature === null || window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1'; Vue.component('warehouse-shipping-note-positions', { //language=Vue props: { positions: Array, hoursEntries: Array }, data() { return { articleData: {}, loading: false, articlePacketData: {}, userData: {} } }, template: `
`, async mounted() { this.loading = true; for (const position of this.positions) { if (position.article) { const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseArticle/autoComplete?searchedID=' + position.article); this.$set(this.articleData, position.article, response.data[0]); } else if (position.articlePacket) { const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseArticlePacket/autoComplete?searchedID=' + position.articlePacket); this.$set(this.articlePacketData, position.articlePacket, response.data[0]); } } for (const entry of this.hoursEntries) { if (entry.userId) { const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseShippingNote/userAutoComplete?searchedID=' + entry.userId); this.$set(this.userData, entry.userId, response.data[0]); } } this.loading = false; } }) Vue.component('add-log-modal-sn', { props: { shippingNoteId: {type: Number, required: true}, }, data() { return { window: window, note: '', file: null, uploadedFiles: [], submitLoading: false }; }, methods: { async handleFileUpload(event) { const files = event.target.files; if (!files.length) return; for (let i = 0; i < files.length; i++) { const file = files[i]; const formData = new FormData(); formData.append('file', file); try { const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseShippingNote/uploadFile`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }); if (response.data.success) { this.uploadedFiles.push({ id: response.data.fileId, name: file.name }); window.notify('success', `File "${file.name}" uploaded successfully`); } else { window.notify('error', `File "${file.name}" upload failed: ${response.data.error || 'Unknown error'}`); } } catch (error) { window.notify('error', `Error uploading file "${file.name}"`); } } // Clear the file input event.target.value = ''; }, removeFile: index => this.uploadedFiles.splice(index, 1), async submit() { this.submitLoading = true; const fileIds = this.uploadedFiles.map(file => file.id); const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseShippingNote/createNewLogAction`, { shippingNoteId: this.shippingNoteId, note: this.note, fileIds: JSON.stringify(fileIds), }); if (response.data.success) { this.$emit('close'); window.notify('success', response.data.message ?? 'Log Eintrag erfolgreich erstellt'); } else { window.notify('error', response.data.errors ? Object.values(response.data.errors).join('
') : response.data.message || 'Ein Fehler ist aufgetreten'); } this.submitLoading = false; }, }, template: ` ` }) Vue.component('tt-file', { props: ['id'], data: () => ({file: null}), async mounted() { const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/File/getById`, {params: {id: this.id}}); this.file = response.data; }, template: `
{{ file.filename }}
` }) Vue.component('warehouse-shipping-note-logs', { props: { shippingNoteId: {type: Number, required: true}, }, data() { return { logs: [], loading: false, }; }, //language=Vue template: `
{{ formatDate(log.create) }} ({{ getUserName(log.createBy) }}) | {{ log.message }}
`, async mounted() { this.loading = true; const response = await axios.get(window.TT_CONFIG.BASE_PATH + '/WarehouseShippingNote/getLog', {params: {shippingNoteId: this.shippingNoteId}}); this.logs = response.data; this.loading = false; }, methods: { formatDate: date => window.moment(date * 1000).format('DD.MM.YYYY HH:mm'), getUserName: id => window.TT_CONFIG.CRUD_CONFIG.columns.find(col => col.key === 'createBy')?.modal.items.find(u => u.value === id)?.text } }) Vue.component('warehouse-shipping-note-see-through', { props: ['wantedState'], //language=Vue template: ` `, data() { return { logModalKey: 0, currentPage: 1, perPage: 1, rows: [], activeTab: 'Editieren', window: window } }, computed: { currentRow() { return this.rows[0]; } }, methods: { tabStyle(tab) { return { padding: '10px 20px', cursor: 'pointer', backgroundColor: this.activeTab === tab ? '#005384' : '#f7c423', color: 'white', border: 'none', borderRadius: '5px', marginRight: '10px' }; }, previousNote() { if (this.currentPage > 1) { this.currentPage--; this.fetchData(); } }, nextNote() { if (this.currentPage) { this.currentPage++; this.fetchData(); } }, async changeStatus(action, row) { this.$emit(action, row); await new Promise(resolve => setTimeout(resolve, 50)); await this.fetchData(); }, async fetchData() { this.loading = true; this.rows = []; const response = await axios.post(window.TT_CONFIG.BASE_PATH + '/WarehouseShippingNote/get', { pagination: {page: this.currentPage, per_page: this.perPage}, filters: {status: this.wantedState}, }) this.rows = response.data.rows; if (this.rows.length === 0) { this.$emit('close'); this.window.notify('info', 'Keine Lieferscheine mit diesem Status gefunden'); } this.loading = false; }, async saveAndSetToProgress() { await this.$refs.modal.submit('in_progress'); await new Promise(resolve => setTimeout(resolve, 50)); await this.fetchData(); } }, mounted() { this.fetchData(); } }); Vue.component('warehouse-shipping-note', { //language=Vue template: ` `, data() { return { window: window, historyModal: false, historyModalId: null, shippingNoteModalId: null, signingShippingNoteId: null, addLogModalId: null, viewerItem: null, viewerUrl: null, shippingNoteSeeThrough: false, articleModalId: null, calendarEvents: [], showCalendarDropdown: false, } }, mounted() { if (window.location.search.includes('doAction=createNew')) { this.shippingNoteModalId = 'create'; } this.fetchCalendarEvents(); document.addEventListener('click', this.closeDropdown); }, beforeDestroy() { document.removeEventListener('click', this.closeDropdown); }, methods: { async fetchCalendarEvents() { try { const response = await axios.get(window.TT_CONFIG.BASE_PATH + '/WarehouseShippingNote/getRecentCalendarEvents'); this.calendarEvents = response.data; } catch (error) { console.error("Could not fetch calendar events", error); window.notify('error', 'Kalendereinträge konnten nicht geladen werden.'); } }, showPrintPreview(row) { this.viewerUrl = `${window.TT_CONFIG['BASE_PATH']}/WarehouseShippingNote/createPDF?id=${row.id}`; this.viewerItem = { mimetype: 'application/pdf' }; }, formatEventDate(dateString) { return window.moment(dateString).format('DD.MM.YYYY HH:mm'); }, async createFromEvent(event) { this.showCalendarDropdown = false; this.shippingNoteModalId = 'create'; await this.$nextTick(); // Wait for the modal component to be created if (this.$refs.modal) { // Parse address string const locationParts = event.location.split(','); const line = locationParts.slice(0, -1).join(',').trim(); const plzCityRaw = locationParts.slice(-1)[0].trim(); const plzMatch = plzCityRaw.match(/^(\d+)/); const plz = plzMatch ? plzMatch[1] : ''; const city = plzCityRaw.replace(/^\d+\s*/, '').trim(); // Directly set the data in the modal's `shippingNote` object const modalData = this.$refs.modal.shippingNote; this.$set(modalData, 'deliveryAddressName', event.category); this.$set(modalData, 'deliveryAddressLine', line); this.$set(modalData, 'deliveryAddressPLZ', plz); this.$set(modalData, 'deliveryAddressCity', city); } }, closeDropdown(event) { if (!event.target.closest('.dropdown')) { this.showCalendarDropdown = false; } }, async changeStatus(id, status) { const response = await axios.post(window.TT_CONFIG.BASE_PATH + '/WarehouseShippingNote/changeStatus', { id, status }); if (response.data.success) { this.window.notify('success', response.data.message || 'Erfolgreich aktualisiert'); this.$refs.table.$refs.table.refreshTable(); } else { this.window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten'); } }, } }) let hiddenTime = null; const RELOAD_AFTER_SECONDS = 300; document.addEventListener('visibilitychange', () => { if (!window.matchMedia('(display-mode: standalone)').matches) return; if (document.visibilityState === 'hidden') { hiddenTime = new Date(); } else if (document.visibilityState === 'visible' && hiddenTime) { const secondsElapsed = (new Date() - hiddenTime) / 1000; if (secondsElapsed > RELOAD_AFTER_SECONDS) { window.location.reload(); } } }); document.addEventListener('DOMContentLoaded', () => { if (!window.matchMedia('(display-mode: standalone)').matches) return; const logoutButton = document.createElement('button'); logoutButton.innerHTML = ' Logout'; Object.assign(logoutButton.style, { position: 'fixed', top: '10px', left: '50%', transform: 'translateX(-50%)', zIndex: '98', padding: '8px 16px', backgroundColor: '#f44336', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '14px', boxShadow: '0 2px 5px rgba(0,0,0,0.2)' }); const handleLogout = async () => { logoutButton.disabled = true; try { const { data } = await axios.get('/WarehouseShippingNote/warehouseLogoutAction'); const type = data.success ? 'success' : 'error'; const message = data.message || (data.success ? 'Erfolgreich ausgeloggt' : 'Logout fehlgeschlagen'); window.notify(type, message); } catch (error) { console.error('Logout request failed:', error); window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.'); } finally { setTimeout(() => window.location.reload(), 400); } }; logoutButton.addEventListener('click', handleLogout); document.body.appendChild(logoutButton); });