PHP 8.2.30
Preview: inventoryController.js Size: 12.15 KB
/home/byroehnu/easepay.easetack.com/controllers/inventoryController.js

const { prisma } = require('../config/prisma');

const inventoryController = {

  // Get all inventory items
  getAllItems: async (req, res) => {
    try {
      const userId = req.user.id;
      const businessId = req.user.businessId;
      const { page = 1, limit = 20, category, search, includeInactive = false } = req.query;

      const whereClause = {
        userId,
        ...(businessId && { businessId }),
        ...(category && { category }),
        ...(search && {
          OR: [
            { name: { contains: search, mode: 'insensitive' } },
            { description: { contains: search, mode: 'insensitive' } }
          ]
        }),
        ...(includeInactive === 'false' && { isActive: true })
      };

      const items = await prisma.inventoryItem.findMany({
        where: whereClause,
        orderBy: { name: 'asc' },
        skip: (parseInt(page) - 1) * parseInt(limit),
        take: parseInt(limit)
      });

      const totalItems = await prisma.inventoryItem.count({ where: whereClause });

      res.json({
        success: true,
        data: {
          items,
          pagination: {
            currentPage: parseInt(page),
            totalPages: Math.ceil(totalItems / parseInt(limit)),
            totalItems,
            limit: parseInt(limit)
          }
        }
      });

    } catch (error) {
      console.error('Error getting inventory items:', error);
      res.status(500).json({
        success: false,
        message: 'Error getting inventory items',
        error: error.message
      });
    }
  },

  // Get single inventory item
  getItem: async (req, res) => {
    try {
      const { id } = req.params;
      const userId = req.user.id;

      const item = await prisma.inventoryItem.findFirst({
        where: { id, userId },
        include: {
          invoiceItems: {
            include: {
              invoice: {
                select: {
                  id: true,
                  invoiceNumber: true,
                  customerName: true,
                  invoiceDate: true,
                  total: true
                }
              }
            },
            orderBy: { createdAt: 'desc' },
            take: 10
          }
        }
      });

      if (!item) {
        return res.status(404).json({
          success: false,
          message: 'Item not found'
        });
      }

      res.json({
        success: true,
        data: item
      });

    } catch (error) {
      console.error('Error getting inventory item:', error);
      res.status(500).json({
        success: false,
        message: 'Error getting inventory item',
        error: error.message
      });
    }
  },

  // Create new inventory item
  createItem: async (req, res) => {
    try {
      const userId = req.user.id;
      const businessId = req.user.businessId;
      
      const {
        name,
        description,
        category,
        unitPrice,
        costPrice,
        stock = 0,
        unit,
        minStock
      } = req.body;

      // Validate required fields
      if (!name || !unitPrice) {
        return res.status(400).json({
          success: false,
          message: 'Name and unit price are required'
        });
      }

      const item = await prisma.inventoryItem.create({
        data: {
          name,
          description,
          category,
          unitPrice: parseFloat(unitPrice),
          costPrice: costPrice ? parseFloat(costPrice) : null,
          stock: parseFloat(stock),
          unit,
          minStock: minStock ? parseFloat(minStock) : null,
          userId,
          businessId
        }
      });

      res.status(201).json({
        success: true,
        message: 'Item created successfully',
        data: item
      });

    } catch (error) {
      console.error('Error creating inventory item:', error);
      res.status(500).json({
        success: false,
        message: 'Error creating inventory item',
        error: error.message
      });
    }
  },

  // Update inventory item
  updateItem: async (req, res) => {
    try {
      const { id } = req.params;
      const userId = req.user.id;
      const updateData = req.body;

      // Check if item exists and belongs to user
      const existingItem = await prisma.inventoryItem.findFirst({
        where: { id, userId }
      });

      if (!existingItem) {
        return res.status(404).json({
          success: false,
          message: 'Item not found'
        });
      }

      // Convert numeric fields
      if (updateData.unitPrice) updateData.unitPrice = parseFloat(updateData.unitPrice);
      if (updateData.costPrice) updateData.costPrice = parseFloat(updateData.costPrice);
      if (updateData.stock !== undefined) updateData.stock = parseFloat(updateData.stock);
      if (updateData.minStock) updateData.minStock = parseFloat(updateData.minStock);

      const updatedItem = await prisma.inventoryItem.update({
        where: { id },
        data: updateData
      });

      res.json({
        success: true,
        message: 'Item updated successfully',
        data: updatedItem
      });

    } catch (error) {
      console.error('Error updating inventory item:', error);
      res.status(500).json({
        success: false,
        message: 'Error updating inventory item',
        error: error.message
      });
    }
  },

  // Delete inventory item
  deleteItem: async (req, res) => {
    try {
      const { id } = req.params;
      const userId = req.user.id;

      const item = await prisma.inventoryItem.findFirst({
        where: { id, userId }
      });

      if (!item) {
        return res.status(404).json({
          success: false,
          message: 'Item not found'
        });
      }

      await prisma.inventoryItem.delete({
        where: { id }
      });

      res.json({
        success: true,
        message: 'Item deleted successfully'
      });

    } catch (error) {
      console.error('Error deleting inventory item:', error);
      res.status(500).json({
        success: false,
        message: 'Error deleting inventory item',
        error: error.message
      });
    }
  },

  // Update stock for multiple items
  updateStock: async (req, res) => {
    try {
      const userId = req.user.id;
      const { updates } = req.body; // Array of { id, quantity, operation: 'add' | 'subtract' | 'set' }

      if (!Array.isArray(updates) || updates.length === 0) {
        return res.status(400).json({
          success: false,
          message: 'Updates array is required'
        });
      }

      const results = [];

      for (const update of updates) {
        const { id, quantity, operation = 'set' } = update;

        const item = await prisma.inventoryItem.findFirst({
          where: { id, userId }
        });

        if (!item) {
          results.push({ id, success: false, message: 'Item not found' });
          continue;
        }

        let newStock;
        switch (operation) {
          case 'add':
            newStock = parseFloat(item.stock) + parseFloat(quantity);
            break;
          case 'subtract':
            newStock = parseFloat(item.stock) - parseFloat(quantity);
            break;
          case 'set':
          default:
            newStock = parseFloat(quantity);
            break;
        }

        if (newStock < 0) {
          results.push({ id, success: false, message: 'Stock cannot be negative' });
          continue;
        }

        const updatedItem = await prisma.inventoryItem.update({
          where: { id },
          data: { stock: newStock }
        });

        results.push({ id, success: true, newStock: updatedItem.stock });
      }

      res.json({
        success: true,
        message: 'Stock update completed',
        data: results
      });

    } catch (error) {
      console.error('Error updating stock:', error);
      res.status(500).json({
        success: false,
        message: 'Error updating stock',
        error: error.message
      });
    }
  },

  // Get low stock items
  getLowStockItems: async (req, res) => {
    try {
      const userId = req.user.id;
      const businessId = req.user.businessId;

      const lowStockItems = await prisma.inventoryItem.findMany({
        where: {
          userId,
          ...(businessId && { businessId }),
          isActive: true,
          minStock: { not: null },
          OR: [
            {
              stock: { lte: { minStock: true } }
            }
          ]
        },
        orderBy: { stock: 'asc' }
      });

      res.json({
        success: true,
        data: {
          lowStockItems,
          count: lowStockItems.length
        }
      });

    } catch (error) {
      console.error('Error getting low stock items:', error);
      res.status(500).json({
        success: false,
        message: 'Error getting low stock items',
        error: error.message
      });
    }
  },

  // Get inventory categories
  getCategories: async (req, res) => {
    try {
      const userId = req.user.id;
      const businessId = req.user.businessId;

      const categories = await prisma.inventoryItem.findMany({
        where: {
          userId,
          ...(businessId && { businessId }),
          category: { not: null },
          isActive: true
        },
        select: {
          category: true
        },
        distinct: ['category']
      });

      const categoryList = categories
        .map(item => item.category)
        .filter(category => category && category.trim() !== '')
        .sort();

      res.json({
        success: true,
        data: {
          categories: categoryList
        }
      });

    } catch (error) {
      console.error('Error getting categories:', error);
      res.status(500).json({
        success: false,
        message: 'Error getting categories',
        error: error.message
      });
    }
  },

  // Get inventory analytics
  getInventoryAnalytics: async (req, res) => {
    try {
      const userId = req.user.id;
      const businessId = req.user.businessId;

      const whereClause = {
        userId,
        ...(businessId && { businessId }),
        isActive: true
      };

      const analytics = await prisma.inventoryItem.aggregate({
        where: whereClause,
        _count: { id: true },
        _sum: { 
          stock: true,
          unitPrice: true,
          costPrice: true
        },
        _avg: {
          unitPrice: true,
          stock: true
        }
      });

      const categoryBreakdown = await prisma.inventoryItem.groupBy({
        by: ['category'],
        where: whereClause,
        _count: { category: true },
        _sum: { stock: true, unitPrice: true }
      });

      // Calculate total inventory value
      const inventoryValue = await prisma.inventoryItem.findMany({
        where: whereClause,
        select: {
          stock: true,
          unitPrice: true,
          costPrice: true
        }
      });

      const totalValue = inventoryValue.reduce((sum, item) => {
        return sum + (parseFloat(item.stock) * parseFloat(item.unitPrice));
      }, 0);

      const totalCost = inventoryValue.reduce((sum, item) => {
        return sum + (parseFloat(item.stock) * parseFloat(item.costPrice || 0));
      }, 0);

      res.json({
        success: true,
        data: {
          totalItems: analytics._count.id,
          totalStock: analytics._sum.stock || 0,
          averagePrice: analytics._avg.unitPrice || 0,
          averageStock: analytics._avg.stock || 0,
          totalInventoryValue: totalValue,
          totalInventoryCost: totalCost,
          potentialProfit: totalValue - totalCost,
          categoryBreakdown
        }
      });

    } catch (error) {
      console.error('Error getting inventory analytics:', error);
      res.status(500).json({
        success: false,
        message: 'Error getting inventory analytics',
        error: error.message
      });
    }
  }
};

module.exports = inventoryController;

Directory Contents

Dirs: 1 × Files: 17

Name Size Perms Modified Actions
- drwxr-xr-x 2026-03-20 23:25:48
Edit Download
127 B lr--r--r-- 2026-03-14 01:49:22
Edit Download
15.34 KB lrw-r--r-- 2026-02-19 22:06:47
Edit Download
30.77 KB lrw-r--r-- 2026-03-04 06:33:18
Edit Download
62.98 KB lrw-r--r-- 2026-03-01 23:46:52
Edit Download
10.79 KB lrw-r--r-- 2026-02-07 16:28:16
Edit Download
14.17 KB lrw-r--r-- 2026-02-27 04:02:38
Edit Download
6.32 KB lrw-r--r-- 2026-02-06 17:10:19
Edit Download
15.13 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
8.50 KB lrw-r--r-- 2026-03-01 05:05:52
Edit Download
12.15 KB lrw-r--r-- 2026-02-27 04:02:38
Edit Download
25.39 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
17.61 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
6.45 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
2.33 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
12.78 KB lrw-r--r-- 2026-02-21 08:13:30
Edit Download
9.96 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
8.51 KB lrw-r--r-- 2026-02-20 19:31:28
Edit Download

If ZipArchive is unavailable, a .tar will be created (no compression).