// ==================== ДАННЫЕ И ХРАНИЛИЩЕ ====================
const DB_KEY = 'inventory_db_v1';
const DEMO_DATA = {
equipment: [
{ id: 1, date: '2024-01-15', sn: 'SN001', imei: '123456789012345', model: 'TP-Link Archer C6', status: 'у абонента', account: '100001', order: 'Z001', employee_id: null, initiator: 'Отдел продаж' },
{ id: 2, date: '2024-01-16', sn: 'SN002', imei: '123456789012346', model: 'Huawei HG8245H', status: 'у сотрудника', account: null, order: null, employee_id: 1, initiator: null },
{ id: 3, date: '2024-01-17', sn: 'SN003', imei: '', model: 'Eltex NTU-2', status: 'на складе', account: null, order: null, employee_id: null, initiator: null },
{ id: 4, date: '2024-01-18', sn: 'SN004', imei: '123456789012347', model: 'TP-Link Archer C6', status: 'поиск / дз', account: null, order: null, employee_id: null, initiator: null },
{ id: 5, date: '2024-01-19', sn: 'SN005', imei: '123456789012348', model: 'D-Link DIR-825', status: 'списано', account: null, order: null, employee_id: null, initiator: null },
{ id: 6, date: '2024-01-20', sn: 'SN006', imei: '', model: 'Huawei HG8245H', status: 'на складе', account: null, order: null, employee_id: null, initiator: null },
{ id: 7, date: '2024-01-21', sn: 'SN007', imei: '123456789012349', model: 'Eltex NTU-2', status: 'у абонента', account: '100002', order: 'Z002', employee_id: null, initiator: 'Техподдержка' },
{ id: 8, date: '2024-01-22', sn: 'SN008', imei: '', model: 'TP-Link Archer C6', status: 'у сотрудника', account: null, order: null, employee_id: 2, initiator: null }
],
movements: [
{ id: 1, date: '2024-01-15', sn: 'SN001', model: 'TP-Link Archer C6', operation: 'оприходование', from: null, to: null, account: null, order: null, comment: 'Первичное оприходование', status: 'на складе', processed: true },
{ id: 2, date: '2024-01-15', sn: 'SN001', model: 'TP-Link Archer C6', operation: 'установка', from: 'на складе', to: 'абонент', account: '100001', order: 'Z001', comment: 'Монтаж', status: 'у абонента', processed: true },
{ id: 3, date: '2024-01-16', sn: 'SN002', model: 'Huawei HG8245H', operation: 'оприходование', from: null, to: null, account: null, order: null, comment: 'Первичное оприходование', status: 'на складе', processed: true },
{ id: 4, date: '2024-01-16', sn: 'SN002', model: 'Huawei HG8245H', operation: 'выдача', from: 'на складе', to: 'Иванов И.И.', account: null, order: null, comment: 'Выдан сотруднику', status: 'у сотрудника', processed: true },
{ id: 5, date: '2024-01-17', sn: 'SN003', model: 'Eltex NTU-2', operation: 'оприходование', from: null, to: null, account: null, order: null, comment: 'Первичное оприходование', status: 'на складе', processed: true },
{ id: 6, date: '2024-01-18', sn: 'SN004', model: 'TP-Link Archer C6', operation: 'оприходование', from: null, to: null, account: null, order: null, comment: 'Первичное оприходование', status: 'поиск / дз', processed: true },
{ id: 7, date: '2024-01-19', sn: 'SN005', model: 'D-Link DIR-825', operation: 'оприходование', from: null, to: null, account: null, order: null, comment: 'Первичное оприходование', status: 'на складе', processed: true },
{ id: 8, date: '2024-01-19', sn: 'SN005', model: 'D-Link DIR-825', operation: 'списание', from: 'на складе', to: null, account: null, order: null, comment: 'Неисправно', status: 'списано', processed: true },
{ id: 9, date: '2024-01-20', sn: 'SN006', model: 'Huawei HG8245H', operation: 'оприходование', from: null, to: null, account: null, order: null, comment: 'Первичное оприходование', status: 'на складе', processed: true },
{ id: 10, date: '2024-01-21', sn: 'SN007', model: 'Eltex NTU-2', operation: 'оприходование', from: null, to: null, account: null, order: null, comment: 'Первичное оприходование', status: 'на складе', processed: true },
{ id: 11, date: '2024-01-21', sn: 'SN007', model: 'Eltex NTU-2', operation: 'установка', from: 'на складе', to: 'абонент', account: '100002', order: 'Z002', comment: 'Монтаж', status: 'у абонента', processed: true },
{ id: 12, date: '2024-01-22', sn: 'SN008', model: 'TP-Link Archer C6', operation: 'оприходование', from: null, to: null, account: null, order: null, comment: 'Первичное оприходование', status: 'на складе', processed: true },
{ id: 13, date: '2024-01-22', sn: 'SN008', model: 'TP-Link Archer C6', operation: 'выдача', from: 'на складе', to: 'Петров П.П.', account: null, order: null, comment: 'Выдан сотруднику', status: 'у сотрудника', processed: true }
],
employees: [
{ id: 1, name: 'Иванов И.И.', active: true },
{ id: 2, name: 'Петров П.П.', active: true },
{ id: 3, name: 'Сидоров С.С.', active: false }
],
models: [
'TP-Link Archer C6',
'Huawei HG8245H',
'Eltex NTU-2',
'D-Link DIR-825',
'Mikrotik hAP ac2',
'ZTE F660'
],
statuses: [
{ id: 'на складе', color: '#28a745', label: 'На складе' },
{ id: 'у сотрудника', color: '#ffc107', label: 'У сотрудника' },
{ id: 'у абонента', color: '#00b8cc', label: 'У абонента' },
{ id: 'поиск / дз', color: '#dc3545', label: 'Поиск / ДЗ' },
{ id: 'списано', color: '#6c757d', label: 'Списано' }
]
};
// Загрузка данных
function loadDB() {
const data = localStorage.getItem(DB_KEY);
return data ? JSON.parse(data) : DEMO_DATA;
}
// Сохранение данных
function saveDB(data) {
localStorage.setItem(DB_KEY, JSON.stringify(data));
}
// Инициализация БД
let db = loadDB();
// ==================== УТИЛИТЫ ====================
function generateId() {
return Date.now();
}
function formatDate(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr);
return d.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: '2-digit' });
}
function getStatusBadgeClass(status) {
const map = {
'на складе': 'badge-warehouse',
'у сотрудника': 'badge-employee',
'у абонента': 'badge-subscriber',
'поиск / дз': 'badge-search',
'списано': 'badge-written-off'
};
return map[status] || '';
}
// ==================== НАВИГАЦИЯ ====================
document.querySelectorAll('.nav-item').forEach(btn => {
btn.addEventListener('click', () => {
const screen = btn.dataset.screen;
// Переключение активной навигации
document.querySelectorAll('.nav-item').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Переключение экранов
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(screen).classList.add('active');
// Обновление данных при переключении
if (screen === 'dashboard') updateDashboard();
if (screen === 'warehouse') renderWarehouse();
if (screen === 'movements') renderMovements();
if (screen === 'settings') renderSettings();
});
});
// Переключение табов операций
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.getElementById('tab-' + tab).classList.add('active');
});
});
// ==================== ДАШБОРД ====================
function updateDashboard() {
// KPI
const total = db.equipment.length;
const warehouse = db.equipment.filter(e => e.status === 'на складе').length;
const subscribers = db.equipment.filter(e => e.status === 'у абонента').length;
const employees = db.equipment.filter(e => e.status === 'у сотрудника').length;
const search = db.equipment.filter(e => e.status === 'поиск / дз').length;
const writtenOff = db.equipment.filter(e => e.status === 'списано').length;
document.getElementById('kpi-total').textContent = total;
document.getElementById('kpi-warehouse').textContent = warehouse;
document.getElementById('kpi-subscribers').textContent = subscribers;
document.getElementById('kpi-employees').textContent = employees;
document.getElementById('kpi-search').textContent = search;
document.getElementById('kpi-written-off').textContent = writtenOff;
// Диаграмма (простая визуализация)
renderStatusChart();
// Оборудование у сотрудников
renderEmployeeEquipment();
// Последние операции
renderRecentOperations();
}
function renderStatusChart() {
const container = document.getElementById('statusChart');
const statuses = db.statuses.map(s => ({
label: s.label,
count: db.equipment.filter(e => e.status === s.id).length,
color: s.color
}));
const total = statuses.reduce((sum, s) => sum + s.count, 0) || 1;
const segments = statuses.map(s => {
const percent = ((s.count / total) * 100).toFixed(1);
return `
`;
}).join('');
container.innerHTML = `${segments}
`;
}
function renderEmployeeEquipment() {
const tbody = document.querySelector('#employee-equipment-table tbody');
const activeEmployees = db.employees.filter(e => e.active);
const data = activeEmployees.map(emp => {
const items = db.equipment.filter(e => e.employee_id === emp.id);
return {
name: emp.name,
count: items.length,
equipment: items.map(i => `${i.model} (${i.sn})`).join(', ') || '—'
};
}).filter(d => d.count > 0);
tbody.innerHTML = data.map(d => `
| ${d.name} |
${d.count} |
${d.equipment} |
`).join('');
}
function renderRecentOperations() {
const tbody = document.querySelector('#recent-operations-table tbody');
const recent = db.movements.slice(-6).reverse();
tbody.innerHTML = recent.map(m => `
| ${formatDate(m.date)} |
${m.sn} |
${m.model} |
${m.operation} |
${m.to || '—'} |
`).join('');
}
// ==================== ОПЕРАЦИИ ====================
// Инициализация форм
function initOperations() {
// Установка сегодняшней даты
document.getElementById('income-date').value = new Date().toISOString().split('T')[0];
// Заполнение справочников
updateModelSelects();
updateEmployeeSelects();
updateStatusFilters();
updateTypeFilters();
// Обработчики
document.getElementById('income-form').addEventListener('submit', handleIncome);
document.getElementById('issue-form').addEventListener('submit', handleIssue);
document.getElementById('install-form').addEventListener('submit', handleInstall);
document.getElementById('return-form').addEventListener('submit', handleReturn);
document.getElementById('add-model-btn').addEventListener('click', () => {
document.getElementById('model-modal').classList.add('active');
});
document.getElementById('new-model-form').addEventListener('submit', (e) => {
e.preventDefault();
const name = document.getElementById('quick-model-name').value.trim();
if (name && !db.models.includes(name)) {
db.models.push(name);
saveDB(db);
updateModelSelects();
document.getElementById('model-modal').classList.remove('active');
document.getElementById('quick-model-name').value = '';
document.getElementById('income-model').value = name;
}
});
// Поиск оборудования в формах
document.getElementById('issue-sn-search').addEventListener('input', (e) => {
showEquipmentPreview(e.target.value, 'issue');
});
document.getElementById('install-sn-search').addEventListener('input', (e) => {
showEquipmentPreview(e.target.value, 'install');
});
document.getElementById('return-sn-search').addEventListener('input', (e) => {
showEquipmentPreview(e.target.value, 'return');
});
}
function updateModelSelects() {
const selects = ['income-model'];
selects.forEach(id => {
const select = document.getElementById(id);
if (select) {
select.innerHTML = db.models.map(m => ``).join('');
}
});
}
function updateEmployeeSelects() {
const activeEmployees = db.employees.filter(e => e.active);
const selects = ['issue-employee'];
selects.forEach(id => {
const select = document.getElementById(id);
if (select) {
select.innerHTML = activeEmployees.map(e => ``).join('');
}
});
}
function updateStatusFilters() {
const select = document.getElementById('warehouse-status-filter');
select.innerHTML = '' +
db.statuses.map(s => ``).join('');
}
function updateTypeFilters() {
const select = document.getElementById('warehouse-type-filter');
select.innerHTML = '' +
db.models.map(m => ``).join('');
}
function showEquipmentPreview(sn, prefix) {
const equipment = db.equipment.find(e => e.sn.toLowerCase() === sn.toLowerCase());
const preview = document.getElementById(prefix + '-preview');
if (equipment) {
document.getElementById(prefix + '-preview-sn').textContent = equipment.sn;
document.getElementById(prefix + '-preview-model').textContent = equipment.model;
const statusBadge = document.getElementById(prefix + '-preview-status');
statusBadge.textContent = equipment.status;
statusBadge.className = 'badge ' + getStatusBadgeClass(equipment.status);
let holder = '—';
if (equipment.employee_id) {
const emp = db.employees.find(e => e.id === equipment.employee_id);
holder = emp ? emp.name : 'Неизвестный сотрудник';
} else if (equipment.account) {
holder = 'Л/С: ' + equipment.account;
}
document.getElementById(prefix + '-preview-holder').textContent = holder;
preview.style.display = 'block';
} else {
preview.style.display = 'none';
}
}
// Оприходование
function handleIncome(e) {
e.preventDefault();
const date = document.getElementById('income-date').value;
const sn = document.getElementById('income-sn').value.trim().toUpperCase();
const imei = document.getElementById('income-imei').value.trim();
const model = document.getElementById('income-model').value;
const status = document.getElementById('income-status').value;
// Проверка на дубликат SN
if (db.equipment.some(eq => eq.sn === sn)) {
alert('Оборудование с таким SN уже существует!');
return;
}
const equipment = {
id: generateId(),
date,
sn,
imei,
model,
status,
account: null,
order: null,
employee_id: null,
initiator: null
};
db.equipment.push(equipment);
// Запись в архив
db.movements.push({
id: generateId(),
date,
sn,
model,
operation: 'оприходование',
from: null,
to: null,
account: null,
order: null,
comment: 'Первичное оприходование',
status,
processed: true
});
saveDB(db);
alert('Оборудование успешно оприходовано!');
e.target.reset();
document.getElementById('income-date').value = date;
document.getElementById('issue-preview').style.display = 'none';
}
// Выдача сотруднику
function handleIssue(e) {
e.preventDefault();
const sn = document.getElementById('issue-sn-search').value.trim().toUpperCase();
const employeeId = parseInt(document.getElementById('issue-employee').value);
const equipment = db.equipment.find(eq => eq.sn === sn);
if (!equipment) {
alert('Оборудование не найдено!');
return;
}
const oldStatus = equipment.status;
const employee = db.employees.find(emp => emp.id === employeeId);
// Обновление оборудования
equipment.status = 'у сотрудника';
equipment.employee_id = employeeId;
equipment.account = null;
equipment.order = null;
// Запись в архив
db.movements.push({
id: generateId(),
date: new Date().toISOString().split('T')[0],
sn: equipment.sn,
model: equipment.model,
operation: 'выдача',
from: oldStatus,
to: employee.name,
account: null,
order: null,
comment: 'Выдано сотруднику',
status: 'у сотрудника',
processed: true
});
saveDB(db);
alert('Оборудование выдано сотруднику!');
e.target.reset();
document.getElementById('issue-preview').style.display = 'none';
}
// Установка абоненту
function handleInstall(e) {
e.preventDefault();
const sn = document.getElementById('install-sn-search').value.trim().toUpperCase();
const account = document.getElementById('install-account').value.trim();
const order = document.getElementById('install-order').value.trim();
const initiator = document.getElementById('install-initiator').value.trim();
const hasModel = document.getElementById('install-has-model').checked;
const hasImei = document.getElementById('install-has-imei').checked;
const equipment = db.equipment.find(eq => eq.sn === sn);
if (!equipment) {
alert('Оборудование не найдено!');
return;
}
const oldStatus = equipment.status;
// Обновление оборудования
equipment.status = 'у абонента';
equipment.account = account;
equipment.order = order;
equipment.employee_id = null;
equipment.initiator = initiator;
// Авто-дозапись реквизитов
if (hasModel && !equipment.model) {
equipment.model = prompt('Введите наименование модели:');
}
if (hasImei && !equipment.imei) {
equipment.imei = prompt('Введите IMEI:');
}
// Запись в архив
db.movements.push({
id: generateId(),
date: new Date().toISOString().split('T')[0],
sn: equipment.sn,
model: equipment.model,
operation: 'установка',
from: oldStatus,
to: 'абонент',
account,
order,
comment: 'Установка абоненту' + (initiator ? `. Инициатор: ${initiator}` : ''),
status: 'у абонента',
processed: true
});
saveDB(db);
alert('Оборудование установлено абоненту!');
e.target.reset();
document.getElementById('install-preview').style.display = 'none';
document.getElementById('install-has-model').checked = false;
document.getElementById('install-has-imei').checked = false;
}
// Возврат / Списание / ДЗ
function handleReturn(e) {
e.preventDefault();
const type = document.getElementById('return-type').value;
const sn = document.getElementById('return-sn-search').value.trim().toUpperCase();
const comment = document.getElementById('return-comment').value.trim();
const equipment = db.equipment.find(eq => eq.sn === sn);
if (!equipment) {
alert('Оборудование не найдено!');
return;
}
const oldStatus = equipment.status;
let newStatus, operation, to;
switch (type) {
case 'снятие':
newStatus = 'на складе';
operation = 'снятие';
to = 'склад';
break;
case 'списание':
newStatus = 'списано';
operation = 'списание';
to = null;
break;
case 'дз':
newStatus = 'поиск / дз';
operation = 'дз';
to = null;
break;
case 'вывести из склада':
newStatus = 'поиск / дз';
operation = 'перемещение';
to = 'поиск / дз';
break;
case 'перемещение с др складов':
newStatus = 'на складе';
operation = 'перемещение';
to = 'склад';
break;
}
// Обновление оборудования
equipment.status = newStatus;
if (newStatus === 'на складе') {
equipment.employee_id = null;
equipment.account = null;
equipment.order = null;
}
// Запись в архив
db.movements.push({
id: generateId(),
date: new Date().toISOString().split('T')[0],
sn: equipment.sn,
model: equipment.model,
operation,
from: oldStatus,
to,
account: null,
order: null,
comment: comment || type,
status: newStatus,
processed: true
});
saveDB(db);
alert('Операция выполнена!');
e.target.reset();
document.getElementById('return-preview').style.display = 'none';
}
// ==================== СКЛАД ====================
function renderWarehouse() {
const tbody = document.querySelector('#warehouse-table tbody');
const search = document.getElementById('warehouse-search').value.toLowerCase();
const statusFilter = document.getElementById('warehouse-status-filter').value;
const typeFilter = document.getElementById('warehouse-type-filter').value;
const employeeFilter = document.getElementById('warehouse-employee-filter').value;
let filtered = db.equipment;
if (search) {
filtered = filtered.filter(e =>
e.sn.toLowerCase().includes(search) ||
(e.imei && e.imei.toLowerCase().includes(search)) ||
(e.account && e.account.includes(search)) ||
(e.order && e.order.toLowerCase().includes(search)) ||
(e.employee_id && db.employees.find(emp => emp.id === e.employee_id && emp.name.toLowerCase().includes(search)))
);
}
if (statusFilter) {
filtered = filtered.filter(e => e.status === statusFilter);
}
if (typeFilter) {
filtered = filtered.filter(e => e.model === typeFilter);
}
if (employeeFilter) {
const empId = parseInt(employeeFilter);
filtered = filtered.filter(e => e.employee_id === empId);
}
tbody.innerHTML = filtered.map(e => {
const emp = e.employee_id ? db.employees.find(emp => emp.id === e.employee_id) : null;
return `
| ${formatDate(e.date)} |
${e.sn} |
${e.imei || '—'} |
${e.model} |
${e.status} |
${e.account || '—'} |
${e.order || '—'} |
${emp ? emp.name : '—'} |
|
`;
}).join('');
}
// Фильтры склада
document.getElementById('warehouse-search').addEventListener('input', renderWarehouse);
document.getElementById('warehouse-status-filter').addEventListener('change', renderWarehouse);
document.getElementById('warehouse-type-filter').addEventListener('change', renderWarehouse);
// Заполнение фильтра сотрудников
function updateEmployeeFilter() {
const select = document.getElementById('warehouse-employee-filter');
select.innerHTML = '' +
db.employees.filter(e => e.active).map(e => ``).join('');
}
document.getElementById('warehouse-employee-filter').addEventListener('change', renderWarehouse);
// ==================== ПЕРЕДВИЖЕНИЯ ====================
function renderMovements() {
const tbody = document.querySelector('#movements-table tbody');
const search = document.getElementById('movements-search').value.toLowerCase();
const typeFilter = document.getElementById('movements-type-filter').value;
let filtered = db.movements;
if (search) {
filtered = filtered.filter(m =>
m.sn.toLowerCase().includes(search) ||
m.model.toLowerCase().includes(search) ||
(m.to && m.to.toLowerCase().includes(search))
);
}
if (typeFilter) {
filtered = filtered.filter(m => m.operation === typeFilter);
}
tbody.innerHTML = filtered.map(m => `
| ${formatDate(m.date)} |
${m.sn} |
${m.model} |
${m.operation} |
${m.from || '—'} |
${m.to || '—'} |
${m.account || '—'} |
${m.order || '—'} |
${m.comment || '—'} |
|
`).join('');
}
function toggleProcessed(id) {
const movement = db.movements.find(m => m.id === id);
if (movement) {
movement.processed = !movement.processed;
saveDB(db);
renderMovements();
}
}
document.getElementById('movements-search').addEventListener('input', renderMovements);
document.getElementById('movements-type-filter').addEventListener('change', renderMovements);
// ==================== ИСТОРИЯ (МОДАЛЬНОЕ ОКНО) ====================
window.showHistory = function(sn) {
const equipment = db.equipment.find(e => e.sn === sn);
if (!equipment) return;
const movements = db.movements.filter(m => m.sn === sn).sort((a, b) => new Date(b.date) - new Date(a.date));
document.getElementById('modal-equipment-info').innerHTML = `
${equipment.model}
SN: ${equipment.sn}
IMEI: ${equipment.imei || '—'}
Текущий статус: ${equipment.status}
`;
document.getElementById('modal-timeline').innerHTML = movements.map(m => `
${formatDate(m.date)}
${m.operation}
${m.from ? 'Откуда: ' + m.from + '. ' : ''}
${m.to ? 'Кому: ' + m.to + '. ' : ''}
${m.comment || ''}
`).join('');
document.getElementById('history-modal').classList.add('active');
};
// Закрытие модальных окон
document.querySelectorAll('.modal-close').forEach(btn => {
btn.addEventListener('click', () => {
document.getElementById(btn.dataset.modal).classList.remove('active');
});
});
document.querySelectorAll('.modal').forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('active');
}
});
});
// ==================== НАСТРОЙКИ ====================
function renderSettings() {
// Сотрудники
const empTbody = document.querySelector('#employees-table tbody');
empTbody.innerHTML = db.employees.map(e => `
| ${e.name} |
${e.active ? 'Активен' : 'В архиве'} |
|
`).join('');
// Модели
const modelTbody = document.querySelector('#models-table tbody');
const modelCounts = {};
db.equipment.forEach(e => {
modelCounts[e.model] = (modelCounts[e.model] || 0) + 1;
});
modelTbody.innerHTML = db.models.map(m => `
| ${m} |
${modelCounts[m] || 0} |
`).join('');
// Статусы
document.getElementById('status-legend').innerHTML = db.statuses.map(s => `
`).join('');
updateEmployeeFilter();
}
window.toggleEmployee = function(id) {
const employee = db.employees.find(e => e.id === id);
if (employee) {
employee.active = !employee.active;
saveDB(db);
renderSettings();
}
};
// Добавление сотрудника
document.getElementById('add-employee-form').addEventListener('submit', (e) => {
e.preventDefault();
const name = document.getElementById('new-employee-name').value.trim();
if (name) {
db.employees.push({ id: generateId(), name, active: true });
saveDB(db);
document.getElementById('new-employee-name').value = '';
renderSettings();
updateEmployeeSelects();
}
});
// Добавление модели
document.getElementById('add-model-form').addEventListener('submit', (e) => {
e.preventDefault();
const name = document.getElementById('new-model-name').value.trim();
if (name && !db.models.includes(name)) {
db.models.push(name);
saveDB(db);
document.getElementById('new-model-name').value = '';
renderSettings();
updateModelSelects();
updateTypeFilters();
}
});
// ==================== ЭКСПОРТ ====================
document.getElementById('export-warehouse-btn').addEventListener('click', () => {
const headers = ['Дата', 'SN', 'IMEI', 'Модель', 'Статус', 'Л/С', 'Заказ', 'Сотрудник'];
const rows = db.equipment.map(e => {
const emp = e.employee_id ? db.employees.find(emp => emp.id === e.employee_id) : null;
return [
e.date,
e.sn,
e.imei,
e.model,
e.status,
e.account || '',
e.order || '',
emp ? emp.name : ''
];
});
const csv = '\uFEFF' + [headers, ...rows].map(row =>
row.map(cell => '"' + String(cell).replace(/"/g, '""') + '"').join(';')
).join('\n');
downloadFile(csv, 'warehouse.csv', 'text/csv');
});
document.getElementById('export-movements-btn').addEventListener('click', () => {
const headers = ['Дата', 'SN', 'Модель', 'Операция', 'Откуда', 'Кому', 'Л/С', 'Заказ', 'Комментарий', 'Статус'];
const rows = db.movements.map(m => [
m.date,
m.sn,
m.model,
m.operation,
m.from || '',
m.to || '',
m.account || '',
m.order || '',
m.comment || '',
m.status
]);
const csv = '\uFEFF' + [headers, ...rows].map(row =>
row.map(cell => '"' + String(cell).replace(/"/g, '""') + '"').join(';')
).join('\n');
downloadFile(csv, 'movements.csv', 'text/csv');
});
function downloadFile(content, filename, type) {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
// ==================== РЕЗЕРВНЫЕ КОПИИ ====================
document.getElementById('download-backup-btn').addEventListener('click', () => {
const json = JSON.stringify(db, null, 2);
downloadFile(json, 'inventory_backup_' + new Date().toISOString().split('T')[0] + '.json', 'application/json');
});
document.getElementById('restore-backup-input').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const data = JSON.parse(event.target.result);
if (data.equipment && data.movements && data.employees && data.models) {
db = data;
saveDB(db);
alert('База успешно восстановлена!');
location.reload();
} else {
alert('Неверный формат файла backup');
}
} catch (err) {
alert('Ошибка чтения файла: ' + err.message);
}
};
reader.readAsText(file);
});
document.getElementById('reset-demo-btn').addEventListener('click', () => {
if (confirm('Вы уверены? Все данные будут сброшены к демо-версии.')) {
db = JSON.parse(JSON.stringify(DEMO_DATA));
saveDB(db);
location.reload();
}
});
// ==================== ИНИЦИАЛИЗАЦИЯ ====================
initOperations();
updateDashboard();
renderWarehouse();
renderMovements();
renderSettings();