Vue.component('warehouse-offer', { template: ` `, data() { return { window: window, offerModalId: null, sendMailModalId: null, offerTemplates: [], offerTemplatesDropdown: false, } }, async mounted() { await this.loadTemplates(); document.addEventListener('click', this.closeDropdown); }, beforeDestroy() { document.removeEventListener('click', this.closeDropdown); }, methods: { formatDate: ts => ts ? window.moment(ts * 1000).format('DD.MM.YYYY') : '-', formatPrice: price => new Intl.NumberFormat('de-AT', { style: 'currency', currency: 'EUR' }).format(price || 0), async closeModal() { this.offerModalId = null; this.sendMailModalId = null; await new Promise(resolve => setTimeout(resolve, 250)); this.$refs.table.$refs.table.refreshTable(); }, async loadTemplates() { const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/getTemplates`); this.offerTemplates = response.data; }, openPDF(offer) { window.open(`${window.TT_CONFIG['BASE_PATH']}/WarehouseOffer/createPDF?id=${offer.id}&version=${offer.version}`) }, closeDropdown(event) { if (!event.target.closest('#offer-templates-dropdown')) { this.offerTemplatesDropdown = false; } }, async createOfferFromTemplate(template) { this.offerTemplatesDropdown = false; this.offerModalId = 'create'; await this.$nextTick(); this.$refs.modal.offer.positions = JSON.parse(template.positions); this.$refs.modal.offer.totalDiscount = template.totalDiscount; this.$refs.modal.offer.paymentTerms = template.paymentTerms; this.$refs.modal.offer.deliveryTerms = template.deliveryTerms; this.$refs.modal.offer.closingText = template.closingText; this.$refs.modal.offer.notes = template.notes; window.notify('success', 'Angebot aus Vorlage erstellt'); }, async deleteTemplate(id) { if(!confirm('Vorlage wirklich löschen?')) return; const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/deleteTemplate?id=${id}`); if (response.data.success) { await this.loadTemplates(); window.notify('success', 'Vorlage erfolgreich gelöscht'); } else { window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten'); } } } }); Vue.component('warehouse-offer-detail', { template: `
Übersicht
{{ offer.customerName }}
{{ offer.purpose || '-' }}
{{ formatPrice(offer.totalAmount) }}
{{ statusInfo.text }}
{{ editorName }}
{{ formatDate(offer.create, 'DD.MM.YYYY') }}
Journal
Noch keine Einträge vorhanden.
{{ log.createByName ? log.createByName.charAt(0) : '?' }}
{{ log.createByName }} {{ formatDate(log.create, 'relative') }}

{{ log.message }}

`, props: ['id'], data: () => ({ offer: {}, journal: [], loading: true, savingJournal: false, newMessage: '', uploadedFiles: [], userColors: {} }), async mounted() { const [offerResponse, journalResponse] = await Promise.all([ axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOffer/getById`, {params: {id: this.id}}), axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOffer/getJournal`, {params: {id: this.id}}) ]); this.offer = offerResponse.data; this.journal = journalResponse.data.sort((a,b) => b.create - a.create); // Ensure descending order this.loading = false; }, methods: { formatDate(ts, format = 'DD.MM.YYYY HH:mm') { if (!ts) return '-'; if (format === 'relative') { return window.moment(ts * 1000).fromNow(); } return window.moment(ts * 1000).format(format); }, formatPrice: price => new Intl.NumberFormat('de-AT', { style: 'currency', currency: 'EUR' }).format(price || 0), onFileUploaded(file) { this.uploadedFiles.push(file.id); }, async addJournalEntry() { if (!this.newMessage && this.uploadedFiles.length === 0) return; this.savingJournal = true; try { await axios.post(`${window.TT_CONFIG.BASE_PATH}/WarehouseOffer/addJournalEntry`, { id: this.id, message: this.newMessage, fileIds: this.uploadedFiles }); this.newMessage = ''; this.uploadedFiles = []; if (this.$refs.fileUpload) this.$refs.fileUpload.reset(); const journalResponse = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOffer/getJournal`, {params: {id: this.id}}); this.journal = journalResponse.data.sort((a,b) => b.create - a.create); window.notify('success', 'Journaleintrag gespeichert.'); } catch (e) { window.notify('error', 'Fehler beim Speichern des Eintrags.'); } finally { this.savingJournal = false; } }, userColor(userName) { if (!userName) return '#cccccc'; if (this.userColors[userName]) return this.userColors[userName]; let hash = 0; for (let i = 0; i < userName.length; i++) { hash = userName.charCodeAt(i) + ((hash << 5) - hash); } let color = '#'; for (let i = 0; i < 3; i++) { let value = (hash >> (i * 8)) & 0xFF; color += ('00' + value.toString(16)).substr(-2); } this.userColors[userName] = color; return color; } }, computed: { editorName() { const users = window.TT_CONFIG.CRUD_CONFIG.columns.find(c => c.key === 'editor')?.modal?.items || []; const user = users.find(u => u.value == this.offer.editor); return user ? user.text : 'Unbekannt'; }, statusInfo() { const statusMap = { new: { text: 'Neu', badgeClass: 'badge-primary' }, sent: { text: 'Ausgeschickt', badgeClass: 'badge-info' }, accepted: { text: 'Angenommen', badgeClass: 'badge-success' }, rejected: { text: 'Abgelehnt', badgeClass: 'badge-danger' }, cancelled: { text: 'Storniert', badgeClass: 'badge-secondary' }, }; return statusMap[this.offer.status] || { text: this.offer.status, badgeClass: 'badge-light' }; } } }); ; Vue.component('send-mail-modal', { props: ['offerId'], template: ` `, data() { return { loading: false, email: '', subject: '', body: 'Sehr geehrte Damen und Herren,\n\nanbei erhalten Sie das angeforderte Angebot.\n\nMit freundlichen Grüßen\nIhr Team der XINON GmbH' } }, async mounted() { const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOffer/getById`, {params: {id: this.offerId}}); const offer = response.data; this.email = offer.contactPersonEmail || ''; this.subject = `Angebot ${offer.offerNumber} von XINON GmbH`; }, methods: { async sendEmail() { this.loading = true; try { const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WarehouseOffer/sendOfferEmail`, { id: this.offerId, email: this.email, subject: this.subject, body: this.body }); if (response.data.success) { window.notify('success', response.data.message); this.$emit('close'); } else { window.notify('error', response.data.message); } } catch (e) { window.notify('error', 'E-Mail Versand fehlgeschlagen.'); } finally { this.loading = false; } } } }); Vue.component('closing-text-modal', { template: `
{{ template.name }}

Neue Vorlage erstellen
`, data() { return { templates: [], newTemplate: { name: '', text: '' }, window: window, } }, async mounted() { await this.loadTemplates(); }, methods: { async loadTemplates() { const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOffer/getClosingTexts`); this.templates = response.data; }, async saveTemplate() { if (!this.newTemplate.name || !this.newTemplate.text) { return window.notify('error', 'Name und Text dürfen nicht leer sein.'); } await axios.post(`${window.TT_CONFIG.BASE_PATH}/WarehouseOffer/createClosingText`, this.newTemplate); this.newTemplate.name = ''; this.newTemplate.text = ''; await this.loadTemplates(); window.notify('success', 'Vorlage gespeichert.'); } } });