// static/vendor/js/invoices.js /** * Vendor invoice management page logic */ const invoicesLog = window.LogConfig?.createLogger('INVOICES') || console; invoicesLog.info('[VENDOR INVOICES] Loading...'); function vendorInvoices() { invoicesLog.info('[VENDOR INVOICES] vendorInvoices() called'); return { // Inherit base layout state ...data(), // Set page identifier currentPage: 'invoices', // Tab state activeTab: 'invoices', // Loading states loading: false, savingSettings: false, creatingInvoice: false, downloadingPdf: false, // Messages error: '', successMessage: '', // Settings hasSettings: false, settings: null, settingsForm: { company_name: '', company_address: '', company_city: '', company_postal_code: '', company_country: 'LU', vat_number: '', invoice_prefix: 'INV', default_vat_rate: '17.00', bank_name: '', bank_iban: '', bank_bic: '', payment_terms: 'Net 30 days', footer_text: '' }, // Stats stats: { total_invoices: 0, total_revenue_cents: 0, draft_count: 0, issued_count: 0, paid_count: 0, cancelled_count: 0 }, // Invoices list invoices: [], totalInvoices: 0, page: 1, perPage: 20, filters: { status: '' }, // Create invoice modal showCreateModal: false, createForm: { order_id: '', notes: '' }, async init() { // Guard against multiple initialization if (window._vendorInvoicesInitialized) { return; } window._vendorInvoicesInitialized = true; // Call parent init first to set vendorCode from URL const parentInit = data().init; if (parentInit) { await parentInit.call(this); } await this.loadSettings(); await this.loadStats(); await this.loadInvoices(); }, /** * Load invoice settings */ async loadSettings() { try { const response = await apiClient.get('/vendor/invoices/settings'); if (response) { this.settings = response; this.hasSettings = true; // Populate form with existing settings this.settingsForm = { company_name: response.company_name || '', company_address: response.company_address || '', company_city: response.company_city || '', company_postal_code: response.company_postal_code || '', company_country: response.company_country || 'LU', vat_number: response.vat_number || '', invoice_prefix: response.invoice_prefix || 'INV', default_vat_rate: response.default_vat_rate?.toString() || '17.00', bank_name: response.bank_name || '', bank_iban: response.bank_iban || '', bank_bic: response.bank_bic || '', payment_terms: response.payment_terms || 'Net 30 days', footer_text: response.footer_text || '' }; } else { this.hasSettings = false; } } catch (error) { // 404 means not configured yet, which is fine if (error.status !== 404) { invoicesLog.error('[VENDOR INVOICES] Failed to load settings:', error); } this.hasSettings = false; } }, /** * Load invoice statistics */ async loadStats() { try { const response = await apiClient.get('/vendor/invoices/stats'); this.stats = { total_invoices: response.total_invoices || 0, total_revenue_cents: response.total_revenue_cents || 0, draft_count: response.draft_count || 0, issued_count: response.issued_count || 0, paid_count: response.paid_count || 0, cancelled_count: response.cancelled_count || 0 }; } catch (error) { invoicesLog.error('[VENDOR INVOICES] Failed to load stats:', error); } }, /** * Load invoices list */ async loadInvoices() { this.loading = true; this.error = ''; try { const params = new URLSearchParams({ page: this.page.toString(), per_page: this.perPage.toString() }); if (this.filters.status) { params.append('status', this.filters.status); } const response = await apiClient.get(`/vendor/invoices?${params}`); this.invoices = response.items || []; this.totalInvoices = response.total || 0; } catch (error) { invoicesLog.error('[VENDOR INVOICES] Failed to load invoices:', error); this.error = error.message || 'Failed to load invoices'; } finally { this.loading = false; } }, /** * Refresh all data */ async refreshData() { await this.loadSettings(); await this.loadStats(); await this.loadInvoices(); this.successMessage = 'Data refreshed'; setTimeout(() => this.successMessage = '', 3000); }, /** * Save invoice settings */ async saveSettings() { if (!this.settingsForm.company_name) { this.error = 'Company name is required'; return; } this.savingSettings = true; this.error = ''; try { const payload = { company_name: this.settingsForm.company_name, company_address: this.settingsForm.company_address || null, company_city: this.settingsForm.company_city || null, company_postal_code: this.settingsForm.company_postal_code || null, company_country: this.settingsForm.company_country || 'LU', vat_number: this.settingsForm.vat_number || null, invoice_prefix: this.settingsForm.invoice_prefix || 'INV', default_vat_rate: parseFloat(this.settingsForm.default_vat_rate) || 17.0, bank_name: this.settingsForm.bank_name || null, bank_iban: this.settingsForm.bank_iban || null, bank_bic: this.settingsForm.bank_bic || null, payment_terms: this.settingsForm.payment_terms || null, footer_text: this.settingsForm.footer_text || null }; let response; if (this.hasSettings) { // Update existing settings response = await apiClient.put('/vendor/invoices/settings', payload); } else { // Create new settings response = await apiClient.post('/vendor/invoices/settings', payload); } this.settings = response; this.hasSettings = true; this.successMessage = 'Settings saved successfully'; } catch (error) { invoicesLog.error('[VENDOR INVOICES] Failed to save settings:', error); this.error = error.message || 'Failed to save settings'; } finally { this.savingSettings = false; setTimeout(() => this.successMessage = '', 5000); } }, /** * Open create invoice modal */ openCreateModal() { if (!this.hasSettings) { this.error = 'Please configure invoice settings first'; this.activeTab = 'settings'; return; } this.createForm = { order_id: '', notes: '' }; this.showCreateModal = true; }, /** * Create invoice from order */ async createInvoice() { if (!this.createForm.order_id) { this.error = 'Please enter an order ID'; return; } this.creatingInvoice = true; this.error = ''; try { const payload = { order_id: parseInt(this.createForm.order_id), notes: this.createForm.notes || null }; const response = await apiClient.post('/vendor/invoices', payload); this.showCreateModal = false; this.successMessage = `Invoice ${response.invoice_number} created successfully`; await this.loadStats(); await this.loadInvoices(); } catch (error) { invoicesLog.error('[VENDOR INVOICES] Failed to create invoice:', error); this.error = error.message || 'Failed to create invoice'; } finally { this.creatingInvoice = false; setTimeout(() => this.successMessage = '', 5000); } }, /** * Update invoice status */ async updateStatus(invoice, newStatus) { const statusLabels = { 'issued': 'mark as issued', 'paid': 'mark as paid', 'cancelled': 'cancel' }; if (!confirm(`Are you sure you want to ${statusLabels[newStatus] || newStatus} this invoice?`)) { return; } try { await apiClient.put(`/vendor/invoices/${invoice.id}/status`, { status: newStatus }); this.successMessage = `Invoice ${invoice.invoice_number} status updated to ${newStatus}`; await this.loadStats(); await this.loadInvoices(); } catch (error) { invoicesLog.error('[VENDOR INVOICES] Failed to update status:', error); this.error = error.message || 'Failed to update invoice status'; } setTimeout(() => this.successMessage = '', 5000); }, /** * Download invoice PDF */ async downloadPDF(invoice) { this.downloadingPdf = true; try { // Get the token for authentication const token = localStorage.getItem('wizamart_token') || localStorage.getItem('vendor_token'); if (!token) { throw new Error('Not authenticated'); } const response = await fetch(`/api/v1/vendor/invoices/${invoice.id}/pdf`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || 'Failed to download PDF'); } // Get filename from Content-Disposition header const contentDisposition = response.headers.get('Content-Disposition'); let filename = `invoice-${invoice.invoice_number}.pdf`; if (contentDisposition) { const match = contentDisposition.match(/filename="(.+)"/); if (match) { filename = match[1]; } } // Download the file const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); this.successMessage = `Downloaded: ${filename}`; } catch (error) { invoicesLog.error('[VENDOR INVOICES] Failed to download PDF:', error); this.error = error.message || 'Failed to download PDF'; } finally { this.downloadingPdf = false; setTimeout(() => this.successMessage = '', 5000); } }, /** * Format date for display */ formatDate(dateStr) { if (!dateStr) return 'N/A'; const date = new Date(dateStr); return date.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); }, /** * Format currency for display */ formatCurrency(cents, currency = 'EUR') { if (cents === null || cents === undefined) return 'N/A'; const amount = cents / 100; return new Intl.NumberFormat('de-LU', { style: 'currency', currency: currency }).format(amount); } }; }