inventory.js

20.39 KB
27/04/2025 12:02
JS
inventory.js
document.addEventListener('DOMContentLoaded', function() {
  // Initialize inventory management
  initInventory();

  // Event listeners
  document.getElementById('productSearch').addEventListener('input', filterProducts);
  document.getElementById('categoryFilter').addEventListener('change', filterProducts);
  document.getElementById('stockFilter').addEventListener('change', filterProducts);
  document.getElementById('addProductBtn').addEventListener('click', showAddProductModal);
  document.getElementById('manageCategories').addEventListener('click', showCategoryModal);
  document.getElementById('saveProduct').addEventListener('click', saveProduct);
  document.getElementById('cancelProduct').addEventListener('click', hideProductModal);
  document.getElementById('saveCategory').addEventListener('click', saveCategory);
  document.getElementById('cancelCategory').addEventListener('click', resetCategoryForm);
  document.getElementById('saveAdjustment').addEventListener('click', saveStockAdjustment);
  document.getElementById('cancelAdjustment').addEventListener('click', hideStockModal);
  document.getElementById('exportInventory').addEventListener('click', exportInventory);

  // Close modals when clicking on X
  document.querySelectorAll('.close-modal').forEach(button => {
    button.addEventListener('click', function() {
      this.closest('.modal').classList.remove('show');
    });
  });
});

// Global variables
let products = [];
let categories = [];

// Initialize inventory management
async function initInventory() {
  try {
    // Fetch categories
    const categoryResponse = await apiRequest('inventory/categories');
    if (categoryResponse.status === 'success') {
      categories = categoryResponse.data;
      renderCategoryDropdowns();
    }

    // Fetch products
    const productResponse = await apiRequest('inventory/products');
    if (productResponse.status === 'success') {
      products = productResponse.data;
      renderProducts(products);
    }

  } catch (error) {
    console.error('Failed to initialize inventory:', error);
    showNotification('Error loading inventory data', 'error');
  }
}

// Render category dropdowns
function renderCategoryDropdowns() {
  const categoryFilter = document.getElementById('categoryFilter');
  const categorySelect = document.getElementById('categoryId');

  // Clear existing options (except the first one)
  while (categoryFilter.options.length > 1) {
    categoryFilter.remove(1);
  }

  while (categorySelect.options.length > 1) {
    categorySelect.remove(1);
  }

  // Add categories to dropdowns
  categories.forEach(category => {
    if (category.status === 'active') {
      const filterOption = document.createElement('option');
      filterOption.value = category.id;
      filterOption.textContent = category.name;
      categoryFilter.appendChild(filterOption);

      const selectOption = document.createElement('option');
      selectOption.value = category.id;
      selectOption.textContent = category.name;
      categorySelect.appendChild(selectOption);
    }
  });
}

// Render products table
function renderProducts(productsToRender) {
  const tableBody = document.querySelector('#inventoryTable tbody');
  tableBody.innerHTML = '';

  if (productsToRender.length === 0) {
    const row = document.createElement('tr');
    row.innerHTML = '<td colspan="8" class="text-center">No products found</td>';
    tableBody.appendChild(row);
    return;
  }

  productsToRender.forEach(product => {
    const row = document.createElement('tr');

    // Determine stock status
    let stockClass = '';
    if (product.quantity <= 0) {
      stockClass = 'text-danger';
    } else if (product.quantity <= product.low_stock_threshold) {
      stockClass = 'text-warning';
    }

    row.innerHTML = `
      <td>${product.sku}</td>
      <td>${product.name}</td>
      <td>${product.category_name || 'Uncategorized'}</td>
      <td>${formatCurrency(product.price)}</td>
      <td>${formatCurrency(product.cost)}</td>
      <td class="${stockClass}">${product.quantity}</td>
      <td>
        <span class="badge ${product.status === 'active' ? 'badge-success' : 'badge-danger'}">
            ${product.status}
        </span>
      </td>
      <td class="actions">
        <button class="btn btn-sm btn-info edit-product" data-id="${product.id}">
            <i class="icon-edit"></i>
        </button>
        <button class="btn btn-sm btn-success adjust-stock" data-id="${product.id}" data-name="${product.name}" data-stock="${product.quantity}">
            <i class="icon-product"></i>
        </button>
        <button class="btn btn-sm btn-danger delete-product" data-id="${product.id}">
            <i class="icon-delete"></i>
        </button>
      </td>
    `;

    tableBody.appendChild(row);
  });

  // Add event listeners for action buttons
  document.querySelectorAll('.edit-product').forEach(button => {
    button.addEventListener('click', function() {
      const productId = this.getAttribute('data-id');
      if (productId) {
        editProduct(productId);
      } else {
        showNotification('Product ID is missing', 'error');
      }
    });
  });

  document.querySelectorAll('.adjust-stock').forEach(button => {
    button.addEventListener('click', function() {
      const productId = this.getAttribute('data-id');
      const productName = this.getAttribute('data-name');
      const currentStock = this.getAttribute('data-stock');
      showStockModal(productId, productName, currentStock);
    });
  });

  document.querySelectorAll('.delete-product').forEach(button => {
    button.addEventListener('click', function() {
      const productId = this.getAttribute('data-id');
      deleteProduct(productId);
    });
  });
}

// Filter products
function filterProducts() {
  const searchTerm = document.getElementById('productSearch').value.toLowerCase();
  const categoryId = document.getElementById('categoryFilter').value;
  const stockFilter = document.getElementById('stockFilter').value;

  let filtered = [...products];

  // Apply search filter
  if (searchTerm) {
    filtered = filtered.filter(product => {
      return product.name.toLowerCase().includes(searchTerm) ||
        product.sku.toLowerCase().includes(searchTerm) ||
        (product.barcode && product.barcode.toLowerCase().includes(searchTerm));
    });
  }

  // Apply category filter
  if (categoryId) {
    filtered = filtered.filter(product => product.category_id === categoryId);
  }

  // Apply stock filter
  if (stockFilter !== 'all') {
    if (stockFilter === 'low') {
      filtered = filtered.filter(product =>
        product.quantity <= product.low_stock_threshold && product.quantity > 0
      );
    } else if (stockFilter === 'out') {
      filtered = filtered.filter(product => product.quantity <= 0);
    }
  }

  renderProducts(filtered);
}

// Show add product modal
function showAddProductModal() {
  // Reset form
  document.getElementById('productForm').reset();
  document.getElementById('productId').value = '';
  document.getElementById('productModalTitle').textContent = 'Add Product';
  document.getElementById('status').value = 'active';

  // Show modal
  document.getElementById('productModal').classList.add('show');
}

// Show edit product modal
async function editProduct(productId) {
  try {
    if (!productId) {
      showNotification('Product ID is required', 'error');
      return;
    }

    console.log("Editing product with ID:", productId); // Debug line

    const response = await apiRequest(`inventory/product?id=${productId}`);

    if (response.status === 'success') {
      const product = response.data;

      console.log("Product data received:", product); // Debug line

      // Fill form fields
      document.getElementById('productId').value = product.id;
      document.getElementById('sku').value = product.sku;
      document.getElementById('barcode').value = product.barcode || '';
      document.getElementById('name').value = product.name;
      document.getElementById('description').value = product.description || '';
      document.getElementById('categoryId').value = product.category_id || '';
      document.getElementById('price').value = product.price;
      document.getElementById('cost').value = product.cost;
      document.getElementById('quantity').value = product.quantity;
      document.getElementById('lowStockThreshold').value = product.low_stock_threshold;
      document.getElementById('status').value = product.status;

      // Update modal title
      document.getElementById('productModalTitle').textContent = 'Edit Product';

      // Show modal
      document.getElementById('productModal').classList.add('show');
    } else {
      showNotification(response.message || 'Failed to load product data', 'error');
    }
  } catch (error) {
    console.error('Error fetching product:', error);
    showNotification('Error loading product data', 'error');
  }
}

// Hide product modal
function hideProductModal() {
  document.getElementById('productModal').classList.remove('show');
}

// Save product (create or update)
async function saveProduct() {
  try {
    const productId = document.getElementById('productId').value;

    // Gather form data
    const productData = {
      sku: document.getElementById('sku').value,
      barcode: document.getElementById('barcode').value,
      name: document.getElementById('name').value,
      description: document.getElementById('description').value,
      category_id: document.getElementById('categoryId').value,
      price: parseFloat(document.getElementById('price').value),
      cost: parseFloat(document.getElementById('cost').value),
      quantity: parseInt(document.getElementById('quantity').value),
      low_stock_threshold: parseInt(document.getElementById('lowStockThreshold').value),
      status: document.getElementById('status').value
    };

    let response;

    if (productId) {
      // Update existing product
      response = await apiRequest(`inventory/product?id=${productId}`, 'PUT', productData);
    } else {
      // Create new product
      response = await apiRequest('inventory/products', 'POST', productData);
    }

    if (response.status === 'success') {
      showNotification(productId ? 'Product updated successfully' : 'Product created successfully', 'success');
      hideProductModal();

      // Refresh product list
      const productResponse = await apiRequest('inventory/products');
      if (productResponse.status === 'success') {
        products = productResponse.data;
        renderProducts(products);
      }
    } else {
      showNotification(response.message, 'error');
    }
  } catch (error) {
    console.error('Error saving product:', error);
    showNotification('Error saving product data', 'error');
  }
}

// Delete product
async function deleteProduct(productId) {
  if (confirm('Are you sure you want to delete this product?')) {
    try {
      const response = await apiRequest(`inventory/product?id=${productId}`, 'DELETE');

      if (response.status === 'success') {
        showNotification('Product deleted successfully', 'success');

        // Refresh product list
        const productResponse = await apiRequest('inventory/products');
        if (productResponse.status === 'success') {
          products = productResponse.data;
          renderProducts(products);
        }
      } else {
        showNotification(response.message, 'error');
      }
    } catch (error) {
      console.error('Error deleting product:', error);
      showNotification('Error deleting product', 'error');
    }
  }
}

// Show stock adjustment modal
function showStockModal(productId, productName, currentStock) {
  document.getElementById('stockProductId').value = productId;
  document.getElementById('stockProductName').textContent = productName;
  document.getElementById('currentStock').textContent = currentStock;
  document.getElementById('adjustmentType').value = 'add';
  document.getElementById('adjustmentQuantity').value = '';
  document.getElementById('adjustmentNotes').value = '';

  document.getElementById('stockModal').classList.add('show');
}

// Hide stock adjustment modal
function hideStockModal() {
  document.getElementById('stockModal').classList.remove('show');
}

// Save stock adjustment
async function saveStockAdjustment() {
  try {
    const productId = document.getElementById('stockProductId').value;
    const adjustmentType = document.getElementById('adjustmentType').value;
    const quantityInput = parseInt(document.getElementById('adjustmentQuantity').value);
    const notes = document.getElementById('adjustmentNotes').value;

    if (isNaN(quantityInput) || quantityInput < 0) {
      showNotification('Please enter a valid quantity', 'error');
      return;
    }

    let quantity, type;
    const currentStock = parseInt(document.getElementById('currentStock').textContent);

    switch (adjustmentType) {
      case 'add':
        quantity = currentStock + quantityInput;
        type = 'adjustment';
        break;
      case 'subtract':
        if (currentStock < quantityInput) {
          showNotification('Cannot subtract more than current stock', 'error');
          return;
        }
        quantity = currentStock - quantityInput;
        type = 'adjustment';
        break;
      case 'set':
        quantity = quantityInput;
        type = 'adjustment';
        break;
    }

    const adjustmentData = {
      product_id: productId,
      type: type,
      quantity: quantity,
      notes: `${adjustmentType} stock: ${quantityInput} - ${notes}`
    };

    const response = await apiRequest('inventory/transactions', 'POST', adjustmentData);

    if (response.status === 'success') {
      showNotification('Stock adjustment saved successfully', 'success');
      hideStockModal();

      // Refresh product list
      const productResponse = await apiRequest('inventory/products');
      if (productResponse.status === 'success') {
        products = productResponse.data;
        renderProducts(products);
      }
    } else {
      showNotification(response.message, 'error');
    }
  } catch (error) {
    console.error('Error adjusting stock:', error);
    showNotification('Error saving stock adjustment', 'error');
  }
}

// Show category management modal
function showCategoryModal() {
  document.getElementById('categoryForm').reset();
  document.getElementById('categoryId').value = '';
  document.getElementById('categoryModalTitle').textContent = 'Manage Categories';
  renderCategoryList();
  document.getElementById('categoryModal').classList.add('show');
}

// Render category list in modal
function renderCategoryList() {
  const categoryList = document.getElementById('categoryList');
  categoryList.innerHTML = '';

  categories.forEach(category => {
    const categoryItem = document.createElement('div');
    categoryItem.className = 'category-item';

    categoryItem.innerHTML = `
            <div class="category-name">${category.name}</div>
            <div class="category-actions">
                <button class="btn-icon edit-category" data-id="${category.id}">
                    <i class="icon-edit"></i>
                </button>
                <button class="btn-icon delete-category" data-id="${category.id}">
                    <i class="icon-delete"></i>
                </button>
            </div>
        `;

    categoryList.appendChild(categoryItem);
  });

  // Add event listeners for category actions
  document.querySelectorAll('.edit-category').forEach(button => {
    button.addEventListener('click', function() {
      const categoryId = this.dataset.id;
      editCategory(categoryId);
    });
  });

  document.querySelectorAll('.delete-category').forEach(button => {
    button.addEventListener('click', function() {
      const categoryId = this.dataset.id;
      deleteCategory(categoryId);
    });
  });
}

// Edit category
function editCategory(categoryId) {
  const category = categories.find(c => c.id === categoryId);

  if (category) {
    document.getElementById('categoryId').value = category.id;
    document.getElementById('categoryName').value = category.name;
    document.getElementById('categoryDescription').value = category.description || '';
  }
}

// Reset category form
function resetCategoryForm() {
  document.getElementById('categoryForm').reset();
  document.getElementById('categoryId').value = '';
}

// Save category (create or update)
async function saveCategory() {
  try {
    const categoryId = document.getElementById('categoryId').value;
    const categoryName = document.getElementById('categoryName').value;
    const categoryDescription = document.getElementById('categoryDescription').value;

    if (!categoryName) {
      showNotification('Category name is required', 'error');
      return;
    }

    const categoryData = {
      name: categoryName,
      description: categoryDescription
    };

    let response;

    if (categoryId) {
      // Update existing category
      response = await apiRequest(`inventory/category?id=${categoryId}`, 'PUT', categoryData);
    } else {
      // Create new category
      response = await apiRequest('inventory/categories', 'POST', categoryData);
    }

    if (response.status === 'success') {
      showNotification(categoryId ? 'Category updated successfully' : 'Category created successfully', 'success');
      resetCategoryForm();

      // Refresh category list
      const categoryResponse = await apiRequest('inventory/categories');
      if (categoryResponse.status === 'success') {
        categories = categoryResponse.data;
        renderCategoryList();
        renderCategoryDropdowns();
      }
    } else {
      showNotification(response.message, 'error');
    }
  } catch (error) {
    console.error('Error saving category:', error);
    showNotification('Error saving category', 'error');
  }
}

// Delete category
async function deleteCategory(categoryId) {
  if (confirm('Are you sure you want to delete this category?')) {
    try {
      const response = await apiRequest(`inventory/category?id=${categoryId}`, 'DELETE');

      if (response.status === 'success') {
        showNotification('Category deleted successfully', 'success');

        // Refresh category list
        const categoryResponse = await apiRequest('inventory/categories');
        if (categoryResponse.status === 'success') {
          categories = categoryResponse.data;
          renderCategoryList();
          renderCategoryDropdowns();
        }
      } else {
        showNotification(response.message, 'error');
      }
    } catch (error) {
      console.error('Error deleting category:', error);
      showNotification('Error deleting category', 'error');
    }
  }
}

// Export inventory to CSV
function exportInventory() {
  // Get filtered products
  const searchTerm = document.getElementById('productSearch').value.toLowerCase();
  const categoryId = document.getElementById('categoryFilter').value;
  const stockFilter = document.getElementById('stockFilter').value;

  let filtered = [...products];

  // Apply filters
  if (searchTerm) {
    filtered = filtered.filter(product => {
      return product.name.toLowerCase().includes(searchTerm) ||
        product.sku.toLowerCase().includes(searchTerm) ||
        (product.barcode && product.barcode.toLowerCase().includes(searchTerm));
    });
  }

  if (categoryId) {
    filtered = filtered.filter(product => product.category_id === categoryId);
  }

  if (stockFilter !== 'all') {
    if (stockFilter === 'low') {
      filtered = filtered.filter(product =>
        product.quantity <= product.low_stock_threshold && product.quantity > 0
      );
    } else if (stockFilter === 'out') {
      filtered = filtered.filter(product => product.quantity <= 0);
    }
  }

  // Create CSV content
  let csvContent = 'SKU,Name,Category,Price,Cost,Stock,Status\n';

  filtered.forEach(product => {
    const row = [
      product.sku,
      `"${product.name.replace(/"/g, '""')}"`,
      `"${(product.category_name || 'Uncategorized').replace(/"/g, '""')}"`,
      product.price,
      product.cost,
      product.quantity,
      product.status
    ];
    csvContent += row.join(',') + '\n';
  });

  // Create download link
  const blob = new Blob([csvContent], {type: 'text/csv;charset=utf-8;'});
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');

  link.setAttribute('href', url);
  link.setAttribute('download', `inventory_export_${new Date().toISOString().split('T')[0]}.csv`);
  link.style.visibility = 'hidden';

  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

// Show notification
function showNotification(message, type = 'info') {
  // You can implement a notification system here
  // For simplicity, we'll use alert for now
  alert(message);
}