261 lines
7.1 KiB
JavaScript
261 lines
7.1 KiB
JavaScript
// Хранилище данных (localStorage + экспорт/импорт)
|
||
const DB_VERSION = 1;
|
||
|
||
const defaultData = {
|
||
employees: [],
|
||
decrees: [],
|
||
payments: [],
|
||
currentPeriod: '2026-08',
|
||
operations: []
|
||
};
|
||
|
||
let db = loadData();
|
||
|
||
function loadData() {
|
||
try {
|
||
const saved = localStorage.getItem('executive_sheets_db');
|
||
if (saved) {
|
||
const parsed = JSON.parse(saved);
|
||
return { ...defaultData, ...parsed };
|
||
}
|
||
} catch (e) {
|
||
console.error('Ошибка загрузки данных:', e);
|
||
}
|
||
return { ...defaultData };
|
||
}
|
||
|
||
function saveData() {
|
||
try {
|
||
localStorage.setItem('executive_sheets_db', JSON.stringify(db));
|
||
} catch (e) {
|
||
console.error('Ошибка сохранения данных:', e);
|
||
showToast('Ошибка сохранения данных', 'error');
|
||
}
|
||
}
|
||
|
||
function addEmployee(emp) {
|
||
const existing = db.employees.find(e => e.tabNum === emp.tabNum);
|
||
if (existing) {
|
||
existing.name = emp.name;
|
||
existing.iin = emp.iin;
|
||
existing.salary = parseFloat(emp.salary) || 0;
|
||
existing.updatedAt = new Date().toISOString();
|
||
} else {
|
||
db.employees.push({
|
||
...emp,
|
||
id: Date.now(),
|
||
salary: parseFloat(emp.salary) || 0,
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString()
|
||
});
|
||
}
|
||
saveData();
|
||
logOperation('employee', 'create', `Сотрудник ${emp.name} (таб. ${emp.tabNum})`);
|
||
}
|
||
|
||
function updateEmployee(id, updates) {
|
||
const emp = db.employees.find(e => e.id === id);
|
||
if (emp) {
|
||
Object.assign(emp, updates);
|
||
emp.updatedAt = new Date().toISOString();
|
||
saveData();
|
||
logOperation('employee', 'update', `Сотрудник ${emp.name}`);
|
||
}
|
||
}
|
||
|
||
function deleteEmployee(id) {
|
||
const idx = db.employees.findIndex(e => e.id === id);
|
||
if (idx !== -1) {
|
||
const name = db.employees[idx].name;
|
||
db.employees.splice(idx, 1);
|
||
saveData();
|
||
logOperation('employee', 'delete', `Сотрудник ${name}`);
|
||
}
|
||
}
|
||
|
||
function addDecree(decree) {
|
||
decree.id = Date.now();
|
||
decree.status = 'active';
|
||
decree.paidAmount = 0;
|
||
decree.createdAt = new Date().toISOString();
|
||
db.decrees.push(decree);
|
||
saveData();
|
||
logOperation('decree', 'create', `Постановление ${decree.number}`);
|
||
}
|
||
|
||
function updateDecree(id, updates) {
|
||
const d = db.decrees.find(d => d.id === id);
|
||
if (d) {
|
||
Object.assign(d, updates);
|
||
d.updatedAt = new Date().toISOString();
|
||
saveData();
|
||
logOperation('decree', 'update', `Постановление ${d.number}`);
|
||
}
|
||
}
|
||
|
||
function deleteDecree(id) {
|
||
const idx = db.decrees.findIndex(d => d.id === id);
|
||
if (idx !== -1) {
|
||
const num = db.decrees[idx].number;
|
||
db.decrees.splice(idx, 1);
|
||
saveData();
|
||
logOperation('decree', 'delete', `Постановление ${num}`);
|
||
}
|
||
}
|
||
|
||
function addPayment(payment) {
|
||
payment.id = Date.now();
|
||
payment.status = 'processed';
|
||
payment.createdAt = new Date().toISOString();
|
||
db.payments.push(payment);
|
||
|
||
const decree = db.decrees.find(d => d.id === payment.decreeId);
|
||
if (decree) {
|
||
decree.paidAmount = (decree.paidAmount || 0) + parseFloat(payment.amount);
|
||
if (decree.paidAmount >= decree.amount) {
|
||
decree.status = 'closed';
|
||
}
|
||
saveData();
|
||
}
|
||
logOperation('payment', 'create', `Выплата по ${payment.decreeNumber}`);
|
||
}
|
||
|
||
function getEmployees() {
|
||
return db.employees;
|
||
}
|
||
|
||
function getDecrees(filter = 'all') {
|
||
if (filter === 'all') return db.decrees;
|
||
if (filter === 'alimony') return db.decrees.filter(d => d.type === 'alimony');
|
||
if (filter === 'other') return db.decrees.filter(d => d.type === 'other');
|
||
if (filter === 'closed') return db.decrees.filter(d => d.status === 'closed');
|
||
return db.decrees;
|
||
}
|
||
|
||
function getDecreesForEmployee(empId) {
|
||
return db.decrees.filter(d => d.employeeId === empId && d.status === 'active');
|
||
}
|
||
|
||
function getPayments() {
|
||
return db.payments;
|
||
}
|
||
|
||
function getCurrentPeriod() {
|
||
return db.currentPeriod;
|
||
}
|
||
|
||
function setCurrentPeriod(period) {
|
||
db.currentPeriod = period;
|
||
saveData();
|
||
}
|
||
|
||
function logOperation(type, action, description) {
|
||
db.operations.unshift({
|
||
id: Date.now(),
|
||
type,
|
||
action,
|
||
description,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
if (db.operations.length > 100) {
|
||
db.operations = db.operations.slice(0, 100);
|
||
}
|
||
saveData();
|
||
}
|
||
|
||
function getRecentOperations(limit = 5) {
|
||
return db.operations.slice(0, limit);
|
||
}
|
||
|
||
function getStats() {
|
||
const activeDecrees = db.decrees.filter(d => d.status === 'active');
|
||
const totalDebt = activeDecrees.reduce((sum, d) => sum + (d.amount - (d.paidAmount || 0)), 0);
|
||
const currentPeriod = getCurrentPeriod();
|
||
const paidThisMonth = db.payments
|
||
.filter(p => p.period === currentPeriod)
|
||
.reduce((sum, p) => sum + parseFloat(p.amount), 0);
|
||
|
||
return {
|
||
employees: db.employees.length,
|
||
activeDecrees: activeDecrees.length,
|
||
totalDebt,
|
||
paidThisMonth,
|
||
closedDecrees: db.decrees.filter(d => d.status === 'closed').length
|
||
};
|
||
}
|
||
|
||
function calculateDeductionsForEmployee(employee) {
|
||
const decrees = getDecreesForEmployee(employee.id);
|
||
if (decrees.length === 0) return null;
|
||
|
||
const maxDeduction = employee.salary * 0.5;
|
||
const alimonyDecrees = decrees.filter(d => d.type === 'alimony');
|
||
const otherDecrees = decrees.filter(d => d.type === 'other');
|
||
|
||
let totalAllocated = 0;
|
||
const allocation = [];
|
||
|
||
alimonyDecrees.forEach(d => {
|
||
const remaining = d.amount - (d.paidAmount || 0);
|
||
allocation.push({
|
||
decree: d,
|
||
allocated: Math.min(remaining, maxDeduction * 0.5)
|
||
});
|
||
totalAllocated += allocation[allocation.length - 1].allocated;
|
||
});
|
||
|
||
const remainingForOther = maxDeduction - totalAllocated;
|
||
if (remainingForOther > 0 && otherDecrees.length > 0) {
|
||
const perOther = remainingForOther / otherDecrees.length;
|
||
otherDecrees.forEach(d => {
|
||
const remaining = d.amount - (d.paidAmount || 0);
|
||
allocation.push({
|
||
decree: d,
|
||
allocated: Math.min(remaining, perOther)
|
||
});
|
||
});
|
||
}
|
||
|
||
return {
|
||
employee,
|
||
maxDeduction,
|
||
allocation,
|
||
totalAllocated,
|
||
remaining: employee.salary - maxDeduction
|
||
};
|
||
}
|
||
|
||
function exportToCSV(data, filename) {
|
||
if (!data || data.length === 0) {
|
||
showToast('Нет данных для экспорта', 'error');
|
||
return;
|
||
}
|
||
|
||
const headers = Object.keys(data[0]);
|
||
const csv = [
|
||
headers.join(';'),
|
||
...data.map(row =>
|
||
headers.map(h => {
|
||
const val = row[h] !== null && row[h] !== undefined ? String(row[h]) : '';
|
||
return '"' + val.replace(/"/g, '""') + '"';
|
||
}).join(';')
|
||
)
|
||
].join('\n');
|
||
|
||
const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = filename;
|
||
link.click();
|
||
showToast('Файл скачан: ' + filename, 'success');
|
||
}
|
||
|
||
function clearAllData() {
|
||
if (confirm('Вы уверены? Все данные будут удалены безвозвратно.')) {
|
||
localStorage.removeItem('executive_sheets_db');
|
||
db = { ...defaultData };
|
||
showToast('Все данные удалены', 'success');
|
||
location.reload();
|
||
}
|
||
}
|