Add files via upload

v0.2(css + js)
This commit is contained in:
cyrteL
2025-07-28 14:21:39 +03:00
committed by GitHub
parent 0308719223
commit 13c64c503e
8 changed files with 4103 additions and 0 deletions

532
js/warehouse.js Normal file
View File

@ -0,0 +1,532 @@
// Модуль управления складом
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() {
return [
{ id: 'electronics', name: 'Электроника', icon: 'fas fa-laptop' },
{ id: 'clothing', name: 'Одежда', icon: 'fas fa-tshirt' },
{ id: 'tools', name: 'Инструменты', icon: 'fas fa-tools' },
{ id: 'office', name: 'Офисные принадлежности', icon: 'fas fa-briefcase' },
{ id: 'furniture', name: 'Мебель', icon: 'fas fa-couch' },
{ id: 'books', name: 'Книги', icon: 'fas fa-book' }
];
}
// Сохранение данных в localStorage
saveItems() {
localStorage.setItem('warehouse_items', JSON.stringify(this.items));
}
saveOperations() {
localStorage.setItem('warehouse_operations', JSON.stringify(this.operations));
}
// Получение товаров по умолчанию
getDefaultItems() {
return [
{
id: 1,
name: 'Ноутбук Dell Inspiron',
category: 'electronics',
price: 45000,
quantity: 15,
description: '15.6" Full HD, Intel Core i5, 8GB RAM, 256GB SSD',
barcode: '1234567890123',
minQuantity: 5,
location: 'Стеллаж A-1',
supplier: 'Dell Inc.',
createdAt: '2024-01-15T10:00:00Z',
updatedAt: '2024-01-15T10:00:00Z'
},
{
id: 2,
name: 'Принтер HP LaserJet',
category: 'electronics',
price: 12000,
quantity: 8,
description: 'Лазерный принтер, монохромный, 20 стр/мин',
barcode: '1234567890124',
minQuantity: 3,
location: 'Стеллаж A-2',
supplier: 'HP Inc.',
createdAt: '2024-01-10T14:30:00Z',
updatedAt: '2024-01-10T14:30:00Z'
},
{
id: 3,
name: 'Офисный стул',
category: 'furniture',
price: 3500,
quantity: 25,
description: 'Офисный стул с подлокотниками, черный',
barcode: '1234567890125',
minQuantity: 10,
location: 'Стеллаж B-1',
supplier: 'МебельСтрой',
createdAt: '2024-01-05T09:15:00Z',
updatedAt: '2024-01-05T09:15:00Z'
},
{
id: 4,
name: 'Бумага А4',
category: 'office',
price: 250,
quantity: 100,
description: 'Бумага А4, 80 г/м², 500 листов',
barcode: '1234567890126',
minQuantity: 20,
location: 'Стеллаж C-1',
supplier: 'ОфисМаркет',
createdAt: '2024-01-12T11:45:00Z',
updatedAt: '2024-01-12T11:45:00Z'
},
{
id: 5,
name: 'Отвертка крестовая',
category: 'tools',
price: 150,
quantity: 50,
description: 'Отвертка крестовая PH2, 150мм',
barcode: '1234567890127',
minQuantity: 15,
location: 'Стеллаж D-1',
supplier: 'ИнструментСтрой',
createdAt: '2024-01-08T16:20:00Z',
updatedAt: '2024-01-08T16:20:00Z'
}
];
}
// Получение статистики
async 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;
}
// Получение товара по 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,
price: parseFloat(itemData.itemPrice),
quantity: parseInt(itemData.itemQuantity),
description: itemData.itemDescription || '',
barcode: itemData.itemBarcode || '',
minQuantity: parseInt(itemData.itemQuantity) * 0.2, // 20% от начального количества
location: 'Новый стеллаж',
supplier: '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
this.items.push(newItem);
this.saveItems();
// Создаем операцию прихода для нового товара
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();
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();
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();
// Создаем операцию
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();
// Создаем операцию
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;
}
// Добавление категории
async addCategory(categoryData) {
const newCategory = {
id: categoryData.id || this.generateCategoryId(),
name: categoryData.name,
icon: categoryData.icon || 'fas fa-box'
};
this.categories.push(newCategory);
return newCategory;
}
// Генерация следующего 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;