Vue.component('warehouse-offer-create-basic-offer-modal', { props: { show: {type: Boolean, default: false} }, data() { return { window: window, loading: false, billAddrAutoCompleteUrl: window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress', productSearchUrl: window.TT_CONFIG['BASE_PATH'] + '/Product/api?do=findProduct', creatorSignaturePad: null, creatorSignatureNotes: '', positionsConfig: { fields: { article: { type: 'autocomplete', label: 'Artikel', apiUrl: '/WarehouseArticle/autoComplete', customFieldReference: 'WarehouseArticle', }, amount: {type: 'input', label: 'Menge', inputType: 'number'}, unit: {type: 'input', label: 'Einheit'}, articleNumber: {type: 'input', label: 'Artikelnummer'}, isAlternative: {type: 'checkbox', label: 'Alternativposition'}, unitPrice: {type: 'input', label: 'Einzelpreis', inputType: 'number'}, discount: {type: 'input', label: 'Rabatt (%)', inputType: 'number'}, }, validateForm: (formData) => { const requiredFields = ['article', 'amount', 'unitPrice']; for (const field of requiredFields) { if (!formData[field]) { window.notify('error', `Bitte füllen Sie ${this.positionsConfig.fields[field].label} aus`); return false; } } return true; }, }, offer: { editor: window.TT_CONFIG['USER_ID'], customerNumber: '', reference: '', purpose: '', customerName: '', customerStreet: '', customerZip: '', customerCity: '', customerVAT: '', contactPerson: '', positions: [], totalDiscount: 0, paymentTerms: 'net30', deliveryTerms: 'ex_works', closingText: 'Sollten sich die Rohstoffpreise bzw. die Preise unserer Zulieferer um mehr als 10% innerhalb der Angebots bzw.\n\nAuftragsgültigkeit erhöhen (Stichtag Datum), sind wir gezwungen die Preise anzupassen.\n\nVerrechnung erfolgt nach tatsächlichem Aufwand.\n\nWir sind sicher, Ihnen ein konkurenzfähiges Angebot unterbreitet zu haben und sehen gern Ihrer Bestellung entgegen.\n\nSollten Sie noch Fragen oder weitere Informationen benötigen stehen wir Ihnen jederzeit gern zu Verfügung.', notes: '', }, // Simple product search and selection productSearch: '', searchResults: [], selectedProducts: [], showProductSearch: false, paymentTerms: [ {value: 'net30', text: '30 Tage netto'}, {value: 'net60', text: '60 Tage netto'}, {value: 'immediate', text: 'Sofort fällig'}, ], deliveryTerms: [ {value: 'ex_works', text: 'Ab Werk'}, {value: 'free_delivery', text: 'Frei Haus'}, {value: 'fob', text: 'FOB'}, ], } }, template: ` Kunde Kundenadresse {{ offer.customerName }} {{ offer.customerStreet }} {{ offer.customerZip }} {{ offer.customerCity }} USt-IdNr.: {{ offer.customerVAT }} Angebotszweck Produkte Produkt hinzufügen {{ product.text }} Schließen {{ product.name }} {{ product.description || 'Keine Beschreibung' }} {{ formatPrice(calculateProductTotal(product)) }} € Gesamtsumme: {{ formatPrice(totalPrice) }} € Noch keine Produkte ausgewählt Zusätzliche Positionen Konditionen Unterschrift Angebotsersteller Zurücksetzen Unterschrift speichern Notizen `, methods: { clearCreatorSignature() { this.creatorSignaturePad.clear(); }, async fetchArticleData(article) { if (typeof article === 'number') { const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseArticle/getById`, {params: {id: article}}); this.$refs.positionsManager.updateField('articleNumber', response.data.articleNumber); this.$refs.positionsManager.updateField('unitPrice', Object.values(JSON.parse(response.data.cheapestSellPrice)).find(price => price.title === 'Verkauf').price); this.$refs.positionsManager.updateField('unit', response.data.unit); } }, async saveCreatorSignature() { if (this.creatorSignaturePad.isEmpty()) { this.window.notify('error', 'Bitte eine Unterschrift hinzufügen'); return; } this.offer.creatorSignature = this.creatorSignaturePad.toDataURL(); this.offer.creatorSignatureNotes = this.creatorSignatureNotes; this.window.notify('success', 'Unterschrift erfolgreich gespeichert'); }, async searchProducts() { if (!this.productSearch || this.productSearch.length < 2) { this.searchResults = []; return; } try { const response = await axios.get(this.productSearchUrl, { params: { q: this.productSearch } }); this.searchResults = response.data || []; } catch (error) { console.error('Product search failed:', error); window.notify('error', 'Produktsuche fehlgeschlagen'); this.searchResults = []; } }, async addProduct(searchResult) { if (searchResult.value === 0) return; // Skip "more results" indicator try { // Get detailed product information const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/Product/api`, { params: { do: 'getProduct', product_id: searchResult.value } }); if (response.data.status === 'OK' && response.data.result["product"]) { const productData = response.data.result["product"]; const product = { id: searchResult.value, name: searchResult.text, description: productData.description || '', amount: 1, unitPrice: parseFloat(productData.price || '0'), discount: 0, unit: 'Stk', articleNumber: productData.id || '', article: searchResult.value // For compatibility with existing system }; this.selectedProducts.push(product); this.productSearch = ''; this.searchResults = []; this.showProductSearch = false; window.notify('success', 'Produkt hinzugefügt'); } else { window.notify('error', 'Produktdetails konnten nicht geladen werden'); } } catch (error) { console.error('Failed to load product details:', error); window.notify('error', 'Fehler beim Laden der Produktdetails'); } }, removeProduct(index) { this.selectedProducts.splice(index, 1); }, calculateProductTotal(product) { if (!product.amount || !product.unitPrice) return 0; const subtotal = product.amount * product.unitPrice; const discount = product.discount ? (subtotal * product.discount / 100) : 0; return subtotal - discount; }, formatPrice(price) { return parseFloat(price || '0').toFixed(2); }, async submit() { this.loading = true; // Validation if (!this.offer.customerNumber) { this.loading = false; return window.notify('error', 'Bitte wählen Sie einen Kunden aus'); } if (!this.selectedProducts.length) { this.loading = false; return window.notify('error', 'Bitte fügen Sie mindestens ein Produkt hinzu'); } // Convert selected products to the format expected by the existing system this.offer.positions = this.selectedProducts.map(product => ({ article: product.article, amount: product.amount, unit: product.unit, articleNumber: product.articleNumber, unitPrice: product.unitPrice, discount: product.discount || 0, isAlternative: false })); this.offer.totalAmount = this.totalPrice; try { const response = await axios.post( `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/create`, this.offer ); if (response.data.success) { window.notify('success', response.data.message || 'Angebot erfolgreich erstellt'); this.$emit('close'); this.$emit('created', response.data); } else { window.notify('error', response.data.errors ? Object.values(response.data.errors).join('') : response.data.message || 'Ein Fehler ist aufgetreten' ); } } catch (error) { console.error('Submit failed:', error); window.notify('error', 'Fehler beim Speichern des Angebots'); } this.loading = false; }, resetForm() { this.offer = { editor: window.TT_CONFIG['USER_ID'], customerNumber: '', reference: '', purpose: '', customerName: '', customerStreet: '', customerZip: '', customerCity: '', customerVAT: '', contactPerson: '', positions: [], totalDiscount: 0, paymentTerms: 'net30', deliveryTerms: 'ex_works', closingText: this.offer.closingText, // Keep default text notes: '', }; this.selectedProducts = []; this.productSearch = ''; this.searchResults = []; this.showProductSearch = false; } }, mounted() { this.$nextTick(() => { console.log(document.getElementById('creator-signature-pad')); const canvas = document.getElementById('creator-signature-pad'); this.creatorSignaturePad = new SignaturePad(canvas, { penColor: '#000000', penWidth: 2, velocityFilterWeight: 0.5 }); }); }, computed: { totalPrice() { const subtotal = this.selectedProducts.reduce((total, product) => { return total + this.calculateProductTotal(product); }, 0); const totalDiscount = this.offer.totalDiscount ? (subtotal * this.offer.totalDiscount / 100) : 0; return subtotal - totalDiscount; } }, watch: { 'offer.customerNumber': async function() { if (!this.offer.customerNumber) return; try { const response = await axios.get( `${window.TT_CONFIG["BASE_PATH"]}/Address/api?do=getAddress&id=${this.offer.customerNumber}` ); if (response.data.status !== 'OK' || !response.data.result.address) { window.notify('error', 'Kundenadresse konnte nicht gefunden werden'); return; } const address = response.data.result.address; this.offer.customerName = address.company || `${address.firstname} ${address.lastname}`; this.offer.customerStreet = address.street; this.offer.customerZip = address.zip; this.offer.customerCity = address.city; this.offer.customerVAT = address.vat_number || ''; } catch (error) { console.error('Failed to load customer address:', error); window.notify('error', 'Fehler beim Laden der Kundenadresse'); } }, show(newVal) { if (newVal) { this.resetForm(); } } } })
Noch keine Produkte ausgewählt