// Модуль управления складом class WarehouseManager { constructor() { this.items = this.loadItems(); this.operations = this.loadOperations(); this.categories = this.loadCategories(); } // Загрузка позиций из localStorage loadItems() { const items = localStorage.getItem('warehouse_items'); return items ? JSON.parse(items) : this.getDefaultItems(); } // Загрузка операций из localStorage loadOperations() { const operations = localStorage.getItem('warehouse_operations'); return operations ? JSON.parse(operations) : []; } // Загрузка категорий loadCategories() { const categories = localStorage.getItem('warehouse_categories'); if (categories) { this.categories = JSON.parse(categories); } else { // Загружаем категории по умолчанию this.categories = this.getDefaultCategories(); this.saveCategories(); } // Обновляем количество позиций в каждой категории this.updateCategoryItemCounts(); } // Получение категорий по умолчанию getDefaultCategories() { return []; } // Сохранение категорий saveCategories() { localStorage.setItem('warehouse_categories', JSON.stringify(this.categories)); } // Обновление количества позиций в категориях updateCategoryItemCounts() { this.categories.forEach(category => { const itemsInCategory = this.items.filter(item => item.category === category.id); category.itemCount = itemsInCategory.length; category.totalQuantity = itemsInCategory.reduce((sum, item) => sum + (item.quantity || 0), 0); }); this.saveCategories(); } // Сохранение данных в localStorage saveItems() { localStorage.setItem('warehouse_items', JSON.stringify(this.items)); } saveOperations() { localStorage.setItem('warehouse_operations', JSON.stringify(this.operations)); } // Получение позиций по умолчанию getDefaultItems() { return []; } // Получение статистики getStatistics() { const today = new Date().toISOString().split('T')[0]; const totalItems = this.items.reduce((sum, item) => sum + item.quantity, 0); const totalValue = this.items.reduce((sum, item) => sum + (item.price * item.quantity), 0); const lowStockItems = this.items.filter(item => item.quantity <= item.minQuantity).length; const todayOperations = this.operations.filter(op => op.date.startsWith(today) ).length; return { totalItems, totalValue, lowStockItems, todayOperations, totalCategories: this.categories.length, totalOperations: this.operations.length }; } // Получение всех позиций async getAllItems() { return this.items; } // Синхронное получение всех позиций getAllItemsSync() { return this.items; } // Получение позиции по ID async getItemById(id) { return this.items.find(item => item.id === parseInt(id)); } // Добавление новой позиции async addItem(itemData) { const newItem = { id: this.getNextItemId(), name: itemData.itemName, category: itemData.itemCategory || 'uncategorized', price: parseFloat(itemData.itemPrice), quantity: parseInt(itemData.itemQuantity), description: itemData.itemDescription || '', barcode: itemData.itemBarcode || '', minQuantity: parseInt(itemData.itemQuantity) * 0.2, // 20% от начального количества location: itemData.itemLocation || 'Новый стеллаж', supplier: itemData.itemSupplier || '', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; this.items.push(newItem); this.saveItems(); // Обновляем количество товаров в категории this.updateCategoryItemCounts(); // Создаем операцию прихода для новой позиции await this.createOperation({ type: 'incoming', itemId: newItem.id, quantity: newItem.quantity, notes: 'Добавление новой позиции' }); return newItem; } // Обновление позиции async updateItem(id, itemData) { const itemIndex = this.items.findIndex(item => item.id === parseInt(id)); if (itemIndex === -1) { throw new Error('Позиция не найдена'); } this.items[itemIndex] = { ...this.items[itemIndex], ...itemData, updatedAt: new Date().toISOString() }; this.saveItems(); // Обновляем количество товаров в категории this.updateCategoryItemCounts(); return this.items[itemIndex]; } // Удаление позиции async deleteItem(id) { const itemIndex = this.items.findIndex(item => item.id === parseInt(id)); if (itemIndex === -1) { throw new Error('Позиция не найдена'); } this.items.splice(itemIndex, 1); this.saveItems(); // Обновляем количество товаров в категории this.updateCategoryItemCounts(); return true; } // Обработка прихода async processIncoming(operationData) { const itemId = parseInt(operationData.incomingItem); const quantity = parseInt(operationData.incomingQuantity); const item = await this.getItemById(itemId); if (!item) { throw new Error('Позиция не найдена'); } // Обновляем количество позиции item.quantity += quantity; item.updatedAt = new Date().toISOString(); this.saveItems(); // Обновляем количество товаров в категории this.updateCategoryItemCounts(); // Создаем операцию const operation = await this.createOperation({ type: 'incoming', itemId: itemId, quantity: quantity, supplier: operationData.incomingSupplier || '', notes: operationData.incomingNotes || '' }); return operation; } // Обработка расхода async processOutgoing(operationData) { const itemId = parseInt(operationData.outgoingItem); const quantity = parseInt(operationData.outgoingQuantity); const item = await this.getItemById(itemId); if (!item) { throw new Error('Позиция не найдена'); } // Проверяем достаточность позиции if (item.quantity < quantity) { throw new Error(`Недостаточно позиций. Доступно: ${item.quantity} шт.`); } // Обновляем количество позиции item.quantity -= quantity; item.updatedAt = new Date().toISOString(); this.saveItems(); // Обновляем количество товаров в категории this.updateCategoryItemCounts(); // Создаем операцию const operation = await this.createOperation({ type: 'outgoing', itemId: itemId, quantity: quantity, recipient: operationData.outgoingRecipient || '', notes: operationData.outgoingNotes || '' }); return operation; } // Создание операции async createOperation(operationData) { const item = await this.getItemById(operationData.itemId); const currentUser = auth.getCurrentUser(); const operation = { id: this.getNextOperationId(), type: operationData.type, itemId: operationData.itemId, itemName: item.name, quantity: operationData.quantity, employeeId: currentUser.id, employeeName: currentUser.name, date: new Date().toISOString(), status: 'completed', supplier: operationData.supplier || '', recipient: operationData.recipient || '', notes: operationData.notes || '' }; this.operations.push(operation); this.saveOperations(); return operation; } // Получение последних операций async getRecentOperations(limit = 10) { return this.operations .sort((a, b) => new Date(b.date) - new Date(a.date)) .slice(0, limit); } // Получение операций по фильтрам async getOperations(filters = {}) { let filteredOperations = [...this.operations]; // Фильтр по типу if (filters.type) { filteredOperations = filteredOperations.filter(op => op.type === filters.type); } // Фильтр по дате if (filters.startDate) { filteredOperations = filteredOperations.filter(op => new Date(op.date) >= new Date(filters.startDate) ); } if (filters.endDate) { filteredOperations = filteredOperations.filter(op => new Date(op.date) <= new Date(filters.endDate) ); } // Фильтр по товару if (filters.itemId) { filteredOperations = filteredOperations.filter(op => op.itemId === parseInt(filters.itemId) ); } // Сортировка const sortField = filters.sortBy || 'date'; const sortOrder = filters.sortOrder || 'desc'; filteredOperations.sort((a, b) => { let aValue = a[sortField]; let bValue = b[sortField]; if (sortField === 'date') { aValue = new Date(aValue); bValue = new Date(bValue); } if (sortOrder === 'asc') { return aValue > bValue ? 1 : -1; } else { return aValue < bValue ? 1 : -1; } }); return filteredOperations; } // Поиск товаров async searchItems(query, filters = {}) { let results = [...this.items]; // Поиск по названию и описанию if (query) { const searchQuery = query.toLowerCase(); results = results.filter(item => item.name.toLowerCase().includes(searchQuery) || item.description.toLowerCase().includes(searchQuery) || item.barcode.includes(searchQuery) ); } // Фильтр по категории if (filters.category) { results = results.filter(item => item.category === filters.category); } // Фильтр по цене if (filters.minPrice) { results = results.filter(item => item.price >= filters.minPrice); } if (filters.maxPrice) { results = results.filter(item => item.price <= filters.maxPrice); } // Фильтр по наличию if (filters.inStock) { results = results.filter(item => item.quantity > 0); } if (filters.lowStock) { results = results.filter(item => item.quantity <= item.minQuantity); } return results; } // Получение товаров с низким запасом async getLowStockItems() { return this.items.filter(item => item.quantity <= item.minQuantity); } // Получение категорий async getCategories() { return this.categories; } // Получение категории по ID async getCategoryById(id) { return this.categories.find(cat => cat.id === id); } // Добавление категории async addCategory(categoryData) { const newCategory = { id: this.generateCategoryId(), ...categoryData, itemCount: 0, createdAt: new Date().toISOString() }; this.categories.push(newCategory); this.saveCategories(); return newCategory; } // Обновление категории async updateCategory(id, categoryData) { const categoryIndex = this.categories.findIndex(cat => cat.id === id); if (categoryIndex === -1) { throw new Error('Категория не найдена'); } this.categories[categoryIndex] = { ...this.categories[categoryIndex], ...categoryData, updatedAt: new Date().toISOString() }; this.saveCategories(); return this.categories[categoryIndex]; } // Удаление категории async deleteCategory(id) { const categoryIndex = this.categories.findIndex(cat => cat.id === id); if (categoryIndex === -1) { throw new Error('Категория не найдена'); } // Удаляем все позиции этой категории this.items = this.items.filter(item => item.category !== id); this.saveItems(); // Удаляем категорию this.categories.splice(categoryIndex, 1); this.saveCategories(); // Обновляем количество позиций в остальных категориях this.updateCategoryItemCounts(); return true; } // Генерация следующего ID для товара getNextItemId() { const maxId = Math.max(...this.items.map(item => item.id), 0); return maxId + 1; } // Генерация следующего ID для операции getNextOperationId() { const maxId = Math.max(...this.operations.map(op => op.id), 0); return maxId + 1; } // Генерация ID для категории generateCategoryId() { return 'category_' + Date.now(); } // Экспорт данных async exportData(type = 'items') { switch (type) { case 'items': return this.exportItems(); case 'operations': return this.exportOperations(); case 'statistics': return this.exportStatistics(); default: throw new Error('Неизвестный тип экспорта'); } } // Экспорт товаров exportItems() { const headers = ['ID', 'Название', 'Категория', 'Цена', 'Количество', 'Описание', 'Штрих-код', 'Местоположение']; const rows = this.items.map(item => [ item.id, item.name, this.getCategoryName(item.category), item.price, item.quantity, item.description, item.barcode, item.location ]); return { headers, rows }; } // Экспорт операций exportOperations() { const headers = ['ID', 'Дата', 'Тип', 'Товар', 'Количество', 'Сотрудник', 'Статус', 'Примечания']; const rows = this.operations.map(op => [ op.id, new Date(op.date).toLocaleDateString('ru-RU'), op.type === 'incoming' ? 'Приход' : 'Расход', op.itemName, op.quantity, op.employeeName, op.status === 'completed' ? 'Завершено' : 'В обработке', op.notes ]); return { headers, rows }; } // Экспорт статистики exportStatistics() { const stats = this.getStatistics(); return { headers: ['Показатель', 'Значение'], rows: [ ['Всего товаров', stats.totalItems], ['Общая стоимость', stats.totalValue + ' ₽'], ['Товары с низким запасом', stats.lowStockItems], ['Операций сегодня', stats.todayOperations], ['Всего категорий', stats.totalCategories], ['Всего операций', stats.totalOperations] ] }; } // Получение названия категории getCategoryName(categoryId) { const category = this.categories.find(cat => cat.id === categoryId); return category ? category.name : 'Неизвестная категория'; } // Получение отчета по движению товаров async getMovementReport(startDate, endDate, itemId = null) { let operations = this.operations.filter(op => { const opDate = new Date(op.date); return opDate >= new Date(startDate) && opDate <= new Date(endDate); }); if (itemId) { operations = operations.filter(op => op.itemId === parseInt(itemId)); } const report = { period: { startDate, endDate }, totalIncoming: operations.filter(op => op.type === 'incoming').length, totalOutgoing: operations.filter(op => op.type === 'outgoing').length, totalIncomingQuantity: operations .filter(op => op.type === 'incoming') .reduce((sum, op) => sum + op.quantity, 0), totalOutgoingQuantity: operations .filter(op => op.type === 'outgoing') .reduce((sum, op) => sum + op.quantity, 0), operations: operations }; return report; } } // Экспорт для использования в других модулях window.WarehouseManager = WarehouseManager;