lokalnaya-sistema-ucheta-i-p/script.js

889 lines
33 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==================== ДАННЫЕ И ХРАНИЛИЩЕ ====================
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 `<div class="chart-segment" style="flex:${s.count || 0.1}; background:${s.color}20; border-color:${s.color}; padding:12px; border-radius:8px; margin:4px;">
<div style="font-weight:600; color:${s.color}">${percent}%</div>
<div style="font-size:12px; color:var(--text-secondary)">${s.label}</div>
</div>`;
}).join('');
container.innerHTML = `<div style="display:flex; flex-wrap:wrap; gap:8px; justify-content:center;">${segments}</div>`;
}
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 => `
<tr>
<td>${d.name}</td>
<td>${d.count}</td>
<td style="max-width:400px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${d.equipment}</td>
</tr>
`).join('');
}
function renderRecentOperations() {
const tbody = document.querySelector('#recent-operations-table tbody');
const recent = db.movements.slice(-6).reverse();
tbody.innerHTML = recent.map(m => `
<tr>
<td>${formatDate(m.date)}</td>
<td>${m.sn}</td>
<td>${m.model}</td>
<td>${m.operation}</td>
<td>${m.to || '—'}</td>
</tr>
`).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 => `<option value="${m}">${m}</option>`).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 => `<option value="${e.id}">${e.name}</option>`).join('');
}
});
}
function updateStatusFilters() {
const select = document.getElementById('warehouse-status-filter');
select.innerHTML = '<option value="">Все статусы</option>' +
db.statuses.map(s => `<option value="${s.id}">${s.label}</option>`).join('');
}
function updateTypeFilters() {
const select = document.getElementById('warehouse-type-filter');
select.innerHTML = '<option value="">Все типы</option>' +
db.models.map(m => `<option value="${m}">${m}</option>`).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 `
<tr>
<td>${formatDate(e.date)}</td>
<td>${e.sn}</td>
<td>${e.imei || '—'}</td>
<td>${e.model}</td>
<td><span class="badge ${getStatusBadgeClass(e.status)}">${e.status}</span></td>
<td>${e.account || '—'}</td>
<td>${e.order || '—'}</td>
<td>${emp ? emp.name : '—'}</td>
<td>
<button class="action-btn" onclick="showHistory('${e.sn}')" title="История">🔍</button>
</td>
</tr>
`;
}).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 = '<option value="">Все сотрудники</option>' +
db.employees.filter(e => e.active).map(e => `<option value="${e.id}">${e.name}</option>`).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 => `
<tr>
<td>${formatDate(m.date)}</td>
<td>${m.sn}</td>
<td>${m.model}</td>
<td>${m.operation}</td>
<td>${m.from || '—'}</td>
<td>${m.to || '—'}</td>
<td>${m.account || '—'}</td>
<td>${m.order || '—'}</td>
<td>${m.comment || '—'}</td>
<td>
<button class="action-btn ${m.processed ? 'processed' : ''}" onclick="toggleProcessed(${m.id})" title="Отметить">
${m.processed ? '✓' : '○'}
</button>
</td>
</tr>
`).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 = `
<strong>${equipment.model}</strong><br>
SN: ${equipment.sn}<br>
IMEI: ${equipment.imei || '—'}<br>
Текущий статус: <span class="badge ${getStatusBadgeClass(equipment.status)}">${equipment.status}</span>
`;
document.getElementById('modal-timeline').innerHTML = movements.map(m => `
<div class="timeline-item">
<div class="timeline-date">${formatDate(m.date)}</div>
<div class="timeline-content">
<div class="timeline-operation">${m.operation}</div>
<div class="timeline-details">
${m.from ? 'Откуда: ' + m.from + '. ' : ''}
${m.to ? 'Кому: ' + m.to + '. ' : ''}
${m.comment || ''}
</div>
</div>
</div>
`).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 => `
<tr>
<td>${e.name}</td>
<td>${e.active ? '<span class="badge badge-warehouse">Активен</span>' : '<span class="badge badge-written-off">В архиве</span>'}</td>
<td>
<button class="action-btn" onclick="toggleEmployee(${e.id})">${e.active ? '📥 В архив' : '📤 Активен'}</button>
</td>
</tr>
`).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 => `
<tr>
<td>${m}</td>
<td>${modelCounts[m] || 0}</td>
</tr>
`).join('');
// Статусы
document.getElementById('status-legend').innerHTML = db.statuses.map(s => `
<div class="status-item">
<div class="status-color" style="background:${s.color}"></div>
<span>${s.label}</span>
</div>
`).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();