Vue.component('warehouse-offer', {
template: `
{{ formatPrice(row.totalAmount) }}
{{ formatDate(row.create) }}
{{ formatDate(row.lastSentDate) }}
`,
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) : '?' }}
`,
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: `
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.');
}
}
});