// static/vendor/js/order-detail.js /** * Vendor order detail page logic * View order details, manage status, handle shipments, and invoice integration */ const orderDetailLog = window.LogConfig.loggers.orderDetail || window.LogConfig.createLogger('orderDetail', false); orderDetailLog.info('Loading...'); function vendorOrderDetail() { orderDetailLog.info('vendorOrderDetail() called'); return { // Inherit base layout state ...data(), // Set page identifier currentPage: 'orders', // Order ID from URL orderId: window.orderDetailData?.orderId || null, // Loading states loading: true, error: '', saving: false, creatingInvoice: false, downloadingPdf: false, // Order data order: null, shipmentStatus: null, invoice: null, // Modal states showStatusModal: false, showShipAllModal: false, newStatus: '', trackingNumber: '', trackingProvider: '', // Order statuses statuses: [ { value: 'pending', label: 'Pending', color: 'yellow' }, { value: 'processing', label: 'Processing', color: 'blue' }, { value: 'partially_shipped', label: 'Partially Shipped', color: 'orange' }, { value: 'shipped', label: 'Shipped', color: 'indigo' }, { value: 'delivered', label: 'Delivered', color: 'green' }, { value: 'cancelled', label: 'Cancelled', color: 'red' }, { value: 'refunded', label: 'Refunded', color: 'gray' } ], async init() { orderDetailLog.info('Order detail init() called, orderId:', this.orderId); // Guard against multiple initialization if (window._orderDetailInitialized) { orderDetailLog.warn('Already initialized, skipping'); return; } window._orderDetailInitialized = true; // IMPORTANT: Call parent init first to set vendorCode from URL const parentInit = data().init; if (parentInit) { await parentInit.call(this); } if (!this.orderId) { this.error = 'Order ID not provided'; this.loading = false; return; } try { await this.loadOrderDetails(); } catch (error) { orderDetailLog.error('Init failed:', error); this.error = 'Failed to load order details'; } orderDetailLog.info('Order detail initialization complete'); }, /** * Load order details from API */ async loadOrderDetails() { this.loading = true; this.error = ''; try { // Load order details const orderResponse = await apiClient.get( `/vendor/orders/${this.orderId}` ); this.order = orderResponse; this.newStatus = this.order.status; orderDetailLog.info('Loaded order:', this.order.order_number); // Load shipment status await this.loadShipmentStatus(); // Load invoice if exists await this.loadInvoice(); } catch (error) { orderDetailLog.error('Failed to load order details:', error); this.error = error.message || 'Failed to load order details'; } finally { this.loading = false; } }, /** * Load shipment status for partial shipment tracking */ async loadShipmentStatus() { try { const response = await apiClient.get( `/vendor/orders/${this.orderId}/shipment-status` ); this.shipmentStatus = response; orderDetailLog.info('Loaded shipment status:', response); } catch (error) { orderDetailLog.warn('Failed to load shipment status:', error); // Not critical - continue without shipment status } }, /** * Load invoice for this order */ async loadInvoice() { try { // Search for invoices linked to this order const response = await apiClient.get( `/vendor/invoices?order_id=${this.orderId}&limit=1` ); if (response.invoices && response.invoices.length > 0) { this.invoice = response.invoices[0]; orderDetailLog.info('Loaded invoice:', this.invoice.invoice_number); } } catch (error) { orderDetailLog.warn('Failed to load invoice:', error); // Not critical - continue without invoice } }, /** * Get status color class */ getStatusColor(status) { const statusObj = this.statuses.find(s => s.value === status); return statusObj ? statusObj.color : 'gray'; }, /** * Get status label */ getStatusLabel(status) { const statusObj = this.statuses.find(s => s.value === status); return statusObj ? statusObj.label : status; }, /** * Get item shipment status */ getItemShipmentStatus(itemId) { if (!this.shipmentStatus?.items) return null; return this.shipmentStatus.items.find(i => i.item_id === itemId); }, /** * Check if item can be shipped */ canShipItem(itemId) { const status = this.getItemShipmentStatus(itemId); if (!status) return true; // Assume can ship if no status return !status.is_fully_shipped; }, /** * Format price for display */ formatPrice(cents) { if (cents === null || cents === undefined) return '-'; return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(cents / 100); }, /** * Format date/time for display */ formatDateTime(dateStr) { if (!dateStr) return '-'; return new Date(dateStr).toLocaleDateString('de-DE', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); }, /** * Update order status */ async updateOrderStatus(status) { this.saving = true; try { const payload = { status }; // Add tracking info if shipping if (status === 'shipped' && this.trackingNumber) { payload.tracking_number = this.trackingNumber; payload.tracking_provider = this.trackingProvider; } await apiClient.put( `/vendor/orders/${this.orderId}/status`, payload ); Utils.showToast(`Order status updated to ${this.getStatusLabel(status)}`, 'success'); await this.loadOrderDetails(); } catch (error) { orderDetailLog.error('Failed to update status:', error); Utils.showToast(error.message || 'Failed to update status', 'error'); } finally { this.saving = false; } }, /** * Confirm status update from modal */ async confirmStatusUpdate() { await this.updateOrderStatus(this.newStatus); this.showStatusModal = false; this.trackingNumber = ''; this.trackingProvider = ''; }, /** * Ship a single item */ async shipItem(itemId) { this.saving = true; try { await apiClient.post( `/vendor/orders/${this.orderId}/items/${itemId}/ship`, {} ); Utils.showToast('Item shipped successfully', 'success'); await this.loadOrderDetails(); } catch (error) { orderDetailLog.error('Failed to ship item:', error); Utils.showToast(error.message || 'Failed to ship item', 'error'); } finally { this.saving = false; } }, /** * Ship all remaining items */ async shipAllItems() { this.saving = true; try { // Ship each unshipped item const unshippedItems = this.shipmentStatus?.items?.filter(i => !i.is_fully_shipped) || []; for (const item of unshippedItems) { await apiClient.post( `/vendor/orders/${this.orderId}/items/${item.item_id}/ship`, {} ); } // Update order status to shipped with tracking const payload = { status: 'shipped' }; if (this.trackingNumber) { payload.tracking_number = this.trackingNumber; payload.tracking_provider = this.trackingProvider; } await apiClient.put( `/vendor/orders/${this.orderId}/status`, payload ); Utils.showToast('All items shipped', 'success'); this.showShipAllModal = false; this.trackingNumber = ''; this.trackingProvider = ''; await this.loadOrderDetails(); } catch (error) { orderDetailLog.error('Failed to ship all items:', error); Utils.showToast(error.message || 'Failed to ship items', 'error'); } finally { this.saving = false; } }, /** * Create invoice for this order */ async createInvoice() { this.creatingInvoice = true; try { const response = await apiClient.post( `/vendor/invoices`, { order_id: this.orderId } ); this.invoice = response; Utils.showToast(`Invoice ${response.invoice_number} created`, 'success'); } catch (error) { orderDetailLog.error('Failed to create invoice:', error); Utils.showToast(error.message || 'Failed to create invoice', 'error'); } finally { this.creatingInvoice = false; } }, /** * Download invoice PDF */ async downloadInvoicePdf() { if (!this.invoice) return; this.downloadingPdf = true; try { const response = await fetch( `/api/v1/vendor/${this.vendorCode}/invoices/${this.invoice.id}/pdf`, { headers: { 'Authorization': `Bearer ${window.Auth?.getToken()}` } } ); if (!response.ok) { throw new Error('Failed to download PDF'); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${this.invoice.invoice_number}.pdf`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); a.remove(); Utils.showToast('Invoice downloaded', 'success'); } catch (error) { orderDetailLog.error('Failed to download invoice PDF:', error); Utils.showToast(error.message || 'Failed to download PDF', 'error'); } finally { this.downloadingPdf = false; } } }; }