// static/vendor/js/inventory.js /** * Vendor inventory management page logic * View and manage stock levels */ const vendorInventoryLog = window.LogConfig.loggers.vendorInventory || window.LogConfig.createLogger('vendorInventory', false); vendorInventoryLog.info('Loading...'); function vendorInventory() { vendorInventoryLog.info('vendorInventory() called'); return { // Inherit base layout state ...data(), // Set page identifier currentPage: 'inventory', // Loading states loading: true, error: '', saving: false, // Inventory data inventory: [], stats: { total_entries: 0, total_quantity: 0, low_stock_count: 0, out_of_stock_count: 0 }, // Filters filters: { search: '', location: '', low_stock: '' }, // Available locations for filter dropdown locations: [], // Pagination pagination: { page: 1, per_page: 20, total: 0, pages: 0 }, // Modal states showAdjustModal: false, showSetModal: false, selectedItem: null, // Form data adjustForm: { quantity: 0, reason: '' }, setForm: { quantity: 0 }, // Debounce timer searchTimeout: null, // Computed: Total pages get totalPages() { return this.pagination.pages; }, // Computed: Start index for pagination display get startIndex() { if (this.pagination.total === 0) return 0; return (this.pagination.page - 1) * this.pagination.per_page + 1; }, // Computed: End index for pagination display get endIndex() { const end = this.pagination.page * this.pagination.per_page; return end > this.pagination.total ? this.pagination.total : end; }, // Computed: Page numbers for pagination get pageNumbers() { const pages = []; const totalPages = this.totalPages; const current = this.pagination.page; if (totalPages <= 7) { for (let i = 1; i <= totalPages; i++) { pages.push(i); } } else { pages.push(1); if (current > 3) pages.push('...'); const start = Math.max(2, current - 1); const end = Math.min(totalPages - 1, current + 1); for (let i = start; i <= end; i++) { pages.push(i); } if (current < totalPages - 2) pages.push('...'); pages.push(totalPages); } return pages; }, async init() { vendorInventoryLog.info('Inventory init() called'); // Guard against multiple initialization if (window._vendorInventoryInitialized) { vendorInventoryLog.warn('Already initialized, skipping'); return; } window._vendorInventoryInitialized = true; // Load platform settings for rows per page if (window.PlatformSettings) { this.pagination.per_page = await window.PlatformSettings.getRowsPerPage(); } try { await this.loadInventory(); } catch (error) { vendorInventoryLog.error('Init failed:', error); this.error = 'Failed to initialize inventory page'; } vendorInventoryLog.info('Inventory initialization complete'); }, /** * Load inventory with filtering and pagination */ async loadInventory() { this.loading = true; this.error = ''; try { const params = new URLSearchParams({ skip: (this.pagination.page - 1) * this.pagination.per_page, limit: this.pagination.per_page }); // Add filters if (this.filters.search) { params.append('search', this.filters.search); } if (this.filters.location) { params.append('location', this.filters.location); } if (this.filters.low_stock) { params.append('low_stock', this.filters.low_stock); } const response = await apiClient.get(`/vendor/${this.vendorCode}/inventory?${params.toString()}`); this.inventory = response.items || []; this.pagination.total = response.total || 0; this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page); // Extract unique locations this.extractLocations(); // Calculate stats this.calculateStats(); vendorInventoryLog.info('Loaded inventory:', this.inventory.length, 'of', this.pagination.total); } catch (error) { vendorInventoryLog.error('Failed to load inventory:', error); this.error = error.message || 'Failed to load inventory'; } finally { this.loading = false; } }, /** * Extract unique locations from inventory */ extractLocations() { const locationSet = new Set(this.inventory.map(i => i.location).filter(Boolean)); this.locations = Array.from(locationSet).sort(); }, /** * Calculate inventory statistics */ calculateStats() { this.stats = { total_entries: this.pagination.total, total_quantity: this.inventory.reduce((sum, i) => sum + (i.quantity || 0), 0), low_stock_count: this.inventory.filter(i => i.quantity > 0 && i.quantity <= (i.low_stock_threshold || 5)).length, out_of_stock_count: this.inventory.filter(i => i.quantity <= 0).length }; }, /** * Debounced search handler */ debouncedSearch() { clearTimeout(this.searchTimeout); this.searchTimeout = setTimeout(() => { this.pagination.page = 1; this.loadInventory(); }, 300); }, /** * Apply filter and reload */ applyFilter() { this.pagination.page = 1; this.loadInventory(); }, /** * Clear all filters */ clearFilters() { this.filters = { search: '', location: '', low_stock: '' }; this.pagination.page = 1; this.loadInventory(); }, /** * Open adjust stock modal */ openAdjustModal(item) { this.selectedItem = item; this.adjustForm = { quantity: 0, reason: '' }; this.showAdjustModal = true; }, /** * Open set quantity modal */ openSetModal(item) { this.selectedItem = item; this.setForm = { quantity: item.quantity || 0 }; this.showSetModal = true; }, /** * Execute stock adjustment */ async executeAdjust() { if (!this.selectedItem || this.adjustForm.quantity === 0) return; this.saving = true; try { await apiClient.post(`/vendor/${this.vendorCode}/inventory/adjust`, { product_id: this.selectedItem.product_id, location: this.selectedItem.location, quantity: this.adjustForm.quantity, reason: this.adjustForm.reason || null }); vendorInventoryLog.info('Adjusted inventory:', this.selectedItem.id); this.showAdjustModal = false; this.selectedItem = null; Utils.showToast('Stock adjusted successfully', 'success'); await this.loadInventory(); } catch (error) { vendorInventoryLog.error('Failed to adjust inventory:', error); Utils.showToast(error.message || 'Failed to adjust stock', 'error'); } finally { this.saving = false; } }, /** * Execute set quantity */ async executeSet() { if (!this.selectedItem || this.setForm.quantity < 0) return; this.saving = true; try { await apiClient.post(`/vendor/${this.vendorCode}/inventory/set`, { product_id: this.selectedItem.product_id, location: this.selectedItem.location, quantity: this.setForm.quantity }); vendorInventoryLog.info('Set inventory quantity:', this.selectedItem.id); this.showSetModal = false; this.selectedItem = null; Utils.showToast('Quantity set successfully', 'success'); await this.loadInventory(); } catch (error) { vendorInventoryLog.error('Failed to set inventory:', error); Utils.showToast(error.message || 'Failed to set quantity', 'error'); } finally { this.saving = false; } }, /** * Get stock status class */ getStockStatus(item) { if (item.quantity <= 0) return 'out'; if (item.quantity <= (item.low_stock_threshold || 5)) return 'low'; return 'ok'; }, /** * Format number with locale */ formatNumber(num) { if (num === null || num === undefined) return '0'; return new Intl.NumberFormat('en-US').format(num); }, /** * Pagination: Previous page */ previousPage() { if (this.pagination.page > 1) { this.pagination.page--; this.loadInventory(); } }, /** * Pagination: Next page */ nextPage() { if (this.pagination.page < this.totalPages) { this.pagination.page++; this.loadInventory(); } }, /** * Pagination: Go to specific page */ goToPage(pageNum) { if (pageNum !== '...' && pageNum >= 1 && pageNum <= this.totalPages) { this.pagination.page = pageNum; this.loadInventory(); } } }; }